编程语言
首页 > 编程语言> > 200203题(二叉树的Morris遍历算法)

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