遍历二叉树

来源:互联网 发布:瞻博网络中国裁员 编辑:程序博客网 时间:2024/05/21 02:51

在二叉树的一些应用中,常常要求在树中查找具有某些特征的结点,或者对树中全部结点逐一进行某种处理。这就提出了一个遍历二叉树的问题,即如果按某条搜索路径巡访树中的每个结点,使得每个结点均被访问一次,而且仅被访问一次。“访问”的含义很广,可以是对结点作各种处理,如输出结点的信息等。遍历对于线性结构来说是一个很容易解决的问题,而对二叉树则不然,因为二叉树是一种非线性结构。

回顾二叉树的定义,我们知道二叉树可以看成是由三个部分组成的:一个根结点,根的左子树和根的右子树。因此如果能够遍历这三部分,则可以遍历整棵二叉树。如果用L、D、R分别表示遍历左子树、访问根结点、遍历右子树。那么对于二叉树的遍历次序就可以有6中方案,即L、D、R的排列组合,那么如果限制对左子树的遍历要先于对右子树的遍历,这就剩下3中情况,分别是:

  • (1)访问根,遍历左子树,遍历右子树(DLR)
  • (2)遍历左子树,访问根,遍历右子树(LDR)
  • (3)遍历左子树,遍历右子树,访问根(LRD)

根据对根访问的不同顺序,分别成DLR为先根(序)遍历,LDR为中根(序)遍历,LRD为后根(序)遍历。

需要注意的是,这里的先序遍历、中序遍历和后序遍历是递归定义的,即在左右子树中也是按相应的规律进行遍历。


这里写图片描述

例如,如上图所示的二叉树表示下述表达式:

4 + (6 - 8 + 2 * 2)*2

若先序遍历此二叉树,按访问结点的先后次序将结点排列起来,可得二叉树的先序序列为:

+ 4 * + - 6 8 * 2 2 2   (式1)

类似的,中序遍历此二叉树,可得此二叉树的中序序列为:

4 + 6 - 8 + 2 * 2 * 2   (式2)

后序遍历此二叉树,可得此二叉树的后序序列为:

4 6 8 - 2 2 * + 2 * +    (式3)

从表达式上看,式1、2、3恰好是表达式的前缀表示(波兰式)、中缀表示和后缀表示(逆波兰式)。


我们可以很简单地写出遍历二叉树的递归算法,同时,根据递归时工作栈的状态变化情况,我们也可以很简单地写出遍历二叉树的非递归算法。

每一个遍历的非递归算法都有两种思路,如果借助访问标记的话,我们可以根据递归的特点,手动地用一个栈来保存每一个结点,举例来说,先序遍历时先访问根,再访问左子树,接着访问右子树,即DLR,那么我们对于每一个结点,都按照RLD的顺序入栈,如果一个结点的左右子树已经入过栈,那么该结点视为已访问过的,下一次再遇到的话直接从栈中弹出,并且访问即可;而如果一个结点是未访问过的,则先将其弹出,再按照RLD的顺序,将其右孩子、左孩子入栈,接着自己再入栈。重复这个过程直到栈为空。这种方法实现和理解起来都比较简单。

另一种思路不需要借助访问标记,但是也要借助一个栈来实现,这种方式理解起来比上一种方法稍微有点复杂。


那么下述代码,实现了二叉树的递归与非递归的遍历方法。并且,每一种非递归方法都采用至少两种思路实现。

另外,我们是通过二叉树的顺序存储结构(即数组)来创建二叉树,与此同时,也实现了二叉树的层序遍历。

