Chapter 4 樹與二叉樹

1. 樹的基本概念


1.1 樹的定義

自由樹
有關自由樹的研究是圖論討論的主要內容之一,這裏不做討論

有根樹
簡稱樹,它是n(n0)n(n\ge 0)個結點的有限集合。當n=0n=0時,稱爲空樹。
在任意一棵非空樹中應滿足:

  1. 有且僅有一個特定的稱爲根的結點
  2. n>1n>1時,其餘結點可分爲m(m>0)m(m>0)個互不相交的有限集合T1,T2,,TmT_1,T_2,\cdots ,T_m,其中每個集合本身又是一棵樹,並且稱爲根結點的子樹。

樹的定義是遞歸的,是一種遞歸的數據結構。樹作爲一種邏輯結構,同時也是一種分層結構,具有以下特點:

  • 樹的根結點沒有前驅結點,除根結點以外的所有結點有且僅有一個前驅結點。
  • 樹中所有結點可以有零個或多個後繼結點。

nn個結點的樹中有n1n-1條邊

樹的性質

  1. 樹中的結點數=所有結點的度數(相當於所有邊的條數)+1
  2. 度爲mm的樹中第ii層上至多有mi1m^{i-1}個結點(i1)(i\ge1)
  3. 高度爲hhmm叉樹至多有mh1m1\frac{m^h-1}{m-1}個結點
  4. 具有nn個結點的mm叉樹的最小高度爲logm(n(m1)+1)\lceil log_m(n(m-1)+1) \rceil

1.2 術語

結點——它包含數據項及指向其他結點的分支
結點的度——是結點擁有的子樹棵樹
葉結點——即度爲0的結點,又稱終端結點
分支結點——除葉結點以外的其他結點,又稱非終端結點
子女結點
父結點
兄弟結點
祖先結點
子孫結點
結點所處層次
樹的深度
樹的高度
樹的度
有序樹
無序樹
森林
路徑和路徑長度

2. 二叉樹


2.1 二叉樹的定義

與樹相似,二叉樹也以遞歸的形式定義。二叉樹是n(n0)n(n\ge0)個結點的有限集合:

  1. 或者爲空樹,即n=0n=0
  2. 或者由一個根結點和兩個互不相交的被稱爲根的左子樹和右子樹組成。左子樹和右子樹又分別是一棵二叉樹。

幾種特殊的二叉樹

  • 滿二叉樹
  • 完全二叉樹
  • 二叉排序樹
  • 平衡二叉樹

二叉樹的性質 王P109

  1. 非空二叉樹上的葉子結點數=度爲2的結點數+1
  2. 非空二叉樹第kk層上至多有2k12^{k-1}個結點(k1)(k\ge1)
  3. 高度爲hh的二叉樹至多有2h12^h-1個結點(h1)(h\ge1)

2.2 二叉樹的存儲結構

順序存儲結構
完全二叉樹可採用一維數組存儲。
將完全二叉樹自頂向下,從左往右編號,該編號即數組的下標。
這種方式是完全二叉樹最簡單最省存儲的存儲方式。

鏈式存儲結構
由於順序結構空間利用率低,因此二叉樹一般都採用鏈式存儲結構。
鏈表可採用二叉鏈表(只存左子結點和右子結點)和三叉鏈表(多存了父結點)

3. 二叉樹的遍歷

3.1 二叉樹遍歷的遞歸算法

3.1.1 前序遍歷

操作步驟
若二叉樹爲空,什麼也不做

  1. 訪問根結點
  2. 前序遍歷左子樹
  3. 前序遍歷右子樹

代碼

void PreOrder (BiTree T){
	if (T != NULL){
		visit(T);
		PreOrder(T->lchild);
		PreOrder(T->rchild);
	}
}

3.1.2 中序遍歷

操作步驟
若二叉樹爲空,什麼也不做

  1. 中序遍歷左子樹
  2. 訪問根結點
  3. 中序遍歷右子樹

代碼

void InOrder(BiTree T){
	if(T != NULL){
		InOrder(T->lchild);
		visit(T);
		InOrder(T->rchild);
	}
}

3.1.3 後序遍歷

操作步驟
若二叉樹爲空,什麼也不做

  1. 後序遍歷左子樹
  2. 後序遍歷右子樹
  3. 訪問根結點

代碼

