C++高級數據結構算法 | 二叉樹的四種遍歷算法詳解(遞歸與非遞歸實現)


二叉樹的遍歷(traversing  binary  treetraversing \ \ binary \ \ tree)是指從根節點出發,按照某種次序依次訪問二叉樹中所有結點,使得每個結點被訪問一次且僅被訪問一次

這裏有兩個關鍵詞:訪問次序

訪問其實是要根據實際的需要來確定做什麼,比如對每個結點進行相關計算,輸出打印等,它算是一個抽象操作。在這裏我們可以簡單地假定就是輸出結點的數據信息。

二叉樹的遍歷次序不同於線性結構,一般的線性結構最多也就是從頭至尾、循環、雙向等簡單的遍歷方式。樹的結點之間不存在唯一的前驅和後繼關係,在訪問一個結點後,下一個被訪問的結點面臨着不同的選擇,由於選擇方式的不同,遍歷的次序也就完全不同了。


二叉樹遍歷方法

二叉樹的遍歷方式可以很多,如果我們限制了從左到右的習慣方式,那麼主要就分爲四種:

  • 前序遍歷
  • 中序遍歷
  • 後序遍歷
  • 層序遍歷

前序遍歷

規則是若二叉樹爲空,則空操作返回,否則先訪問根結點,然後前序遍歷左子樹,再前序遍歷右子樹

如下圖,遍歷順序爲:ABDGHCEIF。


接下來我們以下圖爲例對前序遍歷過程做以分析:

遍歷步驟:

  • 先訪問根節點A,結果爲A
  • 前序遍歷根節點A的左子樹,左子樹的根節點是B,此時結果爲AB
  • 繼續前序遍歷以B爲根節點的左子樹,此時的根節點是D,結果爲ABD
  • 繼續遍歷以D爲根節點的左子樹H,此時結果爲ABDH
  • 節點H不存在左子樹,則遍歷以D爲根節點的右子樹,此時結果爲ABDHI
  • 遞歸遍歷以B爲根節點右子樹E,由於E存在左子樹J,此時結果爲ABDHIE
  • E存在左子樹,此時結果爲ABDHIEJ。此時根節點A的左子樹遍歷完成,開始遍歷右子樹
  • 右子樹根節點爲C,此時結果爲ABDHIEJC
  • 以C爲根節點的子樹存在左節點F,結果變爲ABDHIEJCF
  • 節點F不存在子節點,遍歷C的右節點,前序遍歷最後結果爲ABDHIEJCFG

中序遍歷

規則是若樹爲空,則空操作返回,否則從根結點開始(注意並不是先訪問根結點),中序遍歷根結點的左子樹,然後是訪問根結點,最後中序遍歷右子樹。

如下圖,遍歷的順序爲:GDHBAEICF


接下來我們以下圖爲例對中序遍歷過程做以分析:

遍歷步驟:

  • 根節點A存在左子樹,先中序遍歷以節點B爲根節點的左子樹
  • 節點B存在左子樹,中序遍歷以節點D爲根節點的左子樹
  • 節點D存在左葉子節點H,此時結果爲HD
  • 節點D存在右葉子節點I,此時結果爲HDI
  • 以節點B爲根節點的左子樹遍歷完成,此時訪問根節點B,結果爲HDIB
  • 訪問B節點的右子樹,右子樹根節點存在左子樹J,此時結果爲HDIBJE
  • 根節點A的左子樹訪問完成,訪問根節點A,結果變爲HDIBJEA
  • 遍歷根節點A的右子樹,右子樹存在左葉子節點F,此時結果爲HDIBJEAF
  • 然後訪問右子樹的根節點C,中序遍歷最後結果爲HDIBJEAFCG

後序遍歷

規則是若樹爲空,則空操作返回,否則從左到右先葉子後結點的方式遍歷訪問左右子樹,最後是訪問根結點。

如下圖,遍歷的順序爲:GHDBIEFCA


接下來我們以下圖爲例對後序遍歷過程做以分析:

