一、算法介绍

  Morris算法充分利用了二叉树叶子结点下的空间,从而可以在时间复杂度为O(N),空间复杂度为O(1)的条件下,前中后序遍历二叉树(不是完全二叉树也可以使用)。

  而常见的遍历二叉树的方法为递归和栈迭代,这两种方法的时间复杂度虽然也为O(N),但是空间复杂度需要O(N),因此Morris算法可以极大节省空间。

二、算法原理

  首先来到当前结点记为cur。

  1.如果cur无左孩子,cur向右移动。

  2.如果cur有左孩子,那么找到cur左子树的最右的结点,记为mostRight。

    2.1如果mostRight的right指针指向空,则让其指向cur,然后cur向左移动

    2.2如果mostRight的right指针指向cur,让其指回空,然后cur向右移动。

三、算法实现

  困难之处在于在遍历的子结点的时候如何重新返回其父结点?在Morris遍历算法中,通过修改叶子结点的左右空指针来指向其前驱或者后继结点来实现的。
  1. 中序遍历
    如果当前结点pNode的左孩子为空,那么输出该结点,并把该结点的右孩子作为当前结点;
    如果当前结点pNode的左孩子非空,那么就找出该结点在中序遍历中的前驱结点pPre
    当第一次访问该前驱结点pPre时,其右孩子必定为空,那么就将其右孩子设置为当前结点,以便根据这个指针返回到当前结点pNode中,并将当前结点pNode设置为其左孩子;
    当该前驱结点pPre的右孩子为当前结点,那么就输出当前结点,并把前驱结点的右孩子设置为空(恢复树的结构),将当前结点更新为当前结点的右孩子
    重复以上两步,直到当前结点为空。
var inOrder3= function (root) {
    if(root === null)
        return;
    let cur = root;
    let res = [];
    while(cur){
        if(cur.left === null){
            res.push(cur.val);
            cur = cur.right;
        }else{
            let node = cur.left;
            while(node!==null && node.right !== null && node.right !== cur){
                node=node.right;
            }
            if(node.right === null){
                node.right = cur;
                cur = cur.left;
            }else{
                node.right = null;
                res.push(cur.val);
                cur = cur.right;
            }
        }
    }
    return res;
};
  2.前序遍历
    与中序遍历类似,区别仅仅是输出的顺序不同。
var preOrder3= function (root) {
    if(root == null)
        return;
    let cur = root;
    let res = [];
    while(cur){
        if(cur.left === null){
            res.push(cur.val);
            cur = cur.right;
        }else{
            let node = cur.left;
            while(node!==null && node.right !== null && node.right !== cur){
                node=node.right;
            }
            if(node.right === null){
                res.push(cur.val);
                node.right = cur;
                cur = cur.left;
            }else{
                node.right = null;
                cur = cur.right;
            }
        }
    }
    return res;
};
3.后序遍历(正确性还有待验证)
// 先建立一个临时结点dummy,并令其左孩子为根结点root,将当前结点设置为dummy;
// 如果当前结点的左孩子为空,则将其右孩子作为当前结点;
// 如果当前结点的左孩子不为空,则找到其在中序遍历中的前驱结点
// 如果前驱结点的右孩子为空,将它的右孩子设置为当前结点,将当前结点更新为当前结点的左孩子;
// 如果前驱结点的右孩子为当前结点,倒序输出从当前结点的左孩子到该前驱结点这条路径上所有的结点。
// 将前驱结点的右孩子设置为空,将当前结点更新为当前结点的右孩子。
// 重复以上过程,直到当前结点为空。
var postOrder3= function (root) {
    if(root === null)
        return;
    let cur = root;
    let res = [];
    let right = [];
    while(cur){
        if(cur.left === null){
            cur = cur.right;
        }else{
            let node = cur.left;
            let tmp=[];
            while(node!==null && node.right !== null && node.right !== cur){
                tmp.push(node.val);
                node=node.right;
            }
            if(node.right === null){
                node.right = cur;
                cur = cur.left;
            }else{
                //倒叙遍历cur.left -> node 
                res.push(...tmp.reverse())                
                if(tmp.length>0 || right.length>0){
                    right.push(cur.val);
                }
                node.right = null;
                cur = cur.right;
            }
        }
    }
    //最后再加上最右边界
    res.push(...right.reverse())
    return res;
}