void PostOrder(BiTree T){
	if(T != NULL){
		PostOrder(T->lchild);
		PostOrder(T->rchild);
		visit(T);
	}
}

三種算法的時間複雜度都爲O(n)O(n)。在遞歸遍歷中,遞歸工作棧的棧深恰好爲樹的深度,所以在最壞情況下,遍歷算法的空間複雜度爲O(n)O(n)

3.2 二叉樹遍歷的非遞歸算法

爲了把一個遞歸過程改爲非遞歸過程,一般需要利用一個工作棧,記錄遍歷時的回退路徑。

3.2.1 利用棧的非遞歸算法

前序遍歷的非遞歸算法

中序遍歷的非遞歸算法

void InOrder2(BiTree T){
	InitStack(S);
	BiTree p = T;
	while(p || !IsEmpty(S)){
		if(p){
			Push(S,p); //每遇到非空二叉樹往左走
			p = p->lchild;
		}
		else{
			Pop(S,p);
			visit(p);   //退棧,訪問根結點
			p = p->rchild;
		}
	}
}

後序遍歷的非遞歸算法

3.2.2 層次遍歷

進行層次遍歷,需要藉助一個隊列。先將二叉樹根結點入隊,然後出隊,訪問該結點,若它有左子樹,則將左子樹根結點入隊;若它有右子樹,則將右子樹根結點入隊。然後出隊,對出隊結點訪問,如此反覆,直到隊列爲空。

void LevelOrder(BiTree T){
	InitQueue(Q);
	BiTree p;
	EnQueue(Q,T);
	while(!IsEmpty(Q)){
		DeQueue(Q,p);
		visit(p);
		if(p->lchild != NUll)
			EnQueue(Q,p->lchild);
		if(P->rchild != NULL)
			EnQueue(Q,p->rchild);
	}
}

3.3 由遍歷序列構造二叉樹

由二叉樹的前序序列和中序序列可以唯一地確定一棵二叉樹,同理由後序序列和中序序列也可以唯一確定一棵二叉樹,而僅通過前序序列和後序序列不能唯一確定一棵二叉樹。

由二叉樹的層序序列和中序序列也可以唯一確定一棵二叉樹。

4. 線索二叉樹


引入線索二叉樹是爲了加快查找結點前驅和結點後繼的速度。

王P120

5. 樹與森林


5.1 樹的存儲結構

雙親表示法

孩子表示法
孩子表示法是將每個孩子結點都用單鏈錶鏈接起來形成一個線性結構,此時n個結點就有n個孩子鏈表。

孩子兄弟表示法
這種存儲表示法比較靈活,其最大的優點是可以方便地實現樹轉換爲二叉樹的操作,易於查找結點的孩子等,…

5.2 森林與二叉樹的轉換

樹轉換爲二叉樹的規則
每個結點左指針指向它第一個孩子結點,右指針指向它在樹中的相鄰兄弟結點,可表示爲“左孩子右兄弟”。由於根結點沒有兄弟,所以由樹轉換而得的二叉樹沒有右子樹。

森林轉換爲二叉樹的規則
先將森林中的每棵樹轉換爲二叉樹,再將第一棵樹的根作爲轉換後二叉樹的根,將第一棵樹的左子樹作爲轉換後二叉樹根的左子樹,將第二棵樹作爲轉換後二叉樹的右子樹,將第三棵樹作爲轉換後二叉樹根的右子樹的右子樹,以此類推,就可以完成。

二叉樹轉換爲森林的規則
若二叉樹非空,則二叉樹的根及其左子樹爲第一棵樹的二叉樹形式,二叉樹根的右子樹又可視爲一個由除第一棵樹外的森林轉換後的二叉樹,應用同樣的方法,直到最後產生一棵沒有右子樹的二叉樹爲止,這樣就得到了原森林。

樹轉換成二叉樹的畫法

  1. 在兄弟結點之間加一條線
  2. 對每個結點,只保留它與第一個子結點的連線,與其他結點的連線全部抹掉
  3. 以樹根爲中心,順時針旋轉45°

森林轉換爲二叉樹的畫法

  1. 將每棵樹的根相連
  2. 將森林中的每棵樹轉換成相應的二叉樹
  3. 以第一棵樹的根爲軸心順時針旋轉45°

5.3 樹與森林的遍歷

樹的遍歷

  • 先根遍歷
  • 後根遍歷

森林的遍歷

  • 先序遍歷森林
  • 後序遍歷森林
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章