遍歷步驟:

  • 根節點A存在左子樹,先後序遍歷以節點B爲根節點的左子樹
  • 節點B存在左子樹,後序遍歷以節點D爲根節點的左子樹
  • 節點D存在左葉子節點H,右葉子節點I,此時結果爲HID
  • 節點B存在右子樹,後序遍歷以節點E爲根節點的右子樹
  • 節點E存在左葉子節點,不存在右葉子節點,此時結果爲HIDJE
  • B節點的左子樹、右子樹遍歷完成,訪問B節點,結果爲HIDJEB
  • 根節點的左子樹遍歷完成,後序遍歷根節點的右子樹,結果爲HIDJEBFGC
  • 最後訪問根節點,後序遍歷最後結果爲HIDJEBFGCA

層序遍歷

規則是若樹爲空,則空操作返回,否則從樹的第一層,也就是根結點開始訪問,從上而下逐層遍歷,在同一層中,按從左到右的順序對結點逐個訪問。

如下圖,遍歷的順序爲:ABCDEFGHI


接下來我們以下圖爲例對層序遍歷過程做以分析:

遍歷步驟:

  • 從第一層根節點開始訪問,結果爲A
  • 訪問第二層,從左到右,結果爲ABC
  • 訪問第三層,結果爲ABCDEFG
  • 訪問第四層,層序遍歷結果爲ABCDEFGHIJ

前序遍歷算法

遞歸實現

/*
 * 前序遍歷的遞歸實現,我們直接根據定義,首先先訪問根節點,
 * 然後前序遍歷左子樹,接着前序遍歷右子樹。
 * 上述的每個前序遍歷就是一個遞歸的過程。
 */
void preOrder()
{
	preOrder(_root);
	cout << endl;
}

void preOrder(BSTNode* node)
{
	if (node != nullptr)
	{
		cout << node->_data << " ";
		preOrder(node->_left);
		preOrder(node->_right);
	}
}

非遞歸實現

/*
 * 前序遍歷的非遞歸實現,我們需要藉助棧這個數據結構,由於前序遍歷是
 * 先訪問根節點,然後前序遍歷左子樹,接着前序遍歷右子樹。
 * 那麼非遞歸實現的話,我們先壓入根節點,然後打印棧頂元素,但是由於
 * 出棧和入棧的順序是相反的,因此我們接着需要先壓入右孩子,再壓入左孩子
 * 然後打印棧頂元素,繼續壓入左孩子的右孩子,左孩子的左孩子···
 * 上述便是迭代循環的過程。
 */
void nonpreOrder()
{
	if (_root == nullptr)
	{
		return;
	}

	stack <BSTNode*> stack;

	stack.push(_root);

	while (!stack.empty())
	{
		BSTNode* top = stack.top();
		cout << top->_data << " ";
		stack.pop();

		if (top->_right != nullptr)
		{
			stack.push(top->_right);
		}

		if (top->_left != nullptr)
		{
			stack.push(top->_left);
		}
	}
	cout << endl;
}

中序遍歷算法

遞歸實現

/*
 * 中序遍歷的遞歸實現,根據定義,我們首先中序遍歷左子樹,
 * 再打印根節點,然後再中序遍歷右子樹。
 * 上述的每個中序遍歷就是一個遞歸的過程。
 */
void inOrder()
{
	inOrder(_root);
	cout << endl;
}

void inOrder(BSTNode* node)
{
	if (node != nullptr)
	{			
		inOrder(node->_left);
		cout << node->_data << " ";
		inOrder(node->_right);
	}
}

非遞歸實現

/*
 * 中序遍歷的非遞歸實現,我們首先應該一直向左遍歷,將結點壓棧,
 * 直到結點爲空,然後我們打印該節點值,並將其出棧,並繼續遍歷
 * 該節點的右子樹,繼續上述過程。
 */
void noninOrder()
{
	if (_root == nullptr)
	{
		return;
	}
	
	stack<BSTNode*> stack;

	BSTNode* top = _root;

	while (!stack.empty() || top != nullptr)
	{
		if (top != nullptr)
		{
			stack.push(top);
			top = top->_left;
		}
		else
		{
			top = stack.top();
			cout << top->_data << " ";
			stack.pop();
			top = top->_right;
		}
	}
	cout << endl;
}

後序遍歷算法

遞歸實現

/*
 * 後序遍歷的遞歸實現,我們只需先後序遍歷左子樹,再後序遍歷右子樹
 * 最後打印根節點值即可。
 * 上述的每個後序遍歷就是一個遞歸的過程。
 */
void lastOrder()
{
	lastOrder(_root);
	cout << endl;
}

void lastOrder(BSTNode* node)
{
	if (node != nullptr)
	{
		lastOrder(node->_left);
		lastOrder(node->_right);
		cout << node->_data << " ";
	}
}

