二叉树遍历

来源:互联网 发布:mid函数的使用方法vb 编辑:程序博客网 时间:2024/06/03 04:11

二叉树遍历

对于二叉树遍历的自己从大学毕业后都没好好看过,今天就来补习一下。

二叉树遍历,以根节点来区分为先序遍历、中序遍历、后序遍历。

首先挂一个示例图片:

先序遍历

遍历思想

输出结果: ABDECF

非递归的实现思路如下:

对于任一节点P,

  1. 输出节点P,然后将其入栈,再看P的左孩子是否为空;

  2. 若P的左孩子不为空,则置P的左孩子为当前节点,重复1的操作;

  3. 若P的左孩子为空,则将栈顶节点出栈,但不输出,并将出栈节点的右孩子置为当前节点,看其是否为空;

  4. 若不为空,则循环至1操作;

  5. 如果为空,则继续出栈,但不输出,同时将出栈节点的右孩子置为当前节点,看其是否为空,重复4和5操作;

  6. 直到当前节点P为NULL并且栈空,遍历结束。

对于示例具体的遍历顺序:

  1. 首先,从根节点A开始,根据操作1,输出A,并将其入栈,由于A的左孩子不为空,根据操作2,将B置为当前节点,再根据操作1,将B输出,并将其入栈,由于B的左孩子也不为空,根据操作2,将D置为当前节点,再根据操作1,输出D,并将其入栈,此时输出序列为ABD;

  2. 由于D的左孩子为空,根据操作3,将栈顶节点D出栈,但不输出,并将其右孩子置为当前节点;

  3. 由于D的右孩子为空,根据操作5,继续将栈顶节点B出栈,但不输出,并将其右孩子置为当前节点;

  4. 由于B的右孩子E不为空,根据操作1,输出E,并将其入栈,此时输出序列为:ABDE;

  5. 由于E的左孩子为空,根据操作3,将栈顶节点E出栈,但不输出,并将其右孩子置为当前节点;

  6. 由于E的右孩子为空,根据操作5,继续将栈顶节点A出栈,但不输出,并将其右孩子置为当前节点;

  7. 由于A的右孩子C不为空,根据操作1,输出C,并将其入栈,此时输出序列为:ABDEC;

  8. 由于A的左孩子F不为空,根据操作2,则将F置为当前节点,再根据操作1,输出F,并将其入栈,此时输出序列为:ABDECF;

  9. 由于F的左孩子为空,根据操作3,将栈顶节点F出栈,但不输出,并将其右孩子置为当前节点;

  10. 由于F的右孩子为空,根据操作5,继续将栈顶元素C出栈,但不输出,并将其右孩子置为当前节点;

此时栈空,且C的右孩子为NULL,因此遍历结束。


