分層遍歷二叉樹

摘自編程之美
問題1:給定一課二叉樹,要求按分層遍歷該二叉樹,即從上到下按層次訪問該二叉樹(每一層將單獨輸出一行),每一層要求訪問的順序爲從左到右,並將節點依次編號。
問題2:寫另一個函數,打印二叉樹中某層次的節點(從左到右),其中根節點爲第0層,函數原型爲
int PrintNodeAtLevel(Node* root, int level),成功返回1,失敗返回0。
分析與解法
關於二叉樹的問題,由於其本身固有的遞歸特性,通常我們可以用遞歸算法來解決。至於題中的兩個問題,仔細考慮可以發現,如果解決了第二個問題,則問題1可採用問題2的解法來依次遍歷各層節點。那麼我們先來考慮問題2的解法。
我們定義節點的數據結構(設二叉樹中的數據類型爲整數):

struct Node
{
	int data;//節點中的數據
	Node* lChild;//左子指針
	Node* rChild;//右子指針 
}

假設要求訪問二叉樹的第k層的節點,那麼其實可以把它轉換成分別訪問“以該二叉樹根節點的左右子節點爲根節點的兩顆子樹”中層次爲k-1的節點,如題目中的二叉樹,給k=2,即要求訪問原二叉樹中第2層的節點(根節點爲第0層),可把它轉換成分別訪問以節點2、3爲根節點的兩顆子樹中第k-1=1層的節點(如下代碼)

//輸出以root爲根節點中的第level層中的所有節點(從左到右),成功返回1
//失敗返回0
//@param
//level爲層次數,其中根節點爲第0層
int PrintNodeAtLevel(Node* root, int level)
{
	if(!root || level < 0)
		return 0;
	if(level == 0)
	{
		cout << root->data << "  ";
		return 1;
	}
	return PrintNodeAtLevel(root->lChild, level - 1) +  PrintNodeAtLevel(root->rChild, level - 1 );
}

採用遞歸算法,思路比較清晰,寫出來的代碼也很簡潔,但缺點就是遞歸函數的調用效率較低,無論是耗費的計算時間還是佔用的存儲空間都比非遞歸算法要多。
以上解決了遞歸訪問二叉樹中給定層次節點的問題,那麼如何利用該算法來解決問題1呢?如果我們知道該二叉樹的深度n,那麼只需要調用n次PrintNodeAtLevel()

//層次遍歷二叉樹
//@param
//root,二叉樹的根節點
//depth,樹的深度
void PrintNodeByLevel(Node* root, int depth)
{
	for(int level = 0; level < depth; level++)
	{
		PrintNodeAtLevel(root, level);
		cout << endl;
	}
}

如果實現不知道二叉樹的深度,那麼還需要寫一個求二叉樹深度的算法,該算法也可以用遞歸實現。但求二叉樹深度與問題二是同等時間複雜度的問題,能不能不求二叉樹的深度呢?當訪問二叉樹某一層失敗的時候返回就可以了。

//層次遍歷二叉樹
void PrintNodeByLevel(Node* root)
{
	for (int level = 0 ; ; level ++)
	{
		if(!PrintNodeAtLevel(root, level))
			break;
		coun << endl;
	}
}

至此我們解決了題目中的兩個問題,但在問題1的算法中,對二叉樹中每一層的訪問都需要重新從根節點開始,直到訪問完所有的層次。這樣的做法,效率實在不高,那麼有沒有更好的算法?
在訪問第k層的時候,我們只需要直到第k-1層的節點信息就足夠了,所以在訪問第k層的時候,要是能夠知道第k-1層的節點信息,就不再需要從根節點開始遍歷了。
根據上述分析,可以從根節點出發,依次將每層的節點從左到右壓入一個數組,並用一個遊標Cur記錄當前訪問的節點,另一個遊標Last指示當前層次的最後一個節點的下一個位置,以CurLast作爲當前層次訪問結束的條件,在訪問某一層的同時將該層節點的子結點壓入數組,在訪問某一層之後,檢查是否還有新的層次可以訪問,知道訪問完所有的層次。
首先將根節點1壓入數組,並將遊標Cur置爲0,遊標Last置爲1.
Cur<Last,說明此層尚未被訪問,因此,依次訪問Cur到Last之間的所有節點,並依次將被訪問的左右子結點壓入數組,由於Cur
Last,說明該層已被訪問完,此時數組中海油未被訪問到的節點,則輸出換行符,並將Last定位於新一行的末尾(即數組當前最後一個元素的下一位)

void PrintNodeByLevel(Node* root)
{
	if (root == NULL)
		return;
	vector<Node*> vec;//這裏使用STL中的VECTOR來替代數組,可利用到其動態擴展特性
	vec.push_back(root);
	int cur = 0;
	int last = 0;
	while(cur < vec.size())
	{
		last = vec.size();//新的一行訪問開始,重新定位last於當前行最後一個節點的下一個位置
		while(cur < last)
		{
			cout << vec[cur].->data << " ";//訪問節點
			if (vec[cur]->lChild)
				vec.push_back(vec[cur]->lChild);
			if(vec[cur]->rChild)
				vec.push_back(vec[cur]->rChild);
			cur ++;
		}
		cout << endl;//當cur == last時, 說明該層訪問結束,輸出換行符
	}
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章