非遞歸實現

/*
 * 後序遍歷的非遞歸實現,由於其根節點最後打印,因此我們需要藉助兩個棧來完成,
 * 因爲我們需要藉助一個輔助棧來保存其父節點,而另一個棧則保存我們的結果集。
 * 注意壓棧順序,本來我們應該先壓右孩子,再壓左孩子。但是我們使用另一個結果棧
 * 來存儲結果集,輔助棧中的元素最終是出棧並壓入結果棧的,因此,我們程序中
 * 應該先壓入左孩子,再壓入右孩子。
 * 然後每次循環將棧頂元素出棧並壓入結果棧中。
 * 最後,結果棧中所存儲的就是我們所需的後序遍歷結果集。我們打印出棧中所有的
 * 元素即可
 */
void nonlastOrder()
{
	if (_root == nullptr)
	{
		return;
	}

	stack<BSTNode*> Stack;
	stack<BSTNode*> StackRes;

	BSTNode* top = _root;
	Stack.push(top);

	while (!Stack.empty())
	{
		top = Stack.top();
		StackRes.push(top);
		Stack.pop();

		if (top->_left != nullptr)
		{
			Stack.push(top->_left);
		}

		if (top->_right != nullptr)
		{
			Stack.push(top->_right);
		}
	}

	while (!StackRes.empty())
	{
		top = StackRes.top();
		cout << top->_data << " ";
		StackRes.pop();
	}
	cout << endl;
}

層序遍歷算法

遞歸實現

/*
 * 層序遍歷不同於之前講解的前序、中序、後序遍歷的深度優先遍歷,而它是一種典型的
 * 廣度優先遍歷算法,那麼對於層序遍歷的遞歸實現,我們必須要獲取到該樹的層數,
 * 才能對遞歸進行一個有效的控制
 * 整體思路就是,我們在API接口中,向遞歸函數循環傳入層數(0-n)
 * 在遞歸函數中,層數爲零,我們直接打印其值即可,若層數不爲零,我們就遞歸
 * 地繼續向下遍歷。
 * 如何向下繼續遍歷?我們每次遞歸遍歷就將層數減一即可,那麼比如我們遍歷第3層
 * 元素,那麼傳入的參數爲3,我們遞歸調用函數,不斷的將參數減一,當減到0時便是
 * 已經遍歷到第三層了,參數減到0,遞歸結束條件滿足,我們直接打印其值即可。
 */
void levelOrder()
{
	int level = Treelevel(); // 求層數
	for (int i = 0; i < level; ++i)
	{
		levelOrder(_root, i);
	}
}

void levelOrder(BSTNode* node, int level)
{
	if (node == nullptr)
	{
		return;
	}
	if (level == 0)
	{
		cout << node->_data << " ";
	}
	else
	{
		
		levelOrder(node->_left, level - 1);
		levelOrder(node->_right, level - 1);
	}
}

int Treelevel(BSTNode* node)
{
	if (node == nullptr)
	{
		return 0;
	}

	int left = Treelevel(node->_left);
	int right = Treelevel(node->_right);

	return (left > right ? left : right) + 1;
}

非遞歸實現

/*
 * 層序遍歷的非遞歸實現,正常思路我們先遍歷左子樹,再遍歷右子樹,由於是
 * 層序遍歷,從上到下,從左到右,那麼從左到右剛好滿足隊列的性質,先入先出
 * 那麼我們需要藉助一個隊列來保存我們遍歷到的元素。
 * 首先壓入根節點。
 * 每次遍歷我們拿到隊列的首元素,然後遍歷其左孩子,併入隊;然後遍歷其右孩子
 * 併入隊,然後將隊首元素打印並出隊,繼續迭代直到隊列爲空。
*/
void nonlevelOrder()
{
	queue<BSTNode *> Nodequeue;
	
	if (_root == nullptr)
	{
		return;
	}
	
	Nodequeue.push(_root);
	
	while (!Nodequeue.empty())
	{
		BSTNode* front = Nodequeue.front();
		
		if (front->_left != nullptr)
		{
			Nodequeue.push(front->_left);
		}

		if (front->_right != nullptr)
		{
			Nodequeue.push(front->_right);
		}
		  
		cout << front->_data << " ";
		Nodequeue.pop();
	}
	cout << endl;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章