20.9.4 二叉树的Morris遍历

1.二叉树的Morris遍历(莫里斯)

使用递归和栈遍历二叉树,空间复杂度都是O(n),而Morris遍历通过使用叶子结点的null指针标记前驱结点,使空间复杂度降为了O(1)。

中序遍历:左子树-根节点-右子树
所以对于根节点,它的前驱结点是左子树的最后一个结点(即左子树的最右结点)

	记当前的结点为cur
	若cur有左子树,则pre=cur的左子树的最右结点
		若pre的右孩子为空,则将它的右孩子指向cur,并进入cur的左孩子
		若pre的右孩子是cur(上次访问时,上一种情况下设置的),则访问cur,删去pre的指向(还原原来的树结构),并进入cur的右孩子
	若cur没有左子树,那么访问cur本身,然后进入cur的右孩子
class Solution {
public:
    void morris(TreeNode* root) {
        TreeNode *pre = nullptr;
        while (root != nullptr) {
            if (root->left != nullptr) {// 当前结点root的左子树不为空                
                pre = root->left;
                while (pre->right != nullptr && pre->right != root) {
                    pre=pre->right;
                }//找到左子树的最右结点,或者回到了root本身
                
                if (pre->right == nullptr) {//右孩子为空
                    pre->right = root;//将它的右孩子指向cur(此处改变了树结构)
                    root = root->left;//进入root的左孩子
                }else {
                	//此处访问root
                	
                    pre->right = nullptr;//恢复原来的树结构
                    root = root->right;//进入root的右孩子
                }
            }else {//root没有左孩子
                //此处访问root
                
                root = root->right;//进入root的右孩子
            }
        }
    }
};

先序遍历的情况类似:


void morris(TreeNode *root){
	TreeNode *pre = nullptr;
	while (root != nullptr){
		if (root->left != nullptr){
			pre=root->left;
			while(pre->right != nullptr && pre->right != root){
				pre = pre->right;
			}
			
			if (pre->right == nullptr){
				pre->right = root;
				//此处访问root
				
				root = root->left;
			}else{
				p2->right = nullptr;
				root = root->right;
			}
		}else{
			//此处访问root
			
			root = root->right;
		}
	}
}

后序遍历情况比较复杂:

当前结点cur的左子树不为空
	左子树的最右孩子为空,则最右孩子pre的右结点指向当前结点cur,进入右子树
	左子树的最右孩子指向当前结点,则删除这个指向,并“逆序访问左子树的所有右孩子”,进入右子树
		解释:由于右孩子指向当前结点,说明这是第二次访问cur;
		在两次访问之间,一定已经访问过cur的左孩子;
		那么对于cur的左孩子,要么它的左孩子为空,要么已经“逆序访问过了左子树的所有右孩子”;
		一层一层迭代下去,则对于左-右-根的顺序,“左”的部分一定已经访问完成了;
		所以可以看成cur的左子树的每个结点都没有左孩子,所以逆序访问右孩子即可完成“右-根”
		
左子树为空,则进入右子树

如图:
在这里插入图片描述
第二次访问结点2时,访问路径1;
第二次访问结点1时,访问路径2;
第二次访问结点3时,访问路径3;
程序的最后访问根节点代表的路径4,结束

代码:

void morris(TreeNode *root){
	TreeNode* pre,cur;
	cur=root;
	while (cur != nullptr){
		if (cur>left != nullptr){
			pre=cur->left;
			while(pre->right != nullptr && pre->right !=cur){
				pre = pre->right;
			}
			if (pre->right == nullptr){//第一次访问cur,指向空
				pre->right = cur;
				cur = cur->left;
			}else{//第二次访问cur,指向cur
				pre->right = nullptr;
				//逆序访问cur左子树的右边界
				
				cur=cur->right;
			}
		}else//cur没有左孩子的情况
			cur = cur->right;
	}
	//逆序访问root的右边界(注意这里在循环外面了)
	
}

关于逆序访问,在保证空间复杂度为O(1)的情况下,需要先把右孩子们(可以看成链表了)转置,访问完成后再转置回来,这里不再给出代码了

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值