二叉树遍历

1 二叉树的先序遍历

先序遍历从树的头节点开始,按照先根节点,然后左子树,再右子树的顺序遍历整棵树。
先给出二叉树节点的定义

package binaryStree;
/**
 * 二叉树节点定义
 * @author 小锅巴
 */
public class TreeNode {
    public TreeNode left;//左链接
    public TreeNode right;//右链接
    public String value;

    public TreeNode(String value){
        this.value = value;
    }
}

文中所构造的二叉树:

递归实现就不多说了。重点说下非递归实现,个人觉得要正真理解二叉树的遍历就应该写非递归遍历,递归隐藏了遍历的过程。

方式一:充分利用栈的“先进后出”,每次弹出栈顶节点
1. 声明一个树节点变量curNode,声明一个栈s,将头节点压入s中
2. 出栈,将出栈节点赋给前节点curNode,打印节点的value
3. 若curNode有孩子,则将孩子压入s中,先右孩子,再左孩子
4. 重复2 3,直到栈为空

方式二:利用二叉树的递归定义,先不停的遍历左子树并将沿路节点进栈,然后出栈切换到右子树
1. 声明一个树节点变量curNode,curNode初始化为树的头节点,声明一个栈s,
2. 先打印节点的value,将其压入s,然后将curNode的左孩子赋给curNode,
3. 重复2,直到当前节点为null,出栈,将出栈节点赋给curNode,再将curNode的右孩子赋给curNode
4. 重复2 3,直到当前结点为null且栈为空
5.
注意这里的条件:当前节点为null且栈为空,如果只有前者,那么遍历到叶子节点时,比如遍历到了树的左边界,此时栈是不为空的,右子树还没得到遍历;如果只有后者,那么一开始就要将头节点入栈,否则无法完成遍历,可是在遍历左子树时要将当前结点压入栈,那么树的头节点就会被压入两次,从而导致头节点会被遍历两次

package binaryStree;

import java.util.Stack;

/**
 * 二叉树先序遍历
 * @author 小锅巴
 */
public class PreOrdreTraversal {
    //递归实现
    public static void traversal_1(TreeNode node){
        if(node == null)
            return;
        System.out.print(node.value+" ");
        traversal_1(node.left);
        traversal_1(node.right);
    }

    //非递归实现方式一
    public static void traversal_2(TreeNode node){
        TreeNode curNode;
        Stack<TreeNode> s = new Stack<>();
        s.push(node);
        //充分利用栈的先进后出
        while(!s.isEmpty()){
            curNode = s.pop();
            System.out.print(curNode.value+" ");
            if(curNode.right != null)
                s.push(curNode.right);
            if(curNode.left != null)
                s.push(curNode.left);
        }
    }

    //非递归实现方式二
    public static void traversal_3(TreeNode node){
        TreeNode curNode = node;
        Stack<TreeNode> s = new Stack<>();
        while(!(curNode == null && s.isEmpty())){
            //实现遍历左子树
            while(curNode != null){
                System.out.print(curNode.value+" ");
                s.push(curNode);
                curNode = curNode.left;
            }
            //实现遍历右子树
            curNode = s.pop();
            curNode = curNode.right;
        }
    }
    public static void main(String[] args) {
        //新键一个二叉树
        TreeNode root = new TreeNode("A");
        TreeNode node2 = new TreeNode("B"); 
        TreeNode node3 = new TreeNode("C"); 
        TreeNode node4 = new TreeNode("D"); 
        TreeNode node5 = new TreeNode("E"); 
        TreeNode node6 = new TreeNode("F");
        TreeNode node7 = new TreeNode("G");
        TreeNode node8 = new TreeNode("H");
        root.left = node2;
        root.right = node3;
        node2.left = node4;
        node2.right = node5;
        node3.left = node6;
        node4.right = node7;
        node7.left = node8;

        System.out.println("递归实现:");
        traversal_1(root);
        System.out.println();

        System.out.println("非递归实现方式一:");
        traversal_2(root);
        System.out.println();

        System.out.println("非递归实现方式二:");
        traversal_3(root);
        System.out.println();
    }
}
/**
输出
递归实现:
A B D G H E C F 
非递归实现方式一:
A B D G H E C F 
非递归实现方式二:
A B D G H E C F 
 */

