做过二叉树前中后序遍历的同学应该有些印象,这三种遍历方式的递归法非常的简单,并且非常的相似,只需要稍微调整下处理逻辑(这里指将节点值添加进集合)的位置即可
递归法
前序遍历

java
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode() {}
* TreeNode(int val) { this.val = val; }
* TreeNode(int val, TreeNode left, TreeNode right) {
* this.val = val;
* this.left = left;
* this.right = right;
* }
* }
*/
class Solution {
public List<Integer> preorderTraversal(TreeNode root) {
List<Integer> result = new ArrayList<Integer>();
preorder(root, result);
return result;
}
public void preorder(TreeNode root, List<Integer> result) {
if (root == null) {
return;
}
result.add(root.val);
preorder(root.left, result);
preorder(root.right, result);
}
}
中序遍历

java
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode() {}
* TreeNode(int val) { this.val = val; }
* TreeNode(int val, TreeNode left, TreeNode right) {
* this.val = val;
* this.left = left;
* this.right = right;
* }
* }
*/
class Solution {
public List<Integer> inorderTraversal(TreeNode root) {
List<Integer> result = new ArrayList<Integer>();
inorder(root, result);
return result;
}
public void inorder(TreeNode root, List<Integer> result) {
if (root == null) {
return;
}
inorder(root.left, result);
result.add(root.val);
inorder(root.right, result);
}
}
后序遍历

java
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode() {}
* TreeNode(int val) { this.val = val; }
* TreeNode(int val, TreeNode left, TreeNode right) {
* this.val = val;
* this.left = left;
* this.right = right;
* }
* }
*/
class Solution {
public List<Integer> postorderTraversal(TreeNode root) {
List<Integer> result = new ArrayList<Integer>();
postorder(root, result);
return result;
}
public void postorder(TreeNode root, List<Integer> result) {
if (root == null) {
return;
}
postorder(root.left, result);
postorder(root.right, result);
result.add(root.val);
}
}
可是迭代法就没那么简单了,我第一次写的时候三种遍历除了都用了while循环都用了栈结构,其他的逻辑都是重新捋的,并不像👆迭代法一样,写出一个,另外两种遍历改下顺序就成= =,But不应该啊🤔🤔🤔🤔🤔🤔🤔
为什么递归可以,迭代不行?
二刷的时候又重新捋了捋这二叉树的遍历逻辑,发现其实迭代法之所以看起来如此简洁,是因为由于递归中方法不断调用调用,系统为我们隐性的维护了一个"栈",而在迭代法中,我们只能自己来维护这个"栈",而前序/中序/后序遍历之所以无法统一的主要原因在于处理节点的顺序和访问节点的顺序是不一致的(感谢卡哥的代码随想录),简单来说就是无法确定当前访问的节点是不是已经访问过了。
卡哥在代码随想录中提供了一种适用于三种遍历方式的统一写法,用这种统一的写法,先序遍历、中序遍历、后序遍历也像递归法一样只需要进行位置上的简单调整即可,这种统一写法的基本原理就是在处理过的节点再次入栈时,后面紧跟着添加一个"null",当遍历到"null"时,说明下一个节点已经访问过了,直接添加到结果集即可。
很机智的方法,很好的解决了"无法确定当前访问的节点是不是已经处理过了"这个问题,但其实在看代码的时候,还是有些不是很好理解的感觉,在整理笔记时,不经意看到了之前学习JVM GC相关的笔记,忽然看到------"三色标记法"~
简单介绍下垃圾回收的三色标记法,以免有不了解的朋友(知道的朋友可以直接跳过这part~)
三色标记法是一种垃圾回收算法,即在垃圾回收的过程中使用"白"、"灰"、"黑"三种颜色,分别表示"未被标记的垃圾对象"、"已被标记,但其引用的对象尚未全部被标记的非垃圾对象"和"已被标记,且其引用的对象也全部被标记的非垃圾对象",以便回收不再使用的对象,释放内存空间。
于是~有了这篇文章,希望能更好的帮助大家理并掌握二叉树的各种遍历( ̄∇ ̄)/
我们就姑且叫它"黑白标记法",即使用"白色(white)"和"黑色(white)"两种颜色来对入栈的节点进行标记,"白色"表示改节点未访问,"黑色"表示该节点已访问,基本逻辑就是在遍历的过程中,碰到"白色"的节点则继续对其子节点进行访问,碰到"黑色"则直接进行逻辑处理(这里的处理逻辑比较简单,就是添加进结果集,当然以后碰到更复杂的处理逻辑也是适用的)
基于上面的基本思想,我们可以在Leetcode官方提供的TreeNode结构的基础上,再"包"一层
java
// 官方定义的二叉树结构
public class TreeNode {
int val;
TreeNode left;
TreeNode right;
TreeNode() {}
TreeNode(int val) { this.val = val; }
TreeNode(int val, TreeNode left, TreeNode right) {
this.val = val;
this.left = left;
this.right = right;
}
}
// 再"包"一层
public class MyTreeNode {
// white 未处理 black 已处理
String type;
TreeNode node;
MyTreeNode() {}
MyTreeNode(String type, TreeNode node) {
this.type = type;
this.node = node;
}
}
基于上面构建的MyTreeNode
,我们开始遍历!
黑白标记法
前序遍历

java
class Solution {
public List<Integer> preorderTraversal(TreeNode root) {
List<Integer> result = new ArrayList<>();
Stack<MyTreeNode> stack=new Stack<>();
stack.add(new MyTreeNode("white",root));
while(!stack.isEmpty()){
MyTreeNode cur = stack.pop();
if(cur.node==null){
continue;
}
if(cur.type.equals("white")){
stack.push(new MyTreeNode("white",cur.node.right));
stack.push(new MyTreeNode("white",cur.node.left));
stack.push(new MyTreeNode("black",cur.node));
}else{
result.add(cur.node.val);
}
}
return result;
}
}
中序遍历

java
class Solution {
public List<Integer> inorderTraversal(TreeNode root) {
List<Integer> result=new ArrayList<>();
Deque<MyTreeNode> stack = new LinkedList<>();
stack.add(new MyTreeNode("white",root));
while(!stack.isEmpty()){
MyTreeNode cur=stack.pop();
if(cur.node==null){
continue;
}
if(cur.type.equals("white")){
stack.push(new MyTreeNode("white",cur.node.right));
stack.push(new MyTreeNode("black",cur.node));
stack.push(new MyTreeNode("white",cur.node.left));
}else{
result.add(cur.node.val);
}
}
return result;
}
}
后序遍历

java
class Solution {
public List<Integer> postorderTraversal(TreeNode root) {
List<Integer> result = new ArrayList<>();
Deque<MyTreeNode> stack=new LinkedList<>();
stack.add(new MyTreeNode("white",root));
while(!stack.isEmpty()){
MyTreeNode cur=stack.pop();
if(cur.node==null){
continue;
}
if(cur.type.equals("white")){
stack.push(new MyTreeNode("black",cur.node));
stack.push(new MyTreeNode("white",cur.node.right));
stack.push(new MyTreeNode("white",cur.node.left));
}else{
result.add(cur.node.val);
}
}
return result;
}
}
ps:如果也是像我一样使用的是Deque请注意方法add()
和push()
的区别,不然可能结果正好相反嘿嘿(˶‾᷄ ⁻̫ ‾᷅˵)

可以看到只是在入栈顺序进行了调整,就完成了这三种不同的遍历顺序
搞定撒花( ̄∇ ̄)/🎉🎉🎉~~~~~~