二叉树的Morris遍历

第一天,面试官让你写个二叉树的遍历,你兴高采烈的写了前序,中序,后序的二叉树遍历递归版本。面试官看了以后,对你说道:回家等通知吧(卒)。

第二天,另一家公司的面试官又让你写个二叉树的遍历,由于有了第一次的教训,你写了前中后序的非递归版本。面试官看了以后,对你说道:不错,回家等通知吧(卒)。

二叉树遍历这种人人都会的简单问题既然问出来,那就一定不简单。今天讲一个二叉树的Morris遍历算法

我们都知道,二叉树的非递归前中后序遍历需要一个辅助栈,空间复杂度是O(n)。如果数据庞大起来,按照传统的方法,需要消耗大量的空间。

而Morris算法可以在O(1)的空间复杂度中实现遍历。

拿中序遍历来说:
在这里插入图片描述

上面这棵树中序遍历:1,2,3,4,5,6,7,8,9,10

Morris遍历算法一共有三步:

  1. 根据当前节点,找到其前序节点,如果前序节点的右孩子是空,那么把前序节点的右孩子指向当前节点,然后进入当前节点的左孩子。
  2. 如果当前节点的左孩子为空,打印当前节点,然后进入右孩子。
  3. 如果当前节点的前序节点其右孩子指向了它本身,那么把前序节点的右孩子设置为空,打印当前节点,然后进入右孩子。

按照Morris算法来一步一步复盘一下:

  1. 首先访问的是根节点6,得到它的前序节点是5,此时节点5的右孩子是空,所以把节点5的右指针指向节点6

在这里插入图片描述

  1. 进入左孩子,也就到了节点4,此时节点4的前序节点3,右孩子指针是空,于是节点3的右孩子指针指向节点4,然后进入左孩子,也就是节点2

在这里插入图片描述

  1. 此时节点2的左孩子1没有右孩子,因此1就是2的前序节点,并且节点1的右孩子指针为空,于是把1的右孩子指针指向节点2,然后从节点2进入节点1

在这里插入图片描述

  1. 此时节点1没有左孩子,因此打印它自己的值,然后进入右孩子,于是回到节点2.根据算法步骤,节点2再次找到它的前序节点1,发现前序节点1的右指针已经指向它自己了,所以打印它自己的值,同时把前序节点的右孩子指针设置为空,同时进入右孩子,也就是节点3.于是图形变为在这里插入图片描述
  2. 此时节点3没有左孩子,因此打印它自己的值,然后进入它的右孩子,也就是节点4. 到了节点4后,根据算法步骤,节点4先获得它的前序节点,也就是节点3,发现节点3的右孩子节点已经指向自己了,所以打印它自己的值,也就是4,然后把前序节点的右指针设置为空,于是图形变成:

在这里插入图片描述

  1. 接着从节点4进入右孩子,也就是节点5,此时节点5没有左孩子,所以直接打印它本身的值,然后进入右孩子,也就是节点6,根据算法步骤,节点6获得它的前序节点5,发现前序节点的右指针已经指向了自己,于是就打印自己的值,把前序节点的右指针设置为空,然后进入右孩子。接下来的流程跟上面一样,就不再重复了。

总结:

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

在遍历过程中,每个节点最多会被访问两次,一次是从父节点到当前节点,第二次是从前缀节点的右孩子指针返回当前节点,所以Morris遍历算法的复杂度是O(n)。在遍历过程中,没有申请新内存,因此算法的空间复杂度是O(1),时间复杂度为O(n)。

这贴个代码:

#include <iostream>

using namespace std;

typedef int dataType;

struct Node
{
	dataType val;
	struct Node* left, * right;
	Node(dataType _val) :val(_val), left(NULL), right(NULL) {}
};
void MorrisInOrderTraverse(Node* head)
{
	if (!head)return;
	Node* p1 = head, * p2 = NULL;
	while (p1 != NULL)
	{
		p2 = p1->left;
		if (p2)
		{
			while (p2->right && p2->right != p1)p2 = p2->right;
			if (!p2->right)
			{
				p2->right = p1;		// 空闲指针
				p1 = p1->left;
				continue;
			}
			else p2->right = NULL;
		}
		cout << p1->val << " ";
		p1 = p1->right;
	}
}
// Morris前序遍历 (根 -> 左 -> 右)
void MorrisPreOrderTraverse(Node* head)
{
	if (!head)return;
	Node* p1 = head, * p2 = NULL;
	while (p1)
	{
		p2 = p1->left;
		if (p2)
		{
			while (p2->right && p2->right != p1)p2 = p2->right;
			if (!p2->right)
			{
				p2->right = p1;		// 空闲指针
				cout << p1->val << " ";	// 打印结点值的顺序稍微调整
				p1 = p1->left;
				continue;
			}
			else p2->right = NULL;
		}
		else cout << p1->val << " ";
		p1 = p1->right;
	}
}
// 逆序右边界
Node* reverseEdge(Node* head)
{
	Node* pre = NULL, * next = pre;
	while (head != NULL)
	{
		next = head->right;
		head->right = pre;
		pre = head;
		head = next;
	}
	return pre;
}
// 逆序打印左子树右边界
void printEdge(Node* head)
{
	Node* lastNode = reverseEdge(head), * cur = lastNode;
	while (cur != NULL)
	{
		cout << cur->val << " ";
		cur = cur->right;
	}
	reverseEdge(lastNode);
}
// Morris后序遍历 (左 -> 右 -> 根)
void MorrisPostOrderTraverse(Node* head)
{
	if (head == NULL)return;
	Node* p1 = head, * p2 = NULL;
	while (p1)
	{
		p2 = p1->left;
		if (p2)
		{
			while (p2->right && p2->right != p1)p2 = p2->right;
			if (!p2->right)
			{
				p2->right = p1;		// 空闲指针
				p1 = p1->left;
				continue;
			}
			else
			{
				p2->right = NULL;
				printEdge(p1->left);
			}
		}
		p1 = p1->right;
	}
	printEdge(head);
}
void buildBinTree(Node** head)
{
	dataType _val;
	cin >> _val;
	if (_val == -1)*head = NULL;
	else
	{
		*head = (Node*)malloc(sizeof(Node));
		(*head)->val = _val;
		buildBinTree(&(*head)->left);
		buildBinTree(&(*head)->right);
	}
}
int main(void)
{
	Node* head;
	buildBinTree(&head);
	cout << "前序遍历序列为:";
	MorrisPreOrderTraverse(head);
	cout << endl;
	cout << "中序遍历序列为:";
	MorrisInOrderTraverse(head);
	cout << endl;
	cout << "后序遍历序列为:";
	MorrisPostOrderTraverse(head);
	cout << endl;
	return 0;
}
发布了175 篇原创文章 · 获赞 9 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/qq_43461641/article/details/103463840