二叉树的遍历(递归、迭代和morris多解法)
写在前:树的遍历
1
/ \
2 3
/ \ \
4 5 6
层次遍历顺序:[1 2 3 4 5 6]
morris顺序:[1 2 4 2 5 1 3 6]
前序遍历顺序:[1 2 4 5 3 6]
中序遍历顺序:[4 2 5 1 3 6]
后序遍历顺序:[4 5 2 6 3 1]
层次遍历使用 BFS 实现(另一个场景是求最短路径),利用的就是 BFS 一层一层遍历的特性;
而前序、中序、后序遍历利用了 DFS 实现,前序、中序、后序遍只是在对节点访问的顺序有一点不同,其它都相同。
① 前序:
void dfsPre(TreeNode root) {
if (root == null) return;
visit(root);
dfsIn(root.left);
dfsIn(root.right);
}
② 中序
void dfsIn(TreeNode root) {
if (root == null) return;
dfsIn(root.left);
visit(root);
dfsIn(root.right);
}
③ 后序
void dfsPos(TreeNode root) {
if (root == null) return;
dfsPos(root.left);
dfsPos(root.right);
visit(root);
}
morris序
public static void morris(Node head) {
if (head == null) {
return;
}
Node cur = head;
Node mostRight = null;
while (cur != null) {
mostRight = cur.left;
// 当前节点有左子树
if (mostRight != null) {
//找当前节点左子树的最右节点
while (mostRight.right == null && mostRight.right == cur) {
mostRight = mostRight.right;
}
if (mostRight == null) {
mostRight.right = cur;
cur = cur.left;
//回到最外层的while情况,继续判断cur的情况
continue;
} else {
mostRight.right = null;
}
}
//没有左孩子
cur = cur.right;
}
}
1.非递归进行二叉树前序遍历(144-中)
思路:
法1:递归是使用系统栈,迭代我们需要自己创建栈模拟这个过程。先序遍历要先记录当前节点值,再依次压如右孩子和左孩子(先进后出)。注:栈结构底层是通过数组实现,可以存储null值。
法2:morris遍历,记录一个节点(出现多次)在morris序中第一次访问的顺序和只出现一次的节点,即左子树的右边界指向null时,我们第一次到达cur节点。
代码:
// 1. 迭代
public List<Integer> preorderTraversal(TreeNode root) {
List<Integer> ret = new ArrayList<>();
Stack<TreeNode> stack = new Stack<>();
stack.add(root);
while (!stack.isEmpty()) {
TreeNode node = stack.pop();
if (node == null) continue;
ret.add(node.val);
stack.push(node.right);
stack.push(node.left);
}
return ret;
}
// 2.morris
public List<Integer> preorderTraversal_1(TreeNode root) {
List<Integer> ret = new ArrayList<>();
if (root == null) return ret;
TreeNode cur = root;
TreeNode mostRight = null;
while (cur != null) {
mostRight = cur.left;
if (cur.left != null) {
while (mostRight.right != null && mostRight.right != cur) {
mostRight = mostRight.right;
}
if (mostRight.right == null) {
mostRight.right = cur;
ret.add(cur.val); //第一次到达cur
cur = cur.left;
continue;
} else {
mostRight.right = null;
}
} else {
ret.add(cur.val); //只到达一次的点
}
cur = cur.right;
}
return ret;
}
2.非递归进行二叉树后序遍历(145-中)
思路:
法1:反转技巧:前序遍历为 root -> left -> right,后序遍历为 left -> right -> root。可以修改前序遍历成为 root -> right -> left,那么这个顺序就和后序遍历正好相反。
法2:morris遍历:二叉树后序遍历morris改写(逆序打印右边界)。
1、对二叉树遍历中只出现一次的节点,即无左子树的节点,直接跳过
2、对于出现两次的节点,第一次不管,当第二次到达x的时候,逆序打印左子树的右边界
3、遍历完成后,打印整棵树的右边界
代码:
// 1.反转技巧
public List<Integer> postorderTraversal(TreeNode root) {
List<Integer> ret = new ArrayList<>();
Stack<TreeNode> stack = new Stack<>();
stack.add(root);
while (!stack.isEmpty()) {
TreeNode node = stack.pop();
if (node == null) continue;
ret.add(node.val);
stack.push(node.left);
stack.push(node.right);
}
Collections.reverse(ret);
return ret;
}
// 2. morris
public static void morrisPos(Node head) {
if (head == null) {
return;
}
Node cur = head;
Node mostRight = null;
while (cur != null) {
mostRight = cur.left;
if (mostRight != null) { //存在右边界
while (mostRight.right != null && mostRight.right != cur) { //没有到达左子树的右边界
mostRight = mostRight.right;
}
if (mostRight.right == null) {
mostRight.right = cur;
cur = cur.left;
continue;
} else {
mostRight.right = null; //第二次到达这个位置
printEdge(cur.left);
}
}
cur = cur.right;
}
printEdge(head); //打印整棵树的右边界
System.out.println();
}
public static void printEdge(Node head) {
Node tail = reverseEdge(head);
Node cur = tail;
while (cur != null) {
System.out.print(cur.value + " ");
cur = cur.right; //指向前一个节点(逆序)
}
reverseEdge(tail); //打印完成将原结构还原(逆序回去)
}
//逆序右边界
public static Node reverseEdge(Node from) { //逆序打印左子树的右边界,返回值是节点类型
Node pre = null;
Node next = null;
while (from != null) {
next = from.right;
from.right = pre; //改变指针的方向,指向前一个节点
pre = from; //记录前一个节点
from = next; //from = from.right
}
return pre; //返回左子树右边界的最后一个元素
}
3.非递归进行二叉树中序遍历(94-中)
思路:
法1:迭代:中序遍历的顺序是【左中右】。每到一个节点node,因为根节点的访问在中间,先将node入栈。先遍历左子树,访问node节点,最后访问右子树。访问完node节点就可以出栈(已经完成左子树和node节点)。注:循环进入条件包括栈非空。
法2:morris遍历,记录一个节点(出现多次)在morris序中第二次访问的顺序和只出现一次的节点,即左子树的右边界指向cur时,我们第二次到达cur节点。
代码:
// 1.迭代
public List<Integer> inorderTraversal(TreeNode root) {
List<Integer> ret = new ArrayList<>();
if (root == null) return ret;
Stack<TreeNode> stack = new Stack<>();
TreeNode cur = root;
while (cur != null || !stack.isEmpty()) {
while (cur != null) {
stack.push(cur);
cur = cur.left;
}
// 左子树和node节点已经完成
TreeNode node = stack.pop();
ret.add(node.val);
cur = node.right;
}
return ret;
}
// 2.morris
public List<Integer> inorderTraversal_1(TreeNode root) {
List<Integer> ret = new ArrayList<>();
if (root == null) return ret;
TreeNode cur = root;
TreeNode mostRight = null;
while (cur != null) {
mostRight = cur.left;
if (cur.left != null) {
while (mostRight.right != null && mostRight.right != cur) {
mostRight = mostRight.right; //左子树最右边界
}
if (mostRight.right == null) {
mostRight.right = cur;
cur = cur.left;
continue; //回到最外层的while情况,继续判断cur的情况
} else {
mostRight.right = null;
}
}
ret.add(cur.val); //记录只到达一次的点(无左孩子)和第二次到达cur!!!
cur = cur.right;
}
return ret;
}