ICode9

精准搜索请尝试: 精确搜索
首页 > 编程语言> 文章详细

200203题(二叉树的Morris遍历算法)

2020-02-01 21:36:52  阅读:262  来源: 互联网

标签:pre 结点 TreeNode cur 孩子 前序 200203 Morris 二叉树


在这里插入图片描述
法1:中序遍历

//如果对没有错误的二叉树进行中序遍历,应该是按升序排列的 
//那如果对两个结点交换了顺序,那一定有两个地方不满足“前一个元素 < 当前元素 < 后一个元素”  
class Solution {
private:
	TreeNode* first = NULL;
	TreeNode* second = NULL;
	TreeNode* pre = new TreeNode(INT_MIN);//初始化
	void DFS(TreeNode* root) {
		if (root == NULL) {
			return;
		}
		//中序遍历依次找出first和second
		DFS(root->left);
		if (first == NULL&&pre->val > root->val) {
			first = pre;
		}
		if (first != NULL&&pre->val > root->val) {
			second = root;
		}
		pre = root;//更新pre
		DFS(root->right);
	}
public:
	void recoverTree(TreeNode* root) {
		DFS(root);
		swap(first->val, second->val);
	}

};

上面的实现中,有函数的递归调用,递归的深度等于二叉树的高度,也就是说递归导致的调用堆栈的高度等于二叉树的高度h,这样的话,程序虽然没有显式地通过new 来分配内存,但实际上消耗的内存大小也是 O(h). 如果二叉树的高度很大,那么按照传统的中序遍历,需要消耗大量的内存。

接下来引入的Morris遍历法,能以O(1)的空间复杂度实现二叉树的中序遍历。例如给定下面二叉树:
在这里插入图片描述
显然采用中序遍历的话,结果如下:
1,2,3,4,5,6,7,8,9,10
给定某个结点,在中序遍历中,直接排在它前面的结点,我们称之为该节点的前序结点,例如结点5的前序结点就是4,同理,结点10的前序结点就是9.

在二叉树中如何查找一个结点的前序结点呢
如果该结点有左孩子,那么从左孩子开始,沿着右孩子指针一直向右走到底,得到的结点就是它的前序结点,例如结点6的左孩子是4,沿着结点4的右指针走到底,那就是结点5,结点9的左孩子是7,沿着它的右指针走到底对应的结点就是8.如果左孩子的右结点指针是空,那么左孩子就是当前结点的前序结点。

如果当前结点没有左孩子,并且它是其父结点的右孩子,那么它的前序结点就是它的父结点,例如8的前序结点是7,10的前序结点是9.

如果当前结点没有左孩子,并且它是父结点的左孩子,那么它没有前序结点,并且它自己就是首结点,例如结点1.

Morris遍历算法的步骤如下:

1, 根据当前结点,找到其前序结点,如果前序结点的右孩子是空,那么把前序结点的右孩子指向当前结点,然后进入当前结点的左孩子。

2, 如果当前结点的左孩子为空,打印当前结点,然后进入右孩子。

3,如果当前结点的前序结点其右孩子指向了它本身,那么把前序结点的右孩子设置为空,打印当前结点,然后进入右孩子。

我们以上面的例子走一遍。首先访问的是根结点6,得到它的前序结点是5,此时结点5的右孩子是空,所以把结点5的右指针指向结点6:
在这里插入图片描述
进入左孩子,也就到了结点4,此时结点3的前序结点3,右孩子指针是空,于是结点3的右孩子指针指向结点4,然后进入左孩子,也就是结点2;
在这里插入图片描述
此时结点2的左孩子1没有右孩子,因此1就是2的前序结点,并且结点1的右孩子指针为空,于是把1的右孩子指针指向结点2,然后从结点2进入结点1:
在这里插入图片描述
此时结点1没有左孩子,因此打印它自己的值,然后进入右孩子,于是回到结点2.根据算法步骤,结点2再次找到它的前序结点1,发现前序结点1的右指针已经指向它自己了,所以打印它自己的值,同时把前序结点的右孩子指针设置为空,同时进入右孩子,也就是结点3.于是图形变为:
在这里插入图片描述
此时结点3没有左孩子,因此打印它自己的值,然后进入它的右孩子,也就是结点4. 到了结点4后,根据算法步骤,结点4先获得它的前序结点,也就是结点3,发现结点3的右孩子结点已经指向自己了,所以打印它自己的值,也就是4,然后把前序结点的右指针设置为空,于是图形变成:
在这里插入图片描述
接着从结点4进入右孩子,也就是结点5,此时结点5没有左孩子,所以直接打印它本身的值,然后进入右孩子,也就是结点6,根据算法步骤,结点6获得它的前序结点5,发现前序结点的右指针已经指向了自己,于是就打印自己的值,把前序结点的右指针设置为空,然后进入右孩子。