2 二叉树的中序遍历

中序遍历又称顺序遍历,先左子树,根节点,最后右子树。
非递归实现思路与先序遍历的第二种实现类似,也是先不停的遍历左子树,并将沿路节点都压入栈,直到当前节点为空,此时遍历到了树的左边界,出栈,然后打印当前节点的值,再切换到右子树

  1. 声明一个树节点变量curNode,curNode初始化为树的头节点,声明一个栈s,
  2. 将其压入s,然后将curNode的左孩子赋给curNode,
  3. 重复2,直到当前节点为null,出栈,将出栈节点赋给curNode,打印当前节点curNode的value,再将curNode的右孩子赋给curNode
  4. 重复2 3,直到当前结点为null且栈为空
package binaryStree;

import java.util.Stack;
/**
 * 二叉树中序遍历
 * @author 小锅巴
 */
public class InOrderTraversal {
    public static void traversal_1(TreeNode node){
        if(node == null)
            return;
        traversal_1(node.left);
        System.out.print(node.value+" ");
        traversal_1(node.right);
    }
    public static void traversal_2(TreeNode node){
        TreeNode curNode = node;
        Stack<TreeNode> s = new Stack<TreeNode>();
        while(curNode != null || !s.isEmpty()){
            //不停遍历左子树,直到树的边界
            while(curNode != null){
                s.push(curNode);
                curNode = curNode.left;
            }
            curNode = s.pop();
            //实现先访问左孩子
            System.out.print(curNode.value+" ");
            //切换到右子树
            curNode = curNode.right;
        }
    }
    public static void main(String[] args) {
        //新键一个二叉树
        TreeNode root = new TreeNode("A");
        TreeNode node2 = new TreeNode("B"); 
        TreeNode node3 = new TreeNode("C"); 
        TreeNode node4 = new TreeNode("D"); 
        TreeNode node5 = new TreeNode("E"); 
        TreeNode node6 = new TreeNode("F");
        TreeNode node7 = new TreeNode("G");
        TreeNode node8 = new TreeNode("H");
        root.left = node2;
        root.right = node3;
        node2.left = node4;
        node2.right = node5;
        node3.left = node6;
        node4.right = node7;
        node7.left = node8;

        System.out.println("递归实现:");
        traversal_1(root);
        System.out.println();

        System.out.println("非递归实现方式:");
        traversal_2(root);
        System.out.println();
    }
}
/**
输出
递归实现:
D H G B E A F C 
非递归实现方式:
D H G B E A F C 
 */

3 二叉树的后序遍历

后序遍历先左子树,右子树,最后根节点。如果是用一个栈来实现,相比先序遍历和中序遍历,非递归实现要麻烦些,因为是先左右子树,再根节点,所以一个根节点实际上是访问了两次,而先序和中序是一次,即弹出栈顶元素即可实现回到父节点,但是后序不可以,访问左子树返回到父节点若弹出栈顶节点,那么访问右子树就回不到父节点。

方式一:使用两个栈,一个实现后续遍历,另一个存储沿路遍历的节点,注意每个节点都要压入弹出这两个栈的,故压入顺序和一个栈不同
1. 声明两个栈s1 s2,并将树的头节点压入s1,声明树节点curNode表示当前节点,
2. s1出栈,出栈元素赋给curNode,并将curNode压入s2,若当前节点curNode有孩子,则将孩子压入s1,先左孩子,再右孩子
3. 重复2直到栈s1为空
4. 依次将s2的节点出栈并打印

