对于二叉树的遍历,最简单的就是使用递归的方式去实现,容易理解,也容易实现。时间和空间都是O(n)。如果是利用栈去实现,也是同样的复杂度。那么能不能降低空间复杂度呢。当然可以,就是Morris。以下是我自己的理解(建议自己造一棵二叉树,跟着代码跑一遍):
在遍历过程中,我觉得最重要的就是当我们遍历完一个节点的左子树之后,能够回到当前节点,继而继续去遍历右子树。因此我们需要关心的就是如何回到当前节点。
和用栈实现的遍历方式相比, morris需要遍历两遍。
记当前节点为cur,当我们遍历到cur的前驱节点(pre)的时候,也就是意味着我们对cur的左子树遍历完成。为了能在这个时候回到cur节点。当我们第一次处理的cur的时候,就去找到它的前驱节点pre,也就是cur左子树的最右边的一个节点。找到之后,把pre的右节点(初始一定为null)指向cur节点。
最后当我们处理完成之后,还需要把这个pre到cur的连接给切断,所以需要两遍。
- 先序遍历
1 判断当前节cur点是否有左节点,没有就访问当前节点的值并进入右节点
2 如果存在左节点,那么先找到它的前驱节点pre。
3 如果pre还没有和cur建立连接,那么就建立连接,并且访问cur的值,然后处理cur的左节点
4 如果pre已经和cur建立连接了。那么说明这是已经处理完成cur的左子树,可以断开连接,并处理cur的右节点了。
//先序遍历
public List preorderTraversal(TreeNode root) {
ArrayList<Integer> result = new ArrayList<>();
TreeNode cur = root;
while (cur != null) {
if (cur.left == null) {
result.add(cur.val);
cur = cur.right;
} else {
/* 查找前驱 */
TreeNode node = cur.left;
while (node.right != null && node.right != cur)
node = node.right;
if (node.right == null) { /* 还没建立连接,则建立连接 */
result.add(cur.val);
node.right = cur;
cur = cur.left;
} else { /* 已经建立连接,需要删除 */
node.right = null;
cur = cur.right;
}
}
}
return result;
}
- 中序遍历
和先序遍历差不多,都是先出力左子树,右子树,只是访问值的时候不一样
先序遍历是第在建立连接的时候访问节点值。
中序遍历是最后断开连接的时候访问,因为这个时候说明左子树已经遍历完成。
//中序遍历
public List inorderTraversal(TreeNode root) {
ArrayList result = new ArrayList<>();
TreeNode cur = root;
while (cur != null) {
if (cur.left == null) {
result.add(cur.val);
prev = cur;
cur = cur.right;
} else {
/* 查找前驱 */
TreeNode node = cur.left;
while (node.right != null && node.right != cur)
node = node.right;
if (node.right == null) {
node.right = cur;
cur = cur.left;
} else {
result.add(cur.val);
node.right = null;
cur = cur.right;
}
}
}
return result;
}
- 后序遍历
这个相比之下比较麻烦,需要我们记录上次访问过的节点值,
代码和之前差不多,就是访问值得方式改变了。每次都是最后断开连接的时候才访问值,为了保留cur到最后访问,我们这次只访问left到pre的节点。每次访问的值都是如下形式。为了最后能访问到根节点,所以再新键一个虚拟的根节点左指针指向真实的根节点,
还是需要自己用数据跟着程序走会更清楚一点。
//后序遍历
public List postorderTraversal(TreeNode root) {
ArrayList result = new ArrayList<>();
TreeNode dummy = new TreeNode(-1);
dummy.left = root;
TreeNode cur = dummy;
TreeNode prev = null;
while (cur != null) {
if (cur.left == null) {
prev = cur; /* 必须要有 */
cur = cur.right;
} else {
TreeNode node = cur.left;
while (node.right != null && node.right != cur)
node = node.right;
if (node.right == null) {
node.right = cur;
prev = cur; /* 必须要有 */
cur = cur.left;
} else {
visit_reverse(cur.left, prev, result);
prev.right = null;
prev = cur; /* 必须要有 */
cur = cur.right;
}
}
}
return result;
}
// 逆转路径
private static void reverse(TreeNode from, TreeNode to) {
TreeNode x = from;
TreeNode y = from.right;
TreeNode z = null;
if (from == to) return;
while (x != to) {
z = y.right;
y.right = x;
x = y;
y = z;
}
}
// 访问逆转后的路径上的所有结点
private static void visit_reverse(TreeNode from, TreeNode to,
List result) {
TreeNode p = to;
reverse(from, to);
while (true) {
result.add(p.val);
if (p == from)
break;
p = p.right;
}
reverse(to, from);
}