1. 程式人生 > >二叉樹神級遍歷演算法——Morris遍歷(C++版)

二叉樹神級遍歷演算法——Morris遍歷(C++版)

題目:

設計一個演算法實現二叉樹的三種遍歷(前序遍歷 中序遍歷 後序遍歷)

要求時間複雜度為O(n) 空間複雜度為O(1)

思路:

空間複雜度O(1)的要求很嚴格。常規的遞迴實現是顯然不能滿足要求的[其空間複雜度是樹的深度O(h) ]本篇文章介紹著名的Morris遍歷,該方法利用了二叉樹結點中大量指向null的指標。

常規的棧結構遍歷方式,遍歷到某個節點之後並不能回到上層的結點,這是由二叉樹本身的結構所限制的,每個結點並沒有指向父節點的指標,因此需要使用棧來完成回到上層結點的步驟。

Morris遍歷避免了使用棧結構,讓下層有指向上層的指標,但並不是所有的下層結點都有指向上層的指標([這些指標也稱為空閒指標

])

空閒指標的分配規則如下:

1. 當前子樹的頭結點為head,空閒指標由head的左子樹中最右結點的右指標指向head結點。對head的左子樹重複該步驟1,直到遍歷至某個結點沒有左子樹,將該結點記    為node。進入步驟2

2. 從node結點開始通過每個結點的右指標進行移動,並列印結點的值。

  假設遍歷到的當前結點為curNode,做如下判斷:

curNode結點的左子樹中最右結點(記為lastRNode)是否指向curNode

A. 是 讓lastRNode結點的右指標指向null,列印curNode的值。接著通過curNode的右指標遍歷下一個結點,重複步驟2

B. 否 將

curNode為頭結點的子樹重複步驟1

下面舉例說明上述步驟(先以中序遍歷為例),二叉樹結構如下圖所示:

遍歷至結點發現其沒有左子樹 記為Node

curNode : 1 列印1

curNode : 2 滿足空閒指標由1的右指標指向將該空閒指標取消掉 列印2。通過2的右指標遍歷到3

curNode : 3 滿足進行步驟最終列印3

通過空閒指標遍歷至4

curNode : 4 滿足空閒指標由3的右指標指向將該空閒指標取消掉 列印4。通過4的右指標遍歷到6

至此 左子樹和根結點遍歷完畢。

curNode : 6 滿足進行步驟之後二叉樹變為右圖。

遍歷至結點

其沒有左子樹 記為Node

curNode : 5 滿足進行步驟最終列印5

通過空閒指標遍歷至6

curNode : 6 滿足空閒指標由5的右指標指向將該空閒指標取消掉 列印6

通過6的右指標遍歷到7

curNode : 7 滿足進行步驟最終列印7

3. 步驟2最終移動到null結點 整個過程結束。

總結:

列印某個結點時,一定是在步驟2開始移動的過程中。

步驟2最開始從子樹最左結點開始,在通過右指標移動過程中,只有以下兩種移動方式:

①移動到某個結點的右子樹【此時 左子樹和根結點必定已經列印結束】

②移動到某個上層結點(即通過空閒指標移動)【此時 該上層結點的左子樹整體列印完畢 開始處理根結點】

Morris先序遍歷只需要將列印順序稍微調整一下(調整至步驟1中列印)

Morris後序遍歷同樣是需要將列印順序稍微調整一下,即:逆序列印(不能使用額外的資料結構)所有結點的左子樹右邊界,在滿足步驟2中情況A時列印。

注:

二叉樹結點定義如下:
typedef int dataType;

struct Node

{

dataType val;

struct Node *left;

struct Node *right;

Node(dataType _val):

val(_val), left(NULL), right(NULL){}

};

常規的二叉樹遍歷方式採用棧實現,比較容易實現,下面直接給出程式碼。

/*************************Morris遍歷二叉樹*************************/

#include <iostream>

using namespace std;

typedef int dataType;
struct Node
{
	dataType val;
	struct Node *left;
	struct Node *right;

	Node(dataType _val):
		val(_val), left(NULL), right(NULL){}
};

// Morris中序遍歷 (左 -> 根 -> 右)
void MorrisInOrderTraverse(Node *head)
{
	if (head == NULL)
	{
		return;
	}

	Node *p1 = head;
	Node *p2 = NULL;

	while (p1 != NULL)
	{
		p2 = p1->left;
		if (p2 != NULL)
		{
			while(p2->right != NULL && p2->right != p1)
			{
				p2 = p2->right;
			}
			if (p2->right == NULL)
			{
				p2->right = p1;		// 空閒指標
				p1 = p1->left;
				continue;
			}
			else
			{
				p2->right = NULL;
			}
		}
		cout<<p1->val<<" ";
		p1 = p1->right;
	}
}

// Morris前序遍歷 (根 -> 左 -> 右)
void MorrisPreOrderTraverse(Node *head)
{
	if (head == NULL)
	{
		return;
	}

	Node *p1 = head;
	Node *p2 = NULL;

	while (p1 != NULL)
	{
		p2 = p1->left;
		if (p2 != NULL)
		{
			while(p2->right != NULL && p2->right != p1)
			{
				p2 = p2->right;
			}
			if (p2->right == NULL)
			{
				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;
	Node *next = NULL;

	while(head != NULL)
	{
		next = head->right;
		head->right = pre;
		pre = head;
		head = next;
	}

	return pre;
}

// 逆序列印左子樹右邊界
void printEdge(Node *head)
{
	Node *lastNode = reverseEdge(head);
	Node *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;
	Node *p2 = NULL;

	while (p1 != NULL)
	{
		p2 = p1->left;
		if (p2 != NULL)
		{
			while(p2->right != NULL && p2->right != p1)
			{
				p2 = p2->right;
			}
			if (p2->right == NULL)
			{
				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;
}

/*************************Morris遍歷二叉樹 END*************************/

輸入:

1 2 3 -1 -1 4 -1 -1 5 6 8 -1 -1 -1 7 -1 -1

輸出:

致謝:

本篇文章參考自左神新書《程式設計師程式碼面試指南:IT名企演算法與資料結構題目最優解》,在此表示感謝。