方式二:使用一个栈,对比中序遍历,打印当前节点的条件是当前节点为叶子节点或者其子树得到了遍历,这时就可以弹出栈顶元素,否则不弹出且将当前节点的孩子压入栈
1. 声明一个栈s,并将树的头节点压入s,声明两个树节点curNode和preNode,preNode初始化为null,preNode表示以该节点为根的整棵树遍历完了
2. 获得s的栈顶节点,赋给curNode,不出栈,若节点curNode的子树都被遍历了(即curNode的孩子为preNode)或者该节点为叶子节点,则可打印其值,然后弹出栈顶节点, 最后将preNode更新为当前节点Node,
3. 接2,否则不打印、不出栈、不更新preNode,而是将curNode的孩子入栈,先右孩子,再左孩子
4. 重复2 3,直到栈为空

package binaryStree;

import java.util.Stack;
/**
 * 后序遍历
 * @author 小锅巴
 */
public class PostOrderTraversal {
    //递归遍历
    public static void traversal_1(TreeNode node){
        if(node == null)
            return;
        traversal_1(node.left);
        traversal_1(node.right);
        System.out.print(node.value+" ");
    }

    //使用两个栈
    public static void traversal_2(TreeNode node){
        TreeNode curNode;
        Stack<TreeNode> s1 = new Stack<>();
        Stack<TreeNode> s2 = new Stack<>();
        s1.push(node);
        //实现遍历
        while(!s1.isEmpty()){
            curNode = s1.pop();
            s2.push(curNode);//s2存储沿路的节点
            if(curNode.left != null)
                s1.push(curNode.left);
            if(curNode.right != null)
                s1.push(curNode.right);
        }
        //实现打印
        while(!s2.isEmpty())
            System.out.print(s2.pop().value+" ");
    } 

    //使用一个栈
    public static void traversal_3(TreeNode node){
        TreeNode curNode;
        TreeNode preNode = null;
        Stack<TreeNode> s = new Stack<>();
        s.push(node);
        while(!s.isEmpty()){
            curNode = s.peek();
            if(
                    (curNode.left == null && curNode.right == null)//此时curNode为叶子节点
                    || (preNode != null && (curNode.left == preNode || curNode.right == preNode))
                    //curNode的子树得到了遍历,至少有一个子树,且子树不能为空,不然就可能是叶子节点
                    )
            {
                System.out.print(curNode.value+" ");
                preNode = curNode;//标记此节点为根的整棵树都遍历完了
                s.pop();//遍历完了就可以出栈
            }else{
                if(curNode.right != null)
                    s.push(curNode.right);
                if(curNode.left != null)
                    s.push(curNode.left);
            }
        }
    }
    public static void main(String[] args) {
        //新键一个二叉树
        TreeNode root = new TreeNode("A");
        TreeNode node2 = new TreeNode("B"); 
        TreeNode node3 = new TreeNode("C"); 
        TreeNode node4 = new TreeNode("D"); 
        TreeNode node5 = new TreeNode("E"); 
        TreeNode node6 = new TreeNode("F");
        TreeNode node7 = new TreeNode("G");
        TreeNode node8 = new TreeNode("H");
        root.left = node2;
        root.right = node3;
        node2.left = node4;
        node2.right = node5;
        node3.left = node6;
        node4.right = node7;
        node7.left = node8;

        System.out.println("递归实现:");
        traversal_1(root);
        System.out.println();

        System.out.println("非递归实现方式一:");
        traversal_2(root);
        System.out.println();

        System.out.println("非递归实现方式二:");
        traversal_3(root);
        System.out.println();
    }
}
/**
输出:
递归实现:
H G D E B F C A 
非递归实现方式一:
H G D E B F C A 
非递归实现方式二:
H G D E B F C A 
 */

其实还有一种实现的方法,也是使用两个栈,其中一个栈用来标记当前节点为根节点的树是否遍历完,不如一个栈和两个TreeNode变量的实现高效,感兴趣的话可以参考这里

