在计算机科学中,树结构是非常常见的数据结构之一,特别是二叉树。对于二叉树的遍历,通常的方法包括递归和使用栈或队列等辅助数据结构。然而,这些方法往往需要额外的空间来存储中间结果。今天,我们将介绍一种不需要额外空间(除了用于函数调用的栈空间)的二叉树遍历方法——Morris遍历。
目录
一、传统遍历的问题
常规二叉树遍历(递归/栈实现)需要O(n)空间复杂度。当处理超大规模树结构时,空间消耗可能成为瓶颈!
二、Morris遍历核心思想
线索化二叉树:利用叶子节点的空指针,临时存储后续遍历节点的信息
三步核心操作:
-
创建指向后继节点的线索
-
利用线索完成遍历
-
恢复树结构(关键!)
三、Morris中序遍历(Java实现)
3.1 算法步骤
初始化当前节点cur = root
当cur不为空时循环:
如果cur无左子树:访问节点,cur右移
如果cur有左子树:
找到左子树的最右节点(前驱节点)
若前驱右指针为空:建立线索,cur左移
若前驱右指针为cur:断开线索,访问节点,cur右移
3.2 完整代码
public class MorrisTraversal {
static class TreeNode {
int val;
TreeNode left;
TreeNode right;
TreeNode(int x) { val = x; }
}
public static void morrisInOrder(TreeNode root) {
TreeNode cur = root;
while (cur != null) {
if (cur.left == null) {
// 无左子树直接访问
System.out.print(cur.val + " ");
cur = cur.right;
} else {
// 找到左子树的最右节点
TreeNode predecessor = cur.left;
while (predecessor.right != null && predecessor.right != cur) {
predecessor = predecessor.right;
}
if (predecessor.right == null) {
// 建立线索
predecessor.right = cur;
cur = cur.left;
} else {
// 断开线索并访问
predecessor.right = null;
System.out.print(cur.val + " ");
cur = cur.right;
}
}
}
}
// 测试用例
public static void main(String[] args) {
/*
1
/ \
2 3
/ \ / \
4 5 6 7
*/
TreeNode root = new TreeNode(1);
root.left = new TreeNode(2);
root.right = new TreeNode(3);
root.left.left = new TreeNode(4);
root.left.right = new TreeNode(5);
root.right.left = new TreeNode(6);
root.right.right = new TreeNode(7);
System.out.println("Morris中序遍历:");
morrisInOrder(root); // 输出:4 2 5 1 6 3 7
}
}
四、Morris前序遍历实现
仅需调整节点访问时机(建立线索时访问):
public static void morrisPreOrder(TreeNode root) {
TreeNode cur = root;
while (cur != null) {
if (cur.left == null) {
System.out.print(cur.val + " ");
cur = cur.right;
} else {
TreeNode predecessor = cur.left;
while (predecessor.right != null && predecessor.right != cur) {
predecessor = predecessor.right;
}
if (predecessor.right == null) {
// 建立线索前访问节点
System.out.print(cur.val + " ");
predecessor.right = cur;
cur = cur.left;
} else {
predecessor.right = null;
cur = cur.right;
}
}
}
}
五、复杂度分析
-
时间复杂度:O(n)(每个节点被访问2次)
-
空间复杂度:O(1)(仅使用固定数量指针)
六、应用场景与注意事项
适用场景:
-
内存受限的超大规模树处理
-
需要避免递归栈溢出的场景
注意事项:
-
必须恢复树结构(否则会破坏原始树)
-
不能用于存在环的树结构
-
线程不安全(遍历过程中修改树结构)
七、总结
Morris遍历通过临时线索化实现了空间复杂度突破,展现了算法设计的精妙思维。虽然实现稍复杂,但在特定场景下具有重要价值。建议配合调试工具观察指针变化加深理解!