Morris遍历

如果你用递归实现树的遍历,那么他的空间复杂度一定是O(logN)的【树高】。

但是的的确确存在着神奇的O(1)空间复杂度的遍历方法-Morris遍历。

Morris的时间复杂度O(n),简略证明:对于一个节点x,我们需要去访问它左子树的右边界两次,而所有节点的右边界长度累加就是N,所以访问2*N次,复杂度为O(N)。

我们之所以要用递归的方式遍历,原因仅仅是我们没办法直接回到上一个节点,而递归所压的栈,可以帮助我们回退。但是,递归的代价往往是昂贵的,系统帮我们压的栈不仅仅是参数,还有当前句柄等等一堆信息,浪费的空间是很可观的,就算是手写的栈,依旧摆脱不了O(logN)的空间代价。如果有一条指向上面节点的指针就好了。。。。。。嗯,我们可以察觉,叶子节点的左孩子和有孩子都是指向null的,能不能利用起来呢。。。。。。这个很像一种数据结构-线索树。

Morris和线索树的思想很像,但是并没有线索化那么繁琐的代价。Morris的基本过程:

我们把当前正在访问的节点记为cur。

1)如果cur没有左孩子,那么cur到右孩子节点去cur=cur.right。

2)如果cur有左孩子,找到cur的左子树上的最右节点,记为mostright:

    A.如果mostright.right==null  让mostright.right=cur,cur向其左孩子移动

    B.如果mostright.right==cur   让mostright.right=null,cur向右移动

粗略地看完2)情况的A、B,大概可以知道mostright.t的指向就是指明cur的移动方向。

粗略证明:对于先序过程来说,是先根,再左树,最后右树,也就是说,当遍历了cur【旧】之后,我们去遍历cur【旧】的左子树并先将它的mostright.right=cur,那么那棵树中最后一个遍历的就是它的最右节点,此时cur=mostright且为叶子节点,根据之前给mostright.right设置的值跳转至cur【旧】,这时mostright.right==cur【旧】,指明了cur应该去遍历右子树,将这个mostright.right修正为null即可保持不变。

void Morris(Node* root){
    if(!root)return;
    node* cur=root,*mostRight;
    while(cur!=NULL){
        mostRight=cur.left;
        if(mostRight!=NULL){
            while(mostRight->right!=null&&mostRight->right!=root){//第一种对应了最右节点右孩子为空  第二种对应了最右节点右孩子有过修改 
                mostRight=mostRight->right;
            }
            if(mostRight.right==NULL){
                mostRight.right=cur;
                cur=cur.left;
                continue; 
            } 
            else{
                mostRight.right=NULL;    
            }
        }
        cur=cur.right;
    } 
}


Morris的实质:如果一个节点有左子树,会来到该节点两次,否则只来到一次。而递归的实质是,不管如何都访问一个节点三次。先序中序和后序也只是在哪一次访问时打印而已。

 

Morris改先序遍历:

void MorrisPre(Node* root){
    if(!root)return;
    Node* cur=root,*mostRight;
    while(cur!=NULL){
        mostRight=cur->left;
        if(mostRight!=NULL){
            while(mostRight->right!=NULL&&mostRight->right!=root){//第一种对应了最右节点右孩子为空  第二种对应了最右节点右孩子有过修改 
                mostRight=mostRight->right;
            }
            if(mostRight->right==NULL){//第一次访问cur 
                mostRight->right=cur;
                printf("%d ",cur->value);
                cur=cur->left;
                continue; 
            } 
            else{
                mostRight->right=NULL;    
            }
        }
        else{//第一次访问cur  cur没有左子树  直接打印 
            printf("%d ",cur->value);
        }
        cur=cur->right;
    } 
}

 

Morris改中序遍历:【打印完了左子树再打印自身】

void MorrisMid(Node* root){
    if(!root)return;
    Node* cur=root,*mostRight;
    while(cur!=NULL){
        mostRight=cur->left;
        if(mostRight!=NULL){
            while(mostRight->right!=NULL&&mostRight->right!=root){//第一种对应了最右节点右孩子为空  第二种对应了最右节点右孩子有过修改 
                mostRight=mostRight->right;
            }
            if(mostRight->right==NULL){//第一次访问cur 
                mostRight->right=cur;
                cur=cur->left;
                continue; 
            } 
            else{//第二次访问cur 
                mostRight->right=NULL;    
            }
        }
        printf("%d ",cur->value);//cur即将往右  所以左子树要么访问结束要么没有左子树 
        cur=cur->right;
    } 
}

 

Morris改后序:仅仅关注可以来到自身两次的节点-也就是有左子树的节点,当第二次访问这些节点的时候,将他们的左子树的右边界的那条链逆序打印。

void printEdge(Node *root){
    string str;
    Node* p=root->left;
    while(p->right!=NULL){
        str=str+p->value;
        p=p->right;
    }
    for(int i=str.length()-1;i>0;i--){
        printf("%c ",str[i]);
    }
}

void MorrisLater(Node* root){
    if(!root)return;
    Node* cur=root,*mostRight;
    while(cur!=NULL){
        mostRight=cur->left;
        if(mostRight!=NULL){
            while(mostRight->right!=NULL&&mostRight->right!=root){//第一种对应了最右节点右孩子为空  第二种对应了最右节点右孩子有过修改 
                mostRight=mostRight->right;
            }
            if(mostRight->right==NULL){//第一次访问cur 
                mostRight->right=cur;
                cur=cur->left;
                continue; 
            } 
            else{//第二次访问cur 
                mostRight->right=NULL;    
                printEdge(cur);
            }
        }
        cur=cur->right;
    } 
    printEdge(root);
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值