4 二叉树按层遍历

4.1不给出层信息

即遍历结果是一行,不是多行。遍历按层从上到下,每层从左到右。

  1. 声明一个队列q,声明一个树节点curNode,初始时将树的头节点压入队列q
  2. 出队列,将出队列的节点赋给curNode,打印当前节点curNode的value,并将curNode的孩子压入队列,先左孩子,再右孩子
  3. 重复2,直到队列为空

代码后面一并给出

4.2给出层信息

即没层打印完要换行。还是从上到下,从左到右
前面的思路可以实现按层遍历了,现在的问题是如何换行,使用两个TreeNode变量last表示当前行的最后一个节点,nlast表示下一行的最后一个节点,当当前节点curNode为last时就换行,并将last更新为nlast,那么nlast的更新如何实现呢?跟踪每次压入队列q的节点,考虑curNode为last时,此时需要把curNode的孩子压入队列,所以可实现nlast的更新。可以得出换行要放在curNode孩子压入队列的后面。

package binaryStree;

import java.util.*;

/**
 * 二叉树层序遍历
 * @author 小锅巴
 */
public class LayerTraversal {
    //不给出行信息
    public static void traversal_1(TreeNode node){
        Queue<TreeNode> q = new LinkedList<>();
        q.add(node);
        TreeNode curNode;

        while(!q.isEmpty()){
            curNode = q.poll();
            System.out.print(curNode.value+" ");

            if(curNode.left != null)
                q.add(curNode.left);
            if(curNode.right != null)
                q.add(curNode.right);
        }
    }

    //给出行信息
    public static void traversal_2(TreeNode node){
        Queue<TreeNode> q = new LinkedList<>();
        q.add(node);
        TreeNode curNode;
        TreeNode last = node;//方便头节点的换行
        TreeNode nlast = null;

        while(!q.isEmpty()){
                curNode = q.poll();
                System.out.print(curNode.value+" ");

                if(curNode.left != null){
                    nlast = curNode.left;//和下面的nlast赋值语句一起构成nlast的更新
                    q.add(curNode.left);
                }
                if(curNode.right != null){
                    nlast = curNode.right;
                    q.add(curNode.right);
                }

                //换行并更新last
                if(curNode == last){
                    System.out.println();
                    last = nlast;
                }
            }
        }
    public static void main(String[] args) {
        //新建一个二叉树
        TreeNode root = new TreeNode("A");
        TreeNode node2 = new TreeNode("B"); 
        TreeNode node3 = new TreeNode("C"); 
        TreeNode node4 = new TreeNode("D"); 
        TreeNode node5 = new TreeNode("E"); 
        TreeNode node6 = new TreeNode("F");
        TreeNode node7 = new TreeNode("G");
        TreeNode node8 = new TreeNode("H");
        root.left = node2;
        root.right = node3;
        node2.left = node4;
        node2.right = node5;
        node3.left = node6;
        node4.right = node7;
        node7.left = node8;

        System.out.println("按层遍历,但是不给出行信息:");
        traversal_1(root);
        System.out.println();

        System.out.println("按层遍历,给出行信息:");
        traversal_2(root);
        System.out.println();
    }
}
/**
输出:
按层遍历,但是不给出行号信息:
A B C D E F G H 
按层遍历,给出行号信息:
A 
B C 
D E F 
G 
H 
 */

5 二叉树遍历推断

