二叉树的前序遍历-mirrors遍历
今天刷leetcode发现二叉树的前序遍历不只有递归和利用栈\队列迭代的两种算法,于是这篇文章就诞生了。
mirrors来源
该方法由 J. H. Morris 在 1979 年的论文「Traversing Binary Trees Simply and Cheaply」中首次提出,因此被称为 Morris 遍历。
痛点
前序遍历的思路就是先访问root,再访问left,再访问right,但是如果使用常规遍历的话,在访问完left后,怎么回到root或者说访问到right呢?如何找到”回家的路“呢?
完整代码(JavaScript版本)
/**
* Definition for a binary tree node.
* function TreeNode(val, left, right) {
* this.val = (val===undefined ? 0 : val)
* this.left = (left===undefined ? null : left)
* this.right = (right===undefined ? null : right)
* }
*/
/**
* @param {TreeNode} root
* @return {number[]}
*/
var preorderTraversal = function(root) {
const result = [];
let p = root;
let q = null;
while(p) {
q = p.left;
if(q === null){
result.push(p.val);
p = p.right;
}else {
while(q.right!==null && q.right !== p) {
// 这个while循环就是寻找最右边的节点
q = q.right;
}
// 寻找q.right是否连接着p
if(q.right === null) {
// 否
q.right = p;
result.push(p.val);
p = p.left;
}else if(q.right === p) {
// 是
q.right = null;
p = p.right;
}
}
}
return result;
};
思路与算法
在访问的每一个节点p,以及它的左节点q( q = p.left)
- 1、如果q为空:比较简单,访问完p(result.push(p.val))后,访问p的右节点即可。(这里要么访问p的右节点,要么就是条“
回家的路”) - 2、如果q不为空:
- 快速找出q子树中最右边的节点(q = q.right; q = q.right ......)
- 【没发现“
回家的路”】如果发现这个点未连接p,就将最后一个节点去连接p啦(q.right = p)(“回家的路”就打通了),顺便把访问到的p(result.push(p.val))加入结果中,这时候就可以放心的访问p的左节点了(p = p.left) - 【发现“
回家的路”】如果发现这个点已经连接着p,那就说明p的左子树已经访问完了,这时候去访问右子树就行(p = p.right),顺便把这条“回家的路”给删掉(q.right = null)
- 【没发现“
- 快速找出q子树中最右边的节点(q = q.right; q = q.right ......)
复杂度分析
时间复杂度:O(n),其中 n 是二叉树的节点数。没有左子树的节点只被访问一次,有左子树的节点被访问两次。
空间复杂度:O(1)。只操作已经存在的指针(树的空闲指针),因此只需要常数的额外空间。
拓展
中序遍历、后续遍历的mirrors方法都是类似的,只是取val的时机和方法不同而已,整体思路是一样的。
结束语
遇到新鲜的东西就学习记录一下,还是挺有意思的。如果有描述的不对的地方欢迎指出,感谢阅读!