import java.util.HashMap;import java.util.Map;import com.gavin.datastructure.queue.ArrayQueue;import com.gavin.datastructure.stack.ArrayStack;/** * 实现二叉树的构建以及各种遍历 *  * @author Gavin * * @param <T> */public class BinaryTree<T> {    /**     * 从顺序存储结构,即数组中构建二叉树     *      * @param array     *            数组     * @return 返回二叉树根结点     */    public TripleLinkedNode<T> createTreeFromArray(T[] array) {        if (array == null || array.length == 0) {            return null;        }        Map<Integer, TripleLinkedNode<T>> nodeMap = new HashMap<>();        for (int i = 0; i < array.length; i++) {            // 如果值为空,说明当前结点不存在            if (array[i] == null) {                continue;            }            TripleLinkedNode<T> currentNode = null;            if (nodeMap.containsKey(i)) {                // 当前结点已存在                currentNode = nodeMap.get(i);            } else {                // 当前结点不存在                currentNode = new TripleLinkedNode<T>(array[i]);                // 加入                nodeMap.put(i, currentNode);            }            // 设置当前结点的左孩子            if (2 * i + 1 < array.length && array[2 * i + 1] != null) {                TripleLinkedNode<T> leftNode = new TripleLinkedNode<T>(array[2 * i + 1]);                currentNode.setLeft(leftNode);                nodeMap.put(2 * i + 1, leftNode);            }            // 设置当前结点的右孩子            if (2 * i + 2 < array.length && array[2 * i + 2] != null) {                TripleLinkedNode<T> rightNode = new TripleLinkedNode<T>(array[2 * i + 2]);                currentNode.setRight(rightNode);                nodeMap.put(2 * i + 2, rightNode);            }        }        // 返回根结点root        return nodeMap.get(0);    }    /**     * 访问一个结点     *      * @param node     */    public void visit(TripleLinkedNode<T> node) {        System.out.print(node.getData() + " ");    }    /**     * 将访问标记复位为false     *      * @param root     */    public void resetVisited(TripleLinkedNode<?> root) {        if (root == null) {            return;        }        root.setVisited(false);        resetVisited(root.getLeft());        resetVisited(root.getRight());    }    /**     * 递归实现前序遍历     *      * @param root     */    public void preOrderTraverse(TripleLinkedNode<T> root) {        if (root == null) {            return;        }        visit(root);        preOrderTraverse(root.getLeft());        preOrderTraverse(root.getRight());    }    /**     * 递归实现后续遍历     *      * @param root     */    public void postOrderTraverse(TripleLinkedNode<T> root) {        if (root == null) {            return;        }        postOrderTraverse(root.getLeft());        postOrderTraverse(root.getRight());        visit(root);    }    /**     * 递归实现中序遍历     *      * @param root     */    public void inOrderTraverse(TripleLinkedNode<T> root) {        if (root == null) {            return;        }        inOrderTraverse(root.getLeft());        visit(root);        inOrderTraverse(root.getRight());    }    /**     * 层序遍历     *      * @param root     */    public void levelOrderTraverse(TripleLinkedNode<T> root) {        // 要使用一个队列        ArrayQueue<TripleLinkedNode<T>> queue = new ArrayQueue<>();        queue.enqueue(root);        while (!queue.isEmpty()) {            TripleLinkedNode<T> currentNode = queue.dequeue();            // 访问队头结点            visit(currentNode);            // 左孩子入队            if (currentNode.hasLeft()) {                queue.enqueue(currentNode.getLeft());            }            // 右孩子入队            if (currentNode.hasRight()) {                queue.enqueue(currentNode.getRight());            }        }    }    /**     * 使用访问标记的非递归前序遍历     *      * @param root     */    public void preOrderTraverseNoRecursionWithVisited(TripleLinkedNode<T> root) {        ArrayStack<TripleLinkedNode<T>> stack = new ArrayStack<>();        TripleLinkedNode<T> node = root;        stack.push(node);        while (!stack.isEmpty()) {            node = stack.peek();            if (node.isVisited() || (!node.hasLeft() && !node.hasRight())) {                // 已被访问过或者是叶子结点,则直接访问                visit(node);                stack.pop();                continue;            }            // 先弹出            stack.pop();            if (node.hasRight()) {                stack.push(node.getRight());            }            if (node.hasLeft()) {                stack.push(node.getLeft());            }            // 再加入            node.setVisited(true);            stack.push(node);        }    }    /**     * 不使用访问标记的非递归实现前序遍历     *      * @param root     */    public void preOrderTraverseNoRecursionWithoutVisited(TripleLinkedNode<T> root) {        // 要用到一个栈        ArrayStack<TripleLinkedNode<T>> stack = new ArrayStack<>();        TripleLinkedNode<T> node = root;        while (node != null) {            // 向左走到底            while (node != null) {                visit(node); // 访问根结点                // 将该根结点的右孩子入栈                if (node.hasRight()) {                    stack.push(node.getRight());                }                node = node.getLeft();            }            // 右子树根退栈遍历右子树            if (!stack.isEmpty()) {                node = stack.pop();            }        }    }    /**     * 不使用访问标记的非递归实现前序遍历2:简单好理解     *      * @param root     */    public void preOrderTraverseNoRecursionWithoutVisited2(TripleLinkedNode<T> root) {        // 要用到一个栈        ArrayStack<TripleLinkedNode<T>> stack = new ArrayStack<>();        TripleLinkedNode<T> node = root;        stack.push(node);        while (!stack.isEmpty()) {            node = stack.pop();            visit(node);            if (node.hasRight()) {                stack.push(node.getRight());            }            if (node.hasLeft()) {                stack.push(node.getLeft());            }        }    }    /**     * 使用访问标记的非递归中序遍历     *      * @param root     */    public void inOrderTraverseNoRecursionWithVisited(TripleLinkedNode<T> root) {        ArrayStack<TripleLinkedNode<T>> stack = new ArrayStack<>();        TripleLinkedNode<T> node = root;        stack.push(node);        while (!stack.isEmpty()) {            node = stack.peek();            if (node.isVisited() || (!node.hasLeft() && !node.hasRight())) {                visit(node);                stack.pop();                continue;            }            // 先弹出            stack.pop();            if (node.hasRight()) {                stack.push(node.getRight());            }            // 再加入            node.setVisited(true);            stack.push(node);            if (node.hasLeft()) {                stack.push(node.getLeft());            }        }    }    /**     * 不使用访问标记的非递归实现中序遍历     *      * @param root     */    public void inOrderTraverseNoRecursionWithoutVisited(TripleLinkedNode<T> root) {        // 要用到一个栈        ArrayStack<TripleLinkedNode<T>> stack = new ArrayStack<>();        TripleLinkedNode<T> node = root;        while (node != null || !stack.isEmpty()) {            // 向左走到尽头            while (node != null) {                stack.push(node);                node = node.getLeft();            }            if (!stack.isEmpty()) {                // 取出根结点并访问                node = stack.pop();                visit(node);                // 转向根的右子树进行遍历                node = node.getRight();            }        }    }    /**     * 使用访问标记的非递归后序遍历     *      * @param root     */    public void postOrderTraverseNoRecursionWithVisited(TripleLinkedNode<T> root) {        // 要用到一个栈        ArrayStack<TripleLinkedNode<T>> stack = new ArrayStack<>();        TripleLinkedNode<T> node = root;        stack.push(node);        while (!stack.isEmpty()) {            node = stack.peek();            // 如果曾经已经访问过其孩子,这里直接访问即可。或者是叶子结点的话,这里也直接访问            if (node.isVisited() || (!node.hasLeft() && !node.hasRight())) {                // 叶子结点                visit(node);                stack.pop();                continue;            }            // 先加入右孩子            if (node.hasRight()) {                stack.push(node.getRight());            }            // 再加入左孩子            if (node.hasLeft()) {                stack.push(node.getLeft());            }            // 其子结点加入栈,这里设置其访问标记为true            node.setVisited(true);        }    }    /**     * 不使用访问标记的非递归后序遍历     *      * @param root     */    public void postOrderTraverseNoRecursionWithoutVisited(TripleLinkedNode<T> root) {        ArrayStack<TripleLinkedNode<T>> stack = new ArrayStack<>();        TripleLinkedNode<T> node = root;        while (node != null || !stack.isEmpty()) {            // 先左后右不断深入            while (node != null) {                // 将根结点入栈                stack.push(node);                if (node.hasLeft()) {                    node = node.getLeft();                } else {                    node = node.getRight();                }            }            if (!stack.isEmpty()) {                // 取出栈顶元素访问                node = stack.pop();                visit(node);            }            // 满足条件时,说明栈顶根结点右子树已经访问过,应出栈访问之            while (!stack.isEmpty() && stack.peek().getRight() == node) {                node = stack.pop();                visit(node);            }            // 转向栈顶根结点的右子树继续后续遍历            if (!stack.isEmpty()) {                node = stack.peek().getRight();            } else {                node = null;            }        }    }    /**     * 不使用访问标记的非递归后序遍历的第二种解法:使用两个栈,简单也好理解     *      * @param root     */    public void postOrderTraverseNoRecursionWithoutVisited2(TripleLinkedNode<T> root) {        ArrayStack<TripleLinkedNode<T>> stack = new ArrayStack<>();        ArrayStack<TripleLinkedNode<T>> output = new ArrayStack<>();        TripleLinkedNode<T> node = root;        stack.push(node);        while (!stack.isEmpty()) {            node = stack.pop();            output.push(node);            if (node.hasLeft()) {                stack.push(node.getLeft());            }            if (node.hasRight()) {                stack.push(node.getRight());            }        }        while (!output.isEmpty()) {            visit(output.pop());        }    }}

下面代码对当前的二叉树遍历进行测试:

import static org.junit.Assert.*;import org.junit.Test;public class BinaryTreeTest {    @Test    public void testCreateTreeFromArray() {        BinaryTree<String> bt = new BinaryTree<>();        String[] array = { "+", "4", "*", null, null, "+", "2", null, null, null, null, "-", "*", null, null, null,                null, null, null, null, null, null, null, "6", "8", "2", "2", null, null, null, null };        TripleLinkedNode<String> root = bt.createTreeFromArray(array);        assertEquals(11, root.getSize());        assertEquals(5, root.getHeight());        // 遍历        System.out.println("递归先序遍历:");        bt.preOrderTraverse(root);        System.out.println();        System.out.println("使用访问标记的非递归先序遍历:");        bt.preOrderTraverseNoRecursionWithVisited(root);        System.out.println();        System.out.println("不使用访问标记的非递归先序遍历1:");        bt.preOrderTraverseNoRecursionWithoutVisited(root);        // 标记复位        bt.resetVisited(root);        System.out.println();        System.out.println("不使用访问标记的非递归先序遍历2:");        bt.preOrderTraverseNoRecursionWithoutVisited2(root);        System.out.println();        System.out.println();        System.out.println("递归中序遍历:");        bt.inOrderTraverse(root);        System.out.println();        System.out.println("使用访问标记的非递归中序遍历:");        bt.inOrderTraverseNoRecursionWithVisited(root);        System.out.println();        System.out.println("不使用访问标记的非递归中序遍历:");        bt.inOrderTraverseNoRecursionWithoutVisited(root);        //标记复位        bt.resetVisited(root);        System.out.println();        System.out.println();        System.out.println("递归后序遍历:");        bt.postOrderTraverse(root);        System.out.println();        System.out.println("使用访问标记的非递归后序遍历:");        bt.postOrderTraverseNoRecursionWithVisited(root);        System.out.println();        System.out.println("不使用访问标记的非递归后序遍历1:");        bt.postOrderTraverseNoRecursionWithoutVisited(root);        bt.resetVisited(root);        System.out.println();        System.out.println("不使用访问标记的非递归后序遍历2:");        bt.postOrderTraverseNoRecursionWithoutVisited2(root);        System.out.println();        System.out.println();        System.out.println("层序遍历:");        bt.levelOrderTraverse(root);    }}

输出如下:

递归先序遍历:+ 4 * + - 6 8 * 2 2 2 使用访问标记的非递归先序遍历:+ 4 * + - 6 8 * 2 2 2 不使用访问标记的非递归先序遍历1:+ 4 * + - 6 8 * 2 2 2 不使用访问标记的非递归先序遍历2:+ 4 * + - 6 8 * 2 2 2 递归中序遍历:4 + 6 - 8 + 2 * 2 * 2 使用访问标记的非递归中序遍历:4 + 6 - 8 + 2 * 2 * 2 不使用访问标记的非递归中序遍历:4 + 6 - 8 + 2 * 2 * 2 递归后序遍历:4 6 8 - 2 2 * + 2 * + 使用访问标记的非递归后序遍历:4 6 8 - 2 2 * + 2 * + 不使用访问标记的非递归后序遍历1:4 6 8 - 2 2 * + 2 * + 不使用访问标记的非递归后序遍历2:4 6 8 - 2 2 * + 2 * + 层序遍历:+ 4 * + 2 - * 6 8 2 2

可见,每个算法的结果都完全正确。

原创粉丝点击