这道题来自2016年4月阿里巴巴实习生招聘的一个小题:
已知一颗二叉树的先序遍历为:A B C D E F G H I J,中序遍历为:C B A E F D I H J G,那么其后序遍历为?
如果真正理解了二叉树的遍历,是很好做的,很容易把这颗二叉树重建出来,比较基础。
1. 从先序遍历知A为树的头节点,再从中序遍历知C B是在A的左子树中,E F D I H J G在A的右子树中
2. 从先序遍历B C和中序遍历C B,知B为A的左孩子,C为B的左孩子
3. 从先序D E F G H I J和中序E F D I H J G知D为A的右孩子,E F为D的左子树,G H I J为D的右子树
4. 由先序E F和中序E F,知E为D的左孩子,F为E的右孩子
5. 由先序G H I J和中序I H J G,知G为D的右孩子,H为G的左孩子,I为G的左孩子,J为G的右孩子

根据以上推导(有点分治的味道),重建二叉树如下:

所以后序遍历为C B F E I J H G D A
附上完整代码:

package binaryTree;

import java.util.Stack;

/**
 * 一个二叉树的测试
 * @author 小锅巴
 */
class TreeNode<V> {
    public TreeNode left;
    public  TreeNode right;
    public V value;

    public TreeNode(V value){
        this.value = value;
    }
}
public class Test {
    public static void preOrderTraversal(TreeNode node){
        Stack<TreeNode> s = new Stack<>();
        TreeNode curNode;
        s.push(node);
        while(!s.isEmpty()){
            curNode = s.pop();
            System.out.print(curNode.value+" ");
            if(curNode.right != null)
                s.push(curNode.right);
            if(curNode.left != null)
                s.push(curNode.left);
        }
    }

    public static void inOrderTraversal(TreeNode node){
        Stack<TreeNode> s = new Stack<>();
        TreeNode curNode = node;
//      s.push(node);
        while(!(curNode == null && s.isEmpty())){
            while(curNode != null){
                s.push(curNode);
                curNode = curNode.left;
            }
            if(!s.isEmpty()){
            curNode = s.pop();
            System.out.print(curNode.value+" ");
            curNode = curNode.right;
            }
        }
    }
    public static void postOrderTraversal(TreeNode node){
        Stack<TreeNode> s1 = new Stack<>(); 
        Stack<TreeNode> s2 = new Stack<>(); 
        TreeNode curNode;
        s1.push(node);
        while(!s1.isEmpty()){
            curNode = s1.pop();
            s2.push(curNode);
            if(curNode.left != null)
                s1.push(curNode.left);
            if(curNode.right != null)
                s1.push(curNode.right);
        }
        while(!s2.isEmpty())
            System.out.print(s2.pop().value+" ");
    }
    public static void main(String[] args) {
        //根据推导结果重建二叉树
        TreeNode<String> root = new TreeNode<>("A");
        TreeNode<String> node2 = new TreeNode<>("B"); 
        TreeNode<String> node3 = new TreeNode<>("C"); 
        TreeNode<String> node4 = new TreeNode<>("D"); 
        TreeNode<String> node5 = new TreeNode<>("E"); 
        TreeNode<String> node6 = new TreeNode<>("F");
        TreeNode<String> node7 = new TreeNode<>("G");
        TreeNode<String> node8 = new TreeNode<>("H");
        TreeNode<String> node9 = new TreeNode<>("I");
        TreeNode<String> node10 = new TreeNode<>("J");
        root.left = node2;
        root.right = node4;
        node2.left = node3;
        node4.left = node5;
        node5.right = node6;
        node4.right = node7;
        node7.left = node8;
        node8.left = node9;
        node8.right = node10;

        System.out.print("先序遍历:");
        preOrderTraversal(root);
        System.out.println();

        System.out.print("中序遍历:");
        inOrderTraversal(root);
        System.out.println();

        System.out.print("后序遍历:");
        postOrderTraversal(root);
        System.out.println();

        System.out.println("按层遍历:");
        LayerTraversal.traversal_2(root);
        System.out.println();
    }
}
/**
输出:
先序遍历:A B C D E F G H I J 
中序遍历:C B A E F D I H J G 
后序遍历:C B F E I J H G D A 
按层遍历:
A 
B D 
C E G 
F H 
I J 
 */
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值