接下来的流程跟上面一样,就不再重复了。

MorrisTraval函数做的就是前面描述的算法步骤,在while循环中,进入一个结点时,先判断结点是否有左孩子,没有的话就把结点值打印出来,有的话,先获得前序结点,然后判断前序结点的右孩子指针是否指向自己,是的话把自己的值打印出来,进入右孩子,前序孩子的右孩子指针是空的话,就把右孩子指针指向自己,然后进入左孩子。

Morris遍历,由于要把前序结点的右指针指向自己,所以暂时会改变二叉树的结构,但在从前序结点返回到自身时,算法会把前序结点的右指针重新设置为空,所以二叉树在结构改变后,又会更改回来。

在遍历过程中,每个结点最多会被访问两次,一次是从父结点到当前结点,第二次是从前序结点的右孩子指针返回当前结点,所以Morris遍历算法的复杂度是O(n)。在遍历过程中,没有申请新内存,因此算法的空间复杂度是O(1).
自己写的完整代码如下:

#include<iostream>
using namespace std;
struct TreeNode {
	int val;
	TreeNode *left;
	TreeNode *right;
	TreeNode(int x) : val(x), left(NULL), right(NULL) {}

};
//如果对没有错误的二叉树进行中序遍历,应该是按升序排列的 
//那如果对两个结点交换了顺序,那一定有两个地方不满足“前一个元素 < 当前元素 < 后一个元素”  
class Solution {
private:
	TreeNode* pre_node = new TreeNode(INT_MIN);
	TreeNode* first = NULL;
	TreeNode* second = NULL;
public:
	TreeNode* getPre(TreeNode* root) {//如果该结点有左孩子,那么从左孩子开始,沿着右孩子指针一直向右走到底,得到的结点就是它的前序结点
		TreeNode*pre = root;
		if (root->left != NULL)
		{
			pre = pre->left;
			while (pre->right != NULL&&pre->right != root)//注意这里要加上pre->right != root
			{
				pre = pre->right;
			}
		}
		return pre;
	}
	void MorrisTraval(TreeNode* root) {
		TreeNode* cur = root;
		while (cur != NULL) {
			if (cur->left == NULL) {
				// cout << cur->val << endl;
				if (first == NULL&&pre_node->val > cur->val) {
					first = pre_node;
				}
				if (first != NULL&&pre_node->val > cur->val) {
					second = cur;
				}
				pre_node = cur;
				//
				cur = cur->right;
			}
			else //注意,只在当前结点有左孩子的情况下才找前序结点
			{
				TreeNode* pre = getPre(cur);
				if (pre->right == NULL)
				{
					pre->right = cur;
					cur = cur->left;
				}
				else if (pre->right == cur) {
					pre->right = NULL;
					// cout << cur->val << endl;
					if (first == NULL&&pre_node->val > cur->val) {
						first = pre_node;
					}
					if (first != NULL&&pre_node->val > cur->val) {
						second = cur;
					}
					pre_node = cur;
					//
					cur = cur->right;
				}
			}
		}

	}
	void recoverTree(TreeNode* root) {
		if (root == NULL)return;
		MorrisTraval(root);
		swap(first->val, second->val);
	}
};

参考文献:https://www.jianshu.com/p/484f587c967c

ShenHang_ 发布了160 篇原创文章 · 获赞 3 · 访问量 5669 私信 关注

标签:pre,结点,TreeNode,cur,孩子,前序,200203,Morris,二叉树
来源: https://blog.csdn.net/ShenHang_/article/details/104131317

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有