递归
class Solution {
public List<Integer> preorderTraversal(TreeNode root) {
List<Integer> res = new ArrayList<Integer>();
dfs(root, res);
return res;
}
// 前序 根左右
public void dfs(TreeNode root, List<Integer> res) {
if (root == null) return;
res.add(root.val);
dfs(root.left, res);
dfs(root.right, res);
}
// 中序 左根右
public void dfs(TreeNode root, List<Integer> res) {
if (root == null) return;
dfs(root.left, res);
res.add(root.val);
dfs(root.right, res);
}
// 后序 左右根
public void dfs(TreeNode root, List<Integer> res) {
if (root == null) return;
dfs(root.left, res);
dfs(root.right, res);
res.add(root.val);
}
}
迭代
前序
应该先打印左子树,然后右子树。所以先加入Stack的就是右子树,然后左子树
class Solution {
public List<Integer> preorderTraversal(TreeNode root) {
List<Integer> a = new ArrayList();
if (root == null) return a;
Stack<TreeNode> stack = new Stack<>();
stack.push(root);
while (!stack.isEmpty()) {
TreeNode node = stack.pop();
a.add(node.val);
if (node.right != null) {
stack.push(node.right);
}
if (node.left != null) {
stack.push(node.left);
}
}
return a;
}
}
中序
class Solution {
public List<Integer> inorderTraversal(TreeNode root) {
List<Integer> res = new ArrayList<Integer>();
Stack<TreeNode> stack = new Stack<TreeNode>();
while(stack.size()>0 || root!=null) {
//不断往左子树方向走,每走一次就将当前节点保存到栈中
//这是模拟递归的调用
if(root!=null) {
stack.add(root);
root = root.left;
//当前节点为空,说明左边走到头了,从栈中弹出节点并保存
//然后转向右边节点,继续上面整个过程
} else {
TreeNode tmp = stack.pop();
res.add(tmp.val);
root = tmp.right;
}
}
return res;
}
}
后序
把前序改版(根右左)的结果入栈,再出栈就是后序(左右根)
模板解法
class Solution {
// 前序
// 它先将根节点 cur 和所有的左孩子入栈并加入结果中,直至 cur 为空。
// 然后,每弹出一个栈顶元素 tmp,就到达它的右孩子,
// 再将这个节点当作 cur 重新按上面的步骤来一遍,直至栈为空。
public List<Integer> preorderTraversal(TreeNode root) {
List<Integer> a = new ArrayList();
if (root == null) return a;
Stack<TreeNode> stack = new Stack<>();
TreeNode cur = root;
while (!stack.isEmpty() || cur != null) {
while(cur != null) {
a.add(cur.val);
stack.push(cur);
cur = cur.left;
}
cur = stack.pop();
cur = cur.right;
}
return a;
}
// 中序
// 和前序遍历的代码完全相同,只是在出栈的时候才将节点 tmp 的值加入到结果中
public List<Integer> inorderTraversal(TreeNode root) {
List<Integer> a = new ArrayList();
if (root == null) return a;
Stack<TreeNode> stack = new Stack<>();
TreeNode cur = root;
while (!stack.isEmpty() || cur != null) {
while(cur != null) {
stack.push(cur);
cur = cur.left;
}
cur = stack.pop();
a.add(cur.val);
cur = cur.right;
}
return a;
}
// 后序
// 和前序遍历的代码完全相同,只是节点 cur 先到达最右端的叶子节点并将路径上的节点入栈;
// 然后每次从栈中弹出一个元素后,cur 到达它的左孩子,并将左孩子看作 cur 继续执行上面的步骤。
// 最后将结果反向输出即可。
public List<Integer> postorderTraversal(TreeNode root) {
List<Integer> a = new ArrayList();
if (root == null) return a;
Stack<TreeNode> stack = new Stack<>();
TreeNode cur = root;
while (!stack.isEmpty() || cur != null) {
while(cur != null) {
a.add(cur.val);
stack.push(cur);
cur = cur.right;
}
cur = stack.pop();
cur = cur.left;
}
Collections.reverse(a);
return a;
}
}
Morris解法
Morris的整体思路就是将 以某个根结点开始,找到它左子树的最右侧节点之后与这个根结点进行连接
我们可以从 图2 看到,如果这么连接之后,cur 这个指针是可以完整的从一个节点顺着下一个节点遍历,将整棵树遍历完毕,直到 7 这个节点右侧没有指向。
public static void preOrderMorris(TreeNode head) {
if (head == null) {
return;
}
TreeNode cur1 = head;//当前开始遍历的节点
TreeNode cur2 = null;//记录当前结点的左子树
while (cur1 != null) {
cur2 = cur1.left;
if (cur2 != null) {
while (cur2.right != null && cur2.right != cur1) {//找到当前左子树的最右侧节点,且这个节点应该在指向根结点之前,否则整个节点又回到了根结点。
cur2 = cur2.right;
}
if (cur2.right == null) {//这个时候如果最右侧这个节点的右指针没有指向根结点,创建连接然后往下一个左子树的根结点进行连接操作。
cur2.right = cur1;
cur1 = cur1.left;
continue;
} else {//当左子树的最右侧节点有指向根结点,此时说明我们已经回到了根结点并重复了之前的操作,同时在回到根结点的时候我们应该已经处理完 左子树的最右侧节点 了,把路断开。
cur2.right = null;
}
}
cur1 = cur1.right;//一直往右边走,参考图
}
}
前序
- 在某个根结点创建连线的时候打印。因为我们是顺着左边的根节点来创建连线,且创建的过程只有一次。
- 打印某些自身无法创建连线的节点,也就是叶子节点。
public static void preOrderMorris(TreeNode head) {
if (head == null) {
return;
}
TreeNode cur1 = head;
TreeNode cur2 = null;
while (cur1 != null) {
cur2 = cur1.left;
if (cur2 != null) {
while (cur2.right != null && cur2.right != cur1) {
cur2 = cur2.right;
}
if (cur2.right == null) {
cur2.right = cur1;
System.out.print(cur1.value + " ");
cur1 = cur1.left;
continue;
} else {
cur2.right = null;
}
} else {
System.out.print(cur1.value + " ");
}
cur1 = cur1.right;
}
}
中序
从最左侧开始顺着右节点打印。也就是在将cu1切换到上层节点的时候。
public static void inOrderMorris(TreeNode head) {
if (head == null) {
return;
}
TreeNode cur1 = head;
TreeNode cur2 = null;
while (cur1 != null) {
cur2 = cur1.left;
//构建连接线
if (cur2 != null) {
while (cur2.right != null && cur2.right != cur1) {
cur2 = cur2.right;
}
if (cur2.right == null) {
cur2.right = cur1;
cur1 = cur1.left;
continue;
} else {
cur2.right = null;
}
}
System.out.print(cur1.value + " ");
cur1 = cur1.right;
}
}
中序2
强行把一棵二叉树改成一段链表结构
class Solution {
public List<Integer> inorderTraversal(TreeNode root) {
List<Integer> res = new ArrayList<Integer>();
TreeNode pre = null;
while(root!=null) {
//如果左节点不为空,就将当前节点连带右子树全部挂到
//左节点的最右子树下面
if(root.left!=null) {
pre = root.left;
while(pre.right!=null) {
pre = pre.right;
}
pre.right = root;
//将root指向root的left
TreeNode tmp = root;
root = root.left;
tmp.left = null;
//左子树为空,则打印这个节点,并向右边遍历
} else {
res.add(root.val);
root = root.right;
}
}
return res;
}
}
后序
后序遍历就比较复杂了哈,先看一下图
当我们到达最左侧,也就是左边连线已经创建完毕了。
打印 4
打印 5 2
打印 6
打印 7 3 1
我们将一个节点的连续右节点当成一个单链表来看待。
当我们返回上层之后,也就是将连线断开的时候,打印下层的单链表。
比如返回到 2,此时打印 4
比如返回到 1,此时打印 5 2
比如返回到 3,此时打印 6
那么我们只需要将这个单链表逆序打印就行了,下文也给出了 单链表逆序代码
这里不应该打印当前层,而是下一层,否则根结点会先与右边打印。
//后序Morris
public static void postOrderMorris(TreeNode head) {
if (head == null) {
return;
}
TreeNode cur1 = head;//遍历树的指针变量
TreeNode cur2 = null;//当前子树的最右节点
while (cur1 != null) {
cur2 = cur1.left;
if (cur2 != null) {
while (cur2.right != null && cur2.right != cur1) {
cur2 = cur2.right;
}
if (cur2.right == null) {
cur2.right = cur1;
cur1 = cur1.left;
continue;
} else {
cur2.right = null;
postMorrisPrint(cur1.left);
}
}
cur1 = cur1.right;
}
postMorrisPrint(head);
}
//打印函数
public static void postMorrisPrint(TreeNode head) {
TreeNode reverseList = postMorrisReverseList(head);
TreeNode cur = reverseList;
while (cur != null) {
System.out.print(cur.value + " ");
cur = cur.right;
}
postMorrisReverseList(reverseList);
}
//翻转单链表
public static TreeNode postMorrisReverseList(TreeNode head) {
TreeNode cur = head;
TreeNode pre = null;
while (cur != null) {
TreeNode next = cur.right;
cur.right = pre;
pre = cur;
cur = next;
}
return pre;
}
颜色标记法
其核心思想如下:
- 使用颜色标记节点的状态,新节点为白色,已访问的节点为灰色。
- 如果遇到的节点为白色,则将其标记为灰色,然后将其右子节点、自身、左子节点依次入栈。
- 如果遇到的节点为灰色,则将节点的值输出。
- 中序如下,如要实现前序、后序遍历,只需要调整左右子节点的入栈顺序即可。
class Solution {
class ColorNode {
TreeNode node;
String color;
public ColorNode(TreeNode node,String color){
this.node = node;
this.color = color;
}
}
public List<Integer> inorderTraversal(TreeNode root) {
if(root == null) return new ArrayList<Integer>();
List<Integer> res = new ArrayList<>();
Stack<ColorNode> stack = new Stack<>();
stack.push(new ColorNode(root,"white"));
while(!stack.empty()){
ColorNode cn = stack.pop();
if(cn.color.equals("white")){
if(cn.node.right != null) stack.push(new ColorNode(cn.node.right,"white"));
stack.push(new ColorNode(cn.node,"gray"));
if(cn.node.left != null)stack.push(new ColorNode(cn.node.left,"white"));
}else{
res.add(cn.node.val);
}
}
return res;
}
}