代码:
    private static final Stack<TreeNode> stack = new Stack<>();    /**     * 对于任何一个节点     * 1. 是否有左节点,如果有,把当前节点指向左节点,输出并压栈     * @param node 节点     */    private static void traverseWithRecursive(TreeNode node) {        // 如果左节点不为空        if (node == null) {            return;        }        System.out.println(node.getName());        if (node.getLeft() != null) {            traverseWithRecursive(node.getLeft());        }        if (node.getRight() != null) {            traverseWithRecursive(node.getRight());        }    }    /**     * 不使用递归来实现     * @param node 数     */    private static void traverseWithLoop(TreeNode node) {        if (node == null) {            return;        }        while (true) {            if (node == null) {                break;            }            if (node.getLeft() != null) { // 左节点不为空,则压栈,并且当前节点指向左节点                stack.push(node);                System.out.println(node.getName());                node = node.getLeft();            } else if (node.getRight() != null) { // 右节点不为空,弹出当前节点                stack.pop();                node = node.getRight();            } else {                System.out.println(node.getName());                node = stack.pop().getRight(); // 左子树已经遍历了            }        }    }

中序遍历

遍历思想

对于任一节点P,

  1. 若P的左孩子不为空,则将P入栈并将P的左孩子置为当前节点,然后再对当前节点进行相同的处理;

  2. 若P的左孩子为空,则输出P节点,而后将P的右孩子置为当前节点,看其是否为空;

  3. 若不为空,则重复1和2的操作;

  4. 若为空,则执行出栈操作,输出栈顶节点,并将出栈的节点的右孩子置为当前节点,看起是否为空,重复3和4的操作;

  5. 直到当前节点P为NULL并且栈为空,则遍历结束。

按示例的遍历顺序:

  1. 首先,从根节点A开始,A的左孩子不为空,根据操作1将A入栈,接着将B置为当前节点,B的左孩子也不为空,根据操作1,将B也入栈,接着将D置为当前节点,由于D的左子树为空,根据操作2,输出D;

  2. 由于D的右孩子也为空,根据操作4,执行出栈操作,将栈顶结点B出栈,并将B置为当前节点,此时输出序列为DB;

  3. 由于B的右孩子不为空,根据操作3,将其右孩子E置为当前节点,由于E的左孩子为空,根据操作1,输出E,此时输出序列为DBE;

  4. 由于E的右孩子为空,根据操作4,执行出栈操作,将栈顶节点A出栈,并将节点A置为当前节点,此时输出序列为DBEA;

  5. 此时栈为空,但当前节点A的右孩子并不为NULL,继续执行,由于A的右孩子不为空,根据操作3,将其右孩子C置为当前节点,由于C的左孩子不为空,根据操作1,将C入栈,将其左孩子F置为当前节点,由于F的左孩子为空,根据操作2,输出F,此时输出序列为:DBEAF;

  6. 由于F的右孩子也为空,根据操作4,执行出栈操作,将栈顶元素C出栈,并将其置为当前节点,此时的输出序列为:DBEAFC;

  7. 由于C的右孩子为NULL,且此时栈空,根据操作5,遍历结束。


代码:
    private static final Stack<TreeNode> stack = new Stack<>();    private static void traverseWithRecursive(TreeNode node) {        if (node.getLeft() != null) {            traverseWithRecursive(node.getLeft());        }        System.out.println(node.getName());        if (node.getRight() != null) {            traverseWithRecursive(node.getRight());        }    }    private static void traverseWithLoop(TreeNode node) {        if (node == null) {            return;        }        while (true) {            if (node == null) {                break;            }            if (node.getLeft() != null) { // 如果左节点不为空,那么压入栈                stack.push(node);                node = node.getLeft();            } else if (node.getRight() != null) {                stack.pop();                node = node.getRight();            } else {                System.out.println(node.getName());                TreeNode pop = stack.pop();                System.out.println(pop.getName());                node = pop.getRight();            }        }    }

后续遍历

遍历思想

对于任一节点P,

  1. 先将节点P入栈;

  2. 若P不存在左孩子和右孩子,或者P存在左孩子或右孩子,但左右孩子已经被输出,则可以直接输出节点P,并将其出栈,将出栈节点P标记为上一个输出的节点,再将此时的栈顶结点设为当前节点;

  3. 若不满足2中的条件,则将P的右孩子和左孩子依次入栈,当前节点重新置为栈顶结点,之后重复操作2;

  4. 直到栈空,遍历结束。

对于示例的遍历顺序是:

  1. 首先,设置两个指针:Cur指针指向当前访问的节点,它一直指向栈顶节点,每次出栈一个节点后,将其重新置为栈顶结点,Pre节点指向上一个访问的节点;

  2. Cur首先指向根节点A,Pre先设为NULL,由于A存在左孩子和右孩子,根据操作3,先将右孩子C入栈,再将左孩子B入栈,Cur改为指向栈顶结点B;

  3. 由于B的也有左孩子和右孩子,根据操作3,将E、D依次入栈,Cur改为指向栈顶结点D;

  4. 由于D没有左孩子,也没有右孩子,根据操作2,直接输出D,并将其出栈,将Pre指向D,Cur指向栈顶结点E,此时输出序列为:D;

  5. 由于E也没有左右孩子,根据操作2,输出E,并将其出栈,将Pre指向E,Cur指向栈顶结点B,此时输出序列为:DE;

  6. 由于B的左右孩子已经被输出,即满足条件Pre==Cur->lchild或Pre==Cur->rchild,根据操作2,输出B,并将其出栈,将Pre指向B,Cur指向栈顶结点C,此时输出序列为:DEB;

  7. 由于C有左孩子,根据操作3,将其入栈,Cur指向栈顶节点F;

  8. 由于F没有左右孩子,根据操作2,输出F,并将其出栈,将Pre指向F,Cur指向栈顶结点C,此时输出序列为:DEBF;

  9. 由于C的左孩子已经被输出,即满足Pre==Cur->lchild,根据操作2,输出C,并将其出栈,将Pre指向C,Cur指向栈顶结点A,此时输出序列为:DEBFC;

  10. 由于A的左右孩子已经被输出,根据操作2,输出A,并将其出栈,此时输出序列为:DEBFCA;

  11. 此时栈空,遍历结束。


代码:
    private static final Stack<TreeNode> stack = new Stack<>();    private static void traverseWithRecursive(TreeNode node) {        if (node == null) {            return;        }        traverseWithRecursive(node.getLeft());        traverseWithRecursive(node.getRight());        System.out.println(node.getName());    }    private static void traverseWithLoop(TreeNode node) {        if (node == null) {            return;        }        TreeNode pre = null;        TreeNode cur;        stack.push(node);        while(!stack.isEmpty()) {            cur = stack.peek();            if ((cur.getLeft() == null && cur.getRight() == null) ||                    (pre != null && (Objects.equals(cur.getLeft(), pre) || Objects.equals(cur.getRight(), pre)))) {                System.out.println(cur.getName());                pre = cur;                stack.pop();            } else {                if (cur.getRight() != null) {                    stack.push(cur.getRight());                }                if (cur.getLeft() != null) {                    stack.push(cur.getLeft());                }            }        }    }

总结:

  1. 对于后序遍历理解的比较困难,是看着代码写出来的。其他代码是自己根据遍历思想尝试出来的,并非最优代码
  2. 文章内容大部分参考详细讲解二叉树三种遍历方式的递归与非递归实现