本文由 简悦 SimpRead 转码, 原文地址 https://www.jianshu.com/p/456af5480cee
二叉树有多种遍历方法,有层次遍历、深度优先遍历、广度优先遍历等。
本文只涉及二叉树的先序、中序、后序的递归和非递归遍历。
涉及到的代码都用 Java 编写。
首先给出二叉树节点类:
树节点:
1 | class TreeNode { |
无论是哪种遍历方法,考查节点的顺序都是一样的 (思考做试卷的时候,人工遍历考查顺序)。只不过有时候考查了节点,将其暂存,需要之后的过程中输出。
如图 1 所示,三种遍历方法 (人工) 得到的结果分别是:
先序:1 2 4 6 7 8 3 5
中序:4 7 6 8 2 1 3 5
后序:7 8 6 4 2 5 3 1
三种遍历方法的考查顺序一致,得到的结果却不一样,原因在于:
先序:考察到一个节点后,即刻输出该节点的值,并继续遍历其左右子树。(根左右)
中序:考察到一个节点后,将其暂存,遍历完左子树后,再输出该节点的值,然后遍历右子树。(左根右)
后序:考察到一个节点后,将其暂存,遍历完左右子树后,再输出该节点的值。(左右根)
先序遍历
递归先序遍历
递归先序遍历很容易理解,先输出节点的值,再递归遍历左右子树。中序和后序的递归类似,改变根节点输出位置即可。
1 | // 递归先序遍历 |
非递归先序遍历
因为要在遍历完节点的左子树后接着遍历节点的右子树,为了能找到该节点,需要使用栈来进行暂存。中序和后序也都涉及到回溯,所以都需要用到栈。
遍历过程参考注释
1 | // 非递归先序遍历 |
先序遍历结果:
递归先序遍历: 1 2 4 6 7 8 3 5
非递归先序遍历:1 2 4 6 7 8 3 5
中序遍历
递归中序遍历
过程和递归先序遍历类似
1 | // 递归中序遍历 |
非递归中序遍历
和非递归先序遍历类似,唯一区别是考查到当前节点时,并不直接输出该节点。
而是当考查节点为空时,从栈中弹出的时候再进行输出 (永远先考虑左子树,直到左子树为空才访问根节点)。
1 | // 非递归中序遍历 |
中序遍历结果
递归中序遍历: 4 7 6 8 2 1 3 5
非递归中序遍历:4 7 6 8 2 1 3 5
后序遍历
递归后序遍历
过程和递归先序遍历类似
1 | // 递归后序遍历 |
非递归后序遍历
后续遍历和先序、中序遍历不太一样。
后序遍历在决定是否可以输出当前节点的值的时候,需要考虑其左右子树是否都已经遍历完成。
所以需要设置一个 lastVisit 游标。
若 lastVisit 等于当前考查节点的右子树,表示该节点的左右子树都已经遍历完成,则可以输出当前节点。
并把 lastVisit 节点设置成当前节点,将当前游标节点 node 设置为空,下一轮就可以访问栈顶元素。
否者,需要接着考虑右子树,node = node.right。
以下考虑后序遍历中的三种情况:
如图 3 所示,从节点 1 开始考查直到节点 4 的左子树为空。
注:此时的游标节点 node = 4.left == null。
此时需要从栈中查看 Peek() 栈顶元素。
发现节点 4 的右子树非空,需要接着考查右子树,4 不能输出,node = node.right。
如图 4 所示,考查到节点 7(7.left == null,7 是从栈中弹出),其左右子树都为空,可以直接输出 7。
此时需要把 lastVisit 设置成节点 7,并把游标节点 node 设置成 null,下一轮循环的时候会考查栈中的节点 6。
如图 5 所示,考查完节点 8 之后 (lastVisit == 节点 8),将游标节点 node 赋值为栈顶元素 6,节点 6 的右子树正好等于节点 8。表示节点 6 的左右子树都已经遍历完成,直接输出 6。
此时,可以将节点直接从栈中弹出 Pop(),之前用的只是 Peek()。
将游标节点 node 设置成 null。
1 | // 非递归后序遍历 |
后序遍历结果
递归后序遍历: 7 8 6 4 2 5 3 1
非递归后序遍历:7 8 6 4 2 5 3 1
有任何错误请指正,谢谢!
写文字不容易,且写且珍惜!