103
第第第 第第第第第第第第 第第第

第五章 樹

Embed Size (px)

DESCRIPTION

第五章 樹. 資料結構與演算法. 徐熊健. 目錄. 5.1 樹的概念 5.2 樹的表示法 5.3 二元樹 5.4 二元樹表示法 5.5 二元樹的走訪 5.6 二元樹的複製與相等測試 5.7 引線二元樹 5.8 堆積 5.9 二元搜尋樹 5.10 樹林. 5.1  樹的概念. 樹 (tree) 是一種重要的離散結構 (discrete structure) ,它提供一種「具有層次關係」的概念來結構資料。. 生物演化樹 (evolutionary tree). More Examples. 5.1.2 專有名詞. - PowerPoint PPT Presentation

Citation preview

Page 1: 第五章 樹

第五章 樹第五章 樹資料結構與演算法資料結構與演算法

徐熊健徐熊健

目錄51  樹的概念52  樹的表示法53  二元樹54  二元樹表示法55  二元樹的走訪56  二元樹的複製與相等測試57  引線二元樹58 堆積59 二元搜尋樹510 樹林

51  樹的概念bull 樹 (tree) 是一種重要的離散結構 (discrete structure) 它提供一種「具有層次關係」的概念來結構資料

生物演化樹(evolutionary tree)

More Examples

512  專有名詞定義 樹為節點所組成的有限集合其中

(1) 存在一特殊節點 R 稱為樹根 (root) (2) 其它節點可分割成 n 個無交集的集合

T1 T2 hellip Tn n 0 而 T1 T2 hellip Tn 本身皆為樹稱其為 R 的子樹 (subtree)

A recursive definition 本章所提到的樹皆為「有根樹」 (rooted tree)

512  專有名詞 ( 續 )

bull 節點 (node) 圖中圓圈和向下的分支 (branch)bull 樹根 (root) 節點 Abull 分支度 (degree) 一個節點其所有子樹的數目bull 樹葉 (leaf) 分支度為 0 的節點bull 內部節點 (internal node) 非終端節點 (non-terminal

node)

bull 深度 (depth) 樹的最高階層 (level)

512  專有名詞 ( 續 )bull 兒子 (son) 任何節點 X 其子樹的樹根 Y 為 X 的

兒子bull 父親 (father parent) Y 是 X 的「父親」bull 兄弟 (sibling brother) 共有一個父親的數個節點bull 祖先 (ancestor) 該節點走向樹根所經過的所有節點bull 後裔 (descendant) 任一節點的所有子樹節點

52  樹的表示法bull 用陣列表示樹 bull 用鍵結串列表示樹 令分支度 =k

若 T 共有 n 個節點則需要 n 個鍵結節點計有 nk 個指標然而除了樹根外每個節點只須由唯一的指標所指向遂只需要有 n-1 個指標空間用不著的指標空間多達 nk-(n-1)=n(k-1)+1 個比用得著的還多著實浪費

521  一般化的串列表示bull 串列是一個有限且有順序的元素集合

A = (a1 a2 hellip an)

每個元素 ai 1in 皆有相同的資料型態 bull 倘若不限定各個元素都具有相同的資料型態則稱此串列為「一般化串列」 (generalized list) (ai 可以是一個節點或是一棵樹 )

bull 可用一般化串列來表示一棵樹 T T = (R T1 T2 hellip Tn)

其中 R 為 T 的樹根而 T1 T2 hellip Tn 為 R 的子樹 Ti 可能是一個節點也可能是一棵樹(一般化串列)

範例 5-1 以一般化串列表示樹bull (A (B (E K) (F L M) G) (C H) (D (I N) (J

O P)))

(A T1 T2 T3)T1 = (B (E K) (F L M) G)T2 = (C H)T3 = (D (I N) (J O P))

T1 T2T3

程式 5-1 一般化串列鍵結節點的宣告1 struct TreeNode2 int tag3 union4 int data5 struct TreeNode Tlink6 node7 struct TreeNode link8

(A T1 T2 T3)T1 = (B (E K) (F L M) G)T2 = (C H)T3 = (D (I N) (J O P))

522  左子右兄弟表示法

每個節點都有唯一的最左 兒子 (leftmost child) 每個節點都有最靠近它的 右兄弟

523 分支度為 2 的二元樹表示法bull 分支度為 2 的樹又稱為二元樹 (binary tree)

bull 二元樹中任一節點皆有 2個指標分別指向該節點的左子樹和右子樹

bull 二元樹可以表示任何樹事實上二元樹有許多很好的性質我們在一下節中介紹

bull注意樹狀結構中兒子的順序是不重要的試想樹可以樹根為軸心旋轉使得兒子的順序是不固定的

53  二元樹bull 二元樹是一個由節點組成的有限集合可以是空集合或是包含了(1) 一個樹根節點(2) 其它節點分割成兩個沒有交集的二元樹

其一為左子樹 (left subtree) 另一為右子樹 (right subtree) 樹和二元樹之間的差異

(1)樹不允許空節點而空二元樹是允許的

(2) 樹的子樹沒有順序而二元樹的子樹有左右之分

531  樹和二元樹的基本性質

樹或二元樹皆擁有下面的性質

定理 5-1 若一棵樹 T 的總節點數為 V 總邊數為

E 則V = E + 1

證明利用數學歸納法歸納法的基礎當 V=1 時即 T 只有一個樹根節點 E 為 0 所以 V = E + 1 成立歸納法的假設假設當 1 V K 時該式成立歸納法的推論當 V = K + 1 時令此樹的樹根為 R 而 R 有 m 個子樹 T1 T2 hellip

Tm (如圖 5-12 所示)分別有個 V1 V2 hellip Vm 個節點其中 V1 +V2 + hellip+ Vm =K 若 E1 E2 hellip Em 分別為此 m 個子樹的邊數則

E = E1 +E2 + hellip+ Em + m (1)根據歸納法的假設可知

V1 = E1+1V2 = E2+1 Vm = Em+1

所以V1 +V2 + hellip+Vm = E1 + E2 + hellip+ Em +m (2)

然而V = V1 +V2 + hellip+Vm+1 (3)

將式 (2) 代入式 (3) 可得V = E1 + E2 + hellip+ Em + m + 1

引用式 (1) 則可得V = E + 1

由數學歸納法的原理可得證本定理

532 二元樹的性質(a) 稱為歪斜樹 (skew tree) (b) 稱為完備二元樹 (complete binary tree)

( 其樹葉節點皆在相鄰階層完整定義在後 ) 同樣數目的樹節點存放在歪斜樹上會花較多的階層放在完備二元樹上則只須最少的階層

532 二元樹的性質 ( 續 )定理 5-2 (1) 二元樹上階層 i 的節點數目最多為 2i-1 i 1 (2) 深度為 k 的二元樹其節點數目最多為 2k-

1 k1 bull 定理 5-2說明了 (1) 每一階層上可放的最多節點數

(2) 階層數已知時二元樹可放的最多節點數證明 (1) 利用數學歸納法歸納法的基礎當 i=1 時只有樹根一個節點 2i-1=21-1=1 原式成

立歸納法的假設假設 i = k 時該式成立即階層 k 的最大節點數為

2k-1 k 1 歸納法的推論當 i = k+1 時因在階層 k 的每一個節點最多會有

2 個子節點遂在 k+1 階層最多會有 22k-1 = 2k

= 2i-1 節點由數學歸納法的原理可知本定理得證(2) 由 (1) 可知二元樹在階層 i 的最多節點數為 2i-1那麼深度 k 的二元樹最多可有節點如下

k

i

ki

1

1-1- 24212 1-2

)12(1 k= 2k-1

=

bull 對二元樹而言任一節點的分支度可能為 0 1 或 2 分支度為 0 者是為樹葉

bull 樹葉的數目與分支度為 2 的節點數目存在著有趣的關係請見定理 5-3

定理 5-3 若 T 為一非空的二元樹 n0 為樹葉節點數目 n2為分支度為 2 的節點數目則 n0 = n2+1

證明令 n 為所有節點數目而 n1 為分支度為 1 的節點數目可得 n = n0 + n1 +n2 (1)令 B 為二元樹的分支數除了樹根外每一節點皆有一分支指向之所以

n = B+1 (亦可參考定理 5-1 )又 B = n1+2n2

且 n = n1 + 2n2 +1 (2)由式 (1) 和式 (2) 我們得到

n0 = n2 + 1

完滿二元樹 (full binary tree)

定義一個深度為 k 的完滿二元樹即為一深度為 k 且有 2k-1 個節點的二元樹k 0

bull 由定理 5-2(2) 可知深度為 k 的二元樹其最多節點數為 2k-1 這樣的樹會將樹上所有可能存放節點的位置都放滿完滿 (full) 之名因而生成

完備二元樹 (complete binary tree)我們可對二元樹上節點予以編號階層小者先編同一階

層則自左向右編號這樣的編號方式使我們定義出完備(complete) 二元樹

定義深度為 k 節點數為 n 的二元樹稱為完備二元樹若且唯若 (1) k = 1 時樹有一個節點

(2) 當 k 2 且 1iltk 時深度 i 有 2i-1 個節點且第 k 層的節點皆由第 k-1 層的分支由左至右逐一連接而成

完滿或完備二元樹之所以受人注意乃因它們可以將節點以較少的階層數存放

定理 5-4 n 個節點的完備或完滿二元樹其深度為 log2(n+1)

證明 完滿或完備二元樹的節點皆自階層

1 起排放任一階層 i 可排放 2i-1

個節點遂由定理 5-2 得知此 n 個節點所佔的深度為 log2(n+1)

正規二元樹 (formal binary tree)bull 在二元樹中非樹葉的節點又稱為內部節點

(internal node) 若所有內部節點恰有 2 個子節點即成為正規二元樹

定義若二元樹中所有內部節點恰有 2 個子節點則稱之為正規二元樹

正規二元樹常被引用在單淘汰賽正規二元樹常被引用在單淘汰賽制的各項賽程安排樹葉為參賽制的各項賽程安排樹葉為參賽隊伍內部節點為優勝隊伍樹隊伍內部節點為優勝隊伍樹根即為冠軍隊伍根即為冠軍隊伍

在單淘汰賽制中自在單淘汰賽制中自 n n 個隊伍中產個隊伍中產生冠軍須舉辦 生冠軍須舉辦 nn-1-1場比賽場比賽

54  二元樹的表示法541 以陣列表示二元樹定理 5-5 若一個具有 n 個節點的完備二元樹以循序的方式編號並依序存放在陣列 A 中即第 i 個節點放在 A[i] 中 1 i n 則

(1) 節點 i 的父節點位在 A[i2]處 i ne 1( i = 1 時其節點正為樹根無父節點)

(2) 若 2i n 則節點 i 的左兒子節點位在 A[2i] 處若 2i gtn 節點 i 沒有左子節點

(3) 若 2i+1 n 節點 i 的右兒子節點位 A[2i+1]處若 2i+1gtn 節點 i 沒有左子

節點

證明先用數學歸納法證明 (2) bull 歸納法的基礎當 i = 1 時其左子節點確實在 A[2] 處(除非 nlt2 此時節點 1 沒有左子節點)

bull 歸納法的假設假設當 I = k 時其左子節點確在 A[2i] = A[2k] 處(除非 2i 已大於 n 此時節點 i 無左子節點)

bull 歸納法的推論當 i = k+1 時因第 k 節點的左子節點在 A[2k] 處其右子節點則位在 A[2k+1] 處那麼第 k+1 節點的左子節點應緊臨 A[2k+1] 亦即位於 A[2k+2] = A[2(k+1)] 處(除非 2i 已大於 n 此時節點 i 無左子節點)

bull 由數學歸納法的原理得證定理 5-5 (2) (3) 由於 (2) 成立而且節點 i 的右子節點會緊臨

A[2i] 亦即其位於 A[2i+1]處除非 i 根本沒有右子節點得證定理 5-5 (3)

由 (2) 和 (3) 我們可知定理 5-5 (1) 成立

542 以鍵結串列表示二元樹bull 以陣列來表示完備二元樹算是相當方便但ndash一般的二元樹將導致陣列空間的浪費ndash如果二元樹中節點的新增刪除動作頻繁則會造成陣列對應資料的搬動隨而頻繁

bull 以鍵結串列表示二元樹

為方便辦識樹根我們常用 root 指標指向樹根做為樹鍵結串列的起點

陣列表示和鍵結表示的空間需求bull 完備二元樹的陣列表示和鍵結表示皆沒浪費儲存資料的空間

bull 但歪斜樹的陣列表示比起鍵結表示則浪費許多

bull 以 n 個節點的歪斜樹而言陣列表示法可能需要 2n-1 個陣列元素其中 2n-1-n 個陣列元素空間是浪費的而鍵結表示法只需要 n 個節點空間

二元樹節點的宣告程式 5-2

1 struct BTreeNode

2 struct BtreeNode leftchiled

3 int data

4 struct BTreeNode rightchild

5

6 struct BTreeNode root

bull 這樣的二元樹節點要取得兒子節點的指標十分方便但若有取得父節點的需求則嫌不足我們可以加上 parent 欄位指向父節點

55  二元樹的走訪bull 假如一組資料已用二元樹的組織在一起總有需求對其全數資料做動作例如計算所有數目印出所有資料在所有資料中搜尋某項資料hellip等此時即須對此二元樹做走訪 (traversal) 的運算利用走訪二元樹的同時將計算列印或搜尋的動作完成

bull 事實上走訪即在決定二元樹上資料被處理(計算列印或搜尋)的順序我們也希望走訪的演算法對任何節點皆一致是容易撰寫程式實作的

bull 若對一個二元樹上的節點而言 V 表示處理節點上的資料 L 表示走訪其左子樹 R 表示走訪其右子樹

55  二元樹的走訪 ( 續 )bull 因為對稱的緣故我們只考慮先走訪左邊再走訪右邊的情形那麼右圖只剩下三種走訪方式

bull 以 V 所在的相對位置分別對此三種走訪方式取名如下

(1) LVR 中序走訪 (inorder traversal)或中序表示法 (infix notation)

(2) LRV 後序走訪 (postorder traversal) 或後序表示法 (postfix notation)

(3) VLR 前序走訪 (preorder traversal)或前序表示法 (prefix notation)

551  中序走訪bull 利用遞迴的方式撰寫中序走訪的程序希望把二元樹中序走訪的

順序印出來

程式 5-3二元樹的中序走訪1 struct BTreeNode2 struct BTreeNode leftchild3 char data4 struct BTreeNode rightchild5 6 struct BTreeNode root7 void inorder(struct BTreeNode node)8 if (node = NULL)9 inorder(node-gtleftchild)10 cout ltlt node-gtdata11 inorder(node-gtrightchild)12 13

範例 5-12此為運算式二元樹

其對應中序表示運算式為 (A+B)C+D(E-F)

6 struct BTreeNode root7 void inorder(struct BTreeNode node)8 if (node = NULL)9 inorder(node-gtleftchild)10 cout ltlt node-gtdata11 inorder(node-gtrightchild)12 13

6 struct BTreeNode root7 void inorder(struct BTreeNode node)8 if (node = NULL)9 inorder(node-gtleftchild)10 cout ltlt node-gtdata11 inorder(node-gtrightchild)12 13

(A+B)C+D(E-F)

如何加上 ( )

552  後序走訪程式 5-4二元樹的後序走訪14 void postorder(struct BTreeNode node)

15 if (node =NULL)16 postorder(node-gtleftchild)17 postorder(node-gtrightchild)18 cout ltlt node-gtdata19 20

以後序走訪可得到AB+CDEF-+

其正為對應的後序運算式

553  前序走訪程式 5-5二元樹的前序走訪21 void preorder(struct BTreeNode node)

22 if (node = NULL)

23 cout ltlt node-gtdata

24 preorder(node-gtleftchild)

25 preorder(node-gtrightchild)

26

27

以前序走訪可得到++ABCD-EF

其正為對應的前序運算式

554 利用堆疊和迴圈取代遞迴進行二元樹的走訪1 struct BTreeNode 2 struct BTreeNode leftchild3 char data4 struct BtreeNode rightchild5 6 struct BTreeNode root7 struct StackNode8 struct BTreeNode treenode9 struct StackNode next1011 struct StackNode top

12 void push_node(struct BtreeNode node )13 struct StackNode old_top14 old_top = top15 top = (struct StackNode ) malloc

(sizeof(struct StackNode))16 top-gttreenode = node17 top-gtnext = old_top18 19 struct BTreeNode pop_node()20 struct BTreeNode Tnode21 struct StackNode old_top22 if (top == NULL)23 StackEmpty()24 else25 Tnode = top-gttreenode26 old_top = top27 top = top-gtnext28 free(old_top)29 return Tnode30 31

32 void inorder_Stack(struct BtreeNode node)33 do34 while (node = NULL)35 push_node(node)36 node = node-gtleftchild37 push all left children38 if (top = NULL)39 node = pop_node()40 cout ltlt node-gtdata41 node = node-gtrightchild42 inorder printing and check right child43 while ((top=NULL)||(node=NULL))44 top==NULL 堆疊已空 node==NULL沒有右子

6 struct BTreeNode root7 void inorder(struct BTreeNode node)8 if (node = NULL)9 inorder(node-gtleftchild)10 cout ltlt node-gtdata11 inorder(node-gtrightchild)12 13

中序表示為DBGEHAFIC

32 void inorder_Stack()33 struct BTreeNode node34 node = root35 do36 while (node = NULL)37 push_node(node)38 node = node-gtleftchild39 40 if (top = NULL)41 node = pop_node()42 cout ltlt node-gtdata43 node = node-gtrightchild44 45 while(top=NULL)||(node=NULL))46

45 void Preorder_Stack(struct BSTreeNode node)46 do47 while (node = NULL)48 cout ltlt node-gtdata)49 push_node(node)50 node = node-gtleftchild51 push all left children52 if (top = NULL)53 node = pop_node()54 node = node-gtrightchild55 56 while ((top=NULL)||(node=NULL))57

程式 5-5二元樹的前序走訪21 void preorder(struct BTreeNode node)22 if (node = NULL)23 cout ltlt node-gtdata24 preorder(node-gtleftchild)25 preorder(node-gtrightchild)26 27

58 void Postorder_Stack(struct BTreeNode node)59 do60 while (node = NULL)61 cout ltlt node-gtdata62 push_node(node)63 node = node-gtrightchild64 push all left children65 if (top = NULL)66 node = pop_node()67 node = node-gtleftchild68 69 while ((top=NULL)||(node=NULL))70

程式 5-4二元樹的後序走訪14 void postorder(struct BTreeNode node)15 if (node =NULL)16 postorder(node-gtleftchild)17 postorder(node-gtrightchild)18 cout ltlt node-gtdata19 20

555  階層走訪bull 「階層走訪」 (level-order traversal) 是依階層

的順序進行二元樹的走訪先走訪階層小的節點後走訪階層大的節點同一階層者則依自左向右的順序走訪

bull 對下圖的二元樹進行階層走訪的結果為ABCDEFGHI

bull 對同一階層而言先走訪的節點其子節點亦在下一階層中先被走訪

bull 這種「先進先出」的特性恰可用佇列予以儲存與處理

程式 5-7 利用佇列進行二元樹的階層走訪

1 struct BTreeNode2 struct BTreeNode leftchild3 char data4 struct BTreeNode rightchild5 6 struct BTreeNode root7 struct QNode8 struct BTreeNode treenode9 struct QNode next10 11 struct QNode front rear

12 void AddQueue(struct BTreeNode Tnode)13 struct QNode node14 node=(struct QNode )malloc(sizeof(struct QNode))15 node-gttreenode = Tnode16 node-gtnext = NULL17 if (front == NULL) front = node19 else rear-gtnext = node20 rear = node21

22 struct BTreeNode DeleteQueue()23 struct BTreeNode Tnode24 struct QNode old_front25 if (rear == NULL)26 QueueEmpty()27 else28 Tnode = front-gttreenode29 old_front = front30 front = front-gtnext31 free(old_front)32 return Tnode33 34 35 void LevelOrder (struct BTreeNode node)36 AddQueue(node)37 while (front =NULL)38 node = DeleteQueue()39 cout ltlt node-gtdata40 if (node-gtleftchild = NULL)41 AddQueue(node-gtleftchild)42 if (node-gtrightchild = NULL)43 AddQueue(node-gtrightchild)44 45

35 void LevelOrder (struct BTreeNode node)36 AddQueue(node)37 while (front =NULL)38 node = DeleteQueue()39 cout ltlt node-gtdata40 if (node-gtleftchild = NULL)41 AddQueue(node-gtleftchild)42 if (node-gtrightchild = NULL)43 AddQueue(node-gtrightchild)44 45

bull Infix ABCbull Postfix ABC CBA ACB BCA

BAC CAB

B

A CA

B

C

B

A

C

C

B

AB

A

C

二元樹中序與前序式中序與後序式bull 每一棵二元樹皆有唯一的一對中序與前序表示法也有唯一的中序與後序次序

bull 換句話說給予一對中序與前序或中序與後序即可決定一棵二元樹

bull 然而給予前序和後序次序並不能決定唯一的二元樹可能會產生兩棵不同的二元樹

bull 想想階層走訪和中序走訪順序可否決定唯一的二元樹

A

B C

H J D E

F G

prefix ABHJCDFGEinfix HBJAFDGCEpostfix HJBFGDECA

VLRLVRLRV

A

prefix BHJinfix HBJ

prefix CDFGEinfix FDGCE

prefix ABHJCDFGEinfix HBJAFDGCE

A

B C

H J D E

F G

prefix ABHJCDFGE

infix HBJAFDGCEk-1

struct BTreeNode BTree_InfixPrefix (AnsiString infix AnsiString prefix)

int k struct BTreeNode node if (infixLength==0) return NULLnode = (struct BTreeNode ) malloc (sizeof(struct BTreeNode))node-gtdata = prefix[1]k = find(prefix[1] infix)node-gtleftchild = BTree_InfixPrefix

(infix[1k-1] prefix[2k])node-gtrightchild = BTree_InfixPrefix(infix[k+1infixLength]prefix[k+1prefixLength])

VLR

LVRk1

k2 k+1~

k+1~

1

struct BTreeNode BTree_InfixPostfix (AnsiString infix AnsiString prefix)

int k struct BTreeNode node if (infixLength==0) return NULLnode = (struct BTreeNode ) malloc (sizeof(struct BTreeNode))node-gtdata = postfix[postfixLength]k = find(postfix[postfixLength] infix)node-gtleftchild = BTree_InfixPrefix

(infix[1k-1] postfix[1k-1])node-gtrightchild = BTree_InfixPrefix(infix[k+1infixLength]postfix[k+1postfixLength-1])

56  二元樹的複製與相等測試程式 5-8 二元樹的複製1 struct BTreeNode2 struct BTreeNode leftchild3 char data4 struct BTreeNode rightchild5 6 struct BTreeNode root7 struct BTreeNode Copy(struct BTreeNode TreeRoot)8 struct BTreeNode CopyRoot9 if (TreeRoot == NULL)10 return NULL11 CopyRoot =(struct BTreeNode ) malloc (sizeof(BTreeNode))12 CopyRoot-gtdata = TreeRoot-gtdata13 CopyRoot-gtleftchild = Copy(TreeRoot-gtleftchild)14 CopyRoot-gtrightchild = Copy(TreeRoot-

gtrightchild)15 return CopyRoot16

程式 5-9 檢測兩棵二元樹是否相等1 struct BTreeNode2 struct BTreeNode leftchild3 char data4 struct BTreeNode rightchild5 6 struct BtreeNode rightchild7 int Equal(struct BTreeNode ustruct BTreeNode v)8 if ((u==NULL) ampamp (v==NULL)) return 19 if ((u=NULL) ampamp (v=NULL)10 ampamp (u-gtdata == v-gtdata )11 ampamp Equal(u-gtleftchildv-gtleftchild)12 ampamp Equal(u-gtrightchildv-gtrightchild))

13 return 114 return 015

Counting Nodes in a Binary Tree

int CountingNodes(struct BTreeNodes node)

if (node == NULL) return 0 int leftchildren = CountingNodes(node-gtleftchild)

int rightchildren= CountingNodes(node-gtrightchild)

return leftchildren+rightchildren

Counting Leaves in a Binary Tree

int CountingLeaves(struct BTreeNodes node)

if (node == NULL) return 0 int leftleaves = CountingLeaves(node-gtleftchild)

int rightchildren= CountingLeaves(node-gtrightchild)

if (leftleaves==0 ampamp leftleaves==0) return 1

return leftleaves + leftleaves

57  引線二元樹bull 在二元樹的鍵結表示中樹葉節點的兩個子樹指標皆指向 NULL 由定理 5-1 得知 n = E+1 其中 n 為節點個數 E 為分支數

bull 而 n 個鍵結節點有 2n 個指標空間每個分支恰佔用一個指標空間共有 E (=n-1) 個指標有用到(不是空的)而共有 n+1 個指標空間是 NULL 比非空的節點還多

bull 有學者提出對這些「放空指標的空間」加以利用的概念mdash與其放空指標不如放指向其它節點的指標稱之為引線 (thread) 使得某些運算(如走訪hellip等)可以加快

bull 這個概念發展出了引線二元樹 (threaded binary tree) 這種資料結構

範例 5-17下圖為加了引線的二元樹

加入的引線將使二元樹的中序走訪更加便利沿著上右圖箭頭所指示的順序走訪即可完成此二元樹的中序走訪GDHBAIECF 為此二元樹的中序表示

bull 範例 5-17 中所加的引線目的在方便二元樹的中序走訪

bull 對樹葉節點 p 而言令其資料為 P 則引線指標的規則如下

(1) p 的左子樹指標指向中序表示中 P 的前一個 元素(前接元素 (predecessor) )

(2) p 的右子樹指標指向中序表示中 P 的後一個 元素(後繼元素 (successor) )

為了區別節點指標放的是一般指標(內部節點)抑或是引線指標(樹葉節點)我們另以 2 個欄位分別區別左和右子樹指標空間存放的對象其節點的記憶體配置有如下圖所示

其中 leftthread (rightthread) 為 0 時表示 leftchild (rightchild) 存放的是一般節點指標而 leftthread (rightthread) 為 1 時表示 leftchild (rightchild) 存放的是引線指標1 struct TBTreeNode2 int leftthread3 struct TBTreeNode leftchild4 char data5 struct TBTreeNode rightchild6 int rightthread7

由右側的引線二元樹可知有最左和最右兩條引線尚無妥善的安排我們另設計一空的引線節點做為樹根如下圖所示

571  引線二元樹的中序走訪決定節點 p 在中序表示中的後繼節點 q (簡稱中序後繼點)

程式 5-10決定節點 node的中序後繼點1 struct TBTreeNode2 int leftthread3 struct TBTreeNode leftchild4 char data5 struct TBTreeNode rightchild6 int rightthread7 8 struct TBTreeNode root9 struct TBTreeNode Next(struct TBTreeNode node)

10 struct TBTreeNode temp11 temp = node-gtrightchild12 if (node-gtrightthead) return temp13 while (temp-gtleftthead) temp=temp-gtleftchild

14 return temp15

程式 5-11引線二元樹的中序表示(接程式 5-10)1 void InorderTBTree()2 struct TBTreeNode node3 for(node=Next(root)node=rootnode=Next(node))

4 cout ltlt node-gtdata5

bull只要 node 是引線二元樹上的任一節點透過呼叫 Next(node) 即可找到其在中序表示中的後繼節點bull(試想若程式 5-10 中的程序 Next 所傳入的參數指標正是 root Next(root) 是否可正常運作請思考)bull於是只須從中序表示中的第一節點起讓每一節點 node 都執行 Next(node) 程序即可得引線二元樹的中序表示

572  在引線二元樹中加入節點在引線二元樹中樹葉節點節點的右子樹指標所指向或內部節點右子樹的最左樹葉為其中序後繼節點而且任一節點若無左子樹則其左子樹指標乃指向其中序前接節點hellip這些性質必須在節點新增進入引線二元樹時也要保持接下來討論節點 new 插入成為引線二元樹的節點 p 的右子節點的情形至於插入成為左子節點的情形則讓各位模擬推敲

572572  在引線二元樹中加入節點 在引線二元樹中加入節點 (( 續續 ))

newnew 會成為會成為 pp 的右子節點的情況共有以下兩種的右子節點的情況共有以下兩種(1) (1) 若若 pp 沒有右子節點 沒有右子節點 ((p-gtrightthread p-gtrightthread 為為1) 1) 如圖如圖 ((a) a) 則 則 new new 插入後應形成如圖插入後應形成如圖 ((b) b) 所示所示

(2)(2) 若若 pp 有右子節點 有右子節點 ((p-gtrightthread p-gtrightthread 為為0) 0) 如圖如圖 ((c) c) 則 則 new new 插入後應形成如插入後應形成如圖圖 ((d) d) 所示所示

程式 5-12  插入節點 new 成為引線二元樹中節點 p的右子節點(上接程式 5-10 5-11 )

1 void InsertRight(struct TBTreeNode p 2 struct TBTreeNode new)3 struct TBTreeNode q4 new-gtrightchild = p-gtrightchild5 new-gtrightthead = p-gtrightthread6 new-gtleftchild = p7 new-gtleftthread = 18 p-gtrightchild = new9 p-gtrightthead = 010 if (new-gtrightthread)11 q = Next(new)12 q-gtleftchild = new13 14

58  堆積bull 「堆積」 (heap) 是一個特殊的完備二元樹節點的資料與其子節點的資料之間有事先定好的大小關係存在bull 定義ndash最大堆疊為一完備二元樹任一節點的資料內容不小於其子節點的資料內容ndash最小堆疊為一完備二元樹任一節點的資料內容不大於其子節點的資料內容

581  插入節點至最大堆積中最大堆積是一完備二元樹所以如圖 (a) 之最大堆積於加入新節點後樹的形狀必須如圖 (b) 所示灰色節點即為新節點若加入的節點資料分別為 1 9 12 則其最大堆積將分別變成如圖 (c) (d) (e) 所示

程式 5-13 新增節點至最大堆積中最大堆積本身是一棵完備二元樹可以用陣列表示之 1 define maxsize 1002 int heap[maxsize]3 int n i4 void InsertHeap(int x)5 if (n == maxsize) HeapFull()6 else7 n++8 i = n9 while ((i gt 1) ampamp (x gt heap[i2]))10 heap[i] = heap[i2]11 i = 212 13 heap[i] = x14 16

程序 InsertHeap 在插入第 n 個資料時需要的時間複雜度為 O(logn)

1 100

2 93

3 84

4 77

5 65

6 81

7 79

8 66

9 70

10 50

100

84

77

93

8165

66 70

79

50

1

2 3

4 5 6 7

8 9 10

1 define maxsize 1002 int heap[maxsize]3 int n i4 void InsertHeap(int x)5 if (n == maxsize) HeapFull()6 else7 n++8 i = n9 while ((i gt 1) ampamp (x gt heap[i2]))10 heap[i] = heap[i2]11 i = 212 13 heap[i] = x14 16

582 刪除最大堆積中的最大資料圖 (a) 中的最大堆積在刪除最大資料後其樹形應變成如圖 (b) 所示將最未一個樹葉節點的資料 6 暫挪至樹根節點內如圖 (c) 再將圖 (c) 中非最大堆積的結構調整成為最大堆積 10 和 8 中選大者與 6對調即成圖 (d)

程式 5-14 刪除最大堆積中的最大資料(上接程式 5-13 )1 int DeleteHeap()2 int x i j3 if (n == 0) HeapEmpty()4 else5 x = heap[1]6 heap[1] = heap[n]7 n--8 i = 19 while (i lt= n2)10 if (heap[2i] gt heap[2i+1])11 j = 2i12 else13 j = 2i+114 if (heap[i]gtheap[j]) break15 swap(heap[i] heap[j])16 i = j17 18 return x19 20

其時間複雜度與其堆積高度相同是為O(logn) 其中 n 為堆積中的元素個數

1 100

2 93

3 84

4 77

5 65

6 81

7 79

8 66

9 70

10 50

100

84

77

93

8165

66 70

79

50

1

2 3

4 5 6 7

8 9 10

1 int DeletHeap()2 int x i j3 if(n == 0) HeapEmpty()4 else5 x = heap[1]6 heap[1] = heap[n]7 n--8 i = 19 while (i lt= n2)10 if ([heap[2i]] gt heap[2i+1])11 j = 2i12 else13 j = 2i+114 if (heap[i]gtheap[j]) break15 swap(heap[i] heap[j])16 i = j17 18 return x19 20

583  優先佇列bull 由於最大堆積(或最小堆積)的結構可提供最大元素(或最小元素)而其維護堆積的成本為 O(logn) n 為總元素個數

bull 堆積常被當成「優先佇列」 (priority queue) 使用mdash此時佇列被組織成完備二元樹若新加入佇列的資料有大小關係優先順序的需求時此優先佇列即以 O(logn) 的代價將新資料放入優先佇列(可能是最大或最小堆積依實際大小關係優先順序的需求而定)中mdash提供依元素的優先順序做為處理順序的服務

bull 一般的佇列為先進先出先到的先行處理優先佇列則是依據其優先順序優先順序高者先行處理與來到的先後無關

範例 5-21bull 在「分時」 (time sharing) 的作業系統中我們不希望使用中央處理器 (Central Processing Unit CPU) 時間短的「處理程序」 (process) 等候過久於是有「最短時間工作優先」 (shortest-job-first) 的排程策略此時等候的處理程序即可組織成之一最小堆積而 CPU 每次即取出存於樹根的處理程序予以處理任何新來的處理程序則以 O(logn) 的時間加入此優先佇列(最小堆積)中

bull 注意之前提過的線性佇列新來的直接加入成為新的尾端元素花O(1) 的時間然線性佇列本身沒有大小的關係每次要取出最大(小)的資料得花O(n) 的代價(在所有元素中挑出最大(小)者) n 為佇列內的元素個數而優先佇列是在新元素加入時要花O(logn) 的時間至於取出最大(小)則只須 O(1) 的時間

59  二元搜尋樹bull 二元搜尋樹是一棵二元樹可能是空二元樹若不為空二元樹則滿足下列性質ndash所有節點內的資料內容是相異的 ndash左子節點的資內容 ( 如果有 ) 比父節點的資料內容小 ndash右子節點的資料內容 ( 如果有 ) 比父節點的資料內容大 ndash以左和右子節點為樹根的左子樹和右子樹也是二元搜尋樹

在二元搜尋樹中搜尋資料二元搜尋樹對資料大小的掌握十分明確若恰與節點 p 資料相等則搜尋成功若欲搜尋的資料 x 比樹節點 p 中的資料小 則只須搜尋節點 p 的左子樹若比節點 p 中的資料大 則只須搜尋 p 的右子樹這樣的判斷可以遞迴地應用在各子樹節點 中

程式 5-15 在二元搜尋樹中搜尋資料(遞迴呼叫)1 struct BSTreeNode2 struct BSTreeNode leftchild3 int data4 struct BSTreeNode rightchild5 6 struct BSTreeNode root7 struct BSTreeNode SearchBST(

struct BSTreeNode tree int x)8 if (tree == NULL) return NULL9 if (x == tree-gtdata) return tree10 if (x lt tree-gtdata)11 return SearchBST(tree-gtleftchild x)

12 return SearchBST(tree-gtrightchild x)13

程式 5-16 搜尋二元搜尋樹的資料(非遞迴呼叫)在二元搜尋樹十分龐大時遞迴呼叫的效率不會太好可利用迴圈來完成搜尋的動作以提高執行效率請看程式 5-16

14 struct BSTreeNode Search BST_iterative15 (struct BSTreeNode node int x)16 while (node = NULL)17 if (x == node-gtdata) return node18 if (x lt node-gtdata)19 node = node-gtleftchild20 else21 node = node-gtrightchild22 23 return NULL24

592  新增資料於二元搜尋樹中欲新增資料進入一二元搜尋樹中得先決定該資料在二元搜尋樹中應出現的位置這可透過上節提到的搜尋動作來解決

在二元搜尋樹定義的性質 (1) 中我們限定了元素必須相異所以搜尋新資料的結果必是 NULL

必須將搜尋的結果改為新增資料所應在位置的節點指標

程式 5-17  在二元搜尋樹中新增資料25 int InsertBST(int x)26 struct BSTreeNode p q27 p = root q = NULL28 while (p = NULL)29 q = p30 if (x == p-gtdata) return 0

若不考慮重複則可省略之31 if (x lt p-gtdata)32 p = p-gtleftchild33 else34 p = p-gtrightchild35

3636 p = (struct BSTreeNode ) p = (struct BSTreeNode ) malloc (sizeof(BSTreeNode))malloc (sizeof(BSTreeNode))

3737 p-gtdata = x p-gtdata = x3838 p-gtleftchild = p-gtrightchild = p-gtleftchild = p-gtrightchild = NULLNULL3939 if (root == NULL) root = p if (root == NULL) root = p4040 else if (x lt q-gtdata) else if (x lt q-gtdata)4141 q-gtleftchild = p q-gtleftchild = p4242 else else4343 q-gtrightchild = p q-gtrightchild = p4444 return 1 return 145 45

程式 程式 5-175-17  在二元搜尋樹中新增資料  在二元搜尋樹中新增資料 (( 續續 ))

範例 5-23 圖 (b) 為圖 (a) 新增整數 6 之後的結果圖 (c) (d) (e) (f) 則分別是逐步再加入 28 18

10 21 的結果

bull若目前的二元搜尋樹高度為 h 則新增資料得花O(h) 的時間找到其所應在的位置遂新增資料至二元搜尋樹中的時間複雜之度為 O(h)

593 自二元搜尋樹中刪除資料欲自二元搜尋樹中刪除資料可能會面臨兩種情況(1) 刪除節點 p 位於樹葉節點上如下圖所示 這是比較容易處理的情況

(2) 刪除的節點 p 不是樹葉刪除節點 p 後原 p 所指的節點可由 q ( q 中包含了 p 左子樹中的最大節點值)或 r ( r 中包含了 p 右子樹中的最小節點值)來取代以維持二元搜尋樹中資料的大小關係其結果分別如圖 (b) 和 (c) 所示而圖 (d) 則標明了指標會改變的節點位置即 p 會消失 father 的左或右子樹指標會異動 k(可能是 q 或 r )會移到原來 p 的位置其父節點 f 的左或右子樹指標會異動其中 father 為 p 父節點 f 為 k 的父節點

程式 5-18 自二元搜尋樹中刪除資料46 int DeleteBST (int x)47 Struct BSTreeNode p father k f48 p = root father = NULL49 while (p = NULL)50 if (x == p-gtdata) 找到 x 所在的節點 p 了51 if ((p-gtleftchild == NULL) ampamp 52 (p-gtrightchild == NULL))53 k = NULL p為樹葉節點54 else if (p-gtleftchild = NULL )x位於非子節

點55 f = p 找出 p左子樹的最右樹葉56 k = p-gtleftchild57 while (k-gtrightchild =NULL)58 f = k59 k = k-gtrightchild60 k將挪至原 p處61 if (p == f)62 f-gtleftchild = k-gtleftchild 63 else64 f-gtrightchild = k-gtleftchild 65

66 else p無左子樹67 f = p 找出 p右子樹的最左樹葉68 k = p-gtrightchild69 while (k-gtleftchild = NULL)70 f = k71 k = k -gtleftchild72 73 if (p == f)74 f-gtrightchild = k-gtrightchild 75 else76 f-gtleftchild = k-gtrightchild77 78 if (k = NULL) k挪至原 p處繼承 p的左右指標79 k-gtleftchild = p-gtleftchild80 k-gtrightchild = p-gtrightchild81 82 if (father == NULL) root = k83 else if (x lt father-gtdata) 決定 k是 father的左

或右兒子84 father-gtleftchild = k85 else86 father-gtrightchild = k87 free(p)88 return 1 成功地刪除 x於此傳回 1返回呼叫處89 51~89行為找到 x時應有的處理步驟

90 else 尚未找到 x繼續往下一階層找

91 father = p92 if (x lt p-gtdata)93 p = p-gtleftchild94 else95 p = p-gtrightchild96 97 98 return 0 未找到 x傳回 099

510  樹林 (forest)

bull 定義樹林(森林)是由 n 棵沒有交集的樹所構成的集合 n 0

下圖為一包含三棵樹的樹林

5101 轉換樹林為二元樹 之前我們以二元樹來表示樹因為二元樹有較多

好用的性質在此我們也將樹林轉換成二元樹看待

在 522 節中我們嘗試了用左子右兄弟的方式來表示樹這個方法事實上已將樹轉換成二元樹

即用左子右兄弟的結構重新組織樹林中的每一棵樹這些樹的樹根將視為兄弟而最左邊的樹則視為樹林二元樹的樹根如此一來樹林即可轉換成二元樹

範例 5-25右下圖為右上圖三棵樹所對應的三棵左子右兄弟二元樹

下圖則為上圖樹林所對應的一棵樹林二元樹

演算法 5-1  轉換樹林成二元樹輸入樹林 F = T1 T2 Tn

輸出 F 的二元樹表示 BT=(T1 T2 Tn)1 if (n == 0) 輸出空二元樹 2 else

3 root(T1 T2 Tn) = T1

4 leftsubtree(T1 T2 Tn) =

BT(T11 T12 T1m)5 BT(T11 T12 T1m)為 T1的子樹6 rightsubtree(T1 T2 Tn) =

BT(T2 T3 Tn)

樹林轉換成二元樹的過程樹林轉換成二元樹的過程

  • Slide 1
  • Slide 2
  • Slide 3
  • Slide 4
  • Slide 5
  • Slide 6
  • Slide 7
  • Slide 8
  • Slide 9
  • Slide 10
  • Slide 11
  • Slide 12
  • Slide 13
  • Slide 14
  • Slide 15
  • Slide 16
  • Slide 17
  • Slide 18
  • Slide 19
  • Slide 20
  • Slide 21
  • Slide 22
  • Slide 23
  • Slide 24
  • Slide 25
  • Slide 26
  • Slide 27
  • Slide 28
  • Slide 29
  • Slide 30
  • Slide 31
  • Slide 32
  • Slide 33
  • Slide 34
  • Slide 35
  • Slide 36
  • Slide 37
  • Slide 38
  • Slide 39
  • Slide 40
  • Slide 41
  • Slide 42
  • Slide 43
  • Slide 44
  • Slide 45
  • Slide 46
  • Slide 47
  • Slide 48
  • Slide 49
  • Slide 50
  • Slide 51
  • Slide 52
  • Slide 53
  • Slide 54
  • Slide 55
  • Slide 56
  • Slide 57
  • Slide 58
  • Slide 59
  • Slide 60
  • Slide 61
  • Slide 62
  • Slide 63
  • Slide 64
  • Slide 65
  • Slide 66
  • Slide 67
  • Slide 68
  • Slide 69
  • Slide 70
  • Slide 71
  • Slide 72
  • Slide 73
  • Slide 74
  • Slide 75
  • Slide 76
  • Slide 77
  • Slide 78
  • Slide 79
  • Slide 80
  • Slide 81
  • Slide 82
  • Slide 83
  • Slide 84
  • Slide 85
  • Slide 86
  • Slide 87
  • Slide 88
  • Slide 89
  • Slide 90
  • Slide 91
  • Slide 92
  • Slide 93
  • Slide 94
  • Slide 95
  • Slide 96
  • Slide 97
  • Slide 98
  • Slide 99
  • Slide 100
  • Slide 101
  • Slide 102
  • Slide 103
Page 2: 第五章 樹

目錄51  樹的概念52  樹的表示法53  二元樹54  二元樹表示法55  二元樹的走訪56  二元樹的複製與相等測試57  引線二元樹58 堆積59 二元搜尋樹510 樹林

51  樹的概念bull 樹 (tree) 是一種重要的離散結構 (discrete structure) 它提供一種「具有層次關係」的概念來結構資料

生物演化樹(evolutionary tree)

More Examples

512  專有名詞定義 樹為節點所組成的有限集合其中

(1) 存在一特殊節點 R 稱為樹根 (root) (2) 其它節點可分割成 n 個無交集的集合

T1 T2 hellip Tn n 0 而 T1 T2 hellip Tn 本身皆為樹稱其為 R 的子樹 (subtree)

A recursive definition 本章所提到的樹皆為「有根樹」 (rooted tree)

512  專有名詞 ( 續 )

bull 節點 (node) 圖中圓圈和向下的分支 (branch)bull 樹根 (root) 節點 Abull 分支度 (degree) 一個節點其所有子樹的數目bull 樹葉 (leaf) 分支度為 0 的節點bull 內部節點 (internal node) 非終端節點 (non-terminal

node)

bull 深度 (depth) 樹的最高階層 (level)

512  專有名詞 ( 續 )bull 兒子 (son) 任何節點 X 其子樹的樹根 Y 為 X 的

兒子bull 父親 (father parent) Y 是 X 的「父親」bull 兄弟 (sibling brother) 共有一個父親的數個節點bull 祖先 (ancestor) 該節點走向樹根所經過的所有節點bull 後裔 (descendant) 任一節點的所有子樹節點

52  樹的表示法bull 用陣列表示樹 bull 用鍵結串列表示樹 令分支度 =k

若 T 共有 n 個節點則需要 n 個鍵結節點計有 nk 個指標然而除了樹根外每個節點只須由唯一的指標所指向遂只需要有 n-1 個指標空間用不著的指標空間多達 nk-(n-1)=n(k-1)+1 個比用得著的還多著實浪費

521  一般化的串列表示bull 串列是一個有限且有順序的元素集合

A = (a1 a2 hellip an)

每個元素 ai 1in 皆有相同的資料型態 bull 倘若不限定各個元素都具有相同的資料型態則稱此串列為「一般化串列」 (generalized list) (ai 可以是一個節點或是一棵樹 )

bull 可用一般化串列來表示一棵樹 T T = (R T1 T2 hellip Tn)

其中 R 為 T 的樹根而 T1 T2 hellip Tn 為 R 的子樹 Ti 可能是一個節點也可能是一棵樹(一般化串列)

範例 5-1 以一般化串列表示樹bull (A (B (E K) (F L M) G) (C H) (D (I N) (J

O P)))

(A T1 T2 T3)T1 = (B (E K) (F L M) G)T2 = (C H)T3 = (D (I N) (J O P))

T1 T2T3

程式 5-1 一般化串列鍵結節點的宣告1 struct TreeNode2 int tag3 union4 int data5 struct TreeNode Tlink6 node7 struct TreeNode link8

(A T1 T2 T3)T1 = (B (E K) (F L M) G)T2 = (C H)T3 = (D (I N) (J O P))

522  左子右兄弟表示法

每個節點都有唯一的最左 兒子 (leftmost child) 每個節點都有最靠近它的 右兄弟

523 分支度為 2 的二元樹表示法bull 分支度為 2 的樹又稱為二元樹 (binary tree)

bull 二元樹中任一節點皆有 2個指標分別指向該節點的左子樹和右子樹

bull 二元樹可以表示任何樹事實上二元樹有許多很好的性質我們在一下節中介紹

bull注意樹狀結構中兒子的順序是不重要的試想樹可以樹根為軸心旋轉使得兒子的順序是不固定的

53  二元樹bull 二元樹是一個由節點組成的有限集合可以是空集合或是包含了(1) 一個樹根節點(2) 其它節點分割成兩個沒有交集的二元樹

其一為左子樹 (left subtree) 另一為右子樹 (right subtree) 樹和二元樹之間的差異

(1)樹不允許空節點而空二元樹是允許的

(2) 樹的子樹沒有順序而二元樹的子樹有左右之分

531  樹和二元樹的基本性質

樹或二元樹皆擁有下面的性質

定理 5-1 若一棵樹 T 的總節點數為 V 總邊數為

E 則V = E + 1

證明利用數學歸納法歸納法的基礎當 V=1 時即 T 只有一個樹根節點 E 為 0 所以 V = E + 1 成立歸納法的假設假設當 1 V K 時該式成立歸納法的推論當 V = K + 1 時令此樹的樹根為 R 而 R 有 m 個子樹 T1 T2 hellip

Tm (如圖 5-12 所示)分別有個 V1 V2 hellip Vm 個節點其中 V1 +V2 + hellip+ Vm =K 若 E1 E2 hellip Em 分別為此 m 個子樹的邊數則

E = E1 +E2 + hellip+ Em + m (1)根據歸納法的假設可知

V1 = E1+1V2 = E2+1 Vm = Em+1

所以V1 +V2 + hellip+Vm = E1 + E2 + hellip+ Em +m (2)

然而V = V1 +V2 + hellip+Vm+1 (3)

將式 (2) 代入式 (3) 可得V = E1 + E2 + hellip+ Em + m + 1

引用式 (1) 則可得V = E + 1

由數學歸納法的原理可得證本定理

532 二元樹的性質(a) 稱為歪斜樹 (skew tree) (b) 稱為完備二元樹 (complete binary tree)

( 其樹葉節點皆在相鄰階層完整定義在後 ) 同樣數目的樹節點存放在歪斜樹上會花較多的階層放在完備二元樹上則只須最少的階層

532 二元樹的性質 ( 續 )定理 5-2 (1) 二元樹上階層 i 的節點數目最多為 2i-1 i 1 (2) 深度為 k 的二元樹其節點數目最多為 2k-

1 k1 bull 定理 5-2說明了 (1) 每一階層上可放的最多節點數

(2) 階層數已知時二元樹可放的最多節點數證明 (1) 利用數學歸納法歸納法的基礎當 i=1 時只有樹根一個節點 2i-1=21-1=1 原式成

立歸納法的假設假設 i = k 時該式成立即階層 k 的最大節點數為

2k-1 k 1 歸納法的推論當 i = k+1 時因在階層 k 的每一個節點最多會有

2 個子節點遂在 k+1 階層最多會有 22k-1 = 2k

= 2i-1 節點由數學歸納法的原理可知本定理得證(2) 由 (1) 可知二元樹在階層 i 的最多節點數為 2i-1那麼深度 k 的二元樹最多可有節點如下

k

i

ki

1

1-1- 24212 1-2

)12(1 k= 2k-1

=

bull 對二元樹而言任一節點的分支度可能為 0 1 或 2 分支度為 0 者是為樹葉

bull 樹葉的數目與分支度為 2 的節點數目存在著有趣的關係請見定理 5-3

定理 5-3 若 T 為一非空的二元樹 n0 為樹葉節點數目 n2為分支度為 2 的節點數目則 n0 = n2+1

證明令 n 為所有節點數目而 n1 為分支度為 1 的節點數目可得 n = n0 + n1 +n2 (1)令 B 為二元樹的分支數除了樹根外每一節點皆有一分支指向之所以

n = B+1 (亦可參考定理 5-1 )又 B = n1+2n2

且 n = n1 + 2n2 +1 (2)由式 (1) 和式 (2) 我們得到

n0 = n2 + 1

完滿二元樹 (full binary tree)

定義一個深度為 k 的完滿二元樹即為一深度為 k 且有 2k-1 個節點的二元樹k 0

bull 由定理 5-2(2) 可知深度為 k 的二元樹其最多節點數為 2k-1 這樣的樹會將樹上所有可能存放節點的位置都放滿完滿 (full) 之名因而生成

完備二元樹 (complete binary tree)我們可對二元樹上節點予以編號階層小者先編同一階

層則自左向右編號這樣的編號方式使我們定義出完備(complete) 二元樹

定義深度為 k 節點數為 n 的二元樹稱為完備二元樹若且唯若 (1) k = 1 時樹有一個節點

(2) 當 k 2 且 1iltk 時深度 i 有 2i-1 個節點且第 k 層的節點皆由第 k-1 層的分支由左至右逐一連接而成

完滿或完備二元樹之所以受人注意乃因它們可以將節點以較少的階層數存放

定理 5-4 n 個節點的完備或完滿二元樹其深度為 log2(n+1)

證明 完滿或完備二元樹的節點皆自階層

1 起排放任一階層 i 可排放 2i-1

個節點遂由定理 5-2 得知此 n 個節點所佔的深度為 log2(n+1)

正規二元樹 (formal binary tree)bull 在二元樹中非樹葉的節點又稱為內部節點

(internal node) 若所有內部節點恰有 2 個子節點即成為正規二元樹

定義若二元樹中所有內部節點恰有 2 個子節點則稱之為正規二元樹

正規二元樹常被引用在單淘汰賽正規二元樹常被引用在單淘汰賽制的各項賽程安排樹葉為參賽制的各項賽程安排樹葉為參賽隊伍內部節點為優勝隊伍樹隊伍內部節點為優勝隊伍樹根即為冠軍隊伍根即為冠軍隊伍

在單淘汰賽制中自在單淘汰賽制中自 n n 個隊伍中產個隊伍中產生冠軍須舉辦 生冠軍須舉辦 nn-1-1場比賽場比賽

54  二元樹的表示法541 以陣列表示二元樹定理 5-5 若一個具有 n 個節點的完備二元樹以循序的方式編號並依序存放在陣列 A 中即第 i 個節點放在 A[i] 中 1 i n 則

(1) 節點 i 的父節點位在 A[i2]處 i ne 1( i = 1 時其節點正為樹根無父節點)

(2) 若 2i n 則節點 i 的左兒子節點位在 A[2i] 處若 2i gtn 節點 i 沒有左子節點

(3) 若 2i+1 n 節點 i 的右兒子節點位 A[2i+1]處若 2i+1gtn 節點 i 沒有左子

節點

證明先用數學歸納法證明 (2) bull 歸納法的基礎當 i = 1 時其左子節點確實在 A[2] 處(除非 nlt2 此時節點 1 沒有左子節點)

bull 歸納法的假設假設當 I = k 時其左子節點確在 A[2i] = A[2k] 處(除非 2i 已大於 n 此時節點 i 無左子節點)

bull 歸納法的推論當 i = k+1 時因第 k 節點的左子節點在 A[2k] 處其右子節點則位在 A[2k+1] 處那麼第 k+1 節點的左子節點應緊臨 A[2k+1] 亦即位於 A[2k+2] = A[2(k+1)] 處(除非 2i 已大於 n 此時節點 i 無左子節點)

bull 由數學歸納法的原理得證定理 5-5 (2) (3) 由於 (2) 成立而且節點 i 的右子節點會緊臨

A[2i] 亦即其位於 A[2i+1]處除非 i 根本沒有右子節點得證定理 5-5 (3)

由 (2) 和 (3) 我們可知定理 5-5 (1) 成立

542 以鍵結串列表示二元樹bull 以陣列來表示完備二元樹算是相當方便但ndash一般的二元樹將導致陣列空間的浪費ndash如果二元樹中節點的新增刪除動作頻繁則會造成陣列對應資料的搬動隨而頻繁

bull 以鍵結串列表示二元樹

為方便辦識樹根我們常用 root 指標指向樹根做為樹鍵結串列的起點

陣列表示和鍵結表示的空間需求bull 完備二元樹的陣列表示和鍵結表示皆沒浪費儲存資料的空間

bull 但歪斜樹的陣列表示比起鍵結表示則浪費許多

bull 以 n 個節點的歪斜樹而言陣列表示法可能需要 2n-1 個陣列元素其中 2n-1-n 個陣列元素空間是浪費的而鍵結表示法只需要 n 個節點空間

二元樹節點的宣告程式 5-2

1 struct BTreeNode

2 struct BtreeNode leftchiled

3 int data

4 struct BTreeNode rightchild

5

6 struct BTreeNode root

bull 這樣的二元樹節點要取得兒子節點的指標十分方便但若有取得父節點的需求則嫌不足我們可以加上 parent 欄位指向父節點

55  二元樹的走訪bull 假如一組資料已用二元樹的組織在一起總有需求對其全數資料做動作例如計算所有數目印出所有資料在所有資料中搜尋某項資料hellip等此時即須對此二元樹做走訪 (traversal) 的運算利用走訪二元樹的同時將計算列印或搜尋的動作完成

bull 事實上走訪即在決定二元樹上資料被處理(計算列印或搜尋)的順序我們也希望走訪的演算法對任何節點皆一致是容易撰寫程式實作的

bull 若對一個二元樹上的節點而言 V 表示處理節點上的資料 L 表示走訪其左子樹 R 表示走訪其右子樹

55  二元樹的走訪 ( 續 )bull 因為對稱的緣故我們只考慮先走訪左邊再走訪右邊的情形那麼右圖只剩下三種走訪方式

bull 以 V 所在的相對位置分別對此三種走訪方式取名如下

(1) LVR 中序走訪 (inorder traversal)或中序表示法 (infix notation)

(2) LRV 後序走訪 (postorder traversal) 或後序表示法 (postfix notation)

(3) VLR 前序走訪 (preorder traversal)或前序表示法 (prefix notation)

551  中序走訪bull 利用遞迴的方式撰寫中序走訪的程序希望把二元樹中序走訪的

順序印出來

程式 5-3二元樹的中序走訪1 struct BTreeNode2 struct BTreeNode leftchild3 char data4 struct BTreeNode rightchild5 6 struct BTreeNode root7 void inorder(struct BTreeNode node)8 if (node = NULL)9 inorder(node-gtleftchild)10 cout ltlt node-gtdata11 inorder(node-gtrightchild)12 13

範例 5-12此為運算式二元樹

其對應中序表示運算式為 (A+B)C+D(E-F)

6 struct BTreeNode root7 void inorder(struct BTreeNode node)8 if (node = NULL)9 inorder(node-gtleftchild)10 cout ltlt node-gtdata11 inorder(node-gtrightchild)12 13

6 struct BTreeNode root7 void inorder(struct BTreeNode node)8 if (node = NULL)9 inorder(node-gtleftchild)10 cout ltlt node-gtdata11 inorder(node-gtrightchild)12 13

(A+B)C+D(E-F)

如何加上 ( )

552  後序走訪程式 5-4二元樹的後序走訪14 void postorder(struct BTreeNode node)

15 if (node =NULL)16 postorder(node-gtleftchild)17 postorder(node-gtrightchild)18 cout ltlt node-gtdata19 20

以後序走訪可得到AB+CDEF-+

其正為對應的後序運算式

553  前序走訪程式 5-5二元樹的前序走訪21 void preorder(struct BTreeNode node)

22 if (node = NULL)

23 cout ltlt node-gtdata

24 preorder(node-gtleftchild)

25 preorder(node-gtrightchild)

26

27

以前序走訪可得到++ABCD-EF

其正為對應的前序運算式

554 利用堆疊和迴圈取代遞迴進行二元樹的走訪1 struct BTreeNode 2 struct BTreeNode leftchild3 char data4 struct BtreeNode rightchild5 6 struct BTreeNode root7 struct StackNode8 struct BTreeNode treenode9 struct StackNode next1011 struct StackNode top

12 void push_node(struct BtreeNode node )13 struct StackNode old_top14 old_top = top15 top = (struct StackNode ) malloc

(sizeof(struct StackNode))16 top-gttreenode = node17 top-gtnext = old_top18 19 struct BTreeNode pop_node()20 struct BTreeNode Tnode21 struct StackNode old_top22 if (top == NULL)23 StackEmpty()24 else25 Tnode = top-gttreenode26 old_top = top27 top = top-gtnext28 free(old_top)29 return Tnode30 31

32 void inorder_Stack(struct BtreeNode node)33 do34 while (node = NULL)35 push_node(node)36 node = node-gtleftchild37 push all left children38 if (top = NULL)39 node = pop_node()40 cout ltlt node-gtdata41 node = node-gtrightchild42 inorder printing and check right child43 while ((top=NULL)||(node=NULL))44 top==NULL 堆疊已空 node==NULL沒有右子

6 struct BTreeNode root7 void inorder(struct BTreeNode node)8 if (node = NULL)9 inorder(node-gtleftchild)10 cout ltlt node-gtdata11 inorder(node-gtrightchild)12 13

中序表示為DBGEHAFIC

32 void inorder_Stack()33 struct BTreeNode node34 node = root35 do36 while (node = NULL)37 push_node(node)38 node = node-gtleftchild39 40 if (top = NULL)41 node = pop_node()42 cout ltlt node-gtdata43 node = node-gtrightchild44 45 while(top=NULL)||(node=NULL))46

45 void Preorder_Stack(struct BSTreeNode node)46 do47 while (node = NULL)48 cout ltlt node-gtdata)49 push_node(node)50 node = node-gtleftchild51 push all left children52 if (top = NULL)53 node = pop_node()54 node = node-gtrightchild55 56 while ((top=NULL)||(node=NULL))57

程式 5-5二元樹的前序走訪21 void preorder(struct BTreeNode node)22 if (node = NULL)23 cout ltlt node-gtdata24 preorder(node-gtleftchild)25 preorder(node-gtrightchild)26 27

58 void Postorder_Stack(struct BTreeNode node)59 do60 while (node = NULL)61 cout ltlt node-gtdata62 push_node(node)63 node = node-gtrightchild64 push all left children65 if (top = NULL)66 node = pop_node()67 node = node-gtleftchild68 69 while ((top=NULL)||(node=NULL))70

程式 5-4二元樹的後序走訪14 void postorder(struct BTreeNode node)15 if (node =NULL)16 postorder(node-gtleftchild)17 postorder(node-gtrightchild)18 cout ltlt node-gtdata19 20

555  階層走訪bull 「階層走訪」 (level-order traversal) 是依階層

的順序進行二元樹的走訪先走訪階層小的節點後走訪階層大的節點同一階層者則依自左向右的順序走訪

bull 對下圖的二元樹進行階層走訪的結果為ABCDEFGHI

bull 對同一階層而言先走訪的節點其子節點亦在下一階層中先被走訪

bull 這種「先進先出」的特性恰可用佇列予以儲存與處理

程式 5-7 利用佇列進行二元樹的階層走訪

1 struct BTreeNode2 struct BTreeNode leftchild3 char data4 struct BTreeNode rightchild5 6 struct BTreeNode root7 struct QNode8 struct BTreeNode treenode9 struct QNode next10 11 struct QNode front rear

12 void AddQueue(struct BTreeNode Tnode)13 struct QNode node14 node=(struct QNode )malloc(sizeof(struct QNode))15 node-gttreenode = Tnode16 node-gtnext = NULL17 if (front == NULL) front = node19 else rear-gtnext = node20 rear = node21

22 struct BTreeNode DeleteQueue()23 struct BTreeNode Tnode24 struct QNode old_front25 if (rear == NULL)26 QueueEmpty()27 else28 Tnode = front-gttreenode29 old_front = front30 front = front-gtnext31 free(old_front)32 return Tnode33 34 35 void LevelOrder (struct BTreeNode node)36 AddQueue(node)37 while (front =NULL)38 node = DeleteQueue()39 cout ltlt node-gtdata40 if (node-gtleftchild = NULL)41 AddQueue(node-gtleftchild)42 if (node-gtrightchild = NULL)43 AddQueue(node-gtrightchild)44 45

35 void LevelOrder (struct BTreeNode node)36 AddQueue(node)37 while (front =NULL)38 node = DeleteQueue()39 cout ltlt node-gtdata40 if (node-gtleftchild = NULL)41 AddQueue(node-gtleftchild)42 if (node-gtrightchild = NULL)43 AddQueue(node-gtrightchild)44 45

bull Infix ABCbull Postfix ABC CBA ACB BCA

BAC CAB

B

A CA

B

C

B

A

C

C

B

AB

A

C

二元樹中序與前序式中序與後序式bull 每一棵二元樹皆有唯一的一對中序與前序表示法也有唯一的中序與後序次序

bull 換句話說給予一對中序與前序或中序與後序即可決定一棵二元樹

bull 然而給予前序和後序次序並不能決定唯一的二元樹可能會產生兩棵不同的二元樹

bull 想想階層走訪和中序走訪順序可否決定唯一的二元樹

A

B C

H J D E

F G

prefix ABHJCDFGEinfix HBJAFDGCEpostfix HJBFGDECA

VLRLVRLRV

A

prefix BHJinfix HBJ

prefix CDFGEinfix FDGCE

prefix ABHJCDFGEinfix HBJAFDGCE

A

B C

H J D E

F G

prefix ABHJCDFGE

infix HBJAFDGCEk-1

struct BTreeNode BTree_InfixPrefix (AnsiString infix AnsiString prefix)

int k struct BTreeNode node if (infixLength==0) return NULLnode = (struct BTreeNode ) malloc (sizeof(struct BTreeNode))node-gtdata = prefix[1]k = find(prefix[1] infix)node-gtleftchild = BTree_InfixPrefix

(infix[1k-1] prefix[2k])node-gtrightchild = BTree_InfixPrefix(infix[k+1infixLength]prefix[k+1prefixLength])

VLR

LVRk1

k2 k+1~

k+1~

1

struct BTreeNode BTree_InfixPostfix (AnsiString infix AnsiString prefix)

int k struct BTreeNode node if (infixLength==0) return NULLnode = (struct BTreeNode ) malloc (sizeof(struct BTreeNode))node-gtdata = postfix[postfixLength]k = find(postfix[postfixLength] infix)node-gtleftchild = BTree_InfixPrefix

(infix[1k-1] postfix[1k-1])node-gtrightchild = BTree_InfixPrefix(infix[k+1infixLength]postfix[k+1postfixLength-1])

56  二元樹的複製與相等測試程式 5-8 二元樹的複製1 struct BTreeNode2 struct BTreeNode leftchild3 char data4 struct BTreeNode rightchild5 6 struct BTreeNode root7 struct BTreeNode Copy(struct BTreeNode TreeRoot)8 struct BTreeNode CopyRoot9 if (TreeRoot == NULL)10 return NULL11 CopyRoot =(struct BTreeNode ) malloc (sizeof(BTreeNode))12 CopyRoot-gtdata = TreeRoot-gtdata13 CopyRoot-gtleftchild = Copy(TreeRoot-gtleftchild)14 CopyRoot-gtrightchild = Copy(TreeRoot-

gtrightchild)15 return CopyRoot16

程式 5-9 檢測兩棵二元樹是否相等1 struct BTreeNode2 struct BTreeNode leftchild3 char data4 struct BTreeNode rightchild5 6 struct BtreeNode rightchild7 int Equal(struct BTreeNode ustruct BTreeNode v)8 if ((u==NULL) ampamp (v==NULL)) return 19 if ((u=NULL) ampamp (v=NULL)10 ampamp (u-gtdata == v-gtdata )11 ampamp Equal(u-gtleftchildv-gtleftchild)12 ampamp Equal(u-gtrightchildv-gtrightchild))

13 return 114 return 015

Counting Nodes in a Binary Tree

int CountingNodes(struct BTreeNodes node)

if (node == NULL) return 0 int leftchildren = CountingNodes(node-gtleftchild)

int rightchildren= CountingNodes(node-gtrightchild)

return leftchildren+rightchildren

Counting Leaves in a Binary Tree

int CountingLeaves(struct BTreeNodes node)

if (node == NULL) return 0 int leftleaves = CountingLeaves(node-gtleftchild)

int rightchildren= CountingLeaves(node-gtrightchild)

if (leftleaves==0 ampamp leftleaves==0) return 1

return leftleaves + leftleaves

57  引線二元樹bull 在二元樹的鍵結表示中樹葉節點的兩個子樹指標皆指向 NULL 由定理 5-1 得知 n = E+1 其中 n 為節點個數 E 為分支數

bull 而 n 個鍵結節點有 2n 個指標空間每個分支恰佔用一個指標空間共有 E (=n-1) 個指標有用到(不是空的)而共有 n+1 個指標空間是 NULL 比非空的節點還多

bull 有學者提出對這些「放空指標的空間」加以利用的概念mdash與其放空指標不如放指向其它節點的指標稱之為引線 (thread) 使得某些運算(如走訪hellip等)可以加快

bull 這個概念發展出了引線二元樹 (threaded binary tree) 這種資料結構

範例 5-17下圖為加了引線的二元樹

加入的引線將使二元樹的中序走訪更加便利沿著上右圖箭頭所指示的順序走訪即可完成此二元樹的中序走訪GDHBAIECF 為此二元樹的中序表示

bull 範例 5-17 中所加的引線目的在方便二元樹的中序走訪

bull 對樹葉節點 p 而言令其資料為 P 則引線指標的規則如下

(1) p 的左子樹指標指向中序表示中 P 的前一個 元素(前接元素 (predecessor) )

(2) p 的右子樹指標指向中序表示中 P 的後一個 元素(後繼元素 (successor) )

為了區別節點指標放的是一般指標(內部節點)抑或是引線指標(樹葉節點)我們另以 2 個欄位分別區別左和右子樹指標空間存放的對象其節點的記憶體配置有如下圖所示

其中 leftthread (rightthread) 為 0 時表示 leftchild (rightchild) 存放的是一般節點指標而 leftthread (rightthread) 為 1 時表示 leftchild (rightchild) 存放的是引線指標1 struct TBTreeNode2 int leftthread3 struct TBTreeNode leftchild4 char data5 struct TBTreeNode rightchild6 int rightthread7

由右側的引線二元樹可知有最左和最右兩條引線尚無妥善的安排我們另設計一空的引線節點做為樹根如下圖所示

571  引線二元樹的中序走訪決定節點 p 在中序表示中的後繼節點 q (簡稱中序後繼點)

程式 5-10決定節點 node的中序後繼點1 struct TBTreeNode2 int leftthread3 struct TBTreeNode leftchild4 char data5 struct TBTreeNode rightchild6 int rightthread7 8 struct TBTreeNode root9 struct TBTreeNode Next(struct TBTreeNode node)

10 struct TBTreeNode temp11 temp = node-gtrightchild12 if (node-gtrightthead) return temp13 while (temp-gtleftthead) temp=temp-gtleftchild

14 return temp15

程式 5-11引線二元樹的中序表示(接程式 5-10)1 void InorderTBTree()2 struct TBTreeNode node3 for(node=Next(root)node=rootnode=Next(node))

4 cout ltlt node-gtdata5

bull只要 node 是引線二元樹上的任一節點透過呼叫 Next(node) 即可找到其在中序表示中的後繼節點bull(試想若程式 5-10 中的程序 Next 所傳入的參數指標正是 root Next(root) 是否可正常運作請思考)bull於是只須從中序表示中的第一節點起讓每一節點 node 都執行 Next(node) 程序即可得引線二元樹的中序表示

572  在引線二元樹中加入節點在引線二元樹中樹葉節點節點的右子樹指標所指向或內部節點右子樹的最左樹葉為其中序後繼節點而且任一節點若無左子樹則其左子樹指標乃指向其中序前接節點hellip這些性質必須在節點新增進入引線二元樹時也要保持接下來討論節點 new 插入成為引線二元樹的節點 p 的右子節點的情形至於插入成為左子節點的情形則讓各位模擬推敲

572572  在引線二元樹中加入節點 在引線二元樹中加入節點 (( 續續 ))

newnew 會成為會成為 pp 的右子節點的情況共有以下兩種的右子節點的情況共有以下兩種(1) (1) 若若 pp 沒有右子節點 沒有右子節點 ((p-gtrightthread p-gtrightthread 為為1) 1) 如圖如圖 ((a) a) 則 則 new new 插入後應形成如圖插入後應形成如圖 ((b) b) 所示所示

(2)(2) 若若 pp 有右子節點 有右子節點 ((p-gtrightthread p-gtrightthread 為為0) 0) 如圖如圖 ((c) c) 則 則 new new 插入後應形成如插入後應形成如圖圖 ((d) d) 所示所示

程式 5-12  插入節點 new 成為引線二元樹中節點 p的右子節點(上接程式 5-10 5-11 )

1 void InsertRight(struct TBTreeNode p 2 struct TBTreeNode new)3 struct TBTreeNode q4 new-gtrightchild = p-gtrightchild5 new-gtrightthead = p-gtrightthread6 new-gtleftchild = p7 new-gtleftthread = 18 p-gtrightchild = new9 p-gtrightthead = 010 if (new-gtrightthread)11 q = Next(new)12 q-gtleftchild = new13 14

58  堆積bull 「堆積」 (heap) 是一個特殊的完備二元樹節點的資料與其子節點的資料之間有事先定好的大小關係存在bull 定義ndash最大堆疊為一完備二元樹任一節點的資料內容不小於其子節點的資料內容ndash最小堆疊為一完備二元樹任一節點的資料內容不大於其子節點的資料內容

581  插入節點至最大堆積中最大堆積是一完備二元樹所以如圖 (a) 之最大堆積於加入新節點後樹的形狀必須如圖 (b) 所示灰色節點即為新節點若加入的節點資料分別為 1 9 12 則其最大堆積將分別變成如圖 (c) (d) (e) 所示

程式 5-13 新增節點至最大堆積中最大堆積本身是一棵完備二元樹可以用陣列表示之 1 define maxsize 1002 int heap[maxsize]3 int n i4 void InsertHeap(int x)5 if (n == maxsize) HeapFull()6 else7 n++8 i = n9 while ((i gt 1) ampamp (x gt heap[i2]))10 heap[i] = heap[i2]11 i = 212 13 heap[i] = x14 16

程序 InsertHeap 在插入第 n 個資料時需要的時間複雜度為 O(logn)

1 100

2 93

3 84

4 77

5 65

6 81

7 79

8 66

9 70

10 50

100

84

77

93

8165

66 70

79

50

1

2 3

4 5 6 7

8 9 10

1 define maxsize 1002 int heap[maxsize]3 int n i4 void InsertHeap(int x)5 if (n == maxsize) HeapFull()6 else7 n++8 i = n9 while ((i gt 1) ampamp (x gt heap[i2]))10 heap[i] = heap[i2]11 i = 212 13 heap[i] = x14 16

582 刪除最大堆積中的最大資料圖 (a) 中的最大堆積在刪除最大資料後其樹形應變成如圖 (b) 所示將最未一個樹葉節點的資料 6 暫挪至樹根節點內如圖 (c) 再將圖 (c) 中非最大堆積的結構調整成為最大堆積 10 和 8 中選大者與 6對調即成圖 (d)

程式 5-14 刪除最大堆積中的最大資料(上接程式 5-13 )1 int DeleteHeap()2 int x i j3 if (n == 0) HeapEmpty()4 else5 x = heap[1]6 heap[1] = heap[n]7 n--8 i = 19 while (i lt= n2)10 if (heap[2i] gt heap[2i+1])11 j = 2i12 else13 j = 2i+114 if (heap[i]gtheap[j]) break15 swap(heap[i] heap[j])16 i = j17 18 return x19 20

其時間複雜度與其堆積高度相同是為O(logn) 其中 n 為堆積中的元素個數

1 100

2 93

3 84

4 77

5 65

6 81

7 79

8 66

9 70

10 50

100

84

77

93

8165

66 70

79

50

1

2 3

4 5 6 7

8 9 10

1 int DeletHeap()2 int x i j3 if(n == 0) HeapEmpty()4 else5 x = heap[1]6 heap[1] = heap[n]7 n--8 i = 19 while (i lt= n2)10 if ([heap[2i]] gt heap[2i+1])11 j = 2i12 else13 j = 2i+114 if (heap[i]gtheap[j]) break15 swap(heap[i] heap[j])16 i = j17 18 return x19 20

583  優先佇列bull 由於最大堆積(或最小堆積)的結構可提供最大元素(或最小元素)而其維護堆積的成本為 O(logn) n 為總元素個數

bull 堆積常被當成「優先佇列」 (priority queue) 使用mdash此時佇列被組織成完備二元樹若新加入佇列的資料有大小關係優先順序的需求時此優先佇列即以 O(logn) 的代價將新資料放入優先佇列(可能是最大或最小堆積依實際大小關係優先順序的需求而定)中mdash提供依元素的優先順序做為處理順序的服務

bull 一般的佇列為先進先出先到的先行處理優先佇列則是依據其優先順序優先順序高者先行處理與來到的先後無關

範例 5-21bull 在「分時」 (time sharing) 的作業系統中我們不希望使用中央處理器 (Central Processing Unit CPU) 時間短的「處理程序」 (process) 等候過久於是有「最短時間工作優先」 (shortest-job-first) 的排程策略此時等候的處理程序即可組織成之一最小堆積而 CPU 每次即取出存於樹根的處理程序予以處理任何新來的處理程序則以 O(logn) 的時間加入此優先佇列(最小堆積)中

bull 注意之前提過的線性佇列新來的直接加入成為新的尾端元素花O(1) 的時間然線性佇列本身沒有大小的關係每次要取出最大(小)的資料得花O(n) 的代價(在所有元素中挑出最大(小)者) n 為佇列內的元素個數而優先佇列是在新元素加入時要花O(logn) 的時間至於取出最大(小)則只須 O(1) 的時間

59  二元搜尋樹bull 二元搜尋樹是一棵二元樹可能是空二元樹若不為空二元樹則滿足下列性質ndash所有節點內的資料內容是相異的 ndash左子節點的資內容 ( 如果有 ) 比父節點的資料內容小 ndash右子節點的資料內容 ( 如果有 ) 比父節點的資料內容大 ndash以左和右子節點為樹根的左子樹和右子樹也是二元搜尋樹

在二元搜尋樹中搜尋資料二元搜尋樹對資料大小的掌握十分明確若恰與節點 p 資料相等則搜尋成功若欲搜尋的資料 x 比樹節點 p 中的資料小 則只須搜尋節點 p 的左子樹若比節點 p 中的資料大 則只須搜尋 p 的右子樹這樣的判斷可以遞迴地應用在各子樹節點 中

程式 5-15 在二元搜尋樹中搜尋資料(遞迴呼叫)1 struct BSTreeNode2 struct BSTreeNode leftchild3 int data4 struct BSTreeNode rightchild5 6 struct BSTreeNode root7 struct BSTreeNode SearchBST(

struct BSTreeNode tree int x)8 if (tree == NULL) return NULL9 if (x == tree-gtdata) return tree10 if (x lt tree-gtdata)11 return SearchBST(tree-gtleftchild x)

12 return SearchBST(tree-gtrightchild x)13

程式 5-16 搜尋二元搜尋樹的資料(非遞迴呼叫)在二元搜尋樹十分龐大時遞迴呼叫的效率不會太好可利用迴圈來完成搜尋的動作以提高執行效率請看程式 5-16

14 struct BSTreeNode Search BST_iterative15 (struct BSTreeNode node int x)16 while (node = NULL)17 if (x == node-gtdata) return node18 if (x lt node-gtdata)19 node = node-gtleftchild20 else21 node = node-gtrightchild22 23 return NULL24

592  新增資料於二元搜尋樹中欲新增資料進入一二元搜尋樹中得先決定該資料在二元搜尋樹中應出現的位置這可透過上節提到的搜尋動作來解決

在二元搜尋樹定義的性質 (1) 中我們限定了元素必須相異所以搜尋新資料的結果必是 NULL

必須將搜尋的結果改為新增資料所應在位置的節點指標

程式 5-17  在二元搜尋樹中新增資料25 int InsertBST(int x)26 struct BSTreeNode p q27 p = root q = NULL28 while (p = NULL)29 q = p30 if (x == p-gtdata) return 0

若不考慮重複則可省略之31 if (x lt p-gtdata)32 p = p-gtleftchild33 else34 p = p-gtrightchild35

3636 p = (struct BSTreeNode ) p = (struct BSTreeNode ) malloc (sizeof(BSTreeNode))malloc (sizeof(BSTreeNode))

3737 p-gtdata = x p-gtdata = x3838 p-gtleftchild = p-gtrightchild = p-gtleftchild = p-gtrightchild = NULLNULL3939 if (root == NULL) root = p if (root == NULL) root = p4040 else if (x lt q-gtdata) else if (x lt q-gtdata)4141 q-gtleftchild = p q-gtleftchild = p4242 else else4343 q-gtrightchild = p q-gtrightchild = p4444 return 1 return 145 45

程式 程式 5-175-17  在二元搜尋樹中新增資料  在二元搜尋樹中新增資料 (( 續續 ))

範例 5-23 圖 (b) 為圖 (a) 新增整數 6 之後的結果圖 (c) (d) (e) (f) 則分別是逐步再加入 28 18

10 21 的結果

bull若目前的二元搜尋樹高度為 h 則新增資料得花O(h) 的時間找到其所應在的位置遂新增資料至二元搜尋樹中的時間複雜之度為 O(h)

593 自二元搜尋樹中刪除資料欲自二元搜尋樹中刪除資料可能會面臨兩種情況(1) 刪除節點 p 位於樹葉節點上如下圖所示 這是比較容易處理的情況

(2) 刪除的節點 p 不是樹葉刪除節點 p 後原 p 所指的節點可由 q ( q 中包含了 p 左子樹中的最大節點值)或 r ( r 中包含了 p 右子樹中的最小節點值)來取代以維持二元搜尋樹中資料的大小關係其結果分別如圖 (b) 和 (c) 所示而圖 (d) 則標明了指標會改變的節點位置即 p 會消失 father 的左或右子樹指標會異動 k(可能是 q 或 r )會移到原來 p 的位置其父節點 f 的左或右子樹指標會異動其中 father 為 p 父節點 f 為 k 的父節點

程式 5-18 自二元搜尋樹中刪除資料46 int DeleteBST (int x)47 Struct BSTreeNode p father k f48 p = root father = NULL49 while (p = NULL)50 if (x == p-gtdata) 找到 x 所在的節點 p 了51 if ((p-gtleftchild == NULL) ampamp 52 (p-gtrightchild == NULL))53 k = NULL p為樹葉節點54 else if (p-gtleftchild = NULL )x位於非子節

點55 f = p 找出 p左子樹的最右樹葉56 k = p-gtleftchild57 while (k-gtrightchild =NULL)58 f = k59 k = k-gtrightchild60 k將挪至原 p處61 if (p == f)62 f-gtleftchild = k-gtleftchild 63 else64 f-gtrightchild = k-gtleftchild 65

66 else p無左子樹67 f = p 找出 p右子樹的最左樹葉68 k = p-gtrightchild69 while (k-gtleftchild = NULL)70 f = k71 k = k -gtleftchild72 73 if (p == f)74 f-gtrightchild = k-gtrightchild 75 else76 f-gtleftchild = k-gtrightchild77 78 if (k = NULL) k挪至原 p處繼承 p的左右指標79 k-gtleftchild = p-gtleftchild80 k-gtrightchild = p-gtrightchild81 82 if (father == NULL) root = k83 else if (x lt father-gtdata) 決定 k是 father的左

或右兒子84 father-gtleftchild = k85 else86 father-gtrightchild = k87 free(p)88 return 1 成功地刪除 x於此傳回 1返回呼叫處89 51~89行為找到 x時應有的處理步驟

90 else 尚未找到 x繼續往下一階層找

91 father = p92 if (x lt p-gtdata)93 p = p-gtleftchild94 else95 p = p-gtrightchild96 97 98 return 0 未找到 x傳回 099

510  樹林 (forest)

bull 定義樹林(森林)是由 n 棵沒有交集的樹所構成的集合 n 0

下圖為一包含三棵樹的樹林

5101 轉換樹林為二元樹 之前我們以二元樹來表示樹因為二元樹有較多

好用的性質在此我們也將樹林轉換成二元樹看待

在 522 節中我們嘗試了用左子右兄弟的方式來表示樹這個方法事實上已將樹轉換成二元樹

即用左子右兄弟的結構重新組織樹林中的每一棵樹這些樹的樹根將視為兄弟而最左邊的樹則視為樹林二元樹的樹根如此一來樹林即可轉換成二元樹

範例 5-25右下圖為右上圖三棵樹所對應的三棵左子右兄弟二元樹

下圖則為上圖樹林所對應的一棵樹林二元樹

演算法 5-1  轉換樹林成二元樹輸入樹林 F = T1 T2 Tn

輸出 F 的二元樹表示 BT=(T1 T2 Tn)1 if (n == 0) 輸出空二元樹 2 else

3 root(T1 T2 Tn) = T1

4 leftsubtree(T1 T2 Tn) =

BT(T11 T12 T1m)5 BT(T11 T12 T1m)為 T1的子樹6 rightsubtree(T1 T2 Tn) =

BT(T2 T3 Tn)

樹林轉換成二元樹的過程樹林轉換成二元樹的過程

  • Slide 1
  • Slide 2
  • Slide 3
  • Slide 4
  • Slide 5
  • Slide 6
  • Slide 7
  • Slide 8
  • Slide 9
  • Slide 10
  • Slide 11
  • Slide 12
  • Slide 13
  • Slide 14
  • Slide 15
  • Slide 16
  • Slide 17
  • Slide 18
  • Slide 19
  • Slide 20
  • Slide 21
  • Slide 22
  • Slide 23
  • Slide 24
  • Slide 25
  • Slide 26
  • Slide 27
  • Slide 28
  • Slide 29
  • Slide 30
  • Slide 31
  • Slide 32
  • Slide 33
  • Slide 34
  • Slide 35
  • Slide 36
  • Slide 37
  • Slide 38
  • Slide 39
  • Slide 40
  • Slide 41
  • Slide 42
  • Slide 43
  • Slide 44
  • Slide 45
  • Slide 46
  • Slide 47
  • Slide 48
  • Slide 49
  • Slide 50
  • Slide 51
  • Slide 52
  • Slide 53
  • Slide 54
  • Slide 55
  • Slide 56
  • Slide 57
  • Slide 58
  • Slide 59
  • Slide 60
  • Slide 61
  • Slide 62
  • Slide 63
  • Slide 64
  • Slide 65
  • Slide 66
  • Slide 67
  • Slide 68
  • Slide 69
  • Slide 70
  • Slide 71
  • Slide 72
  • Slide 73
  • Slide 74
  • Slide 75
  • Slide 76
  • Slide 77
  • Slide 78
  • Slide 79
  • Slide 80
  • Slide 81
  • Slide 82
  • Slide 83
  • Slide 84
  • Slide 85
  • Slide 86
  • Slide 87
  • Slide 88
  • Slide 89
  • Slide 90
  • Slide 91
  • Slide 92
  • Slide 93
  • Slide 94
  • Slide 95
  • Slide 96
  • Slide 97
  • Slide 98
  • Slide 99
  • Slide 100
  • Slide 101
  • Slide 102
  • Slide 103
Page 3: 第五章 樹

51  樹的概念bull 樹 (tree) 是一種重要的離散結構 (discrete structure) 它提供一種「具有層次關係」的概念來結構資料

生物演化樹(evolutionary tree)

More Examples

512  專有名詞定義 樹為節點所組成的有限集合其中

(1) 存在一特殊節點 R 稱為樹根 (root) (2) 其它節點可分割成 n 個無交集的集合

T1 T2 hellip Tn n 0 而 T1 T2 hellip Tn 本身皆為樹稱其為 R 的子樹 (subtree)

A recursive definition 本章所提到的樹皆為「有根樹」 (rooted tree)

512  專有名詞 ( 續 )

bull 節點 (node) 圖中圓圈和向下的分支 (branch)bull 樹根 (root) 節點 Abull 分支度 (degree) 一個節點其所有子樹的數目bull 樹葉 (leaf) 分支度為 0 的節點bull 內部節點 (internal node) 非終端節點 (non-terminal

node)

bull 深度 (depth) 樹的最高階層 (level)

512  專有名詞 ( 續 )bull 兒子 (son) 任何節點 X 其子樹的樹根 Y 為 X 的

兒子bull 父親 (father parent) Y 是 X 的「父親」bull 兄弟 (sibling brother) 共有一個父親的數個節點bull 祖先 (ancestor) 該節點走向樹根所經過的所有節點bull 後裔 (descendant) 任一節點的所有子樹節點

52  樹的表示法bull 用陣列表示樹 bull 用鍵結串列表示樹 令分支度 =k

若 T 共有 n 個節點則需要 n 個鍵結節點計有 nk 個指標然而除了樹根外每個節點只須由唯一的指標所指向遂只需要有 n-1 個指標空間用不著的指標空間多達 nk-(n-1)=n(k-1)+1 個比用得著的還多著實浪費

521  一般化的串列表示bull 串列是一個有限且有順序的元素集合

A = (a1 a2 hellip an)

每個元素 ai 1in 皆有相同的資料型態 bull 倘若不限定各個元素都具有相同的資料型態則稱此串列為「一般化串列」 (generalized list) (ai 可以是一個節點或是一棵樹 )

bull 可用一般化串列來表示一棵樹 T T = (R T1 T2 hellip Tn)

其中 R 為 T 的樹根而 T1 T2 hellip Tn 為 R 的子樹 Ti 可能是一個節點也可能是一棵樹(一般化串列)

範例 5-1 以一般化串列表示樹bull (A (B (E K) (F L M) G) (C H) (D (I N) (J

O P)))

(A T1 T2 T3)T1 = (B (E K) (F L M) G)T2 = (C H)T3 = (D (I N) (J O P))

T1 T2T3

程式 5-1 一般化串列鍵結節點的宣告1 struct TreeNode2 int tag3 union4 int data5 struct TreeNode Tlink6 node7 struct TreeNode link8

(A T1 T2 T3)T1 = (B (E K) (F L M) G)T2 = (C H)T3 = (D (I N) (J O P))

522  左子右兄弟表示法

每個節點都有唯一的最左 兒子 (leftmost child) 每個節點都有最靠近它的 右兄弟

523 分支度為 2 的二元樹表示法bull 分支度為 2 的樹又稱為二元樹 (binary tree)

bull 二元樹中任一節點皆有 2個指標分別指向該節點的左子樹和右子樹

bull 二元樹可以表示任何樹事實上二元樹有許多很好的性質我們在一下節中介紹

bull注意樹狀結構中兒子的順序是不重要的試想樹可以樹根為軸心旋轉使得兒子的順序是不固定的

53  二元樹bull 二元樹是一個由節點組成的有限集合可以是空集合或是包含了(1) 一個樹根節點(2) 其它節點分割成兩個沒有交集的二元樹

其一為左子樹 (left subtree) 另一為右子樹 (right subtree) 樹和二元樹之間的差異

(1)樹不允許空節點而空二元樹是允許的

(2) 樹的子樹沒有順序而二元樹的子樹有左右之分

531  樹和二元樹的基本性質

樹或二元樹皆擁有下面的性質

定理 5-1 若一棵樹 T 的總節點數為 V 總邊數為

E 則V = E + 1

證明利用數學歸納法歸納法的基礎當 V=1 時即 T 只有一個樹根節點 E 為 0 所以 V = E + 1 成立歸納法的假設假設當 1 V K 時該式成立歸納法的推論當 V = K + 1 時令此樹的樹根為 R 而 R 有 m 個子樹 T1 T2 hellip

Tm (如圖 5-12 所示)分別有個 V1 V2 hellip Vm 個節點其中 V1 +V2 + hellip+ Vm =K 若 E1 E2 hellip Em 分別為此 m 個子樹的邊數則

E = E1 +E2 + hellip+ Em + m (1)根據歸納法的假設可知

V1 = E1+1V2 = E2+1 Vm = Em+1

所以V1 +V2 + hellip+Vm = E1 + E2 + hellip+ Em +m (2)

然而V = V1 +V2 + hellip+Vm+1 (3)

將式 (2) 代入式 (3) 可得V = E1 + E2 + hellip+ Em + m + 1

引用式 (1) 則可得V = E + 1

由數學歸納法的原理可得證本定理

532 二元樹的性質(a) 稱為歪斜樹 (skew tree) (b) 稱為完備二元樹 (complete binary tree)

( 其樹葉節點皆在相鄰階層完整定義在後 ) 同樣數目的樹節點存放在歪斜樹上會花較多的階層放在完備二元樹上則只須最少的階層

532 二元樹的性質 ( 續 )定理 5-2 (1) 二元樹上階層 i 的節點數目最多為 2i-1 i 1 (2) 深度為 k 的二元樹其節點數目最多為 2k-

1 k1 bull 定理 5-2說明了 (1) 每一階層上可放的最多節點數

(2) 階層數已知時二元樹可放的最多節點數證明 (1) 利用數學歸納法歸納法的基礎當 i=1 時只有樹根一個節點 2i-1=21-1=1 原式成

立歸納法的假設假設 i = k 時該式成立即階層 k 的最大節點數為

2k-1 k 1 歸納法的推論當 i = k+1 時因在階層 k 的每一個節點最多會有

2 個子節點遂在 k+1 階層最多會有 22k-1 = 2k

= 2i-1 節點由數學歸納法的原理可知本定理得證(2) 由 (1) 可知二元樹在階層 i 的最多節點數為 2i-1那麼深度 k 的二元樹最多可有節點如下

k

i

ki

1

1-1- 24212 1-2

)12(1 k= 2k-1

=

bull 對二元樹而言任一節點的分支度可能為 0 1 或 2 分支度為 0 者是為樹葉

bull 樹葉的數目與分支度為 2 的節點數目存在著有趣的關係請見定理 5-3

定理 5-3 若 T 為一非空的二元樹 n0 為樹葉節點數目 n2為分支度為 2 的節點數目則 n0 = n2+1

證明令 n 為所有節點數目而 n1 為分支度為 1 的節點數目可得 n = n0 + n1 +n2 (1)令 B 為二元樹的分支數除了樹根外每一節點皆有一分支指向之所以

n = B+1 (亦可參考定理 5-1 )又 B = n1+2n2

且 n = n1 + 2n2 +1 (2)由式 (1) 和式 (2) 我們得到

n0 = n2 + 1

完滿二元樹 (full binary tree)

定義一個深度為 k 的完滿二元樹即為一深度為 k 且有 2k-1 個節點的二元樹k 0

bull 由定理 5-2(2) 可知深度為 k 的二元樹其最多節點數為 2k-1 這樣的樹會將樹上所有可能存放節點的位置都放滿完滿 (full) 之名因而生成

完備二元樹 (complete binary tree)我們可對二元樹上節點予以編號階層小者先編同一階

層則自左向右編號這樣的編號方式使我們定義出完備(complete) 二元樹

定義深度為 k 節點數為 n 的二元樹稱為完備二元樹若且唯若 (1) k = 1 時樹有一個節點

(2) 當 k 2 且 1iltk 時深度 i 有 2i-1 個節點且第 k 層的節點皆由第 k-1 層的分支由左至右逐一連接而成

完滿或完備二元樹之所以受人注意乃因它們可以將節點以較少的階層數存放

定理 5-4 n 個節點的完備或完滿二元樹其深度為 log2(n+1)

證明 完滿或完備二元樹的節點皆自階層

1 起排放任一階層 i 可排放 2i-1

個節點遂由定理 5-2 得知此 n 個節點所佔的深度為 log2(n+1)

正規二元樹 (formal binary tree)bull 在二元樹中非樹葉的節點又稱為內部節點

(internal node) 若所有內部節點恰有 2 個子節點即成為正規二元樹

定義若二元樹中所有內部節點恰有 2 個子節點則稱之為正規二元樹

正規二元樹常被引用在單淘汰賽正規二元樹常被引用在單淘汰賽制的各項賽程安排樹葉為參賽制的各項賽程安排樹葉為參賽隊伍內部節點為優勝隊伍樹隊伍內部節點為優勝隊伍樹根即為冠軍隊伍根即為冠軍隊伍

在單淘汰賽制中自在單淘汰賽制中自 n n 個隊伍中產個隊伍中產生冠軍須舉辦 生冠軍須舉辦 nn-1-1場比賽場比賽

54  二元樹的表示法541 以陣列表示二元樹定理 5-5 若一個具有 n 個節點的完備二元樹以循序的方式編號並依序存放在陣列 A 中即第 i 個節點放在 A[i] 中 1 i n 則

(1) 節點 i 的父節點位在 A[i2]處 i ne 1( i = 1 時其節點正為樹根無父節點)

(2) 若 2i n 則節點 i 的左兒子節點位在 A[2i] 處若 2i gtn 節點 i 沒有左子節點

(3) 若 2i+1 n 節點 i 的右兒子節點位 A[2i+1]處若 2i+1gtn 節點 i 沒有左子

節點

證明先用數學歸納法證明 (2) bull 歸納法的基礎當 i = 1 時其左子節點確實在 A[2] 處(除非 nlt2 此時節點 1 沒有左子節點)

bull 歸納法的假設假設當 I = k 時其左子節點確在 A[2i] = A[2k] 處(除非 2i 已大於 n 此時節點 i 無左子節點)

bull 歸納法的推論當 i = k+1 時因第 k 節點的左子節點在 A[2k] 處其右子節點則位在 A[2k+1] 處那麼第 k+1 節點的左子節點應緊臨 A[2k+1] 亦即位於 A[2k+2] = A[2(k+1)] 處(除非 2i 已大於 n 此時節點 i 無左子節點)

bull 由數學歸納法的原理得證定理 5-5 (2) (3) 由於 (2) 成立而且節點 i 的右子節點會緊臨

A[2i] 亦即其位於 A[2i+1]處除非 i 根本沒有右子節點得證定理 5-5 (3)

由 (2) 和 (3) 我們可知定理 5-5 (1) 成立

542 以鍵結串列表示二元樹bull 以陣列來表示完備二元樹算是相當方便但ndash一般的二元樹將導致陣列空間的浪費ndash如果二元樹中節點的新增刪除動作頻繁則會造成陣列對應資料的搬動隨而頻繁

bull 以鍵結串列表示二元樹

為方便辦識樹根我們常用 root 指標指向樹根做為樹鍵結串列的起點

陣列表示和鍵結表示的空間需求bull 完備二元樹的陣列表示和鍵結表示皆沒浪費儲存資料的空間

bull 但歪斜樹的陣列表示比起鍵結表示則浪費許多

bull 以 n 個節點的歪斜樹而言陣列表示法可能需要 2n-1 個陣列元素其中 2n-1-n 個陣列元素空間是浪費的而鍵結表示法只需要 n 個節點空間

二元樹節點的宣告程式 5-2

1 struct BTreeNode

2 struct BtreeNode leftchiled

3 int data

4 struct BTreeNode rightchild

5

6 struct BTreeNode root

bull 這樣的二元樹節點要取得兒子節點的指標十分方便但若有取得父節點的需求則嫌不足我們可以加上 parent 欄位指向父節點

55  二元樹的走訪bull 假如一組資料已用二元樹的組織在一起總有需求對其全數資料做動作例如計算所有數目印出所有資料在所有資料中搜尋某項資料hellip等此時即須對此二元樹做走訪 (traversal) 的運算利用走訪二元樹的同時將計算列印或搜尋的動作完成

bull 事實上走訪即在決定二元樹上資料被處理(計算列印或搜尋)的順序我們也希望走訪的演算法對任何節點皆一致是容易撰寫程式實作的

bull 若對一個二元樹上的節點而言 V 表示處理節點上的資料 L 表示走訪其左子樹 R 表示走訪其右子樹

55  二元樹的走訪 ( 續 )bull 因為對稱的緣故我們只考慮先走訪左邊再走訪右邊的情形那麼右圖只剩下三種走訪方式

bull 以 V 所在的相對位置分別對此三種走訪方式取名如下

(1) LVR 中序走訪 (inorder traversal)或中序表示法 (infix notation)

(2) LRV 後序走訪 (postorder traversal) 或後序表示法 (postfix notation)

(3) VLR 前序走訪 (preorder traversal)或前序表示法 (prefix notation)

551  中序走訪bull 利用遞迴的方式撰寫中序走訪的程序希望把二元樹中序走訪的

順序印出來

程式 5-3二元樹的中序走訪1 struct BTreeNode2 struct BTreeNode leftchild3 char data4 struct BTreeNode rightchild5 6 struct BTreeNode root7 void inorder(struct BTreeNode node)8 if (node = NULL)9 inorder(node-gtleftchild)10 cout ltlt node-gtdata11 inorder(node-gtrightchild)12 13

範例 5-12此為運算式二元樹

其對應中序表示運算式為 (A+B)C+D(E-F)

6 struct BTreeNode root7 void inorder(struct BTreeNode node)8 if (node = NULL)9 inorder(node-gtleftchild)10 cout ltlt node-gtdata11 inorder(node-gtrightchild)12 13

6 struct BTreeNode root7 void inorder(struct BTreeNode node)8 if (node = NULL)9 inorder(node-gtleftchild)10 cout ltlt node-gtdata11 inorder(node-gtrightchild)12 13

(A+B)C+D(E-F)

如何加上 ( )

552  後序走訪程式 5-4二元樹的後序走訪14 void postorder(struct BTreeNode node)

15 if (node =NULL)16 postorder(node-gtleftchild)17 postorder(node-gtrightchild)18 cout ltlt node-gtdata19 20

以後序走訪可得到AB+CDEF-+

其正為對應的後序運算式

553  前序走訪程式 5-5二元樹的前序走訪21 void preorder(struct BTreeNode node)

22 if (node = NULL)

23 cout ltlt node-gtdata

24 preorder(node-gtleftchild)

25 preorder(node-gtrightchild)

26

27

以前序走訪可得到++ABCD-EF

其正為對應的前序運算式

554 利用堆疊和迴圈取代遞迴進行二元樹的走訪1 struct BTreeNode 2 struct BTreeNode leftchild3 char data4 struct BtreeNode rightchild5 6 struct BTreeNode root7 struct StackNode8 struct BTreeNode treenode9 struct StackNode next1011 struct StackNode top

12 void push_node(struct BtreeNode node )13 struct StackNode old_top14 old_top = top15 top = (struct StackNode ) malloc

(sizeof(struct StackNode))16 top-gttreenode = node17 top-gtnext = old_top18 19 struct BTreeNode pop_node()20 struct BTreeNode Tnode21 struct StackNode old_top22 if (top == NULL)23 StackEmpty()24 else25 Tnode = top-gttreenode26 old_top = top27 top = top-gtnext28 free(old_top)29 return Tnode30 31

32 void inorder_Stack(struct BtreeNode node)33 do34 while (node = NULL)35 push_node(node)36 node = node-gtleftchild37 push all left children38 if (top = NULL)39 node = pop_node()40 cout ltlt node-gtdata41 node = node-gtrightchild42 inorder printing and check right child43 while ((top=NULL)||(node=NULL))44 top==NULL 堆疊已空 node==NULL沒有右子

6 struct BTreeNode root7 void inorder(struct BTreeNode node)8 if (node = NULL)9 inorder(node-gtleftchild)10 cout ltlt node-gtdata11 inorder(node-gtrightchild)12 13

中序表示為DBGEHAFIC

32 void inorder_Stack()33 struct BTreeNode node34 node = root35 do36 while (node = NULL)37 push_node(node)38 node = node-gtleftchild39 40 if (top = NULL)41 node = pop_node()42 cout ltlt node-gtdata43 node = node-gtrightchild44 45 while(top=NULL)||(node=NULL))46

45 void Preorder_Stack(struct BSTreeNode node)46 do47 while (node = NULL)48 cout ltlt node-gtdata)49 push_node(node)50 node = node-gtleftchild51 push all left children52 if (top = NULL)53 node = pop_node()54 node = node-gtrightchild55 56 while ((top=NULL)||(node=NULL))57

程式 5-5二元樹的前序走訪21 void preorder(struct BTreeNode node)22 if (node = NULL)23 cout ltlt node-gtdata24 preorder(node-gtleftchild)25 preorder(node-gtrightchild)26 27

58 void Postorder_Stack(struct BTreeNode node)59 do60 while (node = NULL)61 cout ltlt node-gtdata62 push_node(node)63 node = node-gtrightchild64 push all left children65 if (top = NULL)66 node = pop_node()67 node = node-gtleftchild68 69 while ((top=NULL)||(node=NULL))70

程式 5-4二元樹的後序走訪14 void postorder(struct BTreeNode node)15 if (node =NULL)16 postorder(node-gtleftchild)17 postorder(node-gtrightchild)18 cout ltlt node-gtdata19 20

555  階層走訪bull 「階層走訪」 (level-order traversal) 是依階層

的順序進行二元樹的走訪先走訪階層小的節點後走訪階層大的節點同一階層者則依自左向右的順序走訪

bull 對下圖的二元樹進行階層走訪的結果為ABCDEFGHI

bull 對同一階層而言先走訪的節點其子節點亦在下一階層中先被走訪

bull 這種「先進先出」的特性恰可用佇列予以儲存與處理

程式 5-7 利用佇列進行二元樹的階層走訪

1 struct BTreeNode2 struct BTreeNode leftchild3 char data4 struct BTreeNode rightchild5 6 struct BTreeNode root7 struct QNode8 struct BTreeNode treenode9 struct QNode next10 11 struct QNode front rear

12 void AddQueue(struct BTreeNode Tnode)13 struct QNode node14 node=(struct QNode )malloc(sizeof(struct QNode))15 node-gttreenode = Tnode16 node-gtnext = NULL17 if (front == NULL) front = node19 else rear-gtnext = node20 rear = node21

22 struct BTreeNode DeleteQueue()23 struct BTreeNode Tnode24 struct QNode old_front25 if (rear == NULL)26 QueueEmpty()27 else28 Tnode = front-gttreenode29 old_front = front30 front = front-gtnext31 free(old_front)32 return Tnode33 34 35 void LevelOrder (struct BTreeNode node)36 AddQueue(node)37 while (front =NULL)38 node = DeleteQueue()39 cout ltlt node-gtdata40 if (node-gtleftchild = NULL)41 AddQueue(node-gtleftchild)42 if (node-gtrightchild = NULL)43 AddQueue(node-gtrightchild)44 45

35 void LevelOrder (struct BTreeNode node)36 AddQueue(node)37 while (front =NULL)38 node = DeleteQueue()39 cout ltlt node-gtdata40 if (node-gtleftchild = NULL)41 AddQueue(node-gtleftchild)42 if (node-gtrightchild = NULL)43 AddQueue(node-gtrightchild)44 45

bull Infix ABCbull Postfix ABC CBA ACB BCA

BAC CAB

B

A CA

B

C

B

A

C

C

B

AB

A

C

二元樹中序與前序式中序與後序式bull 每一棵二元樹皆有唯一的一對中序與前序表示法也有唯一的中序與後序次序

bull 換句話說給予一對中序與前序或中序與後序即可決定一棵二元樹

bull 然而給予前序和後序次序並不能決定唯一的二元樹可能會產生兩棵不同的二元樹

bull 想想階層走訪和中序走訪順序可否決定唯一的二元樹

A

B C

H J D E

F G

prefix ABHJCDFGEinfix HBJAFDGCEpostfix HJBFGDECA

VLRLVRLRV

A

prefix BHJinfix HBJ

prefix CDFGEinfix FDGCE

prefix ABHJCDFGEinfix HBJAFDGCE

A

B C

H J D E

F G

prefix ABHJCDFGE

infix HBJAFDGCEk-1

struct BTreeNode BTree_InfixPrefix (AnsiString infix AnsiString prefix)

int k struct BTreeNode node if (infixLength==0) return NULLnode = (struct BTreeNode ) malloc (sizeof(struct BTreeNode))node-gtdata = prefix[1]k = find(prefix[1] infix)node-gtleftchild = BTree_InfixPrefix

(infix[1k-1] prefix[2k])node-gtrightchild = BTree_InfixPrefix(infix[k+1infixLength]prefix[k+1prefixLength])

VLR

LVRk1

k2 k+1~

k+1~

1

struct BTreeNode BTree_InfixPostfix (AnsiString infix AnsiString prefix)

int k struct BTreeNode node if (infixLength==0) return NULLnode = (struct BTreeNode ) malloc (sizeof(struct BTreeNode))node-gtdata = postfix[postfixLength]k = find(postfix[postfixLength] infix)node-gtleftchild = BTree_InfixPrefix

(infix[1k-1] postfix[1k-1])node-gtrightchild = BTree_InfixPrefix(infix[k+1infixLength]postfix[k+1postfixLength-1])

56  二元樹的複製與相等測試程式 5-8 二元樹的複製1 struct BTreeNode2 struct BTreeNode leftchild3 char data4 struct BTreeNode rightchild5 6 struct BTreeNode root7 struct BTreeNode Copy(struct BTreeNode TreeRoot)8 struct BTreeNode CopyRoot9 if (TreeRoot == NULL)10 return NULL11 CopyRoot =(struct BTreeNode ) malloc (sizeof(BTreeNode))12 CopyRoot-gtdata = TreeRoot-gtdata13 CopyRoot-gtleftchild = Copy(TreeRoot-gtleftchild)14 CopyRoot-gtrightchild = Copy(TreeRoot-

gtrightchild)15 return CopyRoot16

程式 5-9 檢測兩棵二元樹是否相等1 struct BTreeNode2 struct BTreeNode leftchild3 char data4 struct BTreeNode rightchild5 6 struct BtreeNode rightchild7 int Equal(struct BTreeNode ustruct BTreeNode v)8 if ((u==NULL) ampamp (v==NULL)) return 19 if ((u=NULL) ampamp (v=NULL)10 ampamp (u-gtdata == v-gtdata )11 ampamp Equal(u-gtleftchildv-gtleftchild)12 ampamp Equal(u-gtrightchildv-gtrightchild))

13 return 114 return 015

Counting Nodes in a Binary Tree

int CountingNodes(struct BTreeNodes node)

if (node == NULL) return 0 int leftchildren = CountingNodes(node-gtleftchild)

int rightchildren= CountingNodes(node-gtrightchild)

return leftchildren+rightchildren

Counting Leaves in a Binary Tree

int CountingLeaves(struct BTreeNodes node)

if (node == NULL) return 0 int leftleaves = CountingLeaves(node-gtleftchild)

int rightchildren= CountingLeaves(node-gtrightchild)

if (leftleaves==0 ampamp leftleaves==0) return 1

return leftleaves + leftleaves

57  引線二元樹bull 在二元樹的鍵結表示中樹葉節點的兩個子樹指標皆指向 NULL 由定理 5-1 得知 n = E+1 其中 n 為節點個數 E 為分支數

bull 而 n 個鍵結節點有 2n 個指標空間每個分支恰佔用一個指標空間共有 E (=n-1) 個指標有用到(不是空的)而共有 n+1 個指標空間是 NULL 比非空的節點還多

bull 有學者提出對這些「放空指標的空間」加以利用的概念mdash與其放空指標不如放指向其它節點的指標稱之為引線 (thread) 使得某些運算(如走訪hellip等)可以加快

bull 這個概念發展出了引線二元樹 (threaded binary tree) 這種資料結構

範例 5-17下圖為加了引線的二元樹

加入的引線將使二元樹的中序走訪更加便利沿著上右圖箭頭所指示的順序走訪即可完成此二元樹的中序走訪GDHBAIECF 為此二元樹的中序表示

bull 範例 5-17 中所加的引線目的在方便二元樹的中序走訪

bull 對樹葉節點 p 而言令其資料為 P 則引線指標的規則如下

(1) p 的左子樹指標指向中序表示中 P 的前一個 元素(前接元素 (predecessor) )

(2) p 的右子樹指標指向中序表示中 P 的後一個 元素(後繼元素 (successor) )

為了區別節點指標放的是一般指標(內部節點)抑或是引線指標(樹葉節點)我們另以 2 個欄位分別區別左和右子樹指標空間存放的對象其節點的記憶體配置有如下圖所示

其中 leftthread (rightthread) 為 0 時表示 leftchild (rightchild) 存放的是一般節點指標而 leftthread (rightthread) 為 1 時表示 leftchild (rightchild) 存放的是引線指標1 struct TBTreeNode2 int leftthread3 struct TBTreeNode leftchild4 char data5 struct TBTreeNode rightchild6 int rightthread7

由右側的引線二元樹可知有最左和最右兩條引線尚無妥善的安排我們另設計一空的引線節點做為樹根如下圖所示

571  引線二元樹的中序走訪決定節點 p 在中序表示中的後繼節點 q (簡稱中序後繼點)

程式 5-10決定節點 node的中序後繼點1 struct TBTreeNode2 int leftthread3 struct TBTreeNode leftchild4 char data5 struct TBTreeNode rightchild6 int rightthread7 8 struct TBTreeNode root9 struct TBTreeNode Next(struct TBTreeNode node)

10 struct TBTreeNode temp11 temp = node-gtrightchild12 if (node-gtrightthead) return temp13 while (temp-gtleftthead) temp=temp-gtleftchild

14 return temp15

程式 5-11引線二元樹的中序表示(接程式 5-10)1 void InorderTBTree()2 struct TBTreeNode node3 for(node=Next(root)node=rootnode=Next(node))

4 cout ltlt node-gtdata5

bull只要 node 是引線二元樹上的任一節點透過呼叫 Next(node) 即可找到其在中序表示中的後繼節點bull(試想若程式 5-10 中的程序 Next 所傳入的參數指標正是 root Next(root) 是否可正常運作請思考)bull於是只須從中序表示中的第一節點起讓每一節點 node 都執行 Next(node) 程序即可得引線二元樹的中序表示

572  在引線二元樹中加入節點在引線二元樹中樹葉節點節點的右子樹指標所指向或內部節點右子樹的最左樹葉為其中序後繼節點而且任一節點若無左子樹則其左子樹指標乃指向其中序前接節點hellip這些性質必須在節點新增進入引線二元樹時也要保持接下來討論節點 new 插入成為引線二元樹的節點 p 的右子節點的情形至於插入成為左子節點的情形則讓各位模擬推敲

572572  在引線二元樹中加入節點 在引線二元樹中加入節點 (( 續續 ))

newnew 會成為會成為 pp 的右子節點的情況共有以下兩種的右子節點的情況共有以下兩種(1) (1) 若若 pp 沒有右子節點 沒有右子節點 ((p-gtrightthread p-gtrightthread 為為1) 1) 如圖如圖 ((a) a) 則 則 new new 插入後應形成如圖插入後應形成如圖 ((b) b) 所示所示

(2)(2) 若若 pp 有右子節點 有右子節點 ((p-gtrightthread p-gtrightthread 為為0) 0) 如圖如圖 ((c) c) 則 則 new new 插入後應形成如插入後應形成如圖圖 ((d) d) 所示所示

程式 5-12  插入節點 new 成為引線二元樹中節點 p的右子節點(上接程式 5-10 5-11 )

1 void InsertRight(struct TBTreeNode p 2 struct TBTreeNode new)3 struct TBTreeNode q4 new-gtrightchild = p-gtrightchild5 new-gtrightthead = p-gtrightthread6 new-gtleftchild = p7 new-gtleftthread = 18 p-gtrightchild = new9 p-gtrightthead = 010 if (new-gtrightthread)11 q = Next(new)12 q-gtleftchild = new13 14

58  堆積bull 「堆積」 (heap) 是一個特殊的完備二元樹節點的資料與其子節點的資料之間有事先定好的大小關係存在bull 定義ndash最大堆疊為一完備二元樹任一節點的資料內容不小於其子節點的資料內容ndash最小堆疊為一完備二元樹任一節點的資料內容不大於其子節點的資料內容

581  插入節點至最大堆積中最大堆積是一完備二元樹所以如圖 (a) 之最大堆積於加入新節點後樹的形狀必須如圖 (b) 所示灰色節點即為新節點若加入的節點資料分別為 1 9 12 則其最大堆積將分別變成如圖 (c) (d) (e) 所示

程式 5-13 新增節點至最大堆積中最大堆積本身是一棵完備二元樹可以用陣列表示之 1 define maxsize 1002 int heap[maxsize]3 int n i4 void InsertHeap(int x)5 if (n == maxsize) HeapFull()6 else7 n++8 i = n9 while ((i gt 1) ampamp (x gt heap[i2]))10 heap[i] = heap[i2]11 i = 212 13 heap[i] = x14 16

程序 InsertHeap 在插入第 n 個資料時需要的時間複雜度為 O(logn)

1 100

2 93

3 84

4 77

5 65

6 81

7 79

8 66

9 70

10 50

100

84

77

93

8165

66 70

79

50

1

2 3

4 5 6 7

8 9 10

1 define maxsize 1002 int heap[maxsize]3 int n i4 void InsertHeap(int x)5 if (n == maxsize) HeapFull()6 else7 n++8 i = n9 while ((i gt 1) ampamp (x gt heap[i2]))10 heap[i] = heap[i2]11 i = 212 13 heap[i] = x14 16

582 刪除最大堆積中的最大資料圖 (a) 中的最大堆積在刪除最大資料後其樹形應變成如圖 (b) 所示將最未一個樹葉節點的資料 6 暫挪至樹根節點內如圖 (c) 再將圖 (c) 中非最大堆積的結構調整成為最大堆積 10 和 8 中選大者與 6對調即成圖 (d)

程式 5-14 刪除最大堆積中的最大資料(上接程式 5-13 )1 int DeleteHeap()2 int x i j3 if (n == 0) HeapEmpty()4 else5 x = heap[1]6 heap[1] = heap[n]7 n--8 i = 19 while (i lt= n2)10 if (heap[2i] gt heap[2i+1])11 j = 2i12 else13 j = 2i+114 if (heap[i]gtheap[j]) break15 swap(heap[i] heap[j])16 i = j17 18 return x19 20

其時間複雜度與其堆積高度相同是為O(logn) 其中 n 為堆積中的元素個數

1 100

2 93

3 84

4 77

5 65

6 81

7 79

8 66

9 70

10 50

100

84

77

93

8165

66 70

79

50

1

2 3

4 5 6 7

8 9 10

1 int DeletHeap()2 int x i j3 if(n == 0) HeapEmpty()4 else5 x = heap[1]6 heap[1] = heap[n]7 n--8 i = 19 while (i lt= n2)10 if ([heap[2i]] gt heap[2i+1])11 j = 2i12 else13 j = 2i+114 if (heap[i]gtheap[j]) break15 swap(heap[i] heap[j])16 i = j17 18 return x19 20

583  優先佇列bull 由於最大堆積(或最小堆積)的結構可提供最大元素(或最小元素)而其維護堆積的成本為 O(logn) n 為總元素個數

bull 堆積常被當成「優先佇列」 (priority queue) 使用mdash此時佇列被組織成完備二元樹若新加入佇列的資料有大小關係優先順序的需求時此優先佇列即以 O(logn) 的代價將新資料放入優先佇列(可能是最大或最小堆積依實際大小關係優先順序的需求而定)中mdash提供依元素的優先順序做為處理順序的服務

bull 一般的佇列為先進先出先到的先行處理優先佇列則是依據其優先順序優先順序高者先行處理與來到的先後無關

範例 5-21bull 在「分時」 (time sharing) 的作業系統中我們不希望使用中央處理器 (Central Processing Unit CPU) 時間短的「處理程序」 (process) 等候過久於是有「最短時間工作優先」 (shortest-job-first) 的排程策略此時等候的處理程序即可組織成之一最小堆積而 CPU 每次即取出存於樹根的處理程序予以處理任何新來的處理程序則以 O(logn) 的時間加入此優先佇列(最小堆積)中

bull 注意之前提過的線性佇列新來的直接加入成為新的尾端元素花O(1) 的時間然線性佇列本身沒有大小的關係每次要取出最大(小)的資料得花O(n) 的代價(在所有元素中挑出最大(小)者) n 為佇列內的元素個數而優先佇列是在新元素加入時要花O(logn) 的時間至於取出最大(小)則只須 O(1) 的時間

59  二元搜尋樹bull 二元搜尋樹是一棵二元樹可能是空二元樹若不為空二元樹則滿足下列性質ndash所有節點內的資料內容是相異的 ndash左子節點的資內容 ( 如果有 ) 比父節點的資料內容小 ndash右子節點的資料內容 ( 如果有 ) 比父節點的資料內容大 ndash以左和右子節點為樹根的左子樹和右子樹也是二元搜尋樹

在二元搜尋樹中搜尋資料二元搜尋樹對資料大小的掌握十分明確若恰與節點 p 資料相等則搜尋成功若欲搜尋的資料 x 比樹節點 p 中的資料小 則只須搜尋節點 p 的左子樹若比節點 p 中的資料大 則只須搜尋 p 的右子樹這樣的判斷可以遞迴地應用在各子樹節點 中

程式 5-15 在二元搜尋樹中搜尋資料(遞迴呼叫)1 struct BSTreeNode2 struct BSTreeNode leftchild3 int data4 struct BSTreeNode rightchild5 6 struct BSTreeNode root7 struct BSTreeNode SearchBST(

struct BSTreeNode tree int x)8 if (tree == NULL) return NULL9 if (x == tree-gtdata) return tree10 if (x lt tree-gtdata)11 return SearchBST(tree-gtleftchild x)

12 return SearchBST(tree-gtrightchild x)13

程式 5-16 搜尋二元搜尋樹的資料(非遞迴呼叫)在二元搜尋樹十分龐大時遞迴呼叫的效率不會太好可利用迴圈來完成搜尋的動作以提高執行效率請看程式 5-16

14 struct BSTreeNode Search BST_iterative15 (struct BSTreeNode node int x)16 while (node = NULL)17 if (x == node-gtdata) return node18 if (x lt node-gtdata)19 node = node-gtleftchild20 else21 node = node-gtrightchild22 23 return NULL24

592  新增資料於二元搜尋樹中欲新增資料進入一二元搜尋樹中得先決定該資料在二元搜尋樹中應出現的位置這可透過上節提到的搜尋動作來解決

在二元搜尋樹定義的性質 (1) 中我們限定了元素必須相異所以搜尋新資料的結果必是 NULL

必須將搜尋的結果改為新增資料所應在位置的節點指標

程式 5-17  在二元搜尋樹中新增資料25 int InsertBST(int x)26 struct BSTreeNode p q27 p = root q = NULL28 while (p = NULL)29 q = p30 if (x == p-gtdata) return 0

若不考慮重複則可省略之31 if (x lt p-gtdata)32 p = p-gtleftchild33 else34 p = p-gtrightchild35

3636 p = (struct BSTreeNode ) p = (struct BSTreeNode ) malloc (sizeof(BSTreeNode))malloc (sizeof(BSTreeNode))

3737 p-gtdata = x p-gtdata = x3838 p-gtleftchild = p-gtrightchild = p-gtleftchild = p-gtrightchild = NULLNULL3939 if (root == NULL) root = p if (root == NULL) root = p4040 else if (x lt q-gtdata) else if (x lt q-gtdata)4141 q-gtleftchild = p q-gtleftchild = p4242 else else4343 q-gtrightchild = p q-gtrightchild = p4444 return 1 return 145 45

程式 程式 5-175-17  在二元搜尋樹中新增資料  在二元搜尋樹中新增資料 (( 續續 ))

範例 5-23 圖 (b) 為圖 (a) 新增整數 6 之後的結果圖 (c) (d) (e) (f) 則分別是逐步再加入 28 18

10 21 的結果

bull若目前的二元搜尋樹高度為 h 則新增資料得花O(h) 的時間找到其所應在的位置遂新增資料至二元搜尋樹中的時間複雜之度為 O(h)

593 自二元搜尋樹中刪除資料欲自二元搜尋樹中刪除資料可能會面臨兩種情況(1) 刪除節點 p 位於樹葉節點上如下圖所示 這是比較容易處理的情況

(2) 刪除的節點 p 不是樹葉刪除節點 p 後原 p 所指的節點可由 q ( q 中包含了 p 左子樹中的最大節點值)或 r ( r 中包含了 p 右子樹中的最小節點值)來取代以維持二元搜尋樹中資料的大小關係其結果分別如圖 (b) 和 (c) 所示而圖 (d) 則標明了指標會改變的節點位置即 p 會消失 father 的左或右子樹指標會異動 k(可能是 q 或 r )會移到原來 p 的位置其父節點 f 的左或右子樹指標會異動其中 father 為 p 父節點 f 為 k 的父節點

程式 5-18 自二元搜尋樹中刪除資料46 int DeleteBST (int x)47 Struct BSTreeNode p father k f48 p = root father = NULL49 while (p = NULL)50 if (x == p-gtdata) 找到 x 所在的節點 p 了51 if ((p-gtleftchild == NULL) ampamp 52 (p-gtrightchild == NULL))53 k = NULL p為樹葉節點54 else if (p-gtleftchild = NULL )x位於非子節

點55 f = p 找出 p左子樹的最右樹葉56 k = p-gtleftchild57 while (k-gtrightchild =NULL)58 f = k59 k = k-gtrightchild60 k將挪至原 p處61 if (p == f)62 f-gtleftchild = k-gtleftchild 63 else64 f-gtrightchild = k-gtleftchild 65

66 else p無左子樹67 f = p 找出 p右子樹的最左樹葉68 k = p-gtrightchild69 while (k-gtleftchild = NULL)70 f = k71 k = k -gtleftchild72 73 if (p == f)74 f-gtrightchild = k-gtrightchild 75 else76 f-gtleftchild = k-gtrightchild77 78 if (k = NULL) k挪至原 p處繼承 p的左右指標79 k-gtleftchild = p-gtleftchild80 k-gtrightchild = p-gtrightchild81 82 if (father == NULL) root = k83 else if (x lt father-gtdata) 決定 k是 father的左

或右兒子84 father-gtleftchild = k85 else86 father-gtrightchild = k87 free(p)88 return 1 成功地刪除 x於此傳回 1返回呼叫處89 51~89行為找到 x時應有的處理步驟

90 else 尚未找到 x繼續往下一階層找

91 father = p92 if (x lt p-gtdata)93 p = p-gtleftchild94 else95 p = p-gtrightchild96 97 98 return 0 未找到 x傳回 099

510  樹林 (forest)

bull 定義樹林(森林)是由 n 棵沒有交集的樹所構成的集合 n 0

下圖為一包含三棵樹的樹林

5101 轉換樹林為二元樹 之前我們以二元樹來表示樹因為二元樹有較多

好用的性質在此我們也將樹林轉換成二元樹看待

在 522 節中我們嘗試了用左子右兄弟的方式來表示樹這個方法事實上已將樹轉換成二元樹

即用左子右兄弟的結構重新組織樹林中的每一棵樹這些樹的樹根將視為兄弟而最左邊的樹則視為樹林二元樹的樹根如此一來樹林即可轉換成二元樹

範例 5-25右下圖為右上圖三棵樹所對應的三棵左子右兄弟二元樹

下圖則為上圖樹林所對應的一棵樹林二元樹

演算法 5-1  轉換樹林成二元樹輸入樹林 F = T1 T2 Tn

輸出 F 的二元樹表示 BT=(T1 T2 Tn)1 if (n == 0) 輸出空二元樹 2 else

3 root(T1 T2 Tn) = T1

4 leftsubtree(T1 T2 Tn) =

BT(T11 T12 T1m)5 BT(T11 T12 T1m)為 T1的子樹6 rightsubtree(T1 T2 Tn) =

BT(T2 T3 Tn)

樹林轉換成二元樹的過程樹林轉換成二元樹的過程

  • Slide 1
  • Slide 2
  • Slide 3
  • Slide 4
  • Slide 5
  • Slide 6
  • Slide 7
  • Slide 8
  • Slide 9
  • Slide 10
  • Slide 11
  • Slide 12
  • Slide 13
  • Slide 14
  • Slide 15
  • Slide 16
  • Slide 17
  • Slide 18
  • Slide 19
  • Slide 20
  • Slide 21
  • Slide 22
  • Slide 23
  • Slide 24
  • Slide 25
  • Slide 26
  • Slide 27
  • Slide 28
  • Slide 29
  • Slide 30
  • Slide 31
  • Slide 32
  • Slide 33
  • Slide 34
  • Slide 35
  • Slide 36
  • Slide 37
  • Slide 38
  • Slide 39
  • Slide 40
  • Slide 41
  • Slide 42
  • Slide 43
  • Slide 44
  • Slide 45
  • Slide 46
  • Slide 47
  • Slide 48
  • Slide 49
  • Slide 50
  • Slide 51
  • Slide 52
  • Slide 53
  • Slide 54
  • Slide 55
  • Slide 56
  • Slide 57
  • Slide 58
  • Slide 59
  • Slide 60
  • Slide 61
  • Slide 62
  • Slide 63
  • Slide 64
  • Slide 65
  • Slide 66
  • Slide 67
  • Slide 68
  • Slide 69
  • Slide 70
  • Slide 71
  • Slide 72
  • Slide 73
  • Slide 74
  • Slide 75
  • Slide 76
  • Slide 77
  • Slide 78
  • Slide 79
  • Slide 80
  • Slide 81
  • Slide 82
  • Slide 83
  • Slide 84
  • Slide 85
  • Slide 86
  • Slide 87
  • Slide 88
  • Slide 89
  • Slide 90
  • Slide 91
  • Slide 92
  • Slide 93
  • Slide 94
  • Slide 95
  • Slide 96
  • Slide 97
  • Slide 98
  • Slide 99
  • Slide 100
  • Slide 101
  • Slide 102
  • Slide 103
Page 4: 第五章 樹

生物演化樹(evolutionary tree)

More Examples

512  專有名詞定義 樹為節點所組成的有限集合其中

(1) 存在一特殊節點 R 稱為樹根 (root) (2) 其它節點可分割成 n 個無交集的集合

T1 T2 hellip Tn n 0 而 T1 T2 hellip Tn 本身皆為樹稱其為 R 的子樹 (subtree)

A recursive definition 本章所提到的樹皆為「有根樹」 (rooted tree)

512  專有名詞 ( 續 )

bull 節點 (node) 圖中圓圈和向下的分支 (branch)bull 樹根 (root) 節點 Abull 分支度 (degree) 一個節點其所有子樹的數目bull 樹葉 (leaf) 分支度為 0 的節點bull 內部節點 (internal node) 非終端節點 (non-terminal

node)

bull 深度 (depth) 樹的最高階層 (level)

512  專有名詞 ( 續 )bull 兒子 (son) 任何節點 X 其子樹的樹根 Y 為 X 的

兒子bull 父親 (father parent) Y 是 X 的「父親」bull 兄弟 (sibling brother) 共有一個父親的數個節點bull 祖先 (ancestor) 該節點走向樹根所經過的所有節點bull 後裔 (descendant) 任一節點的所有子樹節點

52  樹的表示法bull 用陣列表示樹 bull 用鍵結串列表示樹 令分支度 =k

若 T 共有 n 個節點則需要 n 個鍵結節點計有 nk 個指標然而除了樹根外每個節點只須由唯一的指標所指向遂只需要有 n-1 個指標空間用不著的指標空間多達 nk-(n-1)=n(k-1)+1 個比用得著的還多著實浪費

521  一般化的串列表示bull 串列是一個有限且有順序的元素集合

A = (a1 a2 hellip an)

每個元素 ai 1in 皆有相同的資料型態 bull 倘若不限定各個元素都具有相同的資料型態則稱此串列為「一般化串列」 (generalized list) (ai 可以是一個節點或是一棵樹 )

bull 可用一般化串列來表示一棵樹 T T = (R T1 T2 hellip Tn)

其中 R 為 T 的樹根而 T1 T2 hellip Tn 為 R 的子樹 Ti 可能是一個節點也可能是一棵樹(一般化串列)

範例 5-1 以一般化串列表示樹bull (A (B (E K) (F L M) G) (C H) (D (I N) (J

O P)))

(A T1 T2 T3)T1 = (B (E K) (F L M) G)T2 = (C H)T3 = (D (I N) (J O P))

T1 T2T3

程式 5-1 一般化串列鍵結節點的宣告1 struct TreeNode2 int tag3 union4 int data5 struct TreeNode Tlink6 node7 struct TreeNode link8

(A T1 T2 T3)T1 = (B (E K) (F L M) G)T2 = (C H)T3 = (D (I N) (J O P))

522  左子右兄弟表示法

每個節點都有唯一的最左 兒子 (leftmost child) 每個節點都有最靠近它的 右兄弟

523 分支度為 2 的二元樹表示法bull 分支度為 2 的樹又稱為二元樹 (binary tree)

bull 二元樹中任一節點皆有 2個指標分別指向該節點的左子樹和右子樹

bull 二元樹可以表示任何樹事實上二元樹有許多很好的性質我們在一下節中介紹

bull注意樹狀結構中兒子的順序是不重要的試想樹可以樹根為軸心旋轉使得兒子的順序是不固定的

53  二元樹bull 二元樹是一個由節點組成的有限集合可以是空集合或是包含了(1) 一個樹根節點(2) 其它節點分割成兩個沒有交集的二元樹

其一為左子樹 (left subtree) 另一為右子樹 (right subtree) 樹和二元樹之間的差異

(1)樹不允許空節點而空二元樹是允許的

(2) 樹的子樹沒有順序而二元樹的子樹有左右之分

531  樹和二元樹的基本性質

樹或二元樹皆擁有下面的性質

定理 5-1 若一棵樹 T 的總節點數為 V 總邊數為

E 則V = E + 1

證明利用數學歸納法歸納法的基礎當 V=1 時即 T 只有一個樹根節點 E 為 0 所以 V = E + 1 成立歸納法的假設假設當 1 V K 時該式成立歸納法的推論當 V = K + 1 時令此樹的樹根為 R 而 R 有 m 個子樹 T1 T2 hellip

Tm (如圖 5-12 所示)分別有個 V1 V2 hellip Vm 個節點其中 V1 +V2 + hellip+ Vm =K 若 E1 E2 hellip Em 分別為此 m 個子樹的邊數則

E = E1 +E2 + hellip+ Em + m (1)根據歸納法的假設可知

V1 = E1+1V2 = E2+1 Vm = Em+1

所以V1 +V2 + hellip+Vm = E1 + E2 + hellip+ Em +m (2)

然而V = V1 +V2 + hellip+Vm+1 (3)

將式 (2) 代入式 (3) 可得V = E1 + E2 + hellip+ Em + m + 1

引用式 (1) 則可得V = E + 1

由數學歸納法的原理可得證本定理

532 二元樹的性質(a) 稱為歪斜樹 (skew tree) (b) 稱為完備二元樹 (complete binary tree)

( 其樹葉節點皆在相鄰階層完整定義在後 ) 同樣數目的樹節點存放在歪斜樹上會花較多的階層放在完備二元樹上則只須最少的階層

532 二元樹的性質 ( 續 )定理 5-2 (1) 二元樹上階層 i 的節點數目最多為 2i-1 i 1 (2) 深度為 k 的二元樹其節點數目最多為 2k-

1 k1 bull 定理 5-2說明了 (1) 每一階層上可放的最多節點數

(2) 階層數已知時二元樹可放的最多節點數證明 (1) 利用數學歸納法歸納法的基礎當 i=1 時只有樹根一個節點 2i-1=21-1=1 原式成

立歸納法的假設假設 i = k 時該式成立即階層 k 的最大節點數為

2k-1 k 1 歸納法的推論當 i = k+1 時因在階層 k 的每一個節點最多會有

2 個子節點遂在 k+1 階層最多會有 22k-1 = 2k

= 2i-1 節點由數學歸納法的原理可知本定理得證(2) 由 (1) 可知二元樹在階層 i 的最多節點數為 2i-1那麼深度 k 的二元樹最多可有節點如下

k

i

ki

1

1-1- 24212 1-2

)12(1 k= 2k-1

=

bull 對二元樹而言任一節點的分支度可能為 0 1 或 2 分支度為 0 者是為樹葉

bull 樹葉的數目與分支度為 2 的節點數目存在著有趣的關係請見定理 5-3

定理 5-3 若 T 為一非空的二元樹 n0 為樹葉節點數目 n2為分支度為 2 的節點數目則 n0 = n2+1

證明令 n 為所有節點數目而 n1 為分支度為 1 的節點數目可得 n = n0 + n1 +n2 (1)令 B 為二元樹的分支數除了樹根外每一節點皆有一分支指向之所以

n = B+1 (亦可參考定理 5-1 )又 B = n1+2n2

且 n = n1 + 2n2 +1 (2)由式 (1) 和式 (2) 我們得到

n0 = n2 + 1

完滿二元樹 (full binary tree)

定義一個深度為 k 的完滿二元樹即為一深度為 k 且有 2k-1 個節點的二元樹k 0

bull 由定理 5-2(2) 可知深度為 k 的二元樹其最多節點數為 2k-1 這樣的樹會將樹上所有可能存放節點的位置都放滿完滿 (full) 之名因而生成

完備二元樹 (complete binary tree)我們可對二元樹上節點予以編號階層小者先編同一階

層則自左向右編號這樣的編號方式使我們定義出完備(complete) 二元樹

定義深度為 k 節點數為 n 的二元樹稱為完備二元樹若且唯若 (1) k = 1 時樹有一個節點

(2) 當 k 2 且 1iltk 時深度 i 有 2i-1 個節點且第 k 層的節點皆由第 k-1 層的分支由左至右逐一連接而成

完滿或完備二元樹之所以受人注意乃因它們可以將節點以較少的階層數存放

定理 5-4 n 個節點的完備或完滿二元樹其深度為 log2(n+1)

證明 完滿或完備二元樹的節點皆自階層

1 起排放任一階層 i 可排放 2i-1

個節點遂由定理 5-2 得知此 n 個節點所佔的深度為 log2(n+1)

正規二元樹 (formal binary tree)bull 在二元樹中非樹葉的節點又稱為內部節點

(internal node) 若所有內部節點恰有 2 個子節點即成為正規二元樹

定義若二元樹中所有內部節點恰有 2 個子節點則稱之為正規二元樹

正規二元樹常被引用在單淘汰賽正規二元樹常被引用在單淘汰賽制的各項賽程安排樹葉為參賽制的各項賽程安排樹葉為參賽隊伍內部節點為優勝隊伍樹隊伍內部節點為優勝隊伍樹根即為冠軍隊伍根即為冠軍隊伍

在單淘汰賽制中自在單淘汰賽制中自 n n 個隊伍中產個隊伍中產生冠軍須舉辦 生冠軍須舉辦 nn-1-1場比賽場比賽

54  二元樹的表示法541 以陣列表示二元樹定理 5-5 若一個具有 n 個節點的完備二元樹以循序的方式編號並依序存放在陣列 A 中即第 i 個節點放在 A[i] 中 1 i n 則

(1) 節點 i 的父節點位在 A[i2]處 i ne 1( i = 1 時其節點正為樹根無父節點)

(2) 若 2i n 則節點 i 的左兒子節點位在 A[2i] 處若 2i gtn 節點 i 沒有左子節點

(3) 若 2i+1 n 節點 i 的右兒子節點位 A[2i+1]處若 2i+1gtn 節點 i 沒有左子

節點

證明先用數學歸納法證明 (2) bull 歸納法的基礎當 i = 1 時其左子節點確實在 A[2] 處(除非 nlt2 此時節點 1 沒有左子節點)

bull 歸納法的假設假設當 I = k 時其左子節點確在 A[2i] = A[2k] 處(除非 2i 已大於 n 此時節點 i 無左子節點)

bull 歸納法的推論當 i = k+1 時因第 k 節點的左子節點在 A[2k] 處其右子節點則位在 A[2k+1] 處那麼第 k+1 節點的左子節點應緊臨 A[2k+1] 亦即位於 A[2k+2] = A[2(k+1)] 處(除非 2i 已大於 n 此時節點 i 無左子節點)

bull 由數學歸納法的原理得證定理 5-5 (2) (3) 由於 (2) 成立而且節點 i 的右子節點會緊臨

A[2i] 亦即其位於 A[2i+1]處除非 i 根本沒有右子節點得證定理 5-5 (3)

由 (2) 和 (3) 我們可知定理 5-5 (1) 成立

542 以鍵結串列表示二元樹bull 以陣列來表示完備二元樹算是相當方便但ndash一般的二元樹將導致陣列空間的浪費ndash如果二元樹中節點的新增刪除動作頻繁則會造成陣列對應資料的搬動隨而頻繁

bull 以鍵結串列表示二元樹

為方便辦識樹根我們常用 root 指標指向樹根做為樹鍵結串列的起點

陣列表示和鍵結表示的空間需求bull 完備二元樹的陣列表示和鍵結表示皆沒浪費儲存資料的空間

bull 但歪斜樹的陣列表示比起鍵結表示則浪費許多

bull 以 n 個節點的歪斜樹而言陣列表示法可能需要 2n-1 個陣列元素其中 2n-1-n 個陣列元素空間是浪費的而鍵結表示法只需要 n 個節點空間

二元樹節點的宣告程式 5-2

1 struct BTreeNode

2 struct BtreeNode leftchiled

3 int data

4 struct BTreeNode rightchild

5

6 struct BTreeNode root

bull 這樣的二元樹節點要取得兒子節點的指標十分方便但若有取得父節點的需求則嫌不足我們可以加上 parent 欄位指向父節點

55  二元樹的走訪bull 假如一組資料已用二元樹的組織在一起總有需求對其全數資料做動作例如計算所有數目印出所有資料在所有資料中搜尋某項資料hellip等此時即須對此二元樹做走訪 (traversal) 的運算利用走訪二元樹的同時將計算列印或搜尋的動作完成

bull 事實上走訪即在決定二元樹上資料被處理(計算列印或搜尋)的順序我們也希望走訪的演算法對任何節點皆一致是容易撰寫程式實作的

bull 若對一個二元樹上的節點而言 V 表示處理節點上的資料 L 表示走訪其左子樹 R 表示走訪其右子樹

55  二元樹的走訪 ( 續 )bull 因為對稱的緣故我們只考慮先走訪左邊再走訪右邊的情形那麼右圖只剩下三種走訪方式

bull 以 V 所在的相對位置分別對此三種走訪方式取名如下

(1) LVR 中序走訪 (inorder traversal)或中序表示法 (infix notation)

(2) LRV 後序走訪 (postorder traversal) 或後序表示法 (postfix notation)

(3) VLR 前序走訪 (preorder traversal)或前序表示法 (prefix notation)

551  中序走訪bull 利用遞迴的方式撰寫中序走訪的程序希望把二元樹中序走訪的

順序印出來

程式 5-3二元樹的中序走訪1 struct BTreeNode2 struct BTreeNode leftchild3 char data4 struct BTreeNode rightchild5 6 struct BTreeNode root7 void inorder(struct BTreeNode node)8 if (node = NULL)9 inorder(node-gtleftchild)10 cout ltlt node-gtdata11 inorder(node-gtrightchild)12 13

範例 5-12此為運算式二元樹

其對應中序表示運算式為 (A+B)C+D(E-F)

6 struct BTreeNode root7 void inorder(struct BTreeNode node)8 if (node = NULL)9 inorder(node-gtleftchild)10 cout ltlt node-gtdata11 inorder(node-gtrightchild)12 13

6 struct BTreeNode root7 void inorder(struct BTreeNode node)8 if (node = NULL)9 inorder(node-gtleftchild)10 cout ltlt node-gtdata11 inorder(node-gtrightchild)12 13

(A+B)C+D(E-F)

如何加上 ( )

552  後序走訪程式 5-4二元樹的後序走訪14 void postorder(struct BTreeNode node)

15 if (node =NULL)16 postorder(node-gtleftchild)17 postorder(node-gtrightchild)18 cout ltlt node-gtdata19 20

以後序走訪可得到AB+CDEF-+

其正為對應的後序運算式

553  前序走訪程式 5-5二元樹的前序走訪21 void preorder(struct BTreeNode node)

22 if (node = NULL)

23 cout ltlt node-gtdata

24 preorder(node-gtleftchild)

25 preorder(node-gtrightchild)

26

27

以前序走訪可得到++ABCD-EF

其正為對應的前序運算式

554 利用堆疊和迴圈取代遞迴進行二元樹的走訪1 struct BTreeNode 2 struct BTreeNode leftchild3 char data4 struct BtreeNode rightchild5 6 struct BTreeNode root7 struct StackNode8 struct BTreeNode treenode9 struct StackNode next1011 struct StackNode top

12 void push_node(struct BtreeNode node )13 struct StackNode old_top14 old_top = top15 top = (struct StackNode ) malloc

(sizeof(struct StackNode))16 top-gttreenode = node17 top-gtnext = old_top18 19 struct BTreeNode pop_node()20 struct BTreeNode Tnode21 struct StackNode old_top22 if (top == NULL)23 StackEmpty()24 else25 Tnode = top-gttreenode26 old_top = top27 top = top-gtnext28 free(old_top)29 return Tnode30 31

32 void inorder_Stack(struct BtreeNode node)33 do34 while (node = NULL)35 push_node(node)36 node = node-gtleftchild37 push all left children38 if (top = NULL)39 node = pop_node()40 cout ltlt node-gtdata41 node = node-gtrightchild42 inorder printing and check right child43 while ((top=NULL)||(node=NULL))44 top==NULL 堆疊已空 node==NULL沒有右子

6 struct BTreeNode root7 void inorder(struct BTreeNode node)8 if (node = NULL)9 inorder(node-gtleftchild)10 cout ltlt node-gtdata11 inorder(node-gtrightchild)12 13

中序表示為DBGEHAFIC

32 void inorder_Stack()33 struct BTreeNode node34 node = root35 do36 while (node = NULL)37 push_node(node)38 node = node-gtleftchild39 40 if (top = NULL)41 node = pop_node()42 cout ltlt node-gtdata43 node = node-gtrightchild44 45 while(top=NULL)||(node=NULL))46

45 void Preorder_Stack(struct BSTreeNode node)46 do47 while (node = NULL)48 cout ltlt node-gtdata)49 push_node(node)50 node = node-gtleftchild51 push all left children52 if (top = NULL)53 node = pop_node()54 node = node-gtrightchild55 56 while ((top=NULL)||(node=NULL))57

程式 5-5二元樹的前序走訪21 void preorder(struct BTreeNode node)22 if (node = NULL)23 cout ltlt node-gtdata24 preorder(node-gtleftchild)25 preorder(node-gtrightchild)26 27

58 void Postorder_Stack(struct BTreeNode node)59 do60 while (node = NULL)61 cout ltlt node-gtdata62 push_node(node)63 node = node-gtrightchild64 push all left children65 if (top = NULL)66 node = pop_node()67 node = node-gtleftchild68 69 while ((top=NULL)||(node=NULL))70

程式 5-4二元樹的後序走訪14 void postorder(struct BTreeNode node)15 if (node =NULL)16 postorder(node-gtleftchild)17 postorder(node-gtrightchild)18 cout ltlt node-gtdata19 20

555  階層走訪bull 「階層走訪」 (level-order traversal) 是依階層

的順序進行二元樹的走訪先走訪階層小的節點後走訪階層大的節點同一階層者則依自左向右的順序走訪

bull 對下圖的二元樹進行階層走訪的結果為ABCDEFGHI

bull 對同一階層而言先走訪的節點其子節點亦在下一階層中先被走訪

bull 這種「先進先出」的特性恰可用佇列予以儲存與處理

程式 5-7 利用佇列進行二元樹的階層走訪

1 struct BTreeNode2 struct BTreeNode leftchild3 char data4 struct BTreeNode rightchild5 6 struct BTreeNode root7 struct QNode8 struct BTreeNode treenode9 struct QNode next10 11 struct QNode front rear

12 void AddQueue(struct BTreeNode Tnode)13 struct QNode node14 node=(struct QNode )malloc(sizeof(struct QNode))15 node-gttreenode = Tnode16 node-gtnext = NULL17 if (front == NULL) front = node19 else rear-gtnext = node20 rear = node21

22 struct BTreeNode DeleteQueue()23 struct BTreeNode Tnode24 struct QNode old_front25 if (rear == NULL)26 QueueEmpty()27 else28 Tnode = front-gttreenode29 old_front = front30 front = front-gtnext31 free(old_front)32 return Tnode33 34 35 void LevelOrder (struct BTreeNode node)36 AddQueue(node)37 while (front =NULL)38 node = DeleteQueue()39 cout ltlt node-gtdata40 if (node-gtleftchild = NULL)41 AddQueue(node-gtleftchild)42 if (node-gtrightchild = NULL)43 AddQueue(node-gtrightchild)44 45

35 void LevelOrder (struct BTreeNode node)36 AddQueue(node)37 while (front =NULL)38 node = DeleteQueue()39 cout ltlt node-gtdata40 if (node-gtleftchild = NULL)41 AddQueue(node-gtleftchild)42 if (node-gtrightchild = NULL)43 AddQueue(node-gtrightchild)44 45

bull Infix ABCbull Postfix ABC CBA ACB BCA

BAC CAB

B

A CA

B

C

B

A

C

C

B

AB

A

C

二元樹中序與前序式中序與後序式bull 每一棵二元樹皆有唯一的一對中序與前序表示法也有唯一的中序與後序次序

bull 換句話說給予一對中序與前序或中序與後序即可決定一棵二元樹

bull 然而給予前序和後序次序並不能決定唯一的二元樹可能會產生兩棵不同的二元樹

bull 想想階層走訪和中序走訪順序可否決定唯一的二元樹

A

B C

H J D E

F G

prefix ABHJCDFGEinfix HBJAFDGCEpostfix HJBFGDECA

VLRLVRLRV

A

prefix BHJinfix HBJ

prefix CDFGEinfix FDGCE

prefix ABHJCDFGEinfix HBJAFDGCE

A

B C

H J D E

F G

prefix ABHJCDFGE

infix HBJAFDGCEk-1

struct BTreeNode BTree_InfixPrefix (AnsiString infix AnsiString prefix)

int k struct BTreeNode node if (infixLength==0) return NULLnode = (struct BTreeNode ) malloc (sizeof(struct BTreeNode))node-gtdata = prefix[1]k = find(prefix[1] infix)node-gtleftchild = BTree_InfixPrefix

(infix[1k-1] prefix[2k])node-gtrightchild = BTree_InfixPrefix(infix[k+1infixLength]prefix[k+1prefixLength])

VLR

LVRk1

k2 k+1~

k+1~

1

struct BTreeNode BTree_InfixPostfix (AnsiString infix AnsiString prefix)

int k struct BTreeNode node if (infixLength==0) return NULLnode = (struct BTreeNode ) malloc (sizeof(struct BTreeNode))node-gtdata = postfix[postfixLength]k = find(postfix[postfixLength] infix)node-gtleftchild = BTree_InfixPrefix

(infix[1k-1] postfix[1k-1])node-gtrightchild = BTree_InfixPrefix(infix[k+1infixLength]postfix[k+1postfixLength-1])

56  二元樹的複製與相等測試程式 5-8 二元樹的複製1 struct BTreeNode2 struct BTreeNode leftchild3 char data4 struct BTreeNode rightchild5 6 struct BTreeNode root7 struct BTreeNode Copy(struct BTreeNode TreeRoot)8 struct BTreeNode CopyRoot9 if (TreeRoot == NULL)10 return NULL11 CopyRoot =(struct BTreeNode ) malloc (sizeof(BTreeNode))12 CopyRoot-gtdata = TreeRoot-gtdata13 CopyRoot-gtleftchild = Copy(TreeRoot-gtleftchild)14 CopyRoot-gtrightchild = Copy(TreeRoot-

gtrightchild)15 return CopyRoot16

程式 5-9 檢測兩棵二元樹是否相等1 struct BTreeNode2 struct BTreeNode leftchild3 char data4 struct BTreeNode rightchild5 6 struct BtreeNode rightchild7 int Equal(struct BTreeNode ustruct BTreeNode v)8 if ((u==NULL) ampamp (v==NULL)) return 19 if ((u=NULL) ampamp (v=NULL)10 ampamp (u-gtdata == v-gtdata )11 ampamp Equal(u-gtleftchildv-gtleftchild)12 ampamp Equal(u-gtrightchildv-gtrightchild))

13 return 114 return 015

Counting Nodes in a Binary Tree

int CountingNodes(struct BTreeNodes node)

if (node == NULL) return 0 int leftchildren = CountingNodes(node-gtleftchild)

int rightchildren= CountingNodes(node-gtrightchild)

return leftchildren+rightchildren

Counting Leaves in a Binary Tree

int CountingLeaves(struct BTreeNodes node)

if (node == NULL) return 0 int leftleaves = CountingLeaves(node-gtleftchild)

int rightchildren= CountingLeaves(node-gtrightchild)

if (leftleaves==0 ampamp leftleaves==0) return 1

return leftleaves + leftleaves

57  引線二元樹bull 在二元樹的鍵結表示中樹葉節點的兩個子樹指標皆指向 NULL 由定理 5-1 得知 n = E+1 其中 n 為節點個數 E 為分支數

bull 而 n 個鍵結節點有 2n 個指標空間每個分支恰佔用一個指標空間共有 E (=n-1) 個指標有用到(不是空的)而共有 n+1 個指標空間是 NULL 比非空的節點還多

bull 有學者提出對這些「放空指標的空間」加以利用的概念mdash與其放空指標不如放指向其它節點的指標稱之為引線 (thread) 使得某些運算(如走訪hellip等)可以加快

bull 這個概念發展出了引線二元樹 (threaded binary tree) 這種資料結構

範例 5-17下圖為加了引線的二元樹

加入的引線將使二元樹的中序走訪更加便利沿著上右圖箭頭所指示的順序走訪即可完成此二元樹的中序走訪GDHBAIECF 為此二元樹的中序表示

bull 範例 5-17 中所加的引線目的在方便二元樹的中序走訪

bull 對樹葉節點 p 而言令其資料為 P 則引線指標的規則如下

(1) p 的左子樹指標指向中序表示中 P 的前一個 元素(前接元素 (predecessor) )

(2) p 的右子樹指標指向中序表示中 P 的後一個 元素(後繼元素 (successor) )

為了區別節點指標放的是一般指標(內部節點)抑或是引線指標(樹葉節點)我們另以 2 個欄位分別區別左和右子樹指標空間存放的對象其節點的記憶體配置有如下圖所示

其中 leftthread (rightthread) 為 0 時表示 leftchild (rightchild) 存放的是一般節點指標而 leftthread (rightthread) 為 1 時表示 leftchild (rightchild) 存放的是引線指標1 struct TBTreeNode2 int leftthread3 struct TBTreeNode leftchild4 char data5 struct TBTreeNode rightchild6 int rightthread7

由右側的引線二元樹可知有最左和最右兩條引線尚無妥善的安排我們另設計一空的引線節點做為樹根如下圖所示

571  引線二元樹的中序走訪決定節點 p 在中序表示中的後繼節點 q (簡稱中序後繼點)

程式 5-10決定節點 node的中序後繼點1 struct TBTreeNode2 int leftthread3 struct TBTreeNode leftchild4 char data5 struct TBTreeNode rightchild6 int rightthread7 8 struct TBTreeNode root9 struct TBTreeNode Next(struct TBTreeNode node)

10 struct TBTreeNode temp11 temp = node-gtrightchild12 if (node-gtrightthead) return temp13 while (temp-gtleftthead) temp=temp-gtleftchild

14 return temp15

程式 5-11引線二元樹的中序表示(接程式 5-10)1 void InorderTBTree()2 struct TBTreeNode node3 for(node=Next(root)node=rootnode=Next(node))

4 cout ltlt node-gtdata5

bull只要 node 是引線二元樹上的任一節點透過呼叫 Next(node) 即可找到其在中序表示中的後繼節點bull(試想若程式 5-10 中的程序 Next 所傳入的參數指標正是 root Next(root) 是否可正常運作請思考)bull於是只須從中序表示中的第一節點起讓每一節點 node 都執行 Next(node) 程序即可得引線二元樹的中序表示

572  在引線二元樹中加入節點在引線二元樹中樹葉節點節點的右子樹指標所指向或內部節點右子樹的最左樹葉為其中序後繼節點而且任一節點若無左子樹則其左子樹指標乃指向其中序前接節點hellip這些性質必須在節點新增進入引線二元樹時也要保持接下來討論節點 new 插入成為引線二元樹的節點 p 的右子節點的情形至於插入成為左子節點的情形則讓各位模擬推敲

572572  在引線二元樹中加入節點 在引線二元樹中加入節點 (( 續續 ))

newnew 會成為會成為 pp 的右子節點的情況共有以下兩種的右子節點的情況共有以下兩種(1) (1) 若若 pp 沒有右子節點 沒有右子節點 ((p-gtrightthread p-gtrightthread 為為1) 1) 如圖如圖 ((a) a) 則 則 new new 插入後應形成如圖插入後應形成如圖 ((b) b) 所示所示

(2)(2) 若若 pp 有右子節點 有右子節點 ((p-gtrightthread p-gtrightthread 為為0) 0) 如圖如圖 ((c) c) 則 則 new new 插入後應形成如插入後應形成如圖圖 ((d) d) 所示所示

程式 5-12  插入節點 new 成為引線二元樹中節點 p的右子節點(上接程式 5-10 5-11 )

1 void InsertRight(struct TBTreeNode p 2 struct TBTreeNode new)3 struct TBTreeNode q4 new-gtrightchild = p-gtrightchild5 new-gtrightthead = p-gtrightthread6 new-gtleftchild = p7 new-gtleftthread = 18 p-gtrightchild = new9 p-gtrightthead = 010 if (new-gtrightthread)11 q = Next(new)12 q-gtleftchild = new13 14

58  堆積bull 「堆積」 (heap) 是一個特殊的完備二元樹節點的資料與其子節點的資料之間有事先定好的大小關係存在bull 定義ndash最大堆疊為一完備二元樹任一節點的資料內容不小於其子節點的資料內容ndash最小堆疊為一完備二元樹任一節點的資料內容不大於其子節點的資料內容

581  插入節點至最大堆積中最大堆積是一完備二元樹所以如圖 (a) 之最大堆積於加入新節點後樹的形狀必須如圖 (b) 所示灰色節點即為新節點若加入的節點資料分別為 1 9 12 則其最大堆積將分別變成如圖 (c) (d) (e) 所示

程式 5-13 新增節點至最大堆積中最大堆積本身是一棵完備二元樹可以用陣列表示之 1 define maxsize 1002 int heap[maxsize]3 int n i4 void InsertHeap(int x)5 if (n == maxsize) HeapFull()6 else7 n++8 i = n9 while ((i gt 1) ampamp (x gt heap[i2]))10 heap[i] = heap[i2]11 i = 212 13 heap[i] = x14 16

程序 InsertHeap 在插入第 n 個資料時需要的時間複雜度為 O(logn)

1 100

2 93

3 84

4 77

5 65

6 81

7 79

8 66

9 70

10 50

100

84

77

93

8165

66 70

79

50

1

2 3

4 5 6 7

8 9 10

1 define maxsize 1002 int heap[maxsize]3 int n i4 void InsertHeap(int x)5 if (n == maxsize) HeapFull()6 else7 n++8 i = n9 while ((i gt 1) ampamp (x gt heap[i2]))10 heap[i] = heap[i2]11 i = 212 13 heap[i] = x14 16

582 刪除最大堆積中的最大資料圖 (a) 中的最大堆積在刪除最大資料後其樹形應變成如圖 (b) 所示將最未一個樹葉節點的資料 6 暫挪至樹根節點內如圖 (c) 再將圖 (c) 中非最大堆積的結構調整成為最大堆積 10 和 8 中選大者與 6對調即成圖 (d)

程式 5-14 刪除最大堆積中的最大資料(上接程式 5-13 )1 int DeleteHeap()2 int x i j3 if (n == 0) HeapEmpty()4 else5 x = heap[1]6 heap[1] = heap[n]7 n--8 i = 19 while (i lt= n2)10 if (heap[2i] gt heap[2i+1])11 j = 2i12 else13 j = 2i+114 if (heap[i]gtheap[j]) break15 swap(heap[i] heap[j])16 i = j17 18 return x19 20

其時間複雜度與其堆積高度相同是為O(logn) 其中 n 為堆積中的元素個數

1 100

2 93

3 84

4 77

5 65

6 81

7 79

8 66

9 70

10 50

100

84

77

93

8165

66 70

79

50

1

2 3

4 5 6 7

8 9 10

1 int DeletHeap()2 int x i j3 if(n == 0) HeapEmpty()4 else5 x = heap[1]6 heap[1] = heap[n]7 n--8 i = 19 while (i lt= n2)10 if ([heap[2i]] gt heap[2i+1])11 j = 2i12 else13 j = 2i+114 if (heap[i]gtheap[j]) break15 swap(heap[i] heap[j])16 i = j17 18 return x19 20

583  優先佇列bull 由於最大堆積(或最小堆積)的結構可提供最大元素(或最小元素)而其維護堆積的成本為 O(logn) n 為總元素個數

bull 堆積常被當成「優先佇列」 (priority queue) 使用mdash此時佇列被組織成完備二元樹若新加入佇列的資料有大小關係優先順序的需求時此優先佇列即以 O(logn) 的代價將新資料放入優先佇列(可能是最大或最小堆積依實際大小關係優先順序的需求而定)中mdash提供依元素的優先順序做為處理順序的服務

bull 一般的佇列為先進先出先到的先行處理優先佇列則是依據其優先順序優先順序高者先行處理與來到的先後無關

範例 5-21bull 在「分時」 (time sharing) 的作業系統中我們不希望使用中央處理器 (Central Processing Unit CPU) 時間短的「處理程序」 (process) 等候過久於是有「最短時間工作優先」 (shortest-job-first) 的排程策略此時等候的處理程序即可組織成之一最小堆積而 CPU 每次即取出存於樹根的處理程序予以處理任何新來的處理程序則以 O(logn) 的時間加入此優先佇列(最小堆積)中

bull 注意之前提過的線性佇列新來的直接加入成為新的尾端元素花O(1) 的時間然線性佇列本身沒有大小的關係每次要取出最大(小)的資料得花O(n) 的代價(在所有元素中挑出最大(小)者) n 為佇列內的元素個數而優先佇列是在新元素加入時要花O(logn) 的時間至於取出最大(小)則只須 O(1) 的時間

59  二元搜尋樹bull 二元搜尋樹是一棵二元樹可能是空二元樹若不為空二元樹則滿足下列性質ndash所有節點內的資料內容是相異的 ndash左子節點的資內容 ( 如果有 ) 比父節點的資料內容小 ndash右子節點的資料內容 ( 如果有 ) 比父節點的資料內容大 ndash以左和右子節點為樹根的左子樹和右子樹也是二元搜尋樹

在二元搜尋樹中搜尋資料二元搜尋樹對資料大小的掌握十分明確若恰與節點 p 資料相等則搜尋成功若欲搜尋的資料 x 比樹節點 p 中的資料小 則只須搜尋節點 p 的左子樹若比節點 p 中的資料大 則只須搜尋 p 的右子樹這樣的判斷可以遞迴地應用在各子樹節點 中

程式 5-15 在二元搜尋樹中搜尋資料(遞迴呼叫)1 struct BSTreeNode2 struct BSTreeNode leftchild3 int data4 struct BSTreeNode rightchild5 6 struct BSTreeNode root7 struct BSTreeNode SearchBST(

struct BSTreeNode tree int x)8 if (tree == NULL) return NULL9 if (x == tree-gtdata) return tree10 if (x lt tree-gtdata)11 return SearchBST(tree-gtleftchild x)

12 return SearchBST(tree-gtrightchild x)13

程式 5-16 搜尋二元搜尋樹的資料(非遞迴呼叫)在二元搜尋樹十分龐大時遞迴呼叫的效率不會太好可利用迴圈來完成搜尋的動作以提高執行效率請看程式 5-16

14 struct BSTreeNode Search BST_iterative15 (struct BSTreeNode node int x)16 while (node = NULL)17 if (x == node-gtdata) return node18 if (x lt node-gtdata)19 node = node-gtleftchild20 else21 node = node-gtrightchild22 23 return NULL24

592  新增資料於二元搜尋樹中欲新增資料進入一二元搜尋樹中得先決定該資料在二元搜尋樹中應出現的位置這可透過上節提到的搜尋動作來解決

在二元搜尋樹定義的性質 (1) 中我們限定了元素必須相異所以搜尋新資料的結果必是 NULL

必須將搜尋的結果改為新增資料所應在位置的節點指標

程式 5-17  在二元搜尋樹中新增資料25 int InsertBST(int x)26 struct BSTreeNode p q27 p = root q = NULL28 while (p = NULL)29 q = p30 if (x == p-gtdata) return 0

若不考慮重複則可省略之31 if (x lt p-gtdata)32 p = p-gtleftchild33 else34 p = p-gtrightchild35

3636 p = (struct BSTreeNode ) p = (struct BSTreeNode ) malloc (sizeof(BSTreeNode))malloc (sizeof(BSTreeNode))

3737 p-gtdata = x p-gtdata = x3838 p-gtleftchild = p-gtrightchild = p-gtleftchild = p-gtrightchild = NULLNULL3939 if (root == NULL) root = p if (root == NULL) root = p4040 else if (x lt q-gtdata) else if (x lt q-gtdata)4141 q-gtleftchild = p q-gtleftchild = p4242 else else4343 q-gtrightchild = p q-gtrightchild = p4444 return 1 return 145 45

程式 程式 5-175-17  在二元搜尋樹中新增資料  在二元搜尋樹中新增資料 (( 續續 ))

範例 5-23 圖 (b) 為圖 (a) 新增整數 6 之後的結果圖 (c) (d) (e) (f) 則分別是逐步再加入 28 18

10 21 的結果

bull若目前的二元搜尋樹高度為 h 則新增資料得花O(h) 的時間找到其所應在的位置遂新增資料至二元搜尋樹中的時間複雜之度為 O(h)

593 自二元搜尋樹中刪除資料欲自二元搜尋樹中刪除資料可能會面臨兩種情況(1) 刪除節點 p 位於樹葉節點上如下圖所示 這是比較容易處理的情況

(2) 刪除的節點 p 不是樹葉刪除節點 p 後原 p 所指的節點可由 q ( q 中包含了 p 左子樹中的最大節點值)或 r ( r 中包含了 p 右子樹中的最小節點值)來取代以維持二元搜尋樹中資料的大小關係其結果分別如圖 (b) 和 (c) 所示而圖 (d) 則標明了指標會改變的節點位置即 p 會消失 father 的左或右子樹指標會異動 k(可能是 q 或 r )會移到原來 p 的位置其父節點 f 的左或右子樹指標會異動其中 father 為 p 父節點 f 為 k 的父節點

程式 5-18 自二元搜尋樹中刪除資料46 int DeleteBST (int x)47 Struct BSTreeNode p father k f48 p = root father = NULL49 while (p = NULL)50 if (x == p-gtdata) 找到 x 所在的節點 p 了51 if ((p-gtleftchild == NULL) ampamp 52 (p-gtrightchild == NULL))53 k = NULL p為樹葉節點54 else if (p-gtleftchild = NULL )x位於非子節

點55 f = p 找出 p左子樹的最右樹葉56 k = p-gtleftchild57 while (k-gtrightchild =NULL)58 f = k59 k = k-gtrightchild60 k將挪至原 p處61 if (p == f)62 f-gtleftchild = k-gtleftchild 63 else64 f-gtrightchild = k-gtleftchild 65

66 else p無左子樹67 f = p 找出 p右子樹的最左樹葉68 k = p-gtrightchild69 while (k-gtleftchild = NULL)70 f = k71 k = k -gtleftchild72 73 if (p == f)74 f-gtrightchild = k-gtrightchild 75 else76 f-gtleftchild = k-gtrightchild77 78 if (k = NULL) k挪至原 p處繼承 p的左右指標79 k-gtleftchild = p-gtleftchild80 k-gtrightchild = p-gtrightchild81 82 if (father == NULL) root = k83 else if (x lt father-gtdata) 決定 k是 father的左

或右兒子84 father-gtleftchild = k85 else86 father-gtrightchild = k87 free(p)88 return 1 成功地刪除 x於此傳回 1返回呼叫處89 51~89行為找到 x時應有的處理步驟

90 else 尚未找到 x繼續往下一階層找

91 father = p92 if (x lt p-gtdata)93 p = p-gtleftchild94 else95 p = p-gtrightchild96 97 98 return 0 未找到 x傳回 099

510  樹林 (forest)

bull 定義樹林(森林)是由 n 棵沒有交集的樹所構成的集合 n 0

下圖為一包含三棵樹的樹林

5101 轉換樹林為二元樹 之前我們以二元樹來表示樹因為二元樹有較多

好用的性質在此我們也將樹林轉換成二元樹看待

在 522 節中我們嘗試了用左子右兄弟的方式來表示樹這個方法事實上已將樹轉換成二元樹

即用左子右兄弟的結構重新組織樹林中的每一棵樹這些樹的樹根將視為兄弟而最左邊的樹則視為樹林二元樹的樹根如此一來樹林即可轉換成二元樹

範例 5-25右下圖為右上圖三棵樹所對應的三棵左子右兄弟二元樹

下圖則為上圖樹林所對應的一棵樹林二元樹

演算法 5-1  轉換樹林成二元樹輸入樹林 F = T1 T2 Tn

輸出 F 的二元樹表示 BT=(T1 T2 Tn)1 if (n == 0) 輸出空二元樹 2 else

3 root(T1 T2 Tn) = T1

4 leftsubtree(T1 T2 Tn) =

BT(T11 T12 T1m)5 BT(T11 T12 T1m)為 T1的子樹6 rightsubtree(T1 T2 Tn) =

BT(T2 T3 Tn)

樹林轉換成二元樹的過程樹林轉換成二元樹的過程

  • Slide 1
  • Slide 2
  • Slide 3
  • Slide 4
  • Slide 5
  • Slide 6
  • Slide 7
  • Slide 8
  • Slide 9
  • Slide 10
  • Slide 11
  • Slide 12
  • Slide 13
  • Slide 14
  • Slide 15
  • Slide 16
  • Slide 17
  • Slide 18
  • Slide 19
  • Slide 20
  • Slide 21
  • Slide 22
  • Slide 23
  • Slide 24
  • Slide 25
  • Slide 26
  • Slide 27
  • Slide 28
  • Slide 29
  • Slide 30
  • Slide 31
  • Slide 32
  • Slide 33
  • Slide 34
  • Slide 35
  • Slide 36
  • Slide 37
  • Slide 38
  • Slide 39
  • Slide 40
  • Slide 41
  • Slide 42
  • Slide 43
  • Slide 44
  • Slide 45
  • Slide 46
  • Slide 47
  • Slide 48
  • Slide 49
  • Slide 50
  • Slide 51
  • Slide 52
  • Slide 53
  • Slide 54
  • Slide 55
  • Slide 56
  • Slide 57
  • Slide 58
  • Slide 59
  • Slide 60
  • Slide 61
  • Slide 62
  • Slide 63
  • Slide 64
  • Slide 65
  • Slide 66
  • Slide 67
  • Slide 68
  • Slide 69
  • Slide 70
  • Slide 71
  • Slide 72
  • Slide 73
  • Slide 74
  • Slide 75
  • Slide 76
  • Slide 77
  • Slide 78
  • Slide 79
  • Slide 80
  • Slide 81
  • Slide 82
  • Slide 83
  • Slide 84
  • Slide 85
  • Slide 86
  • Slide 87
  • Slide 88
  • Slide 89
  • Slide 90
  • Slide 91
  • Slide 92
  • Slide 93
  • Slide 94
  • Slide 95
  • Slide 96
  • Slide 97
  • Slide 98
  • Slide 99
  • Slide 100
  • Slide 101
  • Slide 102
  • Slide 103
Page 5: 第五章 樹

More Examples

512  專有名詞定義 樹為節點所組成的有限集合其中

(1) 存在一特殊節點 R 稱為樹根 (root) (2) 其它節點可分割成 n 個無交集的集合

T1 T2 hellip Tn n 0 而 T1 T2 hellip Tn 本身皆為樹稱其為 R 的子樹 (subtree)

A recursive definition 本章所提到的樹皆為「有根樹」 (rooted tree)

512  專有名詞 ( 續 )

bull 節點 (node) 圖中圓圈和向下的分支 (branch)bull 樹根 (root) 節點 Abull 分支度 (degree) 一個節點其所有子樹的數目bull 樹葉 (leaf) 分支度為 0 的節點bull 內部節點 (internal node) 非終端節點 (non-terminal

node)

bull 深度 (depth) 樹的最高階層 (level)

512  專有名詞 ( 續 )bull 兒子 (son) 任何節點 X 其子樹的樹根 Y 為 X 的

兒子bull 父親 (father parent) Y 是 X 的「父親」bull 兄弟 (sibling brother) 共有一個父親的數個節點bull 祖先 (ancestor) 該節點走向樹根所經過的所有節點bull 後裔 (descendant) 任一節點的所有子樹節點

52  樹的表示法bull 用陣列表示樹 bull 用鍵結串列表示樹 令分支度 =k

若 T 共有 n 個節點則需要 n 個鍵結節點計有 nk 個指標然而除了樹根外每個節點只須由唯一的指標所指向遂只需要有 n-1 個指標空間用不著的指標空間多達 nk-(n-1)=n(k-1)+1 個比用得著的還多著實浪費

521  一般化的串列表示bull 串列是一個有限且有順序的元素集合

A = (a1 a2 hellip an)

每個元素 ai 1in 皆有相同的資料型態 bull 倘若不限定各個元素都具有相同的資料型態則稱此串列為「一般化串列」 (generalized list) (ai 可以是一個節點或是一棵樹 )

bull 可用一般化串列來表示一棵樹 T T = (R T1 T2 hellip Tn)

其中 R 為 T 的樹根而 T1 T2 hellip Tn 為 R 的子樹 Ti 可能是一個節點也可能是一棵樹(一般化串列)

範例 5-1 以一般化串列表示樹bull (A (B (E K) (F L M) G) (C H) (D (I N) (J

O P)))

(A T1 T2 T3)T1 = (B (E K) (F L M) G)T2 = (C H)T3 = (D (I N) (J O P))

T1 T2T3

程式 5-1 一般化串列鍵結節點的宣告1 struct TreeNode2 int tag3 union4 int data5 struct TreeNode Tlink6 node7 struct TreeNode link8

(A T1 T2 T3)T1 = (B (E K) (F L M) G)T2 = (C H)T3 = (D (I N) (J O P))

522  左子右兄弟表示法

每個節點都有唯一的最左 兒子 (leftmost child) 每個節點都有最靠近它的 右兄弟

523 分支度為 2 的二元樹表示法bull 分支度為 2 的樹又稱為二元樹 (binary tree)

bull 二元樹中任一節點皆有 2個指標分別指向該節點的左子樹和右子樹

bull 二元樹可以表示任何樹事實上二元樹有許多很好的性質我們在一下節中介紹

bull注意樹狀結構中兒子的順序是不重要的試想樹可以樹根為軸心旋轉使得兒子的順序是不固定的

53  二元樹bull 二元樹是一個由節點組成的有限集合可以是空集合或是包含了(1) 一個樹根節點(2) 其它節點分割成兩個沒有交集的二元樹

其一為左子樹 (left subtree) 另一為右子樹 (right subtree) 樹和二元樹之間的差異

(1)樹不允許空節點而空二元樹是允許的

(2) 樹的子樹沒有順序而二元樹的子樹有左右之分

531  樹和二元樹的基本性質

樹或二元樹皆擁有下面的性質

定理 5-1 若一棵樹 T 的總節點數為 V 總邊數為

E 則V = E + 1

證明利用數學歸納法歸納法的基礎當 V=1 時即 T 只有一個樹根節點 E 為 0 所以 V = E + 1 成立歸納法的假設假設當 1 V K 時該式成立歸納法的推論當 V = K + 1 時令此樹的樹根為 R 而 R 有 m 個子樹 T1 T2 hellip

Tm (如圖 5-12 所示)分別有個 V1 V2 hellip Vm 個節點其中 V1 +V2 + hellip+ Vm =K 若 E1 E2 hellip Em 分別為此 m 個子樹的邊數則

E = E1 +E2 + hellip+ Em + m (1)根據歸納法的假設可知

V1 = E1+1V2 = E2+1 Vm = Em+1

所以V1 +V2 + hellip+Vm = E1 + E2 + hellip+ Em +m (2)

然而V = V1 +V2 + hellip+Vm+1 (3)

將式 (2) 代入式 (3) 可得V = E1 + E2 + hellip+ Em + m + 1

引用式 (1) 則可得V = E + 1

由數學歸納法的原理可得證本定理

532 二元樹的性質(a) 稱為歪斜樹 (skew tree) (b) 稱為完備二元樹 (complete binary tree)

( 其樹葉節點皆在相鄰階層完整定義在後 ) 同樣數目的樹節點存放在歪斜樹上會花較多的階層放在完備二元樹上則只須最少的階層

532 二元樹的性質 ( 續 )定理 5-2 (1) 二元樹上階層 i 的節點數目最多為 2i-1 i 1 (2) 深度為 k 的二元樹其節點數目最多為 2k-

1 k1 bull 定理 5-2說明了 (1) 每一階層上可放的最多節點數

(2) 階層數已知時二元樹可放的最多節點數證明 (1) 利用數學歸納法歸納法的基礎當 i=1 時只有樹根一個節點 2i-1=21-1=1 原式成

立歸納法的假設假設 i = k 時該式成立即階層 k 的最大節點數為

2k-1 k 1 歸納法的推論當 i = k+1 時因在階層 k 的每一個節點最多會有

2 個子節點遂在 k+1 階層最多會有 22k-1 = 2k

= 2i-1 節點由數學歸納法的原理可知本定理得證(2) 由 (1) 可知二元樹在階層 i 的最多節點數為 2i-1那麼深度 k 的二元樹最多可有節點如下

k

i

ki

1

1-1- 24212 1-2

)12(1 k= 2k-1

=

bull 對二元樹而言任一節點的分支度可能為 0 1 或 2 分支度為 0 者是為樹葉

bull 樹葉的數目與分支度為 2 的節點數目存在著有趣的關係請見定理 5-3

定理 5-3 若 T 為一非空的二元樹 n0 為樹葉節點數目 n2為分支度為 2 的節點數目則 n0 = n2+1

證明令 n 為所有節點數目而 n1 為分支度為 1 的節點數目可得 n = n0 + n1 +n2 (1)令 B 為二元樹的分支數除了樹根外每一節點皆有一分支指向之所以

n = B+1 (亦可參考定理 5-1 )又 B = n1+2n2

且 n = n1 + 2n2 +1 (2)由式 (1) 和式 (2) 我們得到

n0 = n2 + 1

完滿二元樹 (full binary tree)

定義一個深度為 k 的完滿二元樹即為一深度為 k 且有 2k-1 個節點的二元樹k 0

bull 由定理 5-2(2) 可知深度為 k 的二元樹其最多節點數為 2k-1 這樣的樹會將樹上所有可能存放節點的位置都放滿完滿 (full) 之名因而生成

完備二元樹 (complete binary tree)我們可對二元樹上節點予以編號階層小者先編同一階

層則自左向右編號這樣的編號方式使我們定義出完備(complete) 二元樹

定義深度為 k 節點數為 n 的二元樹稱為完備二元樹若且唯若 (1) k = 1 時樹有一個節點

(2) 當 k 2 且 1iltk 時深度 i 有 2i-1 個節點且第 k 層的節點皆由第 k-1 層的分支由左至右逐一連接而成

完滿或完備二元樹之所以受人注意乃因它們可以將節點以較少的階層數存放

定理 5-4 n 個節點的完備或完滿二元樹其深度為 log2(n+1)

證明 完滿或完備二元樹的節點皆自階層

1 起排放任一階層 i 可排放 2i-1

個節點遂由定理 5-2 得知此 n 個節點所佔的深度為 log2(n+1)

正規二元樹 (formal binary tree)bull 在二元樹中非樹葉的節點又稱為內部節點

(internal node) 若所有內部節點恰有 2 個子節點即成為正規二元樹

定義若二元樹中所有內部節點恰有 2 個子節點則稱之為正規二元樹

正規二元樹常被引用在單淘汰賽正規二元樹常被引用在單淘汰賽制的各項賽程安排樹葉為參賽制的各項賽程安排樹葉為參賽隊伍內部節點為優勝隊伍樹隊伍內部節點為優勝隊伍樹根即為冠軍隊伍根即為冠軍隊伍

在單淘汰賽制中自在單淘汰賽制中自 n n 個隊伍中產個隊伍中產生冠軍須舉辦 生冠軍須舉辦 nn-1-1場比賽場比賽

54  二元樹的表示法541 以陣列表示二元樹定理 5-5 若一個具有 n 個節點的完備二元樹以循序的方式編號並依序存放在陣列 A 中即第 i 個節點放在 A[i] 中 1 i n 則

(1) 節點 i 的父節點位在 A[i2]處 i ne 1( i = 1 時其節點正為樹根無父節點)

(2) 若 2i n 則節點 i 的左兒子節點位在 A[2i] 處若 2i gtn 節點 i 沒有左子節點

(3) 若 2i+1 n 節點 i 的右兒子節點位 A[2i+1]處若 2i+1gtn 節點 i 沒有左子

節點

證明先用數學歸納法證明 (2) bull 歸納法的基礎當 i = 1 時其左子節點確實在 A[2] 處(除非 nlt2 此時節點 1 沒有左子節點)

bull 歸納法的假設假設當 I = k 時其左子節點確在 A[2i] = A[2k] 處(除非 2i 已大於 n 此時節點 i 無左子節點)

bull 歸納法的推論當 i = k+1 時因第 k 節點的左子節點在 A[2k] 處其右子節點則位在 A[2k+1] 處那麼第 k+1 節點的左子節點應緊臨 A[2k+1] 亦即位於 A[2k+2] = A[2(k+1)] 處(除非 2i 已大於 n 此時節點 i 無左子節點)

bull 由數學歸納法的原理得證定理 5-5 (2) (3) 由於 (2) 成立而且節點 i 的右子節點會緊臨

A[2i] 亦即其位於 A[2i+1]處除非 i 根本沒有右子節點得證定理 5-5 (3)

由 (2) 和 (3) 我們可知定理 5-5 (1) 成立

542 以鍵結串列表示二元樹bull 以陣列來表示完備二元樹算是相當方便但ndash一般的二元樹將導致陣列空間的浪費ndash如果二元樹中節點的新增刪除動作頻繁則會造成陣列對應資料的搬動隨而頻繁

bull 以鍵結串列表示二元樹

為方便辦識樹根我們常用 root 指標指向樹根做為樹鍵結串列的起點

陣列表示和鍵結表示的空間需求bull 完備二元樹的陣列表示和鍵結表示皆沒浪費儲存資料的空間

bull 但歪斜樹的陣列表示比起鍵結表示則浪費許多

bull 以 n 個節點的歪斜樹而言陣列表示法可能需要 2n-1 個陣列元素其中 2n-1-n 個陣列元素空間是浪費的而鍵結表示法只需要 n 個節點空間

二元樹節點的宣告程式 5-2

1 struct BTreeNode

2 struct BtreeNode leftchiled

3 int data

4 struct BTreeNode rightchild

5

6 struct BTreeNode root

bull 這樣的二元樹節點要取得兒子節點的指標十分方便但若有取得父節點的需求則嫌不足我們可以加上 parent 欄位指向父節點

55  二元樹的走訪bull 假如一組資料已用二元樹的組織在一起總有需求對其全數資料做動作例如計算所有數目印出所有資料在所有資料中搜尋某項資料hellip等此時即須對此二元樹做走訪 (traversal) 的運算利用走訪二元樹的同時將計算列印或搜尋的動作完成

bull 事實上走訪即在決定二元樹上資料被處理(計算列印或搜尋)的順序我們也希望走訪的演算法對任何節點皆一致是容易撰寫程式實作的

bull 若對一個二元樹上的節點而言 V 表示處理節點上的資料 L 表示走訪其左子樹 R 表示走訪其右子樹

55  二元樹的走訪 ( 續 )bull 因為對稱的緣故我們只考慮先走訪左邊再走訪右邊的情形那麼右圖只剩下三種走訪方式

bull 以 V 所在的相對位置分別對此三種走訪方式取名如下

(1) LVR 中序走訪 (inorder traversal)或中序表示法 (infix notation)

(2) LRV 後序走訪 (postorder traversal) 或後序表示法 (postfix notation)

(3) VLR 前序走訪 (preorder traversal)或前序表示法 (prefix notation)

551  中序走訪bull 利用遞迴的方式撰寫中序走訪的程序希望把二元樹中序走訪的

順序印出來

程式 5-3二元樹的中序走訪1 struct BTreeNode2 struct BTreeNode leftchild3 char data4 struct BTreeNode rightchild5 6 struct BTreeNode root7 void inorder(struct BTreeNode node)8 if (node = NULL)9 inorder(node-gtleftchild)10 cout ltlt node-gtdata11 inorder(node-gtrightchild)12 13

範例 5-12此為運算式二元樹

其對應中序表示運算式為 (A+B)C+D(E-F)

6 struct BTreeNode root7 void inorder(struct BTreeNode node)8 if (node = NULL)9 inorder(node-gtleftchild)10 cout ltlt node-gtdata11 inorder(node-gtrightchild)12 13

6 struct BTreeNode root7 void inorder(struct BTreeNode node)8 if (node = NULL)9 inorder(node-gtleftchild)10 cout ltlt node-gtdata11 inorder(node-gtrightchild)12 13

(A+B)C+D(E-F)

如何加上 ( )

552  後序走訪程式 5-4二元樹的後序走訪14 void postorder(struct BTreeNode node)

15 if (node =NULL)16 postorder(node-gtleftchild)17 postorder(node-gtrightchild)18 cout ltlt node-gtdata19 20

以後序走訪可得到AB+CDEF-+

其正為對應的後序運算式

553  前序走訪程式 5-5二元樹的前序走訪21 void preorder(struct BTreeNode node)

22 if (node = NULL)

23 cout ltlt node-gtdata

24 preorder(node-gtleftchild)

25 preorder(node-gtrightchild)

26

27

以前序走訪可得到++ABCD-EF

其正為對應的前序運算式

554 利用堆疊和迴圈取代遞迴進行二元樹的走訪1 struct BTreeNode 2 struct BTreeNode leftchild3 char data4 struct BtreeNode rightchild5 6 struct BTreeNode root7 struct StackNode8 struct BTreeNode treenode9 struct StackNode next1011 struct StackNode top

12 void push_node(struct BtreeNode node )13 struct StackNode old_top14 old_top = top15 top = (struct StackNode ) malloc

(sizeof(struct StackNode))16 top-gttreenode = node17 top-gtnext = old_top18 19 struct BTreeNode pop_node()20 struct BTreeNode Tnode21 struct StackNode old_top22 if (top == NULL)23 StackEmpty()24 else25 Tnode = top-gttreenode26 old_top = top27 top = top-gtnext28 free(old_top)29 return Tnode30 31

32 void inorder_Stack(struct BtreeNode node)33 do34 while (node = NULL)35 push_node(node)36 node = node-gtleftchild37 push all left children38 if (top = NULL)39 node = pop_node()40 cout ltlt node-gtdata41 node = node-gtrightchild42 inorder printing and check right child43 while ((top=NULL)||(node=NULL))44 top==NULL 堆疊已空 node==NULL沒有右子

6 struct BTreeNode root7 void inorder(struct BTreeNode node)8 if (node = NULL)9 inorder(node-gtleftchild)10 cout ltlt node-gtdata11 inorder(node-gtrightchild)12 13

中序表示為DBGEHAFIC

32 void inorder_Stack()33 struct BTreeNode node34 node = root35 do36 while (node = NULL)37 push_node(node)38 node = node-gtleftchild39 40 if (top = NULL)41 node = pop_node()42 cout ltlt node-gtdata43 node = node-gtrightchild44 45 while(top=NULL)||(node=NULL))46

45 void Preorder_Stack(struct BSTreeNode node)46 do47 while (node = NULL)48 cout ltlt node-gtdata)49 push_node(node)50 node = node-gtleftchild51 push all left children52 if (top = NULL)53 node = pop_node()54 node = node-gtrightchild55 56 while ((top=NULL)||(node=NULL))57

程式 5-5二元樹的前序走訪21 void preorder(struct BTreeNode node)22 if (node = NULL)23 cout ltlt node-gtdata24 preorder(node-gtleftchild)25 preorder(node-gtrightchild)26 27

58 void Postorder_Stack(struct BTreeNode node)59 do60 while (node = NULL)61 cout ltlt node-gtdata62 push_node(node)63 node = node-gtrightchild64 push all left children65 if (top = NULL)66 node = pop_node()67 node = node-gtleftchild68 69 while ((top=NULL)||(node=NULL))70

程式 5-4二元樹的後序走訪14 void postorder(struct BTreeNode node)15 if (node =NULL)16 postorder(node-gtleftchild)17 postorder(node-gtrightchild)18 cout ltlt node-gtdata19 20

555  階層走訪bull 「階層走訪」 (level-order traversal) 是依階層

的順序進行二元樹的走訪先走訪階層小的節點後走訪階層大的節點同一階層者則依自左向右的順序走訪

bull 對下圖的二元樹進行階層走訪的結果為ABCDEFGHI

bull 對同一階層而言先走訪的節點其子節點亦在下一階層中先被走訪

bull 這種「先進先出」的特性恰可用佇列予以儲存與處理

程式 5-7 利用佇列進行二元樹的階層走訪

1 struct BTreeNode2 struct BTreeNode leftchild3 char data4 struct BTreeNode rightchild5 6 struct BTreeNode root7 struct QNode8 struct BTreeNode treenode9 struct QNode next10 11 struct QNode front rear

12 void AddQueue(struct BTreeNode Tnode)13 struct QNode node14 node=(struct QNode )malloc(sizeof(struct QNode))15 node-gttreenode = Tnode16 node-gtnext = NULL17 if (front == NULL) front = node19 else rear-gtnext = node20 rear = node21

22 struct BTreeNode DeleteQueue()23 struct BTreeNode Tnode24 struct QNode old_front25 if (rear == NULL)26 QueueEmpty()27 else28 Tnode = front-gttreenode29 old_front = front30 front = front-gtnext31 free(old_front)32 return Tnode33 34 35 void LevelOrder (struct BTreeNode node)36 AddQueue(node)37 while (front =NULL)38 node = DeleteQueue()39 cout ltlt node-gtdata40 if (node-gtleftchild = NULL)41 AddQueue(node-gtleftchild)42 if (node-gtrightchild = NULL)43 AddQueue(node-gtrightchild)44 45

35 void LevelOrder (struct BTreeNode node)36 AddQueue(node)37 while (front =NULL)38 node = DeleteQueue()39 cout ltlt node-gtdata40 if (node-gtleftchild = NULL)41 AddQueue(node-gtleftchild)42 if (node-gtrightchild = NULL)43 AddQueue(node-gtrightchild)44 45

bull Infix ABCbull Postfix ABC CBA ACB BCA

BAC CAB

B

A CA

B

C

B

A

C

C

B

AB

A

C

二元樹中序與前序式中序與後序式bull 每一棵二元樹皆有唯一的一對中序與前序表示法也有唯一的中序與後序次序

bull 換句話說給予一對中序與前序或中序與後序即可決定一棵二元樹

bull 然而給予前序和後序次序並不能決定唯一的二元樹可能會產生兩棵不同的二元樹

bull 想想階層走訪和中序走訪順序可否決定唯一的二元樹

A

B C

H J D E

F G

prefix ABHJCDFGEinfix HBJAFDGCEpostfix HJBFGDECA

VLRLVRLRV

A

prefix BHJinfix HBJ

prefix CDFGEinfix FDGCE

prefix ABHJCDFGEinfix HBJAFDGCE

A

B C

H J D E

F G

prefix ABHJCDFGE

infix HBJAFDGCEk-1

struct BTreeNode BTree_InfixPrefix (AnsiString infix AnsiString prefix)

int k struct BTreeNode node if (infixLength==0) return NULLnode = (struct BTreeNode ) malloc (sizeof(struct BTreeNode))node-gtdata = prefix[1]k = find(prefix[1] infix)node-gtleftchild = BTree_InfixPrefix

(infix[1k-1] prefix[2k])node-gtrightchild = BTree_InfixPrefix(infix[k+1infixLength]prefix[k+1prefixLength])

VLR

LVRk1

k2 k+1~

k+1~

1

struct BTreeNode BTree_InfixPostfix (AnsiString infix AnsiString prefix)

int k struct BTreeNode node if (infixLength==0) return NULLnode = (struct BTreeNode ) malloc (sizeof(struct BTreeNode))node-gtdata = postfix[postfixLength]k = find(postfix[postfixLength] infix)node-gtleftchild = BTree_InfixPrefix

(infix[1k-1] postfix[1k-1])node-gtrightchild = BTree_InfixPrefix(infix[k+1infixLength]postfix[k+1postfixLength-1])

56  二元樹的複製與相等測試程式 5-8 二元樹的複製1 struct BTreeNode2 struct BTreeNode leftchild3 char data4 struct BTreeNode rightchild5 6 struct BTreeNode root7 struct BTreeNode Copy(struct BTreeNode TreeRoot)8 struct BTreeNode CopyRoot9 if (TreeRoot == NULL)10 return NULL11 CopyRoot =(struct BTreeNode ) malloc (sizeof(BTreeNode))12 CopyRoot-gtdata = TreeRoot-gtdata13 CopyRoot-gtleftchild = Copy(TreeRoot-gtleftchild)14 CopyRoot-gtrightchild = Copy(TreeRoot-

gtrightchild)15 return CopyRoot16

程式 5-9 檢測兩棵二元樹是否相等1 struct BTreeNode2 struct BTreeNode leftchild3 char data4 struct BTreeNode rightchild5 6 struct BtreeNode rightchild7 int Equal(struct BTreeNode ustruct BTreeNode v)8 if ((u==NULL) ampamp (v==NULL)) return 19 if ((u=NULL) ampamp (v=NULL)10 ampamp (u-gtdata == v-gtdata )11 ampamp Equal(u-gtleftchildv-gtleftchild)12 ampamp Equal(u-gtrightchildv-gtrightchild))

13 return 114 return 015

Counting Nodes in a Binary Tree

int CountingNodes(struct BTreeNodes node)

if (node == NULL) return 0 int leftchildren = CountingNodes(node-gtleftchild)

int rightchildren= CountingNodes(node-gtrightchild)

return leftchildren+rightchildren

Counting Leaves in a Binary Tree

int CountingLeaves(struct BTreeNodes node)

if (node == NULL) return 0 int leftleaves = CountingLeaves(node-gtleftchild)

int rightchildren= CountingLeaves(node-gtrightchild)

if (leftleaves==0 ampamp leftleaves==0) return 1

return leftleaves + leftleaves

57  引線二元樹bull 在二元樹的鍵結表示中樹葉節點的兩個子樹指標皆指向 NULL 由定理 5-1 得知 n = E+1 其中 n 為節點個數 E 為分支數

bull 而 n 個鍵結節點有 2n 個指標空間每個分支恰佔用一個指標空間共有 E (=n-1) 個指標有用到(不是空的)而共有 n+1 個指標空間是 NULL 比非空的節點還多

bull 有學者提出對這些「放空指標的空間」加以利用的概念mdash與其放空指標不如放指向其它節點的指標稱之為引線 (thread) 使得某些運算(如走訪hellip等)可以加快

bull 這個概念發展出了引線二元樹 (threaded binary tree) 這種資料結構

範例 5-17下圖為加了引線的二元樹

加入的引線將使二元樹的中序走訪更加便利沿著上右圖箭頭所指示的順序走訪即可完成此二元樹的中序走訪GDHBAIECF 為此二元樹的中序表示

bull 範例 5-17 中所加的引線目的在方便二元樹的中序走訪

bull 對樹葉節點 p 而言令其資料為 P 則引線指標的規則如下

(1) p 的左子樹指標指向中序表示中 P 的前一個 元素(前接元素 (predecessor) )

(2) p 的右子樹指標指向中序表示中 P 的後一個 元素(後繼元素 (successor) )

為了區別節點指標放的是一般指標(內部節點)抑或是引線指標(樹葉節點)我們另以 2 個欄位分別區別左和右子樹指標空間存放的對象其節點的記憶體配置有如下圖所示

其中 leftthread (rightthread) 為 0 時表示 leftchild (rightchild) 存放的是一般節點指標而 leftthread (rightthread) 為 1 時表示 leftchild (rightchild) 存放的是引線指標1 struct TBTreeNode2 int leftthread3 struct TBTreeNode leftchild4 char data5 struct TBTreeNode rightchild6 int rightthread7

由右側的引線二元樹可知有最左和最右兩條引線尚無妥善的安排我們另設計一空的引線節點做為樹根如下圖所示

571  引線二元樹的中序走訪決定節點 p 在中序表示中的後繼節點 q (簡稱中序後繼點)

程式 5-10決定節點 node的中序後繼點1 struct TBTreeNode2 int leftthread3 struct TBTreeNode leftchild4 char data5 struct TBTreeNode rightchild6 int rightthread7 8 struct TBTreeNode root9 struct TBTreeNode Next(struct TBTreeNode node)

10 struct TBTreeNode temp11 temp = node-gtrightchild12 if (node-gtrightthead) return temp13 while (temp-gtleftthead) temp=temp-gtleftchild

14 return temp15

程式 5-11引線二元樹的中序表示(接程式 5-10)1 void InorderTBTree()2 struct TBTreeNode node3 for(node=Next(root)node=rootnode=Next(node))

4 cout ltlt node-gtdata5

bull只要 node 是引線二元樹上的任一節點透過呼叫 Next(node) 即可找到其在中序表示中的後繼節點bull(試想若程式 5-10 中的程序 Next 所傳入的參數指標正是 root Next(root) 是否可正常運作請思考)bull於是只須從中序表示中的第一節點起讓每一節點 node 都執行 Next(node) 程序即可得引線二元樹的中序表示

572  在引線二元樹中加入節點在引線二元樹中樹葉節點節點的右子樹指標所指向或內部節點右子樹的最左樹葉為其中序後繼節點而且任一節點若無左子樹則其左子樹指標乃指向其中序前接節點hellip這些性質必須在節點新增進入引線二元樹時也要保持接下來討論節點 new 插入成為引線二元樹的節點 p 的右子節點的情形至於插入成為左子節點的情形則讓各位模擬推敲

572572  在引線二元樹中加入節點 在引線二元樹中加入節點 (( 續續 ))

newnew 會成為會成為 pp 的右子節點的情況共有以下兩種的右子節點的情況共有以下兩種(1) (1) 若若 pp 沒有右子節點 沒有右子節點 ((p-gtrightthread p-gtrightthread 為為1) 1) 如圖如圖 ((a) a) 則 則 new new 插入後應形成如圖插入後應形成如圖 ((b) b) 所示所示

(2)(2) 若若 pp 有右子節點 有右子節點 ((p-gtrightthread p-gtrightthread 為為0) 0) 如圖如圖 ((c) c) 則 則 new new 插入後應形成如插入後應形成如圖圖 ((d) d) 所示所示

程式 5-12  插入節點 new 成為引線二元樹中節點 p的右子節點(上接程式 5-10 5-11 )

1 void InsertRight(struct TBTreeNode p 2 struct TBTreeNode new)3 struct TBTreeNode q4 new-gtrightchild = p-gtrightchild5 new-gtrightthead = p-gtrightthread6 new-gtleftchild = p7 new-gtleftthread = 18 p-gtrightchild = new9 p-gtrightthead = 010 if (new-gtrightthread)11 q = Next(new)12 q-gtleftchild = new13 14

58  堆積bull 「堆積」 (heap) 是一個特殊的完備二元樹節點的資料與其子節點的資料之間有事先定好的大小關係存在bull 定義ndash最大堆疊為一完備二元樹任一節點的資料內容不小於其子節點的資料內容ndash最小堆疊為一完備二元樹任一節點的資料內容不大於其子節點的資料內容

581  插入節點至最大堆積中最大堆積是一完備二元樹所以如圖 (a) 之最大堆積於加入新節點後樹的形狀必須如圖 (b) 所示灰色節點即為新節點若加入的節點資料分別為 1 9 12 則其最大堆積將分別變成如圖 (c) (d) (e) 所示

程式 5-13 新增節點至最大堆積中最大堆積本身是一棵完備二元樹可以用陣列表示之 1 define maxsize 1002 int heap[maxsize]3 int n i4 void InsertHeap(int x)5 if (n == maxsize) HeapFull()6 else7 n++8 i = n9 while ((i gt 1) ampamp (x gt heap[i2]))10 heap[i] = heap[i2]11 i = 212 13 heap[i] = x14 16

程序 InsertHeap 在插入第 n 個資料時需要的時間複雜度為 O(logn)

1 100

2 93

3 84

4 77

5 65

6 81

7 79

8 66

9 70

10 50

100

84

77

93

8165

66 70

79

50

1

2 3

4 5 6 7

8 9 10

1 define maxsize 1002 int heap[maxsize]3 int n i4 void InsertHeap(int x)5 if (n == maxsize) HeapFull()6 else7 n++8 i = n9 while ((i gt 1) ampamp (x gt heap[i2]))10 heap[i] = heap[i2]11 i = 212 13 heap[i] = x14 16

582 刪除最大堆積中的最大資料圖 (a) 中的最大堆積在刪除最大資料後其樹形應變成如圖 (b) 所示將最未一個樹葉節點的資料 6 暫挪至樹根節點內如圖 (c) 再將圖 (c) 中非最大堆積的結構調整成為最大堆積 10 和 8 中選大者與 6對調即成圖 (d)

程式 5-14 刪除最大堆積中的最大資料(上接程式 5-13 )1 int DeleteHeap()2 int x i j3 if (n == 0) HeapEmpty()4 else5 x = heap[1]6 heap[1] = heap[n]7 n--8 i = 19 while (i lt= n2)10 if (heap[2i] gt heap[2i+1])11 j = 2i12 else13 j = 2i+114 if (heap[i]gtheap[j]) break15 swap(heap[i] heap[j])16 i = j17 18 return x19 20

其時間複雜度與其堆積高度相同是為O(logn) 其中 n 為堆積中的元素個數

1 100

2 93

3 84

4 77

5 65

6 81

7 79

8 66

9 70

10 50

100

84

77

93

8165

66 70

79

50

1

2 3

4 5 6 7

8 9 10

1 int DeletHeap()2 int x i j3 if(n == 0) HeapEmpty()4 else5 x = heap[1]6 heap[1] = heap[n]7 n--8 i = 19 while (i lt= n2)10 if ([heap[2i]] gt heap[2i+1])11 j = 2i12 else13 j = 2i+114 if (heap[i]gtheap[j]) break15 swap(heap[i] heap[j])16 i = j17 18 return x19 20

583  優先佇列bull 由於最大堆積(或最小堆積)的結構可提供最大元素(或最小元素)而其維護堆積的成本為 O(logn) n 為總元素個數

bull 堆積常被當成「優先佇列」 (priority queue) 使用mdash此時佇列被組織成完備二元樹若新加入佇列的資料有大小關係優先順序的需求時此優先佇列即以 O(logn) 的代價將新資料放入優先佇列(可能是最大或最小堆積依實際大小關係優先順序的需求而定)中mdash提供依元素的優先順序做為處理順序的服務

bull 一般的佇列為先進先出先到的先行處理優先佇列則是依據其優先順序優先順序高者先行處理與來到的先後無關

範例 5-21bull 在「分時」 (time sharing) 的作業系統中我們不希望使用中央處理器 (Central Processing Unit CPU) 時間短的「處理程序」 (process) 等候過久於是有「最短時間工作優先」 (shortest-job-first) 的排程策略此時等候的處理程序即可組織成之一最小堆積而 CPU 每次即取出存於樹根的處理程序予以處理任何新來的處理程序則以 O(logn) 的時間加入此優先佇列(最小堆積)中

bull 注意之前提過的線性佇列新來的直接加入成為新的尾端元素花O(1) 的時間然線性佇列本身沒有大小的關係每次要取出最大(小)的資料得花O(n) 的代價(在所有元素中挑出最大(小)者) n 為佇列內的元素個數而優先佇列是在新元素加入時要花O(logn) 的時間至於取出最大(小)則只須 O(1) 的時間

59  二元搜尋樹bull 二元搜尋樹是一棵二元樹可能是空二元樹若不為空二元樹則滿足下列性質ndash所有節點內的資料內容是相異的 ndash左子節點的資內容 ( 如果有 ) 比父節點的資料內容小 ndash右子節點的資料內容 ( 如果有 ) 比父節點的資料內容大 ndash以左和右子節點為樹根的左子樹和右子樹也是二元搜尋樹

在二元搜尋樹中搜尋資料二元搜尋樹對資料大小的掌握十分明確若恰與節點 p 資料相等則搜尋成功若欲搜尋的資料 x 比樹節點 p 中的資料小 則只須搜尋節點 p 的左子樹若比節點 p 中的資料大 則只須搜尋 p 的右子樹這樣的判斷可以遞迴地應用在各子樹節點 中

程式 5-15 在二元搜尋樹中搜尋資料(遞迴呼叫)1 struct BSTreeNode2 struct BSTreeNode leftchild3 int data4 struct BSTreeNode rightchild5 6 struct BSTreeNode root7 struct BSTreeNode SearchBST(

struct BSTreeNode tree int x)8 if (tree == NULL) return NULL9 if (x == tree-gtdata) return tree10 if (x lt tree-gtdata)11 return SearchBST(tree-gtleftchild x)

12 return SearchBST(tree-gtrightchild x)13

程式 5-16 搜尋二元搜尋樹的資料(非遞迴呼叫)在二元搜尋樹十分龐大時遞迴呼叫的效率不會太好可利用迴圈來完成搜尋的動作以提高執行效率請看程式 5-16

14 struct BSTreeNode Search BST_iterative15 (struct BSTreeNode node int x)16 while (node = NULL)17 if (x == node-gtdata) return node18 if (x lt node-gtdata)19 node = node-gtleftchild20 else21 node = node-gtrightchild22 23 return NULL24

592  新增資料於二元搜尋樹中欲新增資料進入一二元搜尋樹中得先決定該資料在二元搜尋樹中應出現的位置這可透過上節提到的搜尋動作來解決

在二元搜尋樹定義的性質 (1) 中我們限定了元素必須相異所以搜尋新資料的結果必是 NULL

必須將搜尋的結果改為新增資料所應在位置的節點指標

程式 5-17  在二元搜尋樹中新增資料25 int InsertBST(int x)26 struct BSTreeNode p q27 p = root q = NULL28 while (p = NULL)29 q = p30 if (x == p-gtdata) return 0

若不考慮重複則可省略之31 if (x lt p-gtdata)32 p = p-gtleftchild33 else34 p = p-gtrightchild35

3636 p = (struct BSTreeNode ) p = (struct BSTreeNode ) malloc (sizeof(BSTreeNode))malloc (sizeof(BSTreeNode))

3737 p-gtdata = x p-gtdata = x3838 p-gtleftchild = p-gtrightchild = p-gtleftchild = p-gtrightchild = NULLNULL3939 if (root == NULL) root = p if (root == NULL) root = p4040 else if (x lt q-gtdata) else if (x lt q-gtdata)4141 q-gtleftchild = p q-gtleftchild = p4242 else else4343 q-gtrightchild = p q-gtrightchild = p4444 return 1 return 145 45

程式 程式 5-175-17  在二元搜尋樹中新增資料  在二元搜尋樹中新增資料 (( 續續 ))

範例 5-23 圖 (b) 為圖 (a) 新增整數 6 之後的結果圖 (c) (d) (e) (f) 則分別是逐步再加入 28 18

10 21 的結果

bull若目前的二元搜尋樹高度為 h 則新增資料得花O(h) 的時間找到其所應在的位置遂新增資料至二元搜尋樹中的時間複雜之度為 O(h)

593 自二元搜尋樹中刪除資料欲自二元搜尋樹中刪除資料可能會面臨兩種情況(1) 刪除節點 p 位於樹葉節點上如下圖所示 這是比較容易處理的情況

(2) 刪除的節點 p 不是樹葉刪除節點 p 後原 p 所指的節點可由 q ( q 中包含了 p 左子樹中的最大節點值)或 r ( r 中包含了 p 右子樹中的最小節點值)來取代以維持二元搜尋樹中資料的大小關係其結果分別如圖 (b) 和 (c) 所示而圖 (d) 則標明了指標會改變的節點位置即 p 會消失 father 的左或右子樹指標會異動 k(可能是 q 或 r )會移到原來 p 的位置其父節點 f 的左或右子樹指標會異動其中 father 為 p 父節點 f 為 k 的父節點

程式 5-18 自二元搜尋樹中刪除資料46 int DeleteBST (int x)47 Struct BSTreeNode p father k f48 p = root father = NULL49 while (p = NULL)50 if (x == p-gtdata) 找到 x 所在的節點 p 了51 if ((p-gtleftchild == NULL) ampamp 52 (p-gtrightchild == NULL))53 k = NULL p為樹葉節點54 else if (p-gtleftchild = NULL )x位於非子節

點55 f = p 找出 p左子樹的最右樹葉56 k = p-gtleftchild57 while (k-gtrightchild =NULL)58 f = k59 k = k-gtrightchild60 k將挪至原 p處61 if (p == f)62 f-gtleftchild = k-gtleftchild 63 else64 f-gtrightchild = k-gtleftchild 65

66 else p無左子樹67 f = p 找出 p右子樹的最左樹葉68 k = p-gtrightchild69 while (k-gtleftchild = NULL)70 f = k71 k = k -gtleftchild72 73 if (p == f)74 f-gtrightchild = k-gtrightchild 75 else76 f-gtleftchild = k-gtrightchild77 78 if (k = NULL) k挪至原 p處繼承 p的左右指標79 k-gtleftchild = p-gtleftchild80 k-gtrightchild = p-gtrightchild81 82 if (father == NULL) root = k83 else if (x lt father-gtdata) 決定 k是 father的左

或右兒子84 father-gtleftchild = k85 else86 father-gtrightchild = k87 free(p)88 return 1 成功地刪除 x於此傳回 1返回呼叫處89 51~89行為找到 x時應有的處理步驟

90 else 尚未找到 x繼續往下一階層找

91 father = p92 if (x lt p-gtdata)93 p = p-gtleftchild94 else95 p = p-gtrightchild96 97 98 return 0 未找到 x傳回 099

510  樹林 (forest)

bull 定義樹林(森林)是由 n 棵沒有交集的樹所構成的集合 n 0

下圖為一包含三棵樹的樹林

5101 轉換樹林為二元樹 之前我們以二元樹來表示樹因為二元樹有較多

好用的性質在此我們也將樹林轉換成二元樹看待

在 522 節中我們嘗試了用左子右兄弟的方式來表示樹這個方法事實上已將樹轉換成二元樹

即用左子右兄弟的結構重新組織樹林中的每一棵樹這些樹的樹根將視為兄弟而最左邊的樹則視為樹林二元樹的樹根如此一來樹林即可轉換成二元樹

範例 5-25右下圖為右上圖三棵樹所對應的三棵左子右兄弟二元樹

下圖則為上圖樹林所對應的一棵樹林二元樹

演算法 5-1  轉換樹林成二元樹輸入樹林 F = T1 T2 Tn

輸出 F 的二元樹表示 BT=(T1 T2 Tn)1 if (n == 0) 輸出空二元樹 2 else

3 root(T1 T2 Tn) = T1

4 leftsubtree(T1 T2 Tn) =

BT(T11 T12 T1m)5 BT(T11 T12 T1m)為 T1的子樹6 rightsubtree(T1 T2 Tn) =

BT(T2 T3 Tn)

樹林轉換成二元樹的過程樹林轉換成二元樹的過程

  • Slide 1
  • Slide 2
  • Slide 3
  • Slide 4
  • Slide 5
  • Slide 6
  • Slide 7
  • Slide 8
  • Slide 9
  • Slide 10
  • Slide 11
  • Slide 12
  • Slide 13
  • Slide 14
  • Slide 15
  • Slide 16
  • Slide 17
  • Slide 18
  • Slide 19
  • Slide 20
  • Slide 21
  • Slide 22
  • Slide 23
  • Slide 24
  • Slide 25
  • Slide 26
  • Slide 27
  • Slide 28
  • Slide 29
  • Slide 30
  • Slide 31
  • Slide 32
  • Slide 33
  • Slide 34
  • Slide 35
  • Slide 36
  • Slide 37
  • Slide 38
  • Slide 39
  • Slide 40
  • Slide 41
  • Slide 42
  • Slide 43
  • Slide 44
  • Slide 45
  • Slide 46
  • Slide 47
  • Slide 48
  • Slide 49
  • Slide 50
  • Slide 51
  • Slide 52
  • Slide 53
  • Slide 54
  • Slide 55
  • Slide 56
  • Slide 57
  • Slide 58
  • Slide 59
  • Slide 60
  • Slide 61
  • Slide 62
  • Slide 63
  • Slide 64
  • Slide 65
  • Slide 66
  • Slide 67
  • Slide 68
  • Slide 69
  • Slide 70
  • Slide 71
  • Slide 72
  • Slide 73
  • Slide 74
  • Slide 75
  • Slide 76
  • Slide 77
  • Slide 78
  • Slide 79
  • Slide 80
  • Slide 81
  • Slide 82
  • Slide 83
  • Slide 84
  • Slide 85
  • Slide 86
  • Slide 87
  • Slide 88
  • Slide 89
  • Slide 90
  • Slide 91
  • Slide 92
  • Slide 93
  • Slide 94
  • Slide 95
  • Slide 96
  • Slide 97
  • Slide 98
  • Slide 99
  • Slide 100
  • Slide 101
  • Slide 102
  • Slide 103
Page 6: 第五章 樹

512  專有名詞定義 樹為節點所組成的有限集合其中

(1) 存在一特殊節點 R 稱為樹根 (root) (2) 其它節點可分割成 n 個無交集的集合

T1 T2 hellip Tn n 0 而 T1 T2 hellip Tn 本身皆為樹稱其為 R 的子樹 (subtree)

A recursive definition 本章所提到的樹皆為「有根樹」 (rooted tree)

512  專有名詞 ( 續 )

bull 節點 (node) 圖中圓圈和向下的分支 (branch)bull 樹根 (root) 節點 Abull 分支度 (degree) 一個節點其所有子樹的數目bull 樹葉 (leaf) 分支度為 0 的節點bull 內部節點 (internal node) 非終端節點 (non-terminal

node)

bull 深度 (depth) 樹的最高階層 (level)

512  專有名詞 ( 續 )bull 兒子 (son) 任何節點 X 其子樹的樹根 Y 為 X 的

兒子bull 父親 (father parent) Y 是 X 的「父親」bull 兄弟 (sibling brother) 共有一個父親的數個節點bull 祖先 (ancestor) 該節點走向樹根所經過的所有節點bull 後裔 (descendant) 任一節點的所有子樹節點

52  樹的表示法bull 用陣列表示樹 bull 用鍵結串列表示樹 令分支度 =k

若 T 共有 n 個節點則需要 n 個鍵結節點計有 nk 個指標然而除了樹根外每個節點只須由唯一的指標所指向遂只需要有 n-1 個指標空間用不著的指標空間多達 nk-(n-1)=n(k-1)+1 個比用得著的還多著實浪費

521  一般化的串列表示bull 串列是一個有限且有順序的元素集合

A = (a1 a2 hellip an)

每個元素 ai 1in 皆有相同的資料型態 bull 倘若不限定各個元素都具有相同的資料型態則稱此串列為「一般化串列」 (generalized list) (ai 可以是一個節點或是一棵樹 )

bull 可用一般化串列來表示一棵樹 T T = (R T1 T2 hellip Tn)

其中 R 為 T 的樹根而 T1 T2 hellip Tn 為 R 的子樹 Ti 可能是一個節點也可能是一棵樹(一般化串列)

範例 5-1 以一般化串列表示樹bull (A (B (E K) (F L M) G) (C H) (D (I N) (J

O P)))

(A T1 T2 T3)T1 = (B (E K) (F L M) G)T2 = (C H)T3 = (D (I N) (J O P))

T1 T2T3

程式 5-1 一般化串列鍵結節點的宣告1 struct TreeNode2 int tag3 union4 int data5 struct TreeNode Tlink6 node7 struct TreeNode link8

(A T1 T2 T3)T1 = (B (E K) (F L M) G)T2 = (C H)T3 = (D (I N) (J O P))

522  左子右兄弟表示法

每個節點都有唯一的最左 兒子 (leftmost child) 每個節點都有最靠近它的 右兄弟

523 分支度為 2 的二元樹表示法bull 分支度為 2 的樹又稱為二元樹 (binary tree)

bull 二元樹中任一節點皆有 2個指標分別指向該節點的左子樹和右子樹

bull 二元樹可以表示任何樹事實上二元樹有許多很好的性質我們在一下節中介紹

bull注意樹狀結構中兒子的順序是不重要的試想樹可以樹根為軸心旋轉使得兒子的順序是不固定的

53  二元樹bull 二元樹是一個由節點組成的有限集合可以是空集合或是包含了(1) 一個樹根節點(2) 其它節點分割成兩個沒有交集的二元樹

其一為左子樹 (left subtree) 另一為右子樹 (right subtree) 樹和二元樹之間的差異

(1)樹不允許空節點而空二元樹是允許的

(2) 樹的子樹沒有順序而二元樹的子樹有左右之分

531  樹和二元樹的基本性質

樹或二元樹皆擁有下面的性質

定理 5-1 若一棵樹 T 的總節點數為 V 總邊數為

E 則V = E + 1

證明利用數學歸納法歸納法的基礎當 V=1 時即 T 只有一個樹根節點 E 為 0 所以 V = E + 1 成立歸納法的假設假設當 1 V K 時該式成立歸納法的推論當 V = K + 1 時令此樹的樹根為 R 而 R 有 m 個子樹 T1 T2 hellip

Tm (如圖 5-12 所示)分別有個 V1 V2 hellip Vm 個節點其中 V1 +V2 + hellip+ Vm =K 若 E1 E2 hellip Em 分別為此 m 個子樹的邊數則

E = E1 +E2 + hellip+ Em + m (1)根據歸納法的假設可知

V1 = E1+1V2 = E2+1 Vm = Em+1

所以V1 +V2 + hellip+Vm = E1 + E2 + hellip+ Em +m (2)

然而V = V1 +V2 + hellip+Vm+1 (3)

將式 (2) 代入式 (3) 可得V = E1 + E2 + hellip+ Em + m + 1

引用式 (1) 則可得V = E + 1

由數學歸納法的原理可得證本定理

532 二元樹的性質(a) 稱為歪斜樹 (skew tree) (b) 稱為完備二元樹 (complete binary tree)

( 其樹葉節點皆在相鄰階層完整定義在後 ) 同樣數目的樹節點存放在歪斜樹上會花較多的階層放在完備二元樹上則只須最少的階層

532 二元樹的性質 ( 續 )定理 5-2 (1) 二元樹上階層 i 的節點數目最多為 2i-1 i 1 (2) 深度為 k 的二元樹其節點數目最多為 2k-

1 k1 bull 定理 5-2說明了 (1) 每一階層上可放的最多節點數

(2) 階層數已知時二元樹可放的最多節點數證明 (1) 利用數學歸納法歸納法的基礎當 i=1 時只有樹根一個節點 2i-1=21-1=1 原式成

立歸納法的假設假設 i = k 時該式成立即階層 k 的最大節點數為

2k-1 k 1 歸納法的推論當 i = k+1 時因在階層 k 的每一個節點最多會有

2 個子節點遂在 k+1 階層最多會有 22k-1 = 2k

= 2i-1 節點由數學歸納法的原理可知本定理得證(2) 由 (1) 可知二元樹在階層 i 的最多節點數為 2i-1那麼深度 k 的二元樹最多可有節點如下

k

i

ki

1

1-1- 24212 1-2

)12(1 k= 2k-1

=

bull 對二元樹而言任一節點的分支度可能為 0 1 或 2 分支度為 0 者是為樹葉

bull 樹葉的數目與分支度為 2 的節點數目存在著有趣的關係請見定理 5-3

定理 5-3 若 T 為一非空的二元樹 n0 為樹葉節點數目 n2為分支度為 2 的節點數目則 n0 = n2+1

證明令 n 為所有節點數目而 n1 為分支度為 1 的節點數目可得 n = n0 + n1 +n2 (1)令 B 為二元樹的分支數除了樹根外每一節點皆有一分支指向之所以

n = B+1 (亦可參考定理 5-1 )又 B = n1+2n2

且 n = n1 + 2n2 +1 (2)由式 (1) 和式 (2) 我們得到

n0 = n2 + 1

完滿二元樹 (full binary tree)

定義一個深度為 k 的完滿二元樹即為一深度為 k 且有 2k-1 個節點的二元樹k 0

bull 由定理 5-2(2) 可知深度為 k 的二元樹其最多節點數為 2k-1 這樣的樹會將樹上所有可能存放節點的位置都放滿完滿 (full) 之名因而生成

完備二元樹 (complete binary tree)我們可對二元樹上節點予以編號階層小者先編同一階

層則自左向右編號這樣的編號方式使我們定義出完備(complete) 二元樹

定義深度為 k 節點數為 n 的二元樹稱為完備二元樹若且唯若 (1) k = 1 時樹有一個節點

(2) 當 k 2 且 1iltk 時深度 i 有 2i-1 個節點且第 k 層的節點皆由第 k-1 層的分支由左至右逐一連接而成

完滿或完備二元樹之所以受人注意乃因它們可以將節點以較少的階層數存放

定理 5-4 n 個節點的完備或完滿二元樹其深度為 log2(n+1)

證明 完滿或完備二元樹的節點皆自階層

1 起排放任一階層 i 可排放 2i-1

個節點遂由定理 5-2 得知此 n 個節點所佔的深度為 log2(n+1)

正規二元樹 (formal binary tree)bull 在二元樹中非樹葉的節點又稱為內部節點

(internal node) 若所有內部節點恰有 2 個子節點即成為正規二元樹

定義若二元樹中所有內部節點恰有 2 個子節點則稱之為正規二元樹

正規二元樹常被引用在單淘汰賽正規二元樹常被引用在單淘汰賽制的各項賽程安排樹葉為參賽制的各項賽程安排樹葉為參賽隊伍內部節點為優勝隊伍樹隊伍內部節點為優勝隊伍樹根即為冠軍隊伍根即為冠軍隊伍

在單淘汰賽制中自在單淘汰賽制中自 n n 個隊伍中產個隊伍中產生冠軍須舉辦 生冠軍須舉辦 nn-1-1場比賽場比賽

54  二元樹的表示法541 以陣列表示二元樹定理 5-5 若一個具有 n 個節點的完備二元樹以循序的方式編號並依序存放在陣列 A 中即第 i 個節點放在 A[i] 中 1 i n 則

(1) 節點 i 的父節點位在 A[i2]處 i ne 1( i = 1 時其節點正為樹根無父節點)

(2) 若 2i n 則節點 i 的左兒子節點位在 A[2i] 處若 2i gtn 節點 i 沒有左子節點

(3) 若 2i+1 n 節點 i 的右兒子節點位 A[2i+1]處若 2i+1gtn 節點 i 沒有左子

節點

證明先用數學歸納法證明 (2) bull 歸納法的基礎當 i = 1 時其左子節點確實在 A[2] 處(除非 nlt2 此時節點 1 沒有左子節點)

bull 歸納法的假設假設當 I = k 時其左子節點確在 A[2i] = A[2k] 處(除非 2i 已大於 n 此時節點 i 無左子節點)

bull 歸納法的推論當 i = k+1 時因第 k 節點的左子節點在 A[2k] 處其右子節點則位在 A[2k+1] 處那麼第 k+1 節點的左子節點應緊臨 A[2k+1] 亦即位於 A[2k+2] = A[2(k+1)] 處(除非 2i 已大於 n 此時節點 i 無左子節點)

bull 由數學歸納法的原理得證定理 5-5 (2) (3) 由於 (2) 成立而且節點 i 的右子節點會緊臨

A[2i] 亦即其位於 A[2i+1]處除非 i 根本沒有右子節點得證定理 5-5 (3)

由 (2) 和 (3) 我們可知定理 5-5 (1) 成立

542 以鍵結串列表示二元樹bull 以陣列來表示完備二元樹算是相當方便但ndash一般的二元樹將導致陣列空間的浪費ndash如果二元樹中節點的新增刪除動作頻繁則會造成陣列對應資料的搬動隨而頻繁

bull 以鍵結串列表示二元樹

為方便辦識樹根我們常用 root 指標指向樹根做為樹鍵結串列的起點

陣列表示和鍵結表示的空間需求bull 完備二元樹的陣列表示和鍵結表示皆沒浪費儲存資料的空間

bull 但歪斜樹的陣列表示比起鍵結表示則浪費許多

bull 以 n 個節點的歪斜樹而言陣列表示法可能需要 2n-1 個陣列元素其中 2n-1-n 個陣列元素空間是浪費的而鍵結表示法只需要 n 個節點空間

二元樹節點的宣告程式 5-2

1 struct BTreeNode

2 struct BtreeNode leftchiled

3 int data

4 struct BTreeNode rightchild

5

6 struct BTreeNode root

bull 這樣的二元樹節點要取得兒子節點的指標十分方便但若有取得父節點的需求則嫌不足我們可以加上 parent 欄位指向父節點

55  二元樹的走訪bull 假如一組資料已用二元樹的組織在一起總有需求對其全數資料做動作例如計算所有數目印出所有資料在所有資料中搜尋某項資料hellip等此時即須對此二元樹做走訪 (traversal) 的運算利用走訪二元樹的同時將計算列印或搜尋的動作完成

bull 事實上走訪即在決定二元樹上資料被處理(計算列印或搜尋)的順序我們也希望走訪的演算法對任何節點皆一致是容易撰寫程式實作的

bull 若對一個二元樹上的節點而言 V 表示處理節點上的資料 L 表示走訪其左子樹 R 表示走訪其右子樹

55  二元樹的走訪 ( 續 )bull 因為對稱的緣故我們只考慮先走訪左邊再走訪右邊的情形那麼右圖只剩下三種走訪方式

bull 以 V 所在的相對位置分別對此三種走訪方式取名如下

(1) LVR 中序走訪 (inorder traversal)或中序表示法 (infix notation)

(2) LRV 後序走訪 (postorder traversal) 或後序表示法 (postfix notation)

(3) VLR 前序走訪 (preorder traversal)或前序表示法 (prefix notation)

551  中序走訪bull 利用遞迴的方式撰寫中序走訪的程序希望把二元樹中序走訪的

順序印出來

程式 5-3二元樹的中序走訪1 struct BTreeNode2 struct BTreeNode leftchild3 char data4 struct BTreeNode rightchild5 6 struct BTreeNode root7 void inorder(struct BTreeNode node)8 if (node = NULL)9 inorder(node-gtleftchild)10 cout ltlt node-gtdata11 inorder(node-gtrightchild)12 13

範例 5-12此為運算式二元樹

其對應中序表示運算式為 (A+B)C+D(E-F)

6 struct BTreeNode root7 void inorder(struct BTreeNode node)8 if (node = NULL)9 inorder(node-gtleftchild)10 cout ltlt node-gtdata11 inorder(node-gtrightchild)12 13

6 struct BTreeNode root7 void inorder(struct BTreeNode node)8 if (node = NULL)9 inorder(node-gtleftchild)10 cout ltlt node-gtdata11 inorder(node-gtrightchild)12 13

(A+B)C+D(E-F)

如何加上 ( )

552  後序走訪程式 5-4二元樹的後序走訪14 void postorder(struct BTreeNode node)

15 if (node =NULL)16 postorder(node-gtleftchild)17 postorder(node-gtrightchild)18 cout ltlt node-gtdata19 20

以後序走訪可得到AB+CDEF-+

其正為對應的後序運算式

553  前序走訪程式 5-5二元樹的前序走訪21 void preorder(struct BTreeNode node)

22 if (node = NULL)

23 cout ltlt node-gtdata

24 preorder(node-gtleftchild)

25 preorder(node-gtrightchild)

26

27

以前序走訪可得到++ABCD-EF

其正為對應的前序運算式

554 利用堆疊和迴圈取代遞迴進行二元樹的走訪1 struct BTreeNode 2 struct BTreeNode leftchild3 char data4 struct BtreeNode rightchild5 6 struct BTreeNode root7 struct StackNode8 struct BTreeNode treenode9 struct StackNode next1011 struct StackNode top

12 void push_node(struct BtreeNode node )13 struct StackNode old_top14 old_top = top15 top = (struct StackNode ) malloc

(sizeof(struct StackNode))16 top-gttreenode = node17 top-gtnext = old_top18 19 struct BTreeNode pop_node()20 struct BTreeNode Tnode21 struct StackNode old_top22 if (top == NULL)23 StackEmpty()24 else25 Tnode = top-gttreenode26 old_top = top27 top = top-gtnext28 free(old_top)29 return Tnode30 31

32 void inorder_Stack(struct BtreeNode node)33 do34 while (node = NULL)35 push_node(node)36 node = node-gtleftchild37 push all left children38 if (top = NULL)39 node = pop_node()40 cout ltlt node-gtdata41 node = node-gtrightchild42 inorder printing and check right child43 while ((top=NULL)||(node=NULL))44 top==NULL 堆疊已空 node==NULL沒有右子

6 struct BTreeNode root7 void inorder(struct BTreeNode node)8 if (node = NULL)9 inorder(node-gtleftchild)10 cout ltlt node-gtdata11 inorder(node-gtrightchild)12 13

中序表示為DBGEHAFIC

32 void inorder_Stack()33 struct BTreeNode node34 node = root35 do36 while (node = NULL)37 push_node(node)38 node = node-gtleftchild39 40 if (top = NULL)41 node = pop_node()42 cout ltlt node-gtdata43 node = node-gtrightchild44 45 while(top=NULL)||(node=NULL))46

45 void Preorder_Stack(struct BSTreeNode node)46 do47 while (node = NULL)48 cout ltlt node-gtdata)49 push_node(node)50 node = node-gtleftchild51 push all left children52 if (top = NULL)53 node = pop_node()54 node = node-gtrightchild55 56 while ((top=NULL)||(node=NULL))57

程式 5-5二元樹的前序走訪21 void preorder(struct BTreeNode node)22 if (node = NULL)23 cout ltlt node-gtdata24 preorder(node-gtleftchild)25 preorder(node-gtrightchild)26 27

58 void Postorder_Stack(struct BTreeNode node)59 do60 while (node = NULL)61 cout ltlt node-gtdata62 push_node(node)63 node = node-gtrightchild64 push all left children65 if (top = NULL)66 node = pop_node()67 node = node-gtleftchild68 69 while ((top=NULL)||(node=NULL))70

程式 5-4二元樹的後序走訪14 void postorder(struct BTreeNode node)15 if (node =NULL)16 postorder(node-gtleftchild)17 postorder(node-gtrightchild)18 cout ltlt node-gtdata19 20

555  階層走訪bull 「階層走訪」 (level-order traversal) 是依階層

的順序進行二元樹的走訪先走訪階層小的節點後走訪階層大的節點同一階層者則依自左向右的順序走訪

bull 對下圖的二元樹進行階層走訪的結果為ABCDEFGHI

bull 對同一階層而言先走訪的節點其子節點亦在下一階層中先被走訪

bull 這種「先進先出」的特性恰可用佇列予以儲存與處理

程式 5-7 利用佇列進行二元樹的階層走訪

1 struct BTreeNode2 struct BTreeNode leftchild3 char data4 struct BTreeNode rightchild5 6 struct BTreeNode root7 struct QNode8 struct BTreeNode treenode9 struct QNode next10 11 struct QNode front rear

12 void AddQueue(struct BTreeNode Tnode)13 struct QNode node14 node=(struct QNode )malloc(sizeof(struct QNode))15 node-gttreenode = Tnode16 node-gtnext = NULL17 if (front == NULL) front = node19 else rear-gtnext = node20 rear = node21

22 struct BTreeNode DeleteQueue()23 struct BTreeNode Tnode24 struct QNode old_front25 if (rear == NULL)26 QueueEmpty()27 else28 Tnode = front-gttreenode29 old_front = front30 front = front-gtnext31 free(old_front)32 return Tnode33 34 35 void LevelOrder (struct BTreeNode node)36 AddQueue(node)37 while (front =NULL)38 node = DeleteQueue()39 cout ltlt node-gtdata40 if (node-gtleftchild = NULL)41 AddQueue(node-gtleftchild)42 if (node-gtrightchild = NULL)43 AddQueue(node-gtrightchild)44 45

35 void LevelOrder (struct BTreeNode node)36 AddQueue(node)37 while (front =NULL)38 node = DeleteQueue()39 cout ltlt node-gtdata40 if (node-gtleftchild = NULL)41 AddQueue(node-gtleftchild)42 if (node-gtrightchild = NULL)43 AddQueue(node-gtrightchild)44 45

bull Infix ABCbull Postfix ABC CBA ACB BCA

BAC CAB

B

A CA

B

C

B

A

C

C

B

AB

A

C

二元樹中序與前序式中序與後序式bull 每一棵二元樹皆有唯一的一對中序與前序表示法也有唯一的中序與後序次序

bull 換句話說給予一對中序與前序或中序與後序即可決定一棵二元樹

bull 然而給予前序和後序次序並不能決定唯一的二元樹可能會產生兩棵不同的二元樹

bull 想想階層走訪和中序走訪順序可否決定唯一的二元樹

A

B C

H J D E

F G

prefix ABHJCDFGEinfix HBJAFDGCEpostfix HJBFGDECA

VLRLVRLRV

A

prefix BHJinfix HBJ

prefix CDFGEinfix FDGCE

prefix ABHJCDFGEinfix HBJAFDGCE

A

B C

H J D E

F G

prefix ABHJCDFGE

infix HBJAFDGCEk-1

struct BTreeNode BTree_InfixPrefix (AnsiString infix AnsiString prefix)

int k struct BTreeNode node if (infixLength==0) return NULLnode = (struct BTreeNode ) malloc (sizeof(struct BTreeNode))node-gtdata = prefix[1]k = find(prefix[1] infix)node-gtleftchild = BTree_InfixPrefix

(infix[1k-1] prefix[2k])node-gtrightchild = BTree_InfixPrefix(infix[k+1infixLength]prefix[k+1prefixLength])

VLR

LVRk1

k2 k+1~

k+1~

1

struct BTreeNode BTree_InfixPostfix (AnsiString infix AnsiString prefix)

int k struct BTreeNode node if (infixLength==0) return NULLnode = (struct BTreeNode ) malloc (sizeof(struct BTreeNode))node-gtdata = postfix[postfixLength]k = find(postfix[postfixLength] infix)node-gtleftchild = BTree_InfixPrefix

(infix[1k-1] postfix[1k-1])node-gtrightchild = BTree_InfixPrefix(infix[k+1infixLength]postfix[k+1postfixLength-1])

56  二元樹的複製與相等測試程式 5-8 二元樹的複製1 struct BTreeNode2 struct BTreeNode leftchild3 char data4 struct BTreeNode rightchild5 6 struct BTreeNode root7 struct BTreeNode Copy(struct BTreeNode TreeRoot)8 struct BTreeNode CopyRoot9 if (TreeRoot == NULL)10 return NULL11 CopyRoot =(struct BTreeNode ) malloc (sizeof(BTreeNode))12 CopyRoot-gtdata = TreeRoot-gtdata13 CopyRoot-gtleftchild = Copy(TreeRoot-gtleftchild)14 CopyRoot-gtrightchild = Copy(TreeRoot-

gtrightchild)15 return CopyRoot16

程式 5-9 檢測兩棵二元樹是否相等1 struct BTreeNode2 struct BTreeNode leftchild3 char data4 struct BTreeNode rightchild5 6 struct BtreeNode rightchild7 int Equal(struct BTreeNode ustruct BTreeNode v)8 if ((u==NULL) ampamp (v==NULL)) return 19 if ((u=NULL) ampamp (v=NULL)10 ampamp (u-gtdata == v-gtdata )11 ampamp Equal(u-gtleftchildv-gtleftchild)12 ampamp Equal(u-gtrightchildv-gtrightchild))

13 return 114 return 015

Counting Nodes in a Binary Tree

int CountingNodes(struct BTreeNodes node)

if (node == NULL) return 0 int leftchildren = CountingNodes(node-gtleftchild)

int rightchildren= CountingNodes(node-gtrightchild)

return leftchildren+rightchildren

Counting Leaves in a Binary Tree

int CountingLeaves(struct BTreeNodes node)

if (node == NULL) return 0 int leftleaves = CountingLeaves(node-gtleftchild)

int rightchildren= CountingLeaves(node-gtrightchild)

if (leftleaves==0 ampamp leftleaves==0) return 1

return leftleaves + leftleaves

57  引線二元樹bull 在二元樹的鍵結表示中樹葉節點的兩個子樹指標皆指向 NULL 由定理 5-1 得知 n = E+1 其中 n 為節點個數 E 為分支數

bull 而 n 個鍵結節點有 2n 個指標空間每個分支恰佔用一個指標空間共有 E (=n-1) 個指標有用到(不是空的)而共有 n+1 個指標空間是 NULL 比非空的節點還多

bull 有學者提出對這些「放空指標的空間」加以利用的概念mdash與其放空指標不如放指向其它節點的指標稱之為引線 (thread) 使得某些運算(如走訪hellip等)可以加快

bull 這個概念發展出了引線二元樹 (threaded binary tree) 這種資料結構

範例 5-17下圖為加了引線的二元樹

加入的引線將使二元樹的中序走訪更加便利沿著上右圖箭頭所指示的順序走訪即可完成此二元樹的中序走訪GDHBAIECF 為此二元樹的中序表示

bull 範例 5-17 中所加的引線目的在方便二元樹的中序走訪

bull 對樹葉節點 p 而言令其資料為 P 則引線指標的規則如下

(1) p 的左子樹指標指向中序表示中 P 的前一個 元素(前接元素 (predecessor) )

(2) p 的右子樹指標指向中序表示中 P 的後一個 元素(後繼元素 (successor) )

為了區別節點指標放的是一般指標(內部節點)抑或是引線指標(樹葉節點)我們另以 2 個欄位分別區別左和右子樹指標空間存放的對象其節點的記憶體配置有如下圖所示

其中 leftthread (rightthread) 為 0 時表示 leftchild (rightchild) 存放的是一般節點指標而 leftthread (rightthread) 為 1 時表示 leftchild (rightchild) 存放的是引線指標1 struct TBTreeNode2 int leftthread3 struct TBTreeNode leftchild4 char data5 struct TBTreeNode rightchild6 int rightthread7

由右側的引線二元樹可知有最左和最右兩條引線尚無妥善的安排我們另設計一空的引線節點做為樹根如下圖所示

571  引線二元樹的中序走訪決定節點 p 在中序表示中的後繼節點 q (簡稱中序後繼點)

程式 5-10決定節點 node的中序後繼點1 struct TBTreeNode2 int leftthread3 struct TBTreeNode leftchild4 char data5 struct TBTreeNode rightchild6 int rightthread7 8 struct TBTreeNode root9 struct TBTreeNode Next(struct TBTreeNode node)

10 struct TBTreeNode temp11 temp = node-gtrightchild12 if (node-gtrightthead) return temp13 while (temp-gtleftthead) temp=temp-gtleftchild

14 return temp15

程式 5-11引線二元樹的中序表示(接程式 5-10)1 void InorderTBTree()2 struct TBTreeNode node3 for(node=Next(root)node=rootnode=Next(node))

4 cout ltlt node-gtdata5

bull只要 node 是引線二元樹上的任一節點透過呼叫 Next(node) 即可找到其在中序表示中的後繼節點bull(試想若程式 5-10 中的程序 Next 所傳入的參數指標正是 root Next(root) 是否可正常運作請思考)bull於是只須從中序表示中的第一節點起讓每一節點 node 都執行 Next(node) 程序即可得引線二元樹的中序表示

572  在引線二元樹中加入節點在引線二元樹中樹葉節點節點的右子樹指標所指向或內部節點右子樹的最左樹葉為其中序後繼節點而且任一節點若無左子樹則其左子樹指標乃指向其中序前接節點hellip這些性質必須在節點新增進入引線二元樹時也要保持接下來討論節點 new 插入成為引線二元樹的節點 p 的右子節點的情形至於插入成為左子節點的情形則讓各位模擬推敲

572572  在引線二元樹中加入節點 在引線二元樹中加入節點 (( 續續 ))

newnew 會成為會成為 pp 的右子節點的情況共有以下兩種的右子節點的情況共有以下兩種(1) (1) 若若 pp 沒有右子節點 沒有右子節點 ((p-gtrightthread p-gtrightthread 為為1) 1) 如圖如圖 ((a) a) 則 則 new new 插入後應形成如圖插入後應形成如圖 ((b) b) 所示所示

(2)(2) 若若 pp 有右子節點 有右子節點 ((p-gtrightthread p-gtrightthread 為為0) 0) 如圖如圖 ((c) c) 則 則 new new 插入後應形成如插入後應形成如圖圖 ((d) d) 所示所示

程式 5-12  插入節點 new 成為引線二元樹中節點 p的右子節點(上接程式 5-10 5-11 )

1 void InsertRight(struct TBTreeNode p 2 struct TBTreeNode new)3 struct TBTreeNode q4 new-gtrightchild = p-gtrightchild5 new-gtrightthead = p-gtrightthread6 new-gtleftchild = p7 new-gtleftthread = 18 p-gtrightchild = new9 p-gtrightthead = 010 if (new-gtrightthread)11 q = Next(new)12 q-gtleftchild = new13 14

58  堆積bull 「堆積」 (heap) 是一個特殊的完備二元樹節點的資料與其子節點的資料之間有事先定好的大小關係存在bull 定義ndash最大堆疊為一完備二元樹任一節點的資料內容不小於其子節點的資料內容ndash最小堆疊為一完備二元樹任一節點的資料內容不大於其子節點的資料內容

581  插入節點至最大堆積中最大堆積是一完備二元樹所以如圖 (a) 之最大堆積於加入新節點後樹的形狀必須如圖 (b) 所示灰色節點即為新節點若加入的節點資料分別為 1 9 12 則其最大堆積將分別變成如圖 (c) (d) (e) 所示

程式 5-13 新增節點至最大堆積中最大堆積本身是一棵完備二元樹可以用陣列表示之 1 define maxsize 1002 int heap[maxsize]3 int n i4 void InsertHeap(int x)5 if (n == maxsize) HeapFull()6 else7 n++8 i = n9 while ((i gt 1) ampamp (x gt heap[i2]))10 heap[i] = heap[i2]11 i = 212 13 heap[i] = x14 16

程序 InsertHeap 在插入第 n 個資料時需要的時間複雜度為 O(logn)

1 100

2 93

3 84

4 77

5 65

6 81

7 79

8 66

9 70

10 50

100

84

77

93

8165

66 70

79

50

1

2 3

4 5 6 7

8 9 10

1 define maxsize 1002 int heap[maxsize]3 int n i4 void InsertHeap(int x)5 if (n == maxsize) HeapFull()6 else7 n++8 i = n9 while ((i gt 1) ampamp (x gt heap[i2]))10 heap[i] = heap[i2]11 i = 212 13 heap[i] = x14 16

582 刪除最大堆積中的最大資料圖 (a) 中的最大堆積在刪除最大資料後其樹形應變成如圖 (b) 所示將最未一個樹葉節點的資料 6 暫挪至樹根節點內如圖 (c) 再將圖 (c) 中非最大堆積的結構調整成為最大堆積 10 和 8 中選大者與 6對調即成圖 (d)

程式 5-14 刪除最大堆積中的最大資料(上接程式 5-13 )1 int DeleteHeap()2 int x i j3 if (n == 0) HeapEmpty()4 else5 x = heap[1]6 heap[1] = heap[n]7 n--8 i = 19 while (i lt= n2)10 if (heap[2i] gt heap[2i+1])11 j = 2i12 else13 j = 2i+114 if (heap[i]gtheap[j]) break15 swap(heap[i] heap[j])16 i = j17 18 return x19 20

其時間複雜度與其堆積高度相同是為O(logn) 其中 n 為堆積中的元素個數

1 100

2 93

3 84

4 77

5 65

6 81

7 79

8 66

9 70

10 50

100

84

77

93

8165

66 70

79

50

1

2 3

4 5 6 7

8 9 10

1 int DeletHeap()2 int x i j3 if(n == 0) HeapEmpty()4 else5 x = heap[1]6 heap[1] = heap[n]7 n--8 i = 19 while (i lt= n2)10 if ([heap[2i]] gt heap[2i+1])11 j = 2i12 else13 j = 2i+114 if (heap[i]gtheap[j]) break15 swap(heap[i] heap[j])16 i = j17 18 return x19 20

583  優先佇列bull 由於最大堆積(或最小堆積)的結構可提供最大元素(或最小元素)而其維護堆積的成本為 O(logn) n 為總元素個數

bull 堆積常被當成「優先佇列」 (priority queue) 使用mdash此時佇列被組織成完備二元樹若新加入佇列的資料有大小關係優先順序的需求時此優先佇列即以 O(logn) 的代價將新資料放入優先佇列(可能是最大或最小堆積依實際大小關係優先順序的需求而定)中mdash提供依元素的優先順序做為處理順序的服務

bull 一般的佇列為先進先出先到的先行處理優先佇列則是依據其優先順序優先順序高者先行處理與來到的先後無關

範例 5-21bull 在「分時」 (time sharing) 的作業系統中我們不希望使用中央處理器 (Central Processing Unit CPU) 時間短的「處理程序」 (process) 等候過久於是有「最短時間工作優先」 (shortest-job-first) 的排程策略此時等候的處理程序即可組織成之一最小堆積而 CPU 每次即取出存於樹根的處理程序予以處理任何新來的處理程序則以 O(logn) 的時間加入此優先佇列(最小堆積)中

bull 注意之前提過的線性佇列新來的直接加入成為新的尾端元素花O(1) 的時間然線性佇列本身沒有大小的關係每次要取出最大(小)的資料得花O(n) 的代價(在所有元素中挑出最大(小)者) n 為佇列內的元素個數而優先佇列是在新元素加入時要花O(logn) 的時間至於取出最大(小)則只須 O(1) 的時間

59  二元搜尋樹bull 二元搜尋樹是一棵二元樹可能是空二元樹若不為空二元樹則滿足下列性質ndash所有節點內的資料內容是相異的 ndash左子節點的資內容 ( 如果有 ) 比父節點的資料內容小 ndash右子節點的資料內容 ( 如果有 ) 比父節點的資料內容大 ndash以左和右子節點為樹根的左子樹和右子樹也是二元搜尋樹

在二元搜尋樹中搜尋資料二元搜尋樹對資料大小的掌握十分明確若恰與節點 p 資料相等則搜尋成功若欲搜尋的資料 x 比樹節點 p 中的資料小 則只須搜尋節點 p 的左子樹若比節點 p 中的資料大 則只須搜尋 p 的右子樹這樣的判斷可以遞迴地應用在各子樹節點 中

程式 5-15 在二元搜尋樹中搜尋資料(遞迴呼叫)1 struct BSTreeNode2 struct BSTreeNode leftchild3 int data4 struct BSTreeNode rightchild5 6 struct BSTreeNode root7 struct BSTreeNode SearchBST(

struct BSTreeNode tree int x)8 if (tree == NULL) return NULL9 if (x == tree-gtdata) return tree10 if (x lt tree-gtdata)11 return SearchBST(tree-gtleftchild x)

12 return SearchBST(tree-gtrightchild x)13

程式 5-16 搜尋二元搜尋樹的資料(非遞迴呼叫)在二元搜尋樹十分龐大時遞迴呼叫的效率不會太好可利用迴圈來完成搜尋的動作以提高執行效率請看程式 5-16

14 struct BSTreeNode Search BST_iterative15 (struct BSTreeNode node int x)16 while (node = NULL)17 if (x == node-gtdata) return node18 if (x lt node-gtdata)19 node = node-gtleftchild20 else21 node = node-gtrightchild22 23 return NULL24

592  新增資料於二元搜尋樹中欲新增資料進入一二元搜尋樹中得先決定該資料在二元搜尋樹中應出現的位置這可透過上節提到的搜尋動作來解決

在二元搜尋樹定義的性質 (1) 中我們限定了元素必須相異所以搜尋新資料的結果必是 NULL

必須將搜尋的結果改為新增資料所應在位置的節點指標

程式 5-17  在二元搜尋樹中新增資料25 int InsertBST(int x)26 struct BSTreeNode p q27 p = root q = NULL28 while (p = NULL)29 q = p30 if (x == p-gtdata) return 0

若不考慮重複則可省略之31 if (x lt p-gtdata)32 p = p-gtleftchild33 else34 p = p-gtrightchild35

3636 p = (struct BSTreeNode ) p = (struct BSTreeNode ) malloc (sizeof(BSTreeNode))malloc (sizeof(BSTreeNode))

3737 p-gtdata = x p-gtdata = x3838 p-gtleftchild = p-gtrightchild = p-gtleftchild = p-gtrightchild = NULLNULL3939 if (root == NULL) root = p if (root == NULL) root = p4040 else if (x lt q-gtdata) else if (x lt q-gtdata)4141 q-gtleftchild = p q-gtleftchild = p4242 else else4343 q-gtrightchild = p q-gtrightchild = p4444 return 1 return 145 45

程式 程式 5-175-17  在二元搜尋樹中新增資料  在二元搜尋樹中新增資料 (( 續續 ))

範例 5-23 圖 (b) 為圖 (a) 新增整數 6 之後的結果圖 (c) (d) (e) (f) 則分別是逐步再加入 28 18

10 21 的結果

bull若目前的二元搜尋樹高度為 h 則新增資料得花O(h) 的時間找到其所應在的位置遂新增資料至二元搜尋樹中的時間複雜之度為 O(h)

593 自二元搜尋樹中刪除資料欲自二元搜尋樹中刪除資料可能會面臨兩種情況(1) 刪除節點 p 位於樹葉節點上如下圖所示 這是比較容易處理的情況

(2) 刪除的節點 p 不是樹葉刪除節點 p 後原 p 所指的節點可由 q ( q 中包含了 p 左子樹中的最大節點值)或 r ( r 中包含了 p 右子樹中的最小節點值)來取代以維持二元搜尋樹中資料的大小關係其結果分別如圖 (b) 和 (c) 所示而圖 (d) 則標明了指標會改變的節點位置即 p 會消失 father 的左或右子樹指標會異動 k(可能是 q 或 r )會移到原來 p 的位置其父節點 f 的左或右子樹指標會異動其中 father 為 p 父節點 f 為 k 的父節點

程式 5-18 自二元搜尋樹中刪除資料46 int DeleteBST (int x)47 Struct BSTreeNode p father k f48 p = root father = NULL49 while (p = NULL)50 if (x == p-gtdata) 找到 x 所在的節點 p 了51 if ((p-gtleftchild == NULL) ampamp 52 (p-gtrightchild == NULL))53 k = NULL p為樹葉節點54 else if (p-gtleftchild = NULL )x位於非子節

點55 f = p 找出 p左子樹的最右樹葉56 k = p-gtleftchild57 while (k-gtrightchild =NULL)58 f = k59 k = k-gtrightchild60 k將挪至原 p處61 if (p == f)62 f-gtleftchild = k-gtleftchild 63 else64 f-gtrightchild = k-gtleftchild 65

66 else p無左子樹67 f = p 找出 p右子樹的最左樹葉68 k = p-gtrightchild69 while (k-gtleftchild = NULL)70 f = k71 k = k -gtleftchild72 73 if (p == f)74 f-gtrightchild = k-gtrightchild 75 else76 f-gtleftchild = k-gtrightchild77 78 if (k = NULL) k挪至原 p處繼承 p的左右指標79 k-gtleftchild = p-gtleftchild80 k-gtrightchild = p-gtrightchild81 82 if (father == NULL) root = k83 else if (x lt father-gtdata) 決定 k是 father的左

或右兒子84 father-gtleftchild = k85 else86 father-gtrightchild = k87 free(p)88 return 1 成功地刪除 x於此傳回 1返回呼叫處89 51~89行為找到 x時應有的處理步驟

90 else 尚未找到 x繼續往下一階層找

91 father = p92 if (x lt p-gtdata)93 p = p-gtleftchild94 else95 p = p-gtrightchild96 97 98 return 0 未找到 x傳回 099

510  樹林 (forest)

bull 定義樹林(森林)是由 n 棵沒有交集的樹所構成的集合 n 0

下圖為一包含三棵樹的樹林

5101 轉換樹林為二元樹 之前我們以二元樹來表示樹因為二元樹有較多

好用的性質在此我們也將樹林轉換成二元樹看待

在 522 節中我們嘗試了用左子右兄弟的方式來表示樹這個方法事實上已將樹轉換成二元樹

即用左子右兄弟的結構重新組織樹林中的每一棵樹這些樹的樹根將視為兄弟而最左邊的樹則視為樹林二元樹的樹根如此一來樹林即可轉換成二元樹

範例 5-25右下圖為右上圖三棵樹所對應的三棵左子右兄弟二元樹

下圖則為上圖樹林所對應的一棵樹林二元樹

演算法 5-1  轉換樹林成二元樹輸入樹林 F = T1 T2 Tn

輸出 F 的二元樹表示 BT=(T1 T2 Tn)1 if (n == 0) 輸出空二元樹 2 else

3 root(T1 T2 Tn) = T1

4 leftsubtree(T1 T2 Tn) =

BT(T11 T12 T1m)5 BT(T11 T12 T1m)為 T1的子樹6 rightsubtree(T1 T2 Tn) =

BT(T2 T3 Tn)

樹林轉換成二元樹的過程樹林轉換成二元樹的過程

  • Slide 1
  • Slide 2
  • Slide 3
  • Slide 4
  • Slide 5
  • Slide 6
  • Slide 7
  • Slide 8
  • Slide 9
  • Slide 10
  • Slide 11
  • Slide 12
  • Slide 13
  • Slide 14
  • Slide 15
  • Slide 16
  • Slide 17
  • Slide 18
  • Slide 19
  • Slide 20
  • Slide 21
  • Slide 22
  • Slide 23
  • Slide 24
  • Slide 25
  • Slide 26
  • Slide 27
  • Slide 28
  • Slide 29
  • Slide 30
  • Slide 31
  • Slide 32
  • Slide 33
  • Slide 34
  • Slide 35
  • Slide 36
  • Slide 37
  • Slide 38
  • Slide 39
  • Slide 40
  • Slide 41
  • Slide 42
  • Slide 43
  • Slide 44
  • Slide 45
  • Slide 46
  • Slide 47
  • Slide 48
  • Slide 49
  • Slide 50
  • Slide 51
  • Slide 52
  • Slide 53
  • Slide 54
  • Slide 55
  • Slide 56
  • Slide 57
  • Slide 58
  • Slide 59
  • Slide 60
  • Slide 61
  • Slide 62
  • Slide 63
  • Slide 64
  • Slide 65
  • Slide 66
  • Slide 67
  • Slide 68
  • Slide 69
  • Slide 70
  • Slide 71
  • Slide 72
  • Slide 73
  • Slide 74
  • Slide 75
  • Slide 76
  • Slide 77
  • Slide 78
  • Slide 79
  • Slide 80
  • Slide 81
  • Slide 82
  • Slide 83
  • Slide 84
  • Slide 85
  • Slide 86
  • Slide 87
  • Slide 88
  • Slide 89
  • Slide 90
  • Slide 91
  • Slide 92
  • Slide 93
  • Slide 94
  • Slide 95
  • Slide 96
  • Slide 97
  • Slide 98
  • Slide 99
  • Slide 100
  • Slide 101
  • Slide 102
  • Slide 103
Page 7: 第五章 樹

512  專有名詞 ( 續 )

bull 節點 (node) 圖中圓圈和向下的分支 (branch)bull 樹根 (root) 節點 Abull 分支度 (degree) 一個節點其所有子樹的數目bull 樹葉 (leaf) 分支度為 0 的節點bull 內部節點 (internal node) 非終端節點 (non-terminal

node)

bull 深度 (depth) 樹的最高階層 (level)

512  專有名詞 ( 續 )bull 兒子 (son) 任何節點 X 其子樹的樹根 Y 為 X 的

兒子bull 父親 (father parent) Y 是 X 的「父親」bull 兄弟 (sibling brother) 共有一個父親的數個節點bull 祖先 (ancestor) 該節點走向樹根所經過的所有節點bull 後裔 (descendant) 任一節點的所有子樹節點

52  樹的表示法bull 用陣列表示樹 bull 用鍵結串列表示樹 令分支度 =k

若 T 共有 n 個節點則需要 n 個鍵結節點計有 nk 個指標然而除了樹根外每個節點只須由唯一的指標所指向遂只需要有 n-1 個指標空間用不著的指標空間多達 nk-(n-1)=n(k-1)+1 個比用得著的還多著實浪費

521  一般化的串列表示bull 串列是一個有限且有順序的元素集合

A = (a1 a2 hellip an)

每個元素 ai 1in 皆有相同的資料型態 bull 倘若不限定各個元素都具有相同的資料型態則稱此串列為「一般化串列」 (generalized list) (ai 可以是一個節點或是一棵樹 )

bull 可用一般化串列來表示一棵樹 T T = (R T1 T2 hellip Tn)

其中 R 為 T 的樹根而 T1 T2 hellip Tn 為 R 的子樹 Ti 可能是一個節點也可能是一棵樹(一般化串列)

範例 5-1 以一般化串列表示樹bull (A (B (E K) (F L M) G) (C H) (D (I N) (J

O P)))

(A T1 T2 T3)T1 = (B (E K) (F L M) G)T2 = (C H)T3 = (D (I N) (J O P))

T1 T2T3

程式 5-1 一般化串列鍵結節點的宣告1 struct TreeNode2 int tag3 union4 int data5 struct TreeNode Tlink6 node7 struct TreeNode link8

(A T1 T2 T3)T1 = (B (E K) (F L M) G)T2 = (C H)T3 = (D (I N) (J O P))

522  左子右兄弟表示法

每個節點都有唯一的最左 兒子 (leftmost child) 每個節點都有最靠近它的 右兄弟

523 分支度為 2 的二元樹表示法bull 分支度為 2 的樹又稱為二元樹 (binary tree)

bull 二元樹中任一節點皆有 2個指標分別指向該節點的左子樹和右子樹

bull 二元樹可以表示任何樹事實上二元樹有許多很好的性質我們在一下節中介紹

bull注意樹狀結構中兒子的順序是不重要的試想樹可以樹根為軸心旋轉使得兒子的順序是不固定的

53  二元樹bull 二元樹是一個由節點組成的有限集合可以是空集合或是包含了(1) 一個樹根節點(2) 其它節點分割成兩個沒有交集的二元樹

其一為左子樹 (left subtree) 另一為右子樹 (right subtree) 樹和二元樹之間的差異

(1)樹不允許空節點而空二元樹是允許的

(2) 樹的子樹沒有順序而二元樹的子樹有左右之分

531  樹和二元樹的基本性質

樹或二元樹皆擁有下面的性質

定理 5-1 若一棵樹 T 的總節點數為 V 總邊數為

E 則V = E + 1

證明利用數學歸納法歸納法的基礎當 V=1 時即 T 只有一個樹根節點 E 為 0 所以 V = E + 1 成立歸納法的假設假設當 1 V K 時該式成立歸納法的推論當 V = K + 1 時令此樹的樹根為 R 而 R 有 m 個子樹 T1 T2 hellip

Tm (如圖 5-12 所示)分別有個 V1 V2 hellip Vm 個節點其中 V1 +V2 + hellip+ Vm =K 若 E1 E2 hellip Em 分別為此 m 個子樹的邊數則

E = E1 +E2 + hellip+ Em + m (1)根據歸納法的假設可知

V1 = E1+1V2 = E2+1 Vm = Em+1

所以V1 +V2 + hellip+Vm = E1 + E2 + hellip+ Em +m (2)

然而V = V1 +V2 + hellip+Vm+1 (3)

將式 (2) 代入式 (3) 可得V = E1 + E2 + hellip+ Em + m + 1

引用式 (1) 則可得V = E + 1

由數學歸納法的原理可得證本定理

532 二元樹的性質(a) 稱為歪斜樹 (skew tree) (b) 稱為完備二元樹 (complete binary tree)

( 其樹葉節點皆在相鄰階層完整定義在後 ) 同樣數目的樹節點存放在歪斜樹上會花較多的階層放在完備二元樹上則只須最少的階層

532 二元樹的性質 ( 續 )定理 5-2 (1) 二元樹上階層 i 的節點數目最多為 2i-1 i 1 (2) 深度為 k 的二元樹其節點數目最多為 2k-

1 k1 bull 定理 5-2說明了 (1) 每一階層上可放的最多節點數

(2) 階層數已知時二元樹可放的最多節點數證明 (1) 利用數學歸納法歸納法的基礎當 i=1 時只有樹根一個節點 2i-1=21-1=1 原式成

立歸納法的假設假設 i = k 時該式成立即階層 k 的最大節點數為

2k-1 k 1 歸納法的推論當 i = k+1 時因在階層 k 的每一個節點最多會有

2 個子節點遂在 k+1 階層最多會有 22k-1 = 2k

= 2i-1 節點由數學歸納法的原理可知本定理得證(2) 由 (1) 可知二元樹在階層 i 的最多節點數為 2i-1那麼深度 k 的二元樹最多可有節點如下

k

i

ki

1

1-1- 24212 1-2

)12(1 k= 2k-1

=

bull 對二元樹而言任一節點的分支度可能為 0 1 或 2 分支度為 0 者是為樹葉

bull 樹葉的數目與分支度為 2 的節點數目存在著有趣的關係請見定理 5-3

定理 5-3 若 T 為一非空的二元樹 n0 為樹葉節點數目 n2為分支度為 2 的節點數目則 n0 = n2+1

證明令 n 為所有節點數目而 n1 為分支度為 1 的節點數目可得 n = n0 + n1 +n2 (1)令 B 為二元樹的分支數除了樹根外每一節點皆有一分支指向之所以

n = B+1 (亦可參考定理 5-1 )又 B = n1+2n2

且 n = n1 + 2n2 +1 (2)由式 (1) 和式 (2) 我們得到

n0 = n2 + 1

完滿二元樹 (full binary tree)

定義一個深度為 k 的完滿二元樹即為一深度為 k 且有 2k-1 個節點的二元樹k 0

bull 由定理 5-2(2) 可知深度為 k 的二元樹其最多節點數為 2k-1 這樣的樹會將樹上所有可能存放節點的位置都放滿完滿 (full) 之名因而生成

完備二元樹 (complete binary tree)我們可對二元樹上節點予以編號階層小者先編同一階

層則自左向右編號這樣的編號方式使我們定義出完備(complete) 二元樹

定義深度為 k 節點數為 n 的二元樹稱為完備二元樹若且唯若 (1) k = 1 時樹有一個節點

(2) 當 k 2 且 1iltk 時深度 i 有 2i-1 個節點且第 k 層的節點皆由第 k-1 層的分支由左至右逐一連接而成

完滿或完備二元樹之所以受人注意乃因它們可以將節點以較少的階層數存放

定理 5-4 n 個節點的完備或完滿二元樹其深度為 log2(n+1)

證明 完滿或完備二元樹的節點皆自階層

1 起排放任一階層 i 可排放 2i-1

個節點遂由定理 5-2 得知此 n 個節點所佔的深度為 log2(n+1)

正規二元樹 (formal binary tree)bull 在二元樹中非樹葉的節點又稱為內部節點

(internal node) 若所有內部節點恰有 2 個子節點即成為正規二元樹

定義若二元樹中所有內部節點恰有 2 個子節點則稱之為正規二元樹

正規二元樹常被引用在單淘汰賽正規二元樹常被引用在單淘汰賽制的各項賽程安排樹葉為參賽制的各項賽程安排樹葉為參賽隊伍內部節點為優勝隊伍樹隊伍內部節點為優勝隊伍樹根即為冠軍隊伍根即為冠軍隊伍

在單淘汰賽制中自在單淘汰賽制中自 n n 個隊伍中產個隊伍中產生冠軍須舉辦 生冠軍須舉辦 nn-1-1場比賽場比賽

54  二元樹的表示法541 以陣列表示二元樹定理 5-5 若一個具有 n 個節點的完備二元樹以循序的方式編號並依序存放在陣列 A 中即第 i 個節點放在 A[i] 中 1 i n 則

(1) 節點 i 的父節點位在 A[i2]處 i ne 1( i = 1 時其節點正為樹根無父節點)

(2) 若 2i n 則節點 i 的左兒子節點位在 A[2i] 處若 2i gtn 節點 i 沒有左子節點

(3) 若 2i+1 n 節點 i 的右兒子節點位 A[2i+1]處若 2i+1gtn 節點 i 沒有左子

節點

證明先用數學歸納法證明 (2) bull 歸納法的基礎當 i = 1 時其左子節點確實在 A[2] 處(除非 nlt2 此時節點 1 沒有左子節點)

bull 歸納法的假設假設當 I = k 時其左子節點確在 A[2i] = A[2k] 處(除非 2i 已大於 n 此時節點 i 無左子節點)

bull 歸納法的推論當 i = k+1 時因第 k 節點的左子節點在 A[2k] 處其右子節點則位在 A[2k+1] 處那麼第 k+1 節點的左子節點應緊臨 A[2k+1] 亦即位於 A[2k+2] = A[2(k+1)] 處(除非 2i 已大於 n 此時節點 i 無左子節點)

bull 由數學歸納法的原理得證定理 5-5 (2) (3) 由於 (2) 成立而且節點 i 的右子節點會緊臨

A[2i] 亦即其位於 A[2i+1]處除非 i 根本沒有右子節點得證定理 5-5 (3)

由 (2) 和 (3) 我們可知定理 5-5 (1) 成立

542 以鍵結串列表示二元樹bull 以陣列來表示完備二元樹算是相當方便但ndash一般的二元樹將導致陣列空間的浪費ndash如果二元樹中節點的新增刪除動作頻繁則會造成陣列對應資料的搬動隨而頻繁

bull 以鍵結串列表示二元樹

為方便辦識樹根我們常用 root 指標指向樹根做為樹鍵結串列的起點

陣列表示和鍵結表示的空間需求bull 完備二元樹的陣列表示和鍵結表示皆沒浪費儲存資料的空間

bull 但歪斜樹的陣列表示比起鍵結表示則浪費許多

bull 以 n 個節點的歪斜樹而言陣列表示法可能需要 2n-1 個陣列元素其中 2n-1-n 個陣列元素空間是浪費的而鍵結表示法只需要 n 個節點空間

二元樹節點的宣告程式 5-2

1 struct BTreeNode

2 struct BtreeNode leftchiled

3 int data

4 struct BTreeNode rightchild

5

6 struct BTreeNode root

bull 這樣的二元樹節點要取得兒子節點的指標十分方便但若有取得父節點的需求則嫌不足我們可以加上 parent 欄位指向父節點

55  二元樹的走訪bull 假如一組資料已用二元樹的組織在一起總有需求對其全數資料做動作例如計算所有數目印出所有資料在所有資料中搜尋某項資料hellip等此時即須對此二元樹做走訪 (traversal) 的運算利用走訪二元樹的同時將計算列印或搜尋的動作完成

bull 事實上走訪即在決定二元樹上資料被處理(計算列印或搜尋)的順序我們也希望走訪的演算法對任何節點皆一致是容易撰寫程式實作的

bull 若對一個二元樹上的節點而言 V 表示處理節點上的資料 L 表示走訪其左子樹 R 表示走訪其右子樹

55  二元樹的走訪 ( 續 )bull 因為對稱的緣故我們只考慮先走訪左邊再走訪右邊的情形那麼右圖只剩下三種走訪方式

bull 以 V 所在的相對位置分別對此三種走訪方式取名如下

(1) LVR 中序走訪 (inorder traversal)或中序表示法 (infix notation)

(2) LRV 後序走訪 (postorder traversal) 或後序表示法 (postfix notation)

(3) VLR 前序走訪 (preorder traversal)或前序表示法 (prefix notation)

551  中序走訪bull 利用遞迴的方式撰寫中序走訪的程序希望把二元樹中序走訪的

順序印出來

程式 5-3二元樹的中序走訪1 struct BTreeNode2 struct BTreeNode leftchild3 char data4 struct BTreeNode rightchild5 6 struct BTreeNode root7 void inorder(struct BTreeNode node)8 if (node = NULL)9 inorder(node-gtleftchild)10 cout ltlt node-gtdata11 inorder(node-gtrightchild)12 13

範例 5-12此為運算式二元樹

其對應中序表示運算式為 (A+B)C+D(E-F)

6 struct BTreeNode root7 void inorder(struct BTreeNode node)8 if (node = NULL)9 inorder(node-gtleftchild)10 cout ltlt node-gtdata11 inorder(node-gtrightchild)12 13

6 struct BTreeNode root7 void inorder(struct BTreeNode node)8 if (node = NULL)9 inorder(node-gtleftchild)10 cout ltlt node-gtdata11 inorder(node-gtrightchild)12 13

(A+B)C+D(E-F)

如何加上 ( )

552  後序走訪程式 5-4二元樹的後序走訪14 void postorder(struct BTreeNode node)

15 if (node =NULL)16 postorder(node-gtleftchild)17 postorder(node-gtrightchild)18 cout ltlt node-gtdata19 20

以後序走訪可得到AB+CDEF-+

其正為對應的後序運算式

553  前序走訪程式 5-5二元樹的前序走訪21 void preorder(struct BTreeNode node)

22 if (node = NULL)

23 cout ltlt node-gtdata

24 preorder(node-gtleftchild)

25 preorder(node-gtrightchild)

26

27

以前序走訪可得到++ABCD-EF

其正為對應的前序運算式

554 利用堆疊和迴圈取代遞迴進行二元樹的走訪1 struct BTreeNode 2 struct BTreeNode leftchild3 char data4 struct BtreeNode rightchild5 6 struct BTreeNode root7 struct StackNode8 struct BTreeNode treenode9 struct StackNode next1011 struct StackNode top

12 void push_node(struct BtreeNode node )13 struct StackNode old_top14 old_top = top15 top = (struct StackNode ) malloc

(sizeof(struct StackNode))16 top-gttreenode = node17 top-gtnext = old_top18 19 struct BTreeNode pop_node()20 struct BTreeNode Tnode21 struct StackNode old_top22 if (top == NULL)23 StackEmpty()24 else25 Tnode = top-gttreenode26 old_top = top27 top = top-gtnext28 free(old_top)29 return Tnode30 31

32 void inorder_Stack(struct BtreeNode node)33 do34 while (node = NULL)35 push_node(node)36 node = node-gtleftchild37 push all left children38 if (top = NULL)39 node = pop_node()40 cout ltlt node-gtdata41 node = node-gtrightchild42 inorder printing and check right child43 while ((top=NULL)||(node=NULL))44 top==NULL 堆疊已空 node==NULL沒有右子

6 struct BTreeNode root7 void inorder(struct BTreeNode node)8 if (node = NULL)9 inorder(node-gtleftchild)10 cout ltlt node-gtdata11 inorder(node-gtrightchild)12 13

中序表示為DBGEHAFIC

32 void inorder_Stack()33 struct BTreeNode node34 node = root35 do36 while (node = NULL)37 push_node(node)38 node = node-gtleftchild39 40 if (top = NULL)41 node = pop_node()42 cout ltlt node-gtdata43 node = node-gtrightchild44 45 while(top=NULL)||(node=NULL))46

45 void Preorder_Stack(struct BSTreeNode node)46 do47 while (node = NULL)48 cout ltlt node-gtdata)49 push_node(node)50 node = node-gtleftchild51 push all left children52 if (top = NULL)53 node = pop_node()54 node = node-gtrightchild55 56 while ((top=NULL)||(node=NULL))57

程式 5-5二元樹的前序走訪21 void preorder(struct BTreeNode node)22 if (node = NULL)23 cout ltlt node-gtdata24 preorder(node-gtleftchild)25 preorder(node-gtrightchild)26 27

58 void Postorder_Stack(struct BTreeNode node)59 do60 while (node = NULL)61 cout ltlt node-gtdata62 push_node(node)63 node = node-gtrightchild64 push all left children65 if (top = NULL)66 node = pop_node()67 node = node-gtleftchild68 69 while ((top=NULL)||(node=NULL))70

程式 5-4二元樹的後序走訪14 void postorder(struct BTreeNode node)15 if (node =NULL)16 postorder(node-gtleftchild)17 postorder(node-gtrightchild)18 cout ltlt node-gtdata19 20

555  階層走訪bull 「階層走訪」 (level-order traversal) 是依階層

的順序進行二元樹的走訪先走訪階層小的節點後走訪階層大的節點同一階層者則依自左向右的順序走訪

bull 對下圖的二元樹進行階層走訪的結果為ABCDEFGHI

bull 對同一階層而言先走訪的節點其子節點亦在下一階層中先被走訪

bull 這種「先進先出」的特性恰可用佇列予以儲存與處理

程式 5-7 利用佇列進行二元樹的階層走訪

1 struct BTreeNode2 struct BTreeNode leftchild3 char data4 struct BTreeNode rightchild5 6 struct BTreeNode root7 struct QNode8 struct BTreeNode treenode9 struct QNode next10 11 struct QNode front rear

12 void AddQueue(struct BTreeNode Tnode)13 struct QNode node14 node=(struct QNode )malloc(sizeof(struct QNode))15 node-gttreenode = Tnode16 node-gtnext = NULL17 if (front == NULL) front = node19 else rear-gtnext = node20 rear = node21

22 struct BTreeNode DeleteQueue()23 struct BTreeNode Tnode24 struct QNode old_front25 if (rear == NULL)26 QueueEmpty()27 else28 Tnode = front-gttreenode29 old_front = front30 front = front-gtnext31 free(old_front)32 return Tnode33 34 35 void LevelOrder (struct BTreeNode node)36 AddQueue(node)37 while (front =NULL)38 node = DeleteQueue()39 cout ltlt node-gtdata40 if (node-gtleftchild = NULL)41 AddQueue(node-gtleftchild)42 if (node-gtrightchild = NULL)43 AddQueue(node-gtrightchild)44 45

35 void LevelOrder (struct BTreeNode node)36 AddQueue(node)37 while (front =NULL)38 node = DeleteQueue()39 cout ltlt node-gtdata40 if (node-gtleftchild = NULL)41 AddQueue(node-gtleftchild)42 if (node-gtrightchild = NULL)43 AddQueue(node-gtrightchild)44 45

bull Infix ABCbull Postfix ABC CBA ACB BCA

BAC CAB

B

A CA

B

C

B

A

C

C

B

AB

A

C

二元樹中序與前序式中序與後序式bull 每一棵二元樹皆有唯一的一對中序與前序表示法也有唯一的中序與後序次序

bull 換句話說給予一對中序與前序或中序與後序即可決定一棵二元樹

bull 然而給予前序和後序次序並不能決定唯一的二元樹可能會產生兩棵不同的二元樹

bull 想想階層走訪和中序走訪順序可否決定唯一的二元樹

A

B C

H J D E

F G

prefix ABHJCDFGEinfix HBJAFDGCEpostfix HJBFGDECA

VLRLVRLRV

A

prefix BHJinfix HBJ

prefix CDFGEinfix FDGCE

prefix ABHJCDFGEinfix HBJAFDGCE

A

B C

H J D E

F G

prefix ABHJCDFGE

infix HBJAFDGCEk-1

struct BTreeNode BTree_InfixPrefix (AnsiString infix AnsiString prefix)

int k struct BTreeNode node if (infixLength==0) return NULLnode = (struct BTreeNode ) malloc (sizeof(struct BTreeNode))node-gtdata = prefix[1]k = find(prefix[1] infix)node-gtleftchild = BTree_InfixPrefix

(infix[1k-1] prefix[2k])node-gtrightchild = BTree_InfixPrefix(infix[k+1infixLength]prefix[k+1prefixLength])

VLR

LVRk1

k2 k+1~

k+1~

1

struct BTreeNode BTree_InfixPostfix (AnsiString infix AnsiString prefix)

int k struct BTreeNode node if (infixLength==0) return NULLnode = (struct BTreeNode ) malloc (sizeof(struct BTreeNode))node-gtdata = postfix[postfixLength]k = find(postfix[postfixLength] infix)node-gtleftchild = BTree_InfixPrefix

(infix[1k-1] postfix[1k-1])node-gtrightchild = BTree_InfixPrefix(infix[k+1infixLength]postfix[k+1postfixLength-1])

56  二元樹的複製與相等測試程式 5-8 二元樹的複製1 struct BTreeNode2 struct BTreeNode leftchild3 char data4 struct BTreeNode rightchild5 6 struct BTreeNode root7 struct BTreeNode Copy(struct BTreeNode TreeRoot)8 struct BTreeNode CopyRoot9 if (TreeRoot == NULL)10 return NULL11 CopyRoot =(struct BTreeNode ) malloc (sizeof(BTreeNode))12 CopyRoot-gtdata = TreeRoot-gtdata13 CopyRoot-gtleftchild = Copy(TreeRoot-gtleftchild)14 CopyRoot-gtrightchild = Copy(TreeRoot-

gtrightchild)15 return CopyRoot16

程式 5-9 檢測兩棵二元樹是否相等1 struct BTreeNode2 struct BTreeNode leftchild3 char data4 struct BTreeNode rightchild5 6 struct BtreeNode rightchild7 int Equal(struct BTreeNode ustruct BTreeNode v)8 if ((u==NULL) ampamp (v==NULL)) return 19 if ((u=NULL) ampamp (v=NULL)10 ampamp (u-gtdata == v-gtdata )11 ampamp Equal(u-gtleftchildv-gtleftchild)12 ampamp Equal(u-gtrightchildv-gtrightchild))

13 return 114 return 015

Counting Nodes in a Binary Tree

int CountingNodes(struct BTreeNodes node)

if (node == NULL) return 0 int leftchildren = CountingNodes(node-gtleftchild)

int rightchildren= CountingNodes(node-gtrightchild)

return leftchildren+rightchildren

Counting Leaves in a Binary Tree

int CountingLeaves(struct BTreeNodes node)

if (node == NULL) return 0 int leftleaves = CountingLeaves(node-gtleftchild)

int rightchildren= CountingLeaves(node-gtrightchild)

if (leftleaves==0 ampamp leftleaves==0) return 1

return leftleaves + leftleaves

57  引線二元樹bull 在二元樹的鍵結表示中樹葉節點的兩個子樹指標皆指向 NULL 由定理 5-1 得知 n = E+1 其中 n 為節點個數 E 為分支數

bull 而 n 個鍵結節點有 2n 個指標空間每個分支恰佔用一個指標空間共有 E (=n-1) 個指標有用到(不是空的)而共有 n+1 個指標空間是 NULL 比非空的節點還多

bull 有學者提出對這些「放空指標的空間」加以利用的概念mdash與其放空指標不如放指向其它節點的指標稱之為引線 (thread) 使得某些運算(如走訪hellip等)可以加快

bull 這個概念發展出了引線二元樹 (threaded binary tree) 這種資料結構

範例 5-17下圖為加了引線的二元樹

加入的引線將使二元樹的中序走訪更加便利沿著上右圖箭頭所指示的順序走訪即可完成此二元樹的中序走訪GDHBAIECF 為此二元樹的中序表示

bull 範例 5-17 中所加的引線目的在方便二元樹的中序走訪

bull 對樹葉節點 p 而言令其資料為 P 則引線指標的規則如下

(1) p 的左子樹指標指向中序表示中 P 的前一個 元素(前接元素 (predecessor) )

(2) p 的右子樹指標指向中序表示中 P 的後一個 元素(後繼元素 (successor) )

為了區別節點指標放的是一般指標(內部節點)抑或是引線指標(樹葉節點)我們另以 2 個欄位分別區別左和右子樹指標空間存放的對象其節點的記憶體配置有如下圖所示

其中 leftthread (rightthread) 為 0 時表示 leftchild (rightchild) 存放的是一般節點指標而 leftthread (rightthread) 為 1 時表示 leftchild (rightchild) 存放的是引線指標1 struct TBTreeNode2 int leftthread3 struct TBTreeNode leftchild4 char data5 struct TBTreeNode rightchild6 int rightthread7

由右側的引線二元樹可知有最左和最右兩條引線尚無妥善的安排我們另設計一空的引線節點做為樹根如下圖所示

571  引線二元樹的中序走訪決定節點 p 在中序表示中的後繼節點 q (簡稱中序後繼點)

程式 5-10決定節點 node的中序後繼點1 struct TBTreeNode2 int leftthread3 struct TBTreeNode leftchild4 char data5 struct TBTreeNode rightchild6 int rightthread7 8 struct TBTreeNode root9 struct TBTreeNode Next(struct TBTreeNode node)

10 struct TBTreeNode temp11 temp = node-gtrightchild12 if (node-gtrightthead) return temp13 while (temp-gtleftthead) temp=temp-gtleftchild

14 return temp15

程式 5-11引線二元樹的中序表示(接程式 5-10)1 void InorderTBTree()2 struct TBTreeNode node3 for(node=Next(root)node=rootnode=Next(node))

4 cout ltlt node-gtdata5

bull只要 node 是引線二元樹上的任一節點透過呼叫 Next(node) 即可找到其在中序表示中的後繼節點bull(試想若程式 5-10 中的程序 Next 所傳入的參數指標正是 root Next(root) 是否可正常運作請思考)bull於是只須從中序表示中的第一節點起讓每一節點 node 都執行 Next(node) 程序即可得引線二元樹的中序表示

572  在引線二元樹中加入節點在引線二元樹中樹葉節點節點的右子樹指標所指向或內部節點右子樹的最左樹葉為其中序後繼節點而且任一節點若無左子樹則其左子樹指標乃指向其中序前接節點hellip這些性質必須在節點新增進入引線二元樹時也要保持接下來討論節點 new 插入成為引線二元樹的節點 p 的右子節點的情形至於插入成為左子節點的情形則讓各位模擬推敲

572572  在引線二元樹中加入節點 在引線二元樹中加入節點 (( 續續 ))

newnew 會成為會成為 pp 的右子節點的情況共有以下兩種的右子節點的情況共有以下兩種(1) (1) 若若 pp 沒有右子節點 沒有右子節點 ((p-gtrightthread p-gtrightthread 為為1) 1) 如圖如圖 ((a) a) 則 則 new new 插入後應形成如圖插入後應形成如圖 ((b) b) 所示所示

(2)(2) 若若 pp 有右子節點 有右子節點 ((p-gtrightthread p-gtrightthread 為為0) 0) 如圖如圖 ((c) c) 則 則 new new 插入後應形成如插入後應形成如圖圖 ((d) d) 所示所示

程式 5-12  插入節點 new 成為引線二元樹中節點 p的右子節點(上接程式 5-10 5-11 )

1 void InsertRight(struct TBTreeNode p 2 struct TBTreeNode new)3 struct TBTreeNode q4 new-gtrightchild = p-gtrightchild5 new-gtrightthead = p-gtrightthread6 new-gtleftchild = p7 new-gtleftthread = 18 p-gtrightchild = new9 p-gtrightthead = 010 if (new-gtrightthread)11 q = Next(new)12 q-gtleftchild = new13 14

58  堆積bull 「堆積」 (heap) 是一個特殊的完備二元樹節點的資料與其子節點的資料之間有事先定好的大小關係存在bull 定義ndash最大堆疊為一完備二元樹任一節點的資料內容不小於其子節點的資料內容ndash最小堆疊為一完備二元樹任一節點的資料內容不大於其子節點的資料內容

581  插入節點至最大堆積中最大堆積是一完備二元樹所以如圖 (a) 之最大堆積於加入新節點後樹的形狀必須如圖 (b) 所示灰色節點即為新節點若加入的節點資料分別為 1 9 12 則其最大堆積將分別變成如圖 (c) (d) (e) 所示

程式 5-13 新增節點至最大堆積中最大堆積本身是一棵完備二元樹可以用陣列表示之 1 define maxsize 1002 int heap[maxsize]3 int n i4 void InsertHeap(int x)5 if (n == maxsize) HeapFull()6 else7 n++8 i = n9 while ((i gt 1) ampamp (x gt heap[i2]))10 heap[i] = heap[i2]11 i = 212 13 heap[i] = x14 16

程序 InsertHeap 在插入第 n 個資料時需要的時間複雜度為 O(logn)

1 100

2 93

3 84

4 77

5 65

6 81

7 79

8 66

9 70

10 50

100

84

77

93

8165

66 70

79

50

1

2 3

4 5 6 7

8 9 10

1 define maxsize 1002 int heap[maxsize]3 int n i4 void InsertHeap(int x)5 if (n == maxsize) HeapFull()6 else7 n++8 i = n9 while ((i gt 1) ampamp (x gt heap[i2]))10 heap[i] = heap[i2]11 i = 212 13 heap[i] = x14 16

582 刪除最大堆積中的最大資料圖 (a) 中的最大堆積在刪除最大資料後其樹形應變成如圖 (b) 所示將最未一個樹葉節點的資料 6 暫挪至樹根節點內如圖 (c) 再將圖 (c) 中非最大堆積的結構調整成為最大堆積 10 和 8 中選大者與 6對調即成圖 (d)

程式 5-14 刪除最大堆積中的最大資料(上接程式 5-13 )1 int DeleteHeap()2 int x i j3 if (n == 0) HeapEmpty()4 else5 x = heap[1]6 heap[1] = heap[n]7 n--8 i = 19 while (i lt= n2)10 if (heap[2i] gt heap[2i+1])11 j = 2i12 else13 j = 2i+114 if (heap[i]gtheap[j]) break15 swap(heap[i] heap[j])16 i = j17 18 return x19 20

其時間複雜度與其堆積高度相同是為O(logn) 其中 n 為堆積中的元素個數

1 100

2 93

3 84

4 77

5 65

6 81

7 79

8 66

9 70

10 50

100

84

77

93

8165

66 70

79

50

1

2 3

4 5 6 7

8 9 10

1 int DeletHeap()2 int x i j3 if(n == 0) HeapEmpty()4 else5 x = heap[1]6 heap[1] = heap[n]7 n--8 i = 19 while (i lt= n2)10 if ([heap[2i]] gt heap[2i+1])11 j = 2i12 else13 j = 2i+114 if (heap[i]gtheap[j]) break15 swap(heap[i] heap[j])16 i = j17 18 return x19 20

583  優先佇列bull 由於最大堆積(或最小堆積)的結構可提供最大元素(或最小元素)而其維護堆積的成本為 O(logn) n 為總元素個數

bull 堆積常被當成「優先佇列」 (priority queue) 使用mdash此時佇列被組織成完備二元樹若新加入佇列的資料有大小關係優先順序的需求時此優先佇列即以 O(logn) 的代價將新資料放入優先佇列(可能是最大或最小堆積依實際大小關係優先順序的需求而定)中mdash提供依元素的優先順序做為處理順序的服務

bull 一般的佇列為先進先出先到的先行處理優先佇列則是依據其優先順序優先順序高者先行處理與來到的先後無關

範例 5-21bull 在「分時」 (time sharing) 的作業系統中我們不希望使用中央處理器 (Central Processing Unit CPU) 時間短的「處理程序」 (process) 等候過久於是有「最短時間工作優先」 (shortest-job-first) 的排程策略此時等候的處理程序即可組織成之一最小堆積而 CPU 每次即取出存於樹根的處理程序予以處理任何新來的處理程序則以 O(logn) 的時間加入此優先佇列(最小堆積)中

bull 注意之前提過的線性佇列新來的直接加入成為新的尾端元素花O(1) 的時間然線性佇列本身沒有大小的關係每次要取出最大(小)的資料得花O(n) 的代價(在所有元素中挑出最大(小)者) n 為佇列內的元素個數而優先佇列是在新元素加入時要花O(logn) 的時間至於取出最大(小)則只須 O(1) 的時間

59  二元搜尋樹bull 二元搜尋樹是一棵二元樹可能是空二元樹若不為空二元樹則滿足下列性質ndash所有節點內的資料內容是相異的 ndash左子節點的資內容 ( 如果有 ) 比父節點的資料內容小 ndash右子節點的資料內容 ( 如果有 ) 比父節點的資料內容大 ndash以左和右子節點為樹根的左子樹和右子樹也是二元搜尋樹

在二元搜尋樹中搜尋資料二元搜尋樹對資料大小的掌握十分明確若恰與節點 p 資料相等則搜尋成功若欲搜尋的資料 x 比樹節點 p 中的資料小 則只須搜尋節點 p 的左子樹若比節點 p 中的資料大 則只須搜尋 p 的右子樹這樣的判斷可以遞迴地應用在各子樹節點 中

程式 5-15 在二元搜尋樹中搜尋資料(遞迴呼叫)1 struct BSTreeNode2 struct BSTreeNode leftchild3 int data4 struct BSTreeNode rightchild5 6 struct BSTreeNode root7 struct BSTreeNode SearchBST(

struct BSTreeNode tree int x)8 if (tree == NULL) return NULL9 if (x == tree-gtdata) return tree10 if (x lt tree-gtdata)11 return SearchBST(tree-gtleftchild x)

12 return SearchBST(tree-gtrightchild x)13

程式 5-16 搜尋二元搜尋樹的資料(非遞迴呼叫)在二元搜尋樹十分龐大時遞迴呼叫的效率不會太好可利用迴圈來完成搜尋的動作以提高執行效率請看程式 5-16

14 struct BSTreeNode Search BST_iterative15 (struct BSTreeNode node int x)16 while (node = NULL)17 if (x == node-gtdata) return node18 if (x lt node-gtdata)19 node = node-gtleftchild20 else21 node = node-gtrightchild22 23 return NULL24

592  新增資料於二元搜尋樹中欲新增資料進入一二元搜尋樹中得先決定該資料在二元搜尋樹中應出現的位置這可透過上節提到的搜尋動作來解決

在二元搜尋樹定義的性質 (1) 中我們限定了元素必須相異所以搜尋新資料的結果必是 NULL

必須將搜尋的結果改為新增資料所應在位置的節點指標

程式 5-17  在二元搜尋樹中新增資料25 int InsertBST(int x)26 struct BSTreeNode p q27 p = root q = NULL28 while (p = NULL)29 q = p30 if (x == p-gtdata) return 0

若不考慮重複則可省略之31 if (x lt p-gtdata)32 p = p-gtleftchild33 else34 p = p-gtrightchild35

3636 p = (struct BSTreeNode ) p = (struct BSTreeNode ) malloc (sizeof(BSTreeNode))malloc (sizeof(BSTreeNode))

3737 p-gtdata = x p-gtdata = x3838 p-gtleftchild = p-gtrightchild = p-gtleftchild = p-gtrightchild = NULLNULL3939 if (root == NULL) root = p if (root == NULL) root = p4040 else if (x lt q-gtdata) else if (x lt q-gtdata)4141 q-gtleftchild = p q-gtleftchild = p4242 else else4343 q-gtrightchild = p q-gtrightchild = p4444 return 1 return 145 45

程式 程式 5-175-17  在二元搜尋樹中新增資料  在二元搜尋樹中新增資料 (( 續續 ))

範例 5-23 圖 (b) 為圖 (a) 新增整數 6 之後的結果圖 (c) (d) (e) (f) 則分別是逐步再加入 28 18

10 21 的結果

bull若目前的二元搜尋樹高度為 h 則新增資料得花O(h) 的時間找到其所應在的位置遂新增資料至二元搜尋樹中的時間複雜之度為 O(h)

593 自二元搜尋樹中刪除資料欲自二元搜尋樹中刪除資料可能會面臨兩種情況(1) 刪除節點 p 位於樹葉節點上如下圖所示 這是比較容易處理的情況

(2) 刪除的節點 p 不是樹葉刪除節點 p 後原 p 所指的節點可由 q ( q 中包含了 p 左子樹中的最大節點值)或 r ( r 中包含了 p 右子樹中的最小節點值)來取代以維持二元搜尋樹中資料的大小關係其結果分別如圖 (b) 和 (c) 所示而圖 (d) 則標明了指標會改變的節點位置即 p 會消失 father 的左或右子樹指標會異動 k(可能是 q 或 r )會移到原來 p 的位置其父節點 f 的左或右子樹指標會異動其中 father 為 p 父節點 f 為 k 的父節點

程式 5-18 自二元搜尋樹中刪除資料46 int DeleteBST (int x)47 Struct BSTreeNode p father k f48 p = root father = NULL49 while (p = NULL)50 if (x == p-gtdata) 找到 x 所在的節點 p 了51 if ((p-gtleftchild == NULL) ampamp 52 (p-gtrightchild == NULL))53 k = NULL p為樹葉節點54 else if (p-gtleftchild = NULL )x位於非子節

點55 f = p 找出 p左子樹的最右樹葉56 k = p-gtleftchild57 while (k-gtrightchild =NULL)58 f = k59 k = k-gtrightchild60 k將挪至原 p處61 if (p == f)62 f-gtleftchild = k-gtleftchild 63 else64 f-gtrightchild = k-gtleftchild 65

66 else p無左子樹67 f = p 找出 p右子樹的最左樹葉68 k = p-gtrightchild69 while (k-gtleftchild = NULL)70 f = k71 k = k -gtleftchild72 73 if (p == f)74 f-gtrightchild = k-gtrightchild 75 else76 f-gtleftchild = k-gtrightchild77 78 if (k = NULL) k挪至原 p處繼承 p的左右指標79 k-gtleftchild = p-gtleftchild80 k-gtrightchild = p-gtrightchild81 82 if (father == NULL) root = k83 else if (x lt father-gtdata) 決定 k是 father的左

或右兒子84 father-gtleftchild = k85 else86 father-gtrightchild = k87 free(p)88 return 1 成功地刪除 x於此傳回 1返回呼叫處89 51~89行為找到 x時應有的處理步驟

90 else 尚未找到 x繼續往下一階層找

91 father = p92 if (x lt p-gtdata)93 p = p-gtleftchild94 else95 p = p-gtrightchild96 97 98 return 0 未找到 x傳回 099

510  樹林 (forest)

bull 定義樹林(森林)是由 n 棵沒有交集的樹所構成的集合 n 0

下圖為一包含三棵樹的樹林

5101 轉換樹林為二元樹 之前我們以二元樹來表示樹因為二元樹有較多

好用的性質在此我們也將樹林轉換成二元樹看待

在 522 節中我們嘗試了用左子右兄弟的方式來表示樹這個方法事實上已將樹轉換成二元樹

即用左子右兄弟的結構重新組織樹林中的每一棵樹這些樹的樹根將視為兄弟而最左邊的樹則視為樹林二元樹的樹根如此一來樹林即可轉換成二元樹

範例 5-25右下圖為右上圖三棵樹所對應的三棵左子右兄弟二元樹

下圖則為上圖樹林所對應的一棵樹林二元樹

演算法 5-1  轉換樹林成二元樹輸入樹林 F = T1 T2 Tn

輸出 F 的二元樹表示 BT=(T1 T2 Tn)1 if (n == 0) 輸出空二元樹 2 else

3 root(T1 T2 Tn) = T1

4 leftsubtree(T1 T2 Tn) =

BT(T11 T12 T1m)5 BT(T11 T12 T1m)為 T1的子樹6 rightsubtree(T1 T2 Tn) =

BT(T2 T3 Tn)

樹林轉換成二元樹的過程樹林轉換成二元樹的過程

  • Slide 1
  • Slide 2
  • Slide 3
  • Slide 4
  • Slide 5
  • Slide 6
  • Slide 7
  • Slide 8
  • Slide 9
  • Slide 10
  • Slide 11
  • Slide 12
  • Slide 13
  • Slide 14
  • Slide 15
  • Slide 16
  • Slide 17
  • Slide 18
  • Slide 19
  • Slide 20
  • Slide 21
  • Slide 22
  • Slide 23
  • Slide 24
  • Slide 25
  • Slide 26
  • Slide 27
  • Slide 28
  • Slide 29
  • Slide 30
  • Slide 31
  • Slide 32
  • Slide 33
  • Slide 34
  • Slide 35
  • Slide 36
  • Slide 37
  • Slide 38
  • Slide 39
  • Slide 40
  • Slide 41
  • Slide 42
  • Slide 43
  • Slide 44
  • Slide 45
  • Slide 46
  • Slide 47
  • Slide 48
  • Slide 49
  • Slide 50
  • Slide 51
  • Slide 52
  • Slide 53
  • Slide 54
  • Slide 55
  • Slide 56
  • Slide 57
  • Slide 58
  • Slide 59
  • Slide 60
  • Slide 61
  • Slide 62
  • Slide 63
  • Slide 64
  • Slide 65
  • Slide 66
  • Slide 67
  • Slide 68
  • Slide 69
  • Slide 70
  • Slide 71
  • Slide 72
  • Slide 73
  • Slide 74
  • Slide 75
  • Slide 76
  • Slide 77
  • Slide 78
  • Slide 79
  • Slide 80
  • Slide 81
  • Slide 82
  • Slide 83
  • Slide 84
  • Slide 85
  • Slide 86
  • Slide 87
  • Slide 88
  • Slide 89
  • Slide 90
  • Slide 91
  • Slide 92
  • Slide 93
  • Slide 94
  • Slide 95
  • Slide 96
  • Slide 97
  • Slide 98
  • Slide 99
  • Slide 100
  • Slide 101
  • Slide 102
  • Slide 103
Page 8: 第五章 樹

512  專有名詞 ( 續 )bull 兒子 (son) 任何節點 X 其子樹的樹根 Y 為 X 的

兒子bull 父親 (father parent) Y 是 X 的「父親」bull 兄弟 (sibling brother) 共有一個父親的數個節點bull 祖先 (ancestor) 該節點走向樹根所經過的所有節點bull 後裔 (descendant) 任一節點的所有子樹節點

52  樹的表示法bull 用陣列表示樹 bull 用鍵結串列表示樹 令分支度 =k

若 T 共有 n 個節點則需要 n 個鍵結節點計有 nk 個指標然而除了樹根外每個節點只須由唯一的指標所指向遂只需要有 n-1 個指標空間用不著的指標空間多達 nk-(n-1)=n(k-1)+1 個比用得著的還多著實浪費

521  一般化的串列表示bull 串列是一個有限且有順序的元素集合

A = (a1 a2 hellip an)

每個元素 ai 1in 皆有相同的資料型態 bull 倘若不限定各個元素都具有相同的資料型態則稱此串列為「一般化串列」 (generalized list) (ai 可以是一個節點或是一棵樹 )

bull 可用一般化串列來表示一棵樹 T T = (R T1 T2 hellip Tn)

其中 R 為 T 的樹根而 T1 T2 hellip Tn 為 R 的子樹 Ti 可能是一個節點也可能是一棵樹(一般化串列)

範例 5-1 以一般化串列表示樹bull (A (B (E K) (F L M) G) (C H) (D (I N) (J

O P)))

(A T1 T2 T3)T1 = (B (E K) (F L M) G)T2 = (C H)T3 = (D (I N) (J O P))

T1 T2T3

程式 5-1 一般化串列鍵結節點的宣告1 struct TreeNode2 int tag3 union4 int data5 struct TreeNode Tlink6 node7 struct TreeNode link8

(A T1 T2 T3)T1 = (B (E K) (F L M) G)T2 = (C H)T3 = (D (I N) (J O P))

522  左子右兄弟表示法

每個節點都有唯一的最左 兒子 (leftmost child) 每個節點都有最靠近它的 右兄弟

523 分支度為 2 的二元樹表示法bull 分支度為 2 的樹又稱為二元樹 (binary tree)

bull 二元樹中任一節點皆有 2個指標分別指向該節點的左子樹和右子樹

bull 二元樹可以表示任何樹事實上二元樹有許多很好的性質我們在一下節中介紹

bull注意樹狀結構中兒子的順序是不重要的試想樹可以樹根為軸心旋轉使得兒子的順序是不固定的

53  二元樹bull 二元樹是一個由節點組成的有限集合可以是空集合或是包含了(1) 一個樹根節點(2) 其它節點分割成兩個沒有交集的二元樹

其一為左子樹 (left subtree) 另一為右子樹 (right subtree) 樹和二元樹之間的差異

(1)樹不允許空節點而空二元樹是允許的

(2) 樹的子樹沒有順序而二元樹的子樹有左右之分

531  樹和二元樹的基本性質

樹或二元樹皆擁有下面的性質

定理 5-1 若一棵樹 T 的總節點數為 V 總邊數為

E 則V = E + 1

證明利用數學歸納法歸納法的基礎當 V=1 時即 T 只有一個樹根節點 E 為 0 所以 V = E + 1 成立歸納法的假設假設當 1 V K 時該式成立歸納法的推論當 V = K + 1 時令此樹的樹根為 R 而 R 有 m 個子樹 T1 T2 hellip

Tm (如圖 5-12 所示)分別有個 V1 V2 hellip Vm 個節點其中 V1 +V2 + hellip+ Vm =K 若 E1 E2 hellip Em 分別為此 m 個子樹的邊數則

E = E1 +E2 + hellip+ Em + m (1)根據歸納法的假設可知

V1 = E1+1V2 = E2+1 Vm = Em+1

所以V1 +V2 + hellip+Vm = E1 + E2 + hellip+ Em +m (2)

然而V = V1 +V2 + hellip+Vm+1 (3)

將式 (2) 代入式 (3) 可得V = E1 + E2 + hellip+ Em + m + 1

引用式 (1) 則可得V = E + 1

由數學歸納法的原理可得證本定理

532 二元樹的性質(a) 稱為歪斜樹 (skew tree) (b) 稱為完備二元樹 (complete binary tree)

( 其樹葉節點皆在相鄰階層完整定義在後 ) 同樣數目的樹節點存放在歪斜樹上會花較多的階層放在完備二元樹上則只須最少的階層

532 二元樹的性質 ( 續 )定理 5-2 (1) 二元樹上階層 i 的節點數目最多為 2i-1 i 1 (2) 深度為 k 的二元樹其節點數目最多為 2k-

1 k1 bull 定理 5-2說明了 (1) 每一階層上可放的最多節點數

(2) 階層數已知時二元樹可放的最多節點數證明 (1) 利用數學歸納法歸納法的基礎當 i=1 時只有樹根一個節點 2i-1=21-1=1 原式成

立歸納法的假設假設 i = k 時該式成立即階層 k 的最大節點數為

2k-1 k 1 歸納法的推論當 i = k+1 時因在階層 k 的每一個節點最多會有

2 個子節點遂在 k+1 階層最多會有 22k-1 = 2k

= 2i-1 節點由數學歸納法的原理可知本定理得證(2) 由 (1) 可知二元樹在階層 i 的最多節點數為 2i-1那麼深度 k 的二元樹最多可有節點如下

k

i

ki

1

1-1- 24212 1-2

)12(1 k= 2k-1

=

bull 對二元樹而言任一節點的分支度可能為 0 1 或 2 分支度為 0 者是為樹葉

bull 樹葉的數目與分支度為 2 的節點數目存在著有趣的關係請見定理 5-3

定理 5-3 若 T 為一非空的二元樹 n0 為樹葉節點數目 n2為分支度為 2 的節點數目則 n0 = n2+1

證明令 n 為所有節點數目而 n1 為分支度為 1 的節點數目可得 n = n0 + n1 +n2 (1)令 B 為二元樹的分支數除了樹根外每一節點皆有一分支指向之所以

n = B+1 (亦可參考定理 5-1 )又 B = n1+2n2

且 n = n1 + 2n2 +1 (2)由式 (1) 和式 (2) 我們得到

n0 = n2 + 1

完滿二元樹 (full binary tree)

定義一個深度為 k 的完滿二元樹即為一深度為 k 且有 2k-1 個節點的二元樹k 0

bull 由定理 5-2(2) 可知深度為 k 的二元樹其最多節點數為 2k-1 這樣的樹會將樹上所有可能存放節點的位置都放滿完滿 (full) 之名因而生成

完備二元樹 (complete binary tree)我們可對二元樹上節點予以編號階層小者先編同一階

層則自左向右編號這樣的編號方式使我們定義出完備(complete) 二元樹

定義深度為 k 節點數為 n 的二元樹稱為完備二元樹若且唯若 (1) k = 1 時樹有一個節點

(2) 當 k 2 且 1iltk 時深度 i 有 2i-1 個節點且第 k 層的節點皆由第 k-1 層的分支由左至右逐一連接而成

完滿或完備二元樹之所以受人注意乃因它們可以將節點以較少的階層數存放

定理 5-4 n 個節點的完備或完滿二元樹其深度為 log2(n+1)

證明 完滿或完備二元樹的節點皆自階層

1 起排放任一階層 i 可排放 2i-1

個節點遂由定理 5-2 得知此 n 個節點所佔的深度為 log2(n+1)

正規二元樹 (formal binary tree)bull 在二元樹中非樹葉的節點又稱為內部節點

(internal node) 若所有內部節點恰有 2 個子節點即成為正規二元樹

定義若二元樹中所有內部節點恰有 2 個子節點則稱之為正規二元樹

正規二元樹常被引用在單淘汰賽正規二元樹常被引用在單淘汰賽制的各項賽程安排樹葉為參賽制的各項賽程安排樹葉為參賽隊伍內部節點為優勝隊伍樹隊伍內部節點為優勝隊伍樹根即為冠軍隊伍根即為冠軍隊伍

在單淘汰賽制中自在單淘汰賽制中自 n n 個隊伍中產個隊伍中產生冠軍須舉辦 生冠軍須舉辦 nn-1-1場比賽場比賽

54  二元樹的表示法541 以陣列表示二元樹定理 5-5 若一個具有 n 個節點的完備二元樹以循序的方式編號並依序存放在陣列 A 中即第 i 個節點放在 A[i] 中 1 i n 則

(1) 節點 i 的父節點位在 A[i2]處 i ne 1( i = 1 時其節點正為樹根無父節點)

(2) 若 2i n 則節點 i 的左兒子節點位在 A[2i] 處若 2i gtn 節點 i 沒有左子節點

(3) 若 2i+1 n 節點 i 的右兒子節點位 A[2i+1]處若 2i+1gtn 節點 i 沒有左子

節點

證明先用數學歸納法證明 (2) bull 歸納法的基礎當 i = 1 時其左子節點確實在 A[2] 處(除非 nlt2 此時節點 1 沒有左子節點)

bull 歸納法的假設假設當 I = k 時其左子節點確在 A[2i] = A[2k] 處(除非 2i 已大於 n 此時節點 i 無左子節點)

bull 歸納法的推論當 i = k+1 時因第 k 節點的左子節點在 A[2k] 處其右子節點則位在 A[2k+1] 處那麼第 k+1 節點的左子節點應緊臨 A[2k+1] 亦即位於 A[2k+2] = A[2(k+1)] 處(除非 2i 已大於 n 此時節點 i 無左子節點)

bull 由數學歸納法的原理得證定理 5-5 (2) (3) 由於 (2) 成立而且節點 i 的右子節點會緊臨

A[2i] 亦即其位於 A[2i+1]處除非 i 根本沒有右子節點得證定理 5-5 (3)

由 (2) 和 (3) 我們可知定理 5-5 (1) 成立

542 以鍵結串列表示二元樹bull 以陣列來表示完備二元樹算是相當方便但ndash一般的二元樹將導致陣列空間的浪費ndash如果二元樹中節點的新增刪除動作頻繁則會造成陣列對應資料的搬動隨而頻繁

bull 以鍵結串列表示二元樹

為方便辦識樹根我們常用 root 指標指向樹根做為樹鍵結串列的起點

陣列表示和鍵結表示的空間需求bull 完備二元樹的陣列表示和鍵結表示皆沒浪費儲存資料的空間

bull 但歪斜樹的陣列表示比起鍵結表示則浪費許多

bull 以 n 個節點的歪斜樹而言陣列表示法可能需要 2n-1 個陣列元素其中 2n-1-n 個陣列元素空間是浪費的而鍵結表示法只需要 n 個節點空間

二元樹節點的宣告程式 5-2

1 struct BTreeNode

2 struct BtreeNode leftchiled

3 int data

4 struct BTreeNode rightchild

5

6 struct BTreeNode root

bull 這樣的二元樹節點要取得兒子節點的指標十分方便但若有取得父節點的需求則嫌不足我們可以加上 parent 欄位指向父節點

55  二元樹的走訪bull 假如一組資料已用二元樹的組織在一起總有需求對其全數資料做動作例如計算所有數目印出所有資料在所有資料中搜尋某項資料hellip等此時即須對此二元樹做走訪 (traversal) 的運算利用走訪二元樹的同時將計算列印或搜尋的動作完成

bull 事實上走訪即在決定二元樹上資料被處理(計算列印或搜尋)的順序我們也希望走訪的演算法對任何節點皆一致是容易撰寫程式實作的

bull 若對一個二元樹上的節點而言 V 表示處理節點上的資料 L 表示走訪其左子樹 R 表示走訪其右子樹

55  二元樹的走訪 ( 續 )bull 因為對稱的緣故我們只考慮先走訪左邊再走訪右邊的情形那麼右圖只剩下三種走訪方式

bull 以 V 所在的相對位置分別對此三種走訪方式取名如下

(1) LVR 中序走訪 (inorder traversal)或中序表示法 (infix notation)

(2) LRV 後序走訪 (postorder traversal) 或後序表示法 (postfix notation)

(3) VLR 前序走訪 (preorder traversal)或前序表示法 (prefix notation)

551  中序走訪bull 利用遞迴的方式撰寫中序走訪的程序希望把二元樹中序走訪的

順序印出來

程式 5-3二元樹的中序走訪1 struct BTreeNode2 struct BTreeNode leftchild3 char data4 struct BTreeNode rightchild5 6 struct BTreeNode root7 void inorder(struct BTreeNode node)8 if (node = NULL)9 inorder(node-gtleftchild)10 cout ltlt node-gtdata11 inorder(node-gtrightchild)12 13

範例 5-12此為運算式二元樹

其對應中序表示運算式為 (A+B)C+D(E-F)

6 struct BTreeNode root7 void inorder(struct BTreeNode node)8 if (node = NULL)9 inorder(node-gtleftchild)10 cout ltlt node-gtdata11 inorder(node-gtrightchild)12 13

6 struct BTreeNode root7 void inorder(struct BTreeNode node)8 if (node = NULL)9 inorder(node-gtleftchild)10 cout ltlt node-gtdata11 inorder(node-gtrightchild)12 13

(A+B)C+D(E-F)

如何加上 ( )

552  後序走訪程式 5-4二元樹的後序走訪14 void postorder(struct BTreeNode node)

15 if (node =NULL)16 postorder(node-gtleftchild)17 postorder(node-gtrightchild)18 cout ltlt node-gtdata19 20

以後序走訪可得到AB+CDEF-+

其正為對應的後序運算式

553  前序走訪程式 5-5二元樹的前序走訪21 void preorder(struct BTreeNode node)

22 if (node = NULL)

23 cout ltlt node-gtdata

24 preorder(node-gtleftchild)

25 preorder(node-gtrightchild)

26

27

以前序走訪可得到++ABCD-EF

其正為對應的前序運算式

554 利用堆疊和迴圈取代遞迴進行二元樹的走訪1 struct BTreeNode 2 struct BTreeNode leftchild3 char data4 struct BtreeNode rightchild5 6 struct BTreeNode root7 struct StackNode8 struct BTreeNode treenode9 struct StackNode next1011 struct StackNode top

12 void push_node(struct BtreeNode node )13 struct StackNode old_top14 old_top = top15 top = (struct StackNode ) malloc

(sizeof(struct StackNode))16 top-gttreenode = node17 top-gtnext = old_top18 19 struct BTreeNode pop_node()20 struct BTreeNode Tnode21 struct StackNode old_top22 if (top == NULL)23 StackEmpty()24 else25 Tnode = top-gttreenode26 old_top = top27 top = top-gtnext28 free(old_top)29 return Tnode30 31

32 void inorder_Stack(struct BtreeNode node)33 do34 while (node = NULL)35 push_node(node)36 node = node-gtleftchild37 push all left children38 if (top = NULL)39 node = pop_node()40 cout ltlt node-gtdata41 node = node-gtrightchild42 inorder printing and check right child43 while ((top=NULL)||(node=NULL))44 top==NULL 堆疊已空 node==NULL沒有右子

6 struct BTreeNode root7 void inorder(struct BTreeNode node)8 if (node = NULL)9 inorder(node-gtleftchild)10 cout ltlt node-gtdata11 inorder(node-gtrightchild)12 13

中序表示為DBGEHAFIC

32 void inorder_Stack()33 struct BTreeNode node34 node = root35 do36 while (node = NULL)37 push_node(node)38 node = node-gtleftchild39 40 if (top = NULL)41 node = pop_node()42 cout ltlt node-gtdata43 node = node-gtrightchild44 45 while(top=NULL)||(node=NULL))46

45 void Preorder_Stack(struct BSTreeNode node)46 do47 while (node = NULL)48 cout ltlt node-gtdata)49 push_node(node)50 node = node-gtleftchild51 push all left children52 if (top = NULL)53 node = pop_node()54 node = node-gtrightchild55 56 while ((top=NULL)||(node=NULL))57

程式 5-5二元樹的前序走訪21 void preorder(struct BTreeNode node)22 if (node = NULL)23 cout ltlt node-gtdata24 preorder(node-gtleftchild)25 preorder(node-gtrightchild)26 27

58 void Postorder_Stack(struct BTreeNode node)59 do60 while (node = NULL)61 cout ltlt node-gtdata62 push_node(node)63 node = node-gtrightchild64 push all left children65 if (top = NULL)66 node = pop_node()67 node = node-gtleftchild68 69 while ((top=NULL)||(node=NULL))70

程式 5-4二元樹的後序走訪14 void postorder(struct BTreeNode node)15 if (node =NULL)16 postorder(node-gtleftchild)17 postorder(node-gtrightchild)18 cout ltlt node-gtdata19 20

555  階層走訪bull 「階層走訪」 (level-order traversal) 是依階層

的順序進行二元樹的走訪先走訪階層小的節點後走訪階層大的節點同一階層者則依自左向右的順序走訪

bull 對下圖的二元樹進行階層走訪的結果為ABCDEFGHI

bull 對同一階層而言先走訪的節點其子節點亦在下一階層中先被走訪

bull 這種「先進先出」的特性恰可用佇列予以儲存與處理

程式 5-7 利用佇列進行二元樹的階層走訪

1 struct BTreeNode2 struct BTreeNode leftchild3 char data4 struct BTreeNode rightchild5 6 struct BTreeNode root7 struct QNode8 struct BTreeNode treenode9 struct QNode next10 11 struct QNode front rear

12 void AddQueue(struct BTreeNode Tnode)13 struct QNode node14 node=(struct QNode )malloc(sizeof(struct QNode))15 node-gttreenode = Tnode16 node-gtnext = NULL17 if (front == NULL) front = node19 else rear-gtnext = node20 rear = node21

22 struct BTreeNode DeleteQueue()23 struct BTreeNode Tnode24 struct QNode old_front25 if (rear == NULL)26 QueueEmpty()27 else28 Tnode = front-gttreenode29 old_front = front30 front = front-gtnext31 free(old_front)32 return Tnode33 34 35 void LevelOrder (struct BTreeNode node)36 AddQueue(node)37 while (front =NULL)38 node = DeleteQueue()39 cout ltlt node-gtdata40 if (node-gtleftchild = NULL)41 AddQueue(node-gtleftchild)42 if (node-gtrightchild = NULL)43 AddQueue(node-gtrightchild)44 45

35 void LevelOrder (struct BTreeNode node)36 AddQueue(node)37 while (front =NULL)38 node = DeleteQueue()39 cout ltlt node-gtdata40 if (node-gtleftchild = NULL)41 AddQueue(node-gtleftchild)42 if (node-gtrightchild = NULL)43 AddQueue(node-gtrightchild)44 45

bull Infix ABCbull Postfix ABC CBA ACB BCA

BAC CAB

B

A CA

B

C

B

A

C

C

B

AB

A

C

二元樹中序與前序式中序與後序式bull 每一棵二元樹皆有唯一的一對中序與前序表示法也有唯一的中序與後序次序

bull 換句話說給予一對中序與前序或中序與後序即可決定一棵二元樹

bull 然而給予前序和後序次序並不能決定唯一的二元樹可能會產生兩棵不同的二元樹

bull 想想階層走訪和中序走訪順序可否決定唯一的二元樹

A

B C

H J D E

F G

prefix ABHJCDFGEinfix HBJAFDGCEpostfix HJBFGDECA

VLRLVRLRV

A

prefix BHJinfix HBJ

prefix CDFGEinfix FDGCE

prefix ABHJCDFGEinfix HBJAFDGCE

A

B C

H J D E

F G

prefix ABHJCDFGE

infix HBJAFDGCEk-1

struct BTreeNode BTree_InfixPrefix (AnsiString infix AnsiString prefix)

int k struct BTreeNode node if (infixLength==0) return NULLnode = (struct BTreeNode ) malloc (sizeof(struct BTreeNode))node-gtdata = prefix[1]k = find(prefix[1] infix)node-gtleftchild = BTree_InfixPrefix

(infix[1k-1] prefix[2k])node-gtrightchild = BTree_InfixPrefix(infix[k+1infixLength]prefix[k+1prefixLength])

VLR

LVRk1

k2 k+1~

k+1~

1

struct BTreeNode BTree_InfixPostfix (AnsiString infix AnsiString prefix)

int k struct BTreeNode node if (infixLength==0) return NULLnode = (struct BTreeNode ) malloc (sizeof(struct BTreeNode))node-gtdata = postfix[postfixLength]k = find(postfix[postfixLength] infix)node-gtleftchild = BTree_InfixPrefix

(infix[1k-1] postfix[1k-1])node-gtrightchild = BTree_InfixPrefix(infix[k+1infixLength]postfix[k+1postfixLength-1])

56  二元樹的複製與相等測試程式 5-8 二元樹的複製1 struct BTreeNode2 struct BTreeNode leftchild3 char data4 struct BTreeNode rightchild5 6 struct BTreeNode root7 struct BTreeNode Copy(struct BTreeNode TreeRoot)8 struct BTreeNode CopyRoot9 if (TreeRoot == NULL)10 return NULL11 CopyRoot =(struct BTreeNode ) malloc (sizeof(BTreeNode))12 CopyRoot-gtdata = TreeRoot-gtdata13 CopyRoot-gtleftchild = Copy(TreeRoot-gtleftchild)14 CopyRoot-gtrightchild = Copy(TreeRoot-

gtrightchild)15 return CopyRoot16

程式 5-9 檢測兩棵二元樹是否相等1 struct BTreeNode2 struct BTreeNode leftchild3 char data4 struct BTreeNode rightchild5 6 struct BtreeNode rightchild7 int Equal(struct BTreeNode ustruct BTreeNode v)8 if ((u==NULL) ampamp (v==NULL)) return 19 if ((u=NULL) ampamp (v=NULL)10 ampamp (u-gtdata == v-gtdata )11 ampamp Equal(u-gtleftchildv-gtleftchild)12 ampamp Equal(u-gtrightchildv-gtrightchild))

13 return 114 return 015

Counting Nodes in a Binary Tree

int CountingNodes(struct BTreeNodes node)

if (node == NULL) return 0 int leftchildren = CountingNodes(node-gtleftchild)

int rightchildren= CountingNodes(node-gtrightchild)

return leftchildren+rightchildren

Counting Leaves in a Binary Tree

int CountingLeaves(struct BTreeNodes node)

if (node == NULL) return 0 int leftleaves = CountingLeaves(node-gtleftchild)

int rightchildren= CountingLeaves(node-gtrightchild)

if (leftleaves==0 ampamp leftleaves==0) return 1

return leftleaves + leftleaves

57  引線二元樹bull 在二元樹的鍵結表示中樹葉節點的兩個子樹指標皆指向 NULL 由定理 5-1 得知 n = E+1 其中 n 為節點個數 E 為分支數

bull 而 n 個鍵結節點有 2n 個指標空間每個分支恰佔用一個指標空間共有 E (=n-1) 個指標有用到(不是空的)而共有 n+1 個指標空間是 NULL 比非空的節點還多

bull 有學者提出對這些「放空指標的空間」加以利用的概念mdash與其放空指標不如放指向其它節點的指標稱之為引線 (thread) 使得某些運算(如走訪hellip等)可以加快

bull 這個概念發展出了引線二元樹 (threaded binary tree) 這種資料結構

範例 5-17下圖為加了引線的二元樹

加入的引線將使二元樹的中序走訪更加便利沿著上右圖箭頭所指示的順序走訪即可完成此二元樹的中序走訪GDHBAIECF 為此二元樹的中序表示

bull 範例 5-17 中所加的引線目的在方便二元樹的中序走訪

bull 對樹葉節點 p 而言令其資料為 P 則引線指標的規則如下

(1) p 的左子樹指標指向中序表示中 P 的前一個 元素(前接元素 (predecessor) )

(2) p 的右子樹指標指向中序表示中 P 的後一個 元素(後繼元素 (successor) )

為了區別節點指標放的是一般指標(內部節點)抑或是引線指標(樹葉節點)我們另以 2 個欄位分別區別左和右子樹指標空間存放的對象其節點的記憶體配置有如下圖所示

其中 leftthread (rightthread) 為 0 時表示 leftchild (rightchild) 存放的是一般節點指標而 leftthread (rightthread) 為 1 時表示 leftchild (rightchild) 存放的是引線指標1 struct TBTreeNode2 int leftthread3 struct TBTreeNode leftchild4 char data5 struct TBTreeNode rightchild6 int rightthread7

由右側的引線二元樹可知有最左和最右兩條引線尚無妥善的安排我們另設計一空的引線節點做為樹根如下圖所示

571  引線二元樹的中序走訪決定節點 p 在中序表示中的後繼節點 q (簡稱中序後繼點)

程式 5-10決定節點 node的中序後繼點1 struct TBTreeNode2 int leftthread3 struct TBTreeNode leftchild4 char data5 struct TBTreeNode rightchild6 int rightthread7 8 struct TBTreeNode root9 struct TBTreeNode Next(struct TBTreeNode node)

10 struct TBTreeNode temp11 temp = node-gtrightchild12 if (node-gtrightthead) return temp13 while (temp-gtleftthead) temp=temp-gtleftchild

14 return temp15

程式 5-11引線二元樹的中序表示(接程式 5-10)1 void InorderTBTree()2 struct TBTreeNode node3 for(node=Next(root)node=rootnode=Next(node))

4 cout ltlt node-gtdata5

bull只要 node 是引線二元樹上的任一節點透過呼叫 Next(node) 即可找到其在中序表示中的後繼節點bull(試想若程式 5-10 中的程序 Next 所傳入的參數指標正是 root Next(root) 是否可正常運作請思考)bull於是只須從中序表示中的第一節點起讓每一節點 node 都執行 Next(node) 程序即可得引線二元樹的中序表示

572  在引線二元樹中加入節點在引線二元樹中樹葉節點節點的右子樹指標所指向或內部節點右子樹的最左樹葉為其中序後繼節點而且任一節點若無左子樹則其左子樹指標乃指向其中序前接節點hellip這些性質必須在節點新增進入引線二元樹時也要保持接下來討論節點 new 插入成為引線二元樹的節點 p 的右子節點的情形至於插入成為左子節點的情形則讓各位模擬推敲

572572  在引線二元樹中加入節點 在引線二元樹中加入節點 (( 續續 ))

newnew 會成為會成為 pp 的右子節點的情況共有以下兩種的右子節點的情況共有以下兩種(1) (1) 若若 pp 沒有右子節點 沒有右子節點 ((p-gtrightthread p-gtrightthread 為為1) 1) 如圖如圖 ((a) a) 則 則 new new 插入後應形成如圖插入後應形成如圖 ((b) b) 所示所示

(2)(2) 若若 pp 有右子節點 有右子節點 ((p-gtrightthread p-gtrightthread 為為0) 0) 如圖如圖 ((c) c) 則 則 new new 插入後應形成如插入後應形成如圖圖 ((d) d) 所示所示

程式 5-12  插入節點 new 成為引線二元樹中節點 p的右子節點(上接程式 5-10 5-11 )

1 void InsertRight(struct TBTreeNode p 2 struct TBTreeNode new)3 struct TBTreeNode q4 new-gtrightchild = p-gtrightchild5 new-gtrightthead = p-gtrightthread6 new-gtleftchild = p7 new-gtleftthread = 18 p-gtrightchild = new9 p-gtrightthead = 010 if (new-gtrightthread)11 q = Next(new)12 q-gtleftchild = new13 14

58  堆積bull 「堆積」 (heap) 是一個特殊的完備二元樹節點的資料與其子節點的資料之間有事先定好的大小關係存在bull 定義ndash最大堆疊為一完備二元樹任一節點的資料內容不小於其子節點的資料內容ndash最小堆疊為一完備二元樹任一節點的資料內容不大於其子節點的資料內容

581  插入節點至最大堆積中最大堆積是一完備二元樹所以如圖 (a) 之最大堆積於加入新節點後樹的形狀必須如圖 (b) 所示灰色節點即為新節點若加入的節點資料分別為 1 9 12 則其最大堆積將分別變成如圖 (c) (d) (e) 所示

程式 5-13 新增節點至最大堆積中最大堆積本身是一棵完備二元樹可以用陣列表示之 1 define maxsize 1002 int heap[maxsize]3 int n i4 void InsertHeap(int x)5 if (n == maxsize) HeapFull()6 else7 n++8 i = n9 while ((i gt 1) ampamp (x gt heap[i2]))10 heap[i] = heap[i2]11 i = 212 13 heap[i] = x14 16

程序 InsertHeap 在插入第 n 個資料時需要的時間複雜度為 O(logn)

1 100

2 93

3 84

4 77

5 65

6 81

7 79

8 66

9 70

10 50

100

84

77

93

8165

66 70

79

50

1

2 3

4 5 6 7

8 9 10

1 define maxsize 1002 int heap[maxsize]3 int n i4 void InsertHeap(int x)5 if (n == maxsize) HeapFull()6 else7 n++8 i = n9 while ((i gt 1) ampamp (x gt heap[i2]))10 heap[i] = heap[i2]11 i = 212 13 heap[i] = x14 16

582 刪除最大堆積中的最大資料圖 (a) 中的最大堆積在刪除最大資料後其樹形應變成如圖 (b) 所示將最未一個樹葉節點的資料 6 暫挪至樹根節點內如圖 (c) 再將圖 (c) 中非最大堆積的結構調整成為最大堆積 10 和 8 中選大者與 6對調即成圖 (d)

程式 5-14 刪除最大堆積中的最大資料(上接程式 5-13 )1 int DeleteHeap()2 int x i j3 if (n == 0) HeapEmpty()4 else5 x = heap[1]6 heap[1] = heap[n]7 n--8 i = 19 while (i lt= n2)10 if (heap[2i] gt heap[2i+1])11 j = 2i12 else13 j = 2i+114 if (heap[i]gtheap[j]) break15 swap(heap[i] heap[j])16 i = j17 18 return x19 20

其時間複雜度與其堆積高度相同是為O(logn) 其中 n 為堆積中的元素個數

1 100

2 93

3 84

4 77

5 65

6 81

7 79

8 66

9 70

10 50

100

84

77

93

8165

66 70

79

50

1

2 3

4 5 6 7

8 9 10

1 int DeletHeap()2 int x i j3 if(n == 0) HeapEmpty()4 else5 x = heap[1]6 heap[1] = heap[n]7 n--8 i = 19 while (i lt= n2)10 if ([heap[2i]] gt heap[2i+1])11 j = 2i12 else13 j = 2i+114 if (heap[i]gtheap[j]) break15 swap(heap[i] heap[j])16 i = j17 18 return x19 20

583  優先佇列bull 由於最大堆積(或最小堆積)的結構可提供最大元素(或最小元素)而其維護堆積的成本為 O(logn) n 為總元素個數

bull 堆積常被當成「優先佇列」 (priority queue) 使用mdash此時佇列被組織成完備二元樹若新加入佇列的資料有大小關係優先順序的需求時此優先佇列即以 O(logn) 的代價將新資料放入優先佇列(可能是最大或最小堆積依實際大小關係優先順序的需求而定)中mdash提供依元素的優先順序做為處理順序的服務

bull 一般的佇列為先進先出先到的先行處理優先佇列則是依據其優先順序優先順序高者先行處理與來到的先後無關

範例 5-21bull 在「分時」 (time sharing) 的作業系統中我們不希望使用中央處理器 (Central Processing Unit CPU) 時間短的「處理程序」 (process) 等候過久於是有「最短時間工作優先」 (shortest-job-first) 的排程策略此時等候的處理程序即可組織成之一最小堆積而 CPU 每次即取出存於樹根的處理程序予以處理任何新來的處理程序則以 O(logn) 的時間加入此優先佇列(最小堆積)中

bull 注意之前提過的線性佇列新來的直接加入成為新的尾端元素花O(1) 的時間然線性佇列本身沒有大小的關係每次要取出最大(小)的資料得花O(n) 的代價(在所有元素中挑出最大(小)者) n 為佇列內的元素個數而優先佇列是在新元素加入時要花O(logn) 的時間至於取出最大(小)則只須 O(1) 的時間

59  二元搜尋樹bull 二元搜尋樹是一棵二元樹可能是空二元樹若不為空二元樹則滿足下列性質ndash所有節點內的資料內容是相異的 ndash左子節點的資內容 ( 如果有 ) 比父節點的資料內容小 ndash右子節點的資料內容 ( 如果有 ) 比父節點的資料內容大 ndash以左和右子節點為樹根的左子樹和右子樹也是二元搜尋樹

在二元搜尋樹中搜尋資料二元搜尋樹對資料大小的掌握十分明確若恰與節點 p 資料相等則搜尋成功若欲搜尋的資料 x 比樹節點 p 中的資料小 則只須搜尋節點 p 的左子樹若比節點 p 中的資料大 則只須搜尋 p 的右子樹這樣的判斷可以遞迴地應用在各子樹節點 中

程式 5-15 在二元搜尋樹中搜尋資料(遞迴呼叫)1 struct BSTreeNode2 struct BSTreeNode leftchild3 int data4 struct BSTreeNode rightchild5 6 struct BSTreeNode root7 struct BSTreeNode SearchBST(

struct BSTreeNode tree int x)8 if (tree == NULL) return NULL9 if (x == tree-gtdata) return tree10 if (x lt tree-gtdata)11 return SearchBST(tree-gtleftchild x)

12 return SearchBST(tree-gtrightchild x)13

程式 5-16 搜尋二元搜尋樹的資料(非遞迴呼叫)在二元搜尋樹十分龐大時遞迴呼叫的效率不會太好可利用迴圈來完成搜尋的動作以提高執行效率請看程式 5-16

14 struct BSTreeNode Search BST_iterative15 (struct BSTreeNode node int x)16 while (node = NULL)17 if (x == node-gtdata) return node18 if (x lt node-gtdata)19 node = node-gtleftchild20 else21 node = node-gtrightchild22 23 return NULL24

592  新增資料於二元搜尋樹中欲新增資料進入一二元搜尋樹中得先決定該資料在二元搜尋樹中應出現的位置這可透過上節提到的搜尋動作來解決

在二元搜尋樹定義的性質 (1) 中我們限定了元素必須相異所以搜尋新資料的結果必是 NULL

必須將搜尋的結果改為新增資料所應在位置的節點指標

程式 5-17  在二元搜尋樹中新增資料25 int InsertBST(int x)26 struct BSTreeNode p q27 p = root q = NULL28 while (p = NULL)29 q = p30 if (x == p-gtdata) return 0

若不考慮重複則可省略之31 if (x lt p-gtdata)32 p = p-gtleftchild33 else34 p = p-gtrightchild35

3636 p = (struct BSTreeNode ) p = (struct BSTreeNode ) malloc (sizeof(BSTreeNode))malloc (sizeof(BSTreeNode))

3737 p-gtdata = x p-gtdata = x3838 p-gtleftchild = p-gtrightchild = p-gtleftchild = p-gtrightchild = NULLNULL3939 if (root == NULL) root = p if (root == NULL) root = p4040 else if (x lt q-gtdata) else if (x lt q-gtdata)4141 q-gtleftchild = p q-gtleftchild = p4242 else else4343 q-gtrightchild = p q-gtrightchild = p4444 return 1 return 145 45

程式 程式 5-175-17  在二元搜尋樹中新增資料  在二元搜尋樹中新增資料 (( 續續 ))

範例 5-23 圖 (b) 為圖 (a) 新增整數 6 之後的結果圖 (c) (d) (e) (f) 則分別是逐步再加入 28 18

10 21 的結果

bull若目前的二元搜尋樹高度為 h 則新增資料得花O(h) 的時間找到其所應在的位置遂新增資料至二元搜尋樹中的時間複雜之度為 O(h)

593 自二元搜尋樹中刪除資料欲自二元搜尋樹中刪除資料可能會面臨兩種情況(1) 刪除節點 p 位於樹葉節點上如下圖所示 這是比較容易處理的情況

(2) 刪除的節點 p 不是樹葉刪除節點 p 後原 p 所指的節點可由 q ( q 中包含了 p 左子樹中的最大節點值)或 r ( r 中包含了 p 右子樹中的最小節點值)來取代以維持二元搜尋樹中資料的大小關係其結果分別如圖 (b) 和 (c) 所示而圖 (d) 則標明了指標會改變的節點位置即 p 會消失 father 的左或右子樹指標會異動 k(可能是 q 或 r )會移到原來 p 的位置其父節點 f 的左或右子樹指標會異動其中 father 為 p 父節點 f 為 k 的父節點

程式 5-18 自二元搜尋樹中刪除資料46 int DeleteBST (int x)47 Struct BSTreeNode p father k f48 p = root father = NULL49 while (p = NULL)50 if (x == p-gtdata) 找到 x 所在的節點 p 了51 if ((p-gtleftchild == NULL) ampamp 52 (p-gtrightchild == NULL))53 k = NULL p為樹葉節點54 else if (p-gtleftchild = NULL )x位於非子節

點55 f = p 找出 p左子樹的最右樹葉56 k = p-gtleftchild57 while (k-gtrightchild =NULL)58 f = k59 k = k-gtrightchild60 k將挪至原 p處61 if (p == f)62 f-gtleftchild = k-gtleftchild 63 else64 f-gtrightchild = k-gtleftchild 65

66 else p無左子樹67 f = p 找出 p右子樹的最左樹葉68 k = p-gtrightchild69 while (k-gtleftchild = NULL)70 f = k71 k = k -gtleftchild72 73 if (p == f)74 f-gtrightchild = k-gtrightchild 75 else76 f-gtleftchild = k-gtrightchild77 78 if (k = NULL) k挪至原 p處繼承 p的左右指標79 k-gtleftchild = p-gtleftchild80 k-gtrightchild = p-gtrightchild81 82 if (father == NULL) root = k83 else if (x lt father-gtdata) 決定 k是 father的左

或右兒子84 father-gtleftchild = k85 else86 father-gtrightchild = k87 free(p)88 return 1 成功地刪除 x於此傳回 1返回呼叫處89 51~89行為找到 x時應有的處理步驟

90 else 尚未找到 x繼續往下一階層找

91 father = p92 if (x lt p-gtdata)93 p = p-gtleftchild94 else95 p = p-gtrightchild96 97 98 return 0 未找到 x傳回 099

510  樹林 (forest)

bull 定義樹林(森林)是由 n 棵沒有交集的樹所構成的集合 n 0

下圖為一包含三棵樹的樹林

5101 轉換樹林為二元樹 之前我們以二元樹來表示樹因為二元樹有較多

好用的性質在此我們也將樹林轉換成二元樹看待

在 522 節中我們嘗試了用左子右兄弟的方式來表示樹這個方法事實上已將樹轉換成二元樹

即用左子右兄弟的結構重新組織樹林中的每一棵樹這些樹的樹根將視為兄弟而最左邊的樹則視為樹林二元樹的樹根如此一來樹林即可轉換成二元樹

範例 5-25右下圖為右上圖三棵樹所對應的三棵左子右兄弟二元樹

下圖則為上圖樹林所對應的一棵樹林二元樹

演算法 5-1  轉換樹林成二元樹輸入樹林 F = T1 T2 Tn

輸出 F 的二元樹表示 BT=(T1 T2 Tn)1 if (n == 0) 輸出空二元樹 2 else

3 root(T1 T2 Tn) = T1

4 leftsubtree(T1 T2 Tn) =

BT(T11 T12 T1m)5 BT(T11 T12 T1m)為 T1的子樹6 rightsubtree(T1 T2 Tn) =

BT(T2 T3 Tn)

樹林轉換成二元樹的過程樹林轉換成二元樹的過程

  • Slide 1
  • Slide 2
  • Slide 3
  • Slide 4
  • Slide 5
  • Slide 6
  • Slide 7
  • Slide 8
  • Slide 9
  • Slide 10
  • Slide 11
  • Slide 12
  • Slide 13
  • Slide 14
  • Slide 15
  • Slide 16
  • Slide 17
  • Slide 18
  • Slide 19
  • Slide 20
  • Slide 21
  • Slide 22
  • Slide 23
  • Slide 24
  • Slide 25
  • Slide 26
  • Slide 27
  • Slide 28
  • Slide 29
  • Slide 30
  • Slide 31
  • Slide 32
  • Slide 33
  • Slide 34
  • Slide 35
  • Slide 36
  • Slide 37
  • Slide 38
  • Slide 39
  • Slide 40
  • Slide 41
  • Slide 42
  • Slide 43
  • Slide 44
  • Slide 45
  • Slide 46
  • Slide 47
  • Slide 48
  • Slide 49
  • Slide 50
  • Slide 51
  • Slide 52
  • Slide 53
  • Slide 54
  • Slide 55
  • Slide 56
  • Slide 57
  • Slide 58
  • Slide 59
  • Slide 60
  • Slide 61
  • Slide 62
  • Slide 63
  • Slide 64
  • Slide 65
  • Slide 66
  • Slide 67
  • Slide 68
  • Slide 69
  • Slide 70
  • Slide 71
  • Slide 72
  • Slide 73
  • Slide 74
  • Slide 75
  • Slide 76
  • Slide 77
  • Slide 78
  • Slide 79
  • Slide 80
  • Slide 81
  • Slide 82
  • Slide 83
  • Slide 84
  • Slide 85
  • Slide 86
  • Slide 87
  • Slide 88
  • Slide 89
  • Slide 90
  • Slide 91
  • Slide 92
  • Slide 93
  • Slide 94
  • Slide 95
  • Slide 96
  • Slide 97
  • Slide 98
  • Slide 99
  • Slide 100
  • Slide 101
  • Slide 102
  • Slide 103
Page 9: 第五章 樹

52  樹的表示法bull 用陣列表示樹 bull 用鍵結串列表示樹 令分支度 =k

若 T 共有 n 個節點則需要 n 個鍵結節點計有 nk 個指標然而除了樹根外每個節點只須由唯一的指標所指向遂只需要有 n-1 個指標空間用不著的指標空間多達 nk-(n-1)=n(k-1)+1 個比用得著的還多著實浪費

521  一般化的串列表示bull 串列是一個有限且有順序的元素集合

A = (a1 a2 hellip an)

每個元素 ai 1in 皆有相同的資料型態 bull 倘若不限定各個元素都具有相同的資料型態則稱此串列為「一般化串列」 (generalized list) (ai 可以是一個節點或是一棵樹 )

bull 可用一般化串列來表示一棵樹 T T = (R T1 T2 hellip Tn)

其中 R 為 T 的樹根而 T1 T2 hellip Tn 為 R 的子樹 Ti 可能是一個節點也可能是一棵樹(一般化串列)

範例 5-1 以一般化串列表示樹bull (A (B (E K) (F L M) G) (C H) (D (I N) (J

O P)))

(A T1 T2 T3)T1 = (B (E K) (F L M) G)T2 = (C H)T3 = (D (I N) (J O P))

T1 T2T3

程式 5-1 一般化串列鍵結節點的宣告1 struct TreeNode2 int tag3 union4 int data5 struct TreeNode Tlink6 node7 struct TreeNode link8

(A T1 T2 T3)T1 = (B (E K) (F L M) G)T2 = (C H)T3 = (D (I N) (J O P))

522  左子右兄弟表示法

每個節點都有唯一的最左 兒子 (leftmost child) 每個節點都有最靠近它的 右兄弟

523 分支度為 2 的二元樹表示法bull 分支度為 2 的樹又稱為二元樹 (binary tree)

bull 二元樹中任一節點皆有 2個指標分別指向該節點的左子樹和右子樹

bull 二元樹可以表示任何樹事實上二元樹有許多很好的性質我們在一下節中介紹

bull注意樹狀結構中兒子的順序是不重要的試想樹可以樹根為軸心旋轉使得兒子的順序是不固定的

53  二元樹bull 二元樹是一個由節點組成的有限集合可以是空集合或是包含了(1) 一個樹根節點(2) 其它節點分割成兩個沒有交集的二元樹

其一為左子樹 (left subtree) 另一為右子樹 (right subtree) 樹和二元樹之間的差異

(1)樹不允許空節點而空二元樹是允許的

(2) 樹的子樹沒有順序而二元樹的子樹有左右之分

531  樹和二元樹的基本性質

樹或二元樹皆擁有下面的性質

定理 5-1 若一棵樹 T 的總節點數為 V 總邊數為

E 則V = E + 1

證明利用數學歸納法歸納法的基礎當 V=1 時即 T 只有一個樹根節點 E 為 0 所以 V = E + 1 成立歸納法的假設假設當 1 V K 時該式成立歸納法的推論當 V = K + 1 時令此樹的樹根為 R 而 R 有 m 個子樹 T1 T2 hellip

Tm (如圖 5-12 所示)分別有個 V1 V2 hellip Vm 個節點其中 V1 +V2 + hellip+ Vm =K 若 E1 E2 hellip Em 分別為此 m 個子樹的邊數則

E = E1 +E2 + hellip+ Em + m (1)根據歸納法的假設可知

V1 = E1+1V2 = E2+1 Vm = Em+1

所以V1 +V2 + hellip+Vm = E1 + E2 + hellip+ Em +m (2)

然而V = V1 +V2 + hellip+Vm+1 (3)

將式 (2) 代入式 (3) 可得V = E1 + E2 + hellip+ Em + m + 1

引用式 (1) 則可得V = E + 1

由數學歸納法的原理可得證本定理

532 二元樹的性質(a) 稱為歪斜樹 (skew tree) (b) 稱為完備二元樹 (complete binary tree)

( 其樹葉節點皆在相鄰階層完整定義在後 ) 同樣數目的樹節點存放在歪斜樹上會花較多的階層放在完備二元樹上則只須最少的階層

532 二元樹的性質 ( 續 )定理 5-2 (1) 二元樹上階層 i 的節點數目最多為 2i-1 i 1 (2) 深度為 k 的二元樹其節點數目最多為 2k-

1 k1 bull 定理 5-2說明了 (1) 每一階層上可放的最多節點數

(2) 階層數已知時二元樹可放的最多節點數證明 (1) 利用數學歸納法歸納法的基礎當 i=1 時只有樹根一個節點 2i-1=21-1=1 原式成

立歸納法的假設假設 i = k 時該式成立即階層 k 的最大節點數為

2k-1 k 1 歸納法的推論當 i = k+1 時因在階層 k 的每一個節點最多會有

2 個子節點遂在 k+1 階層最多會有 22k-1 = 2k

= 2i-1 節點由數學歸納法的原理可知本定理得證(2) 由 (1) 可知二元樹在階層 i 的最多節點數為 2i-1那麼深度 k 的二元樹最多可有節點如下

k

i

ki

1

1-1- 24212 1-2

)12(1 k= 2k-1

=

bull 對二元樹而言任一節點的分支度可能為 0 1 或 2 分支度為 0 者是為樹葉

bull 樹葉的數目與分支度為 2 的節點數目存在著有趣的關係請見定理 5-3

定理 5-3 若 T 為一非空的二元樹 n0 為樹葉節點數目 n2為分支度為 2 的節點數目則 n0 = n2+1

證明令 n 為所有節點數目而 n1 為分支度為 1 的節點數目可得 n = n0 + n1 +n2 (1)令 B 為二元樹的分支數除了樹根外每一節點皆有一分支指向之所以

n = B+1 (亦可參考定理 5-1 )又 B = n1+2n2

且 n = n1 + 2n2 +1 (2)由式 (1) 和式 (2) 我們得到

n0 = n2 + 1

完滿二元樹 (full binary tree)

定義一個深度為 k 的完滿二元樹即為一深度為 k 且有 2k-1 個節點的二元樹k 0

bull 由定理 5-2(2) 可知深度為 k 的二元樹其最多節點數為 2k-1 這樣的樹會將樹上所有可能存放節點的位置都放滿完滿 (full) 之名因而生成

完備二元樹 (complete binary tree)我們可對二元樹上節點予以編號階層小者先編同一階

層則自左向右編號這樣的編號方式使我們定義出完備(complete) 二元樹

定義深度為 k 節點數為 n 的二元樹稱為完備二元樹若且唯若 (1) k = 1 時樹有一個節點

(2) 當 k 2 且 1iltk 時深度 i 有 2i-1 個節點且第 k 層的節點皆由第 k-1 層的分支由左至右逐一連接而成

完滿或完備二元樹之所以受人注意乃因它們可以將節點以較少的階層數存放

定理 5-4 n 個節點的完備或完滿二元樹其深度為 log2(n+1)

證明 完滿或完備二元樹的節點皆自階層

1 起排放任一階層 i 可排放 2i-1

個節點遂由定理 5-2 得知此 n 個節點所佔的深度為 log2(n+1)

正規二元樹 (formal binary tree)bull 在二元樹中非樹葉的節點又稱為內部節點

(internal node) 若所有內部節點恰有 2 個子節點即成為正規二元樹

定義若二元樹中所有內部節點恰有 2 個子節點則稱之為正規二元樹

正規二元樹常被引用在單淘汰賽正規二元樹常被引用在單淘汰賽制的各項賽程安排樹葉為參賽制的各項賽程安排樹葉為參賽隊伍內部節點為優勝隊伍樹隊伍內部節點為優勝隊伍樹根即為冠軍隊伍根即為冠軍隊伍

在單淘汰賽制中自在單淘汰賽制中自 n n 個隊伍中產個隊伍中產生冠軍須舉辦 生冠軍須舉辦 nn-1-1場比賽場比賽

54  二元樹的表示法541 以陣列表示二元樹定理 5-5 若一個具有 n 個節點的完備二元樹以循序的方式編號並依序存放在陣列 A 中即第 i 個節點放在 A[i] 中 1 i n 則

(1) 節點 i 的父節點位在 A[i2]處 i ne 1( i = 1 時其節點正為樹根無父節點)

(2) 若 2i n 則節點 i 的左兒子節點位在 A[2i] 處若 2i gtn 節點 i 沒有左子節點

(3) 若 2i+1 n 節點 i 的右兒子節點位 A[2i+1]處若 2i+1gtn 節點 i 沒有左子

節點

證明先用數學歸納法證明 (2) bull 歸納法的基礎當 i = 1 時其左子節點確實在 A[2] 處(除非 nlt2 此時節點 1 沒有左子節點)

bull 歸納法的假設假設當 I = k 時其左子節點確在 A[2i] = A[2k] 處(除非 2i 已大於 n 此時節點 i 無左子節點)

bull 歸納法的推論當 i = k+1 時因第 k 節點的左子節點在 A[2k] 處其右子節點則位在 A[2k+1] 處那麼第 k+1 節點的左子節點應緊臨 A[2k+1] 亦即位於 A[2k+2] = A[2(k+1)] 處(除非 2i 已大於 n 此時節點 i 無左子節點)

bull 由數學歸納法的原理得證定理 5-5 (2) (3) 由於 (2) 成立而且節點 i 的右子節點會緊臨

A[2i] 亦即其位於 A[2i+1]處除非 i 根本沒有右子節點得證定理 5-5 (3)

由 (2) 和 (3) 我們可知定理 5-5 (1) 成立

542 以鍵結串列表示二元樹bull 以陣列來表示完備二元樹算是相當方便但ndash一般的二元樹將導致陣列空間的浪費ndash如果二元樹中節點的新增刪除動作頻繁則會造成陣列對應資料的搬動隨而頻繁

bull 以鍵結串列表示二元樹

為方便辦識樹根我們常用 root 指標指向樹根做為樹鍵結串列的起點

陣列表示和鍵結表示的空間需求bull 完備二元樹的陣列表示和鍵結表示皆沒浪費儲存資料的空間

bull 但歪斜樹的陣列表示比起鍵結表示則浪費許多

bull 以 n 個節點的歪斜樹而言陣列表示法可能需要 2n-1 個陣列元素其中 2n-1-n 個陣列元素空間是浪費的而鍵結表示法只需要 n 個節點空間

二元樹節點的宣告程式 5-2

1 struct BTreeNode

2 struct BtreeNode leftchiled

3 int data

4 struct BTreeNode rightchild

5

6 struct BTreeNode root

bull 這樣的二元樹節點要取得兒子節點的指標十分方便但若有取得父節點的需求則嫌不足我們可以加上 parent 欄位指向父節點

55  二元樹的走訪bull 假如一組資料已用二元樹的組織在一起總有需求對其全數資料做動作例如計算所有數目印出所有資料在所有資料中搜尋某項資料hellip等此時即須對此二元樹做走訪 (traversal) 的運算利用走訪二元樹的同時將計算列印或搜尋的動作完成

bull 事實上走訪即在決定二元樹上資料被處理(計算列印或搜尋)的順序我們也希望走訪的演算法對任何節點皆一致是容易撰寫程式實作的

bull 若對一個二元樹上的節點而言 V 表示處理節點上的資料 L 表示走訪其左子樹 R 表示走訪其右子樹

55  二元樹的走訪 ( 續 )bull 因為對稱的緣故我們只考慮先走訪左邊再走訪右邊的情形那麼右圖只剩下三種走訪方式

bull 以 V 所在的相對位置分別對此三種走訪方式取名如下

(1) LVR 中序走訪 (inorder traversal)或中序表示法 (infix notation)

(2) LRV 後序走訪 (postorder traversal) 或後序表示法 (postfix notation)

(3) VLR 前序走訪 (preorder traversal)或前序表示法 (prefix notation)

551  中序走訪bull 利用遞迴的方式撰寫中序走訪的程序希望把二元樹中序走訪的

順序印出來

程式 5-3二元樹的中序走訪1 struct BTreeNode2 struct BTreeNode leftchild3 char data4 struct BTreeNode rightchild5 6 struct BTreeNode root7 void inorder(struct BTreeNode node)8 if (node = NULL)9 inorder(node-gtleftchild)10 cout ltlt node-gtdata11 inorder(node-gtrightchild)12 13

範例 5-12此為運算式二元樹

其對應中序表示運算式為 (A+B)C+D(E-F)

6 struct BTreeNode root7 void inorder(struct BTreeNode node)8 if (node = NULL)9 inorder(node-gtleftchild)10 cout ltlt node-gtdata11 inorder(node-gtrightchild)12 13

6 struct BTreeNode root7 void inorder(struct BTreeNode node)8 if (node = NULL)9 inorder(node-gtleftchild)10 cout ltlt node-gtdata11 inorder(node-gtrightchild)12 13

(A+B)C+D(E-F)

如何加上 ( )

552  後序走訪程式 5-4二元樹的後序走訪14 void postorder(struct BTreeNode node)

15 if (node =NULL)16 postorder(node-gtleftchild)17 postorder(node-gtrightchild)18 cout ltlt node-gtdata19 20

以後序走訪可得到AB+CDEF-+

其正為對應的後序運算式

553  前序走訪程式 5-5二元樹的前序走訪21 void preorder(struct BTreeNode node)

22 if (node = NULL)

23 cout ltlt node-gtdata

24 preorder(node-gtleftchild)

25 preorder(node-gtrightchild)

26

27

以前序走訪可得到++ABCD-EF

其正為對應的前序運算式

554 利用堆疊和迴圈取代遞迴進行二元樹的走訪1 struct BTreeNode 2 struct BTreeNode leftchild3 char data4 struct BtreeNode rightchild5 6 struct BTreeNode root7 struct StackNode8 struct BTreeNode treenode9 struct StackNode next1011 struct StackNode top

12 void push_node(struct BtreeNode node )13 struct StackNode old_top14 old_top = top15 top = (struct StackNode ) malloc

(sizeof(struct StackNode))16 top-gttreenode = node17 top-gtnext = old_top18 19 struct BTreeNode pop_node()20 struct BTreeNode Tnode21 struct StackNode old_top22 if (top == NULL)23 StackEmpty()24 else25 Tnode = top-gttreenode26 old_top = top27 top = top-gtnext28 free(old_top)29 return Tnode30 31

32 void inorder_Stack(struct BtreeNode node)33 do34 while (node = NULL)35 push_node(node)36 node = node-gtleftchild37 push all left children38 if (top = NULL)39 node = pop_node()40 cout ltlt node-gtdata41 node = node-gtrightchild42 inorder printing and check right child43 while ((top=NULL)||(node=NULL))44 top==NULL 堆疊已空 node==NULL沒有右子

6 struct BTreeNode root7 void inorder(struct BTreeNode node)8 if (node = NULL)9 inorder(node-gtleftchild)10 cout ltlt node-gtdata11 inorder(node-gtrightchild)12 13

中序表示為DBGEHAFIC

32 void inorder_Stack()33 struct BTreeNode node34 node = root35 do36 while (node = NULL)37 push_node(node)38 node = node-gtleftchild39 40 if (top = NULL)41 node = pop_node()42 cout ltlt node-gtdata43 node = node-gtrightchild44 45 while(top=NULL)||(node=NULL))46

45 void Preorder_Stack(struct BSTreeNode node)46 do47 while (node = NULL)48 cout ltlt node-gtdata)49 push_node(node)50 node = node-gtleftchild51 push all left children52 if (top = NULL)53 node = pop_node()54 node = node-gtrightchild55 56 while ((top=NULL)||(node=NULL))57

程式 5-5二元樹的前序走訪21 void preorder(struct BTreeNode node)22 if (node = NULL)23 cout ltlt node-gtdata24 preorder(node-gtleftchild)25 preorder(node-gtrightchild)26 27

58 void Postorder_Stack(struct BTreeNode node)59 do60 while (node = NULL)61 cout ltlt node-gtdata62 push_node(node)63 node = node-gtrightchild64 push all left children65 if (top = NULL)66 node = pop_node()67 node = node-gtleftchild68 69 while ((top=NULL)||(node=NULL))70

程式 5-4二元樹的後序走訪14 void postorder(struct BTreeNode node)15 if (node =NULL)16 postorder(node-gtleftchild)17 postorder(node-gtrightchild)18 cout ltlt node-gtdata19 20

555  階層走訪bull 「階層走訪」 (level-order traversal) 是依階層

的順序進行二元樹的走訪先走訪階層小的節點後走訪階層大的節點同一階層者則依自左向右的順序走訪

bull 對下圖的二元樹進行階層走訪的結果為ABCDEFGHI

bull 對同一階層而言先走訪的節點其子節點亦在下一階層中先被走訪

bull 這種「先進先出」的特性恰可用佇列予以儲存與處理

程式 5-7 利用佇列進行二元樹的階層走訪

1 struct BTreeNode2 struct BTreeNode leftchild3 char data4 struct BTreeNode rightchild5 6 struct BTreeNode root7 struct QNode8 struct BTreeNode treenode9 struct QNode next10 11 struct QNode front rear

12 void AddQueue(struct BTreeNode Tnode)13 struct QNode node14 node=(struct QNode )malloc(sizeof(struct QNode))15 node-gttreenode = Tnode16 node-gtnext = NULL17 if (front == NULL) front = node19 else rear-gtnext = node20 rear = node21

22 struct BTreeNode DeleteQueue()23 struct BTreeNode Tnode24 struct QNode old_front25 if (rear == NULL)26 QueueEmpty()27 else28 Tnode = front-gttreenode29 old_front = front30 front = front-gtnext31 free(old_front)32 return Tnode33 34 35 void LevelOrder (struct BTreeNode node)36 AddQueue(node)37 while (front =NULL)38 node = DeleteQueue()39 cout ltlt node-gtdata40 if (node-gtleftchild = NULL)41 AddQueue(node-gtleftchild)42 if (node-gtrightchild = NULL)43 AddQueue(node-gtrightchild)44 45

35 void LevelOrder (struct BTreeNode node)36 AddQueue(node)37 while (front =NULL)38 node = DeleteQueue()39 cout ltlt node-gtdata40 if (node-gtleftchild = NULL)41 AddQueue(node-gtleftchild)42 if (node-gtrightchild = NULL)43 AddQueue(node-gtrightchild)44 45

bull Infix ABCbull Postfix ABC CBA ACB BCA

BAC CAB

B

A CA

B

C

B

A

C

C

B

AB

A

C

二元樹中序與前序式中序與後序式bull 每一棵二元樹皆有唯一的一對中序與前序表示法也有唯一的中序與後序次序

bull 換句話說給予一對中序與前序或中序與後序即可決定一棵二元樹

bull 然而給予前序和後序次序並不能決定唯一的二元樹可能會產生兩棵不同的二元樹

bull 想想階層走訪和中序走訪順序可否決定唯一的二元樹

A

B C

H J D E

F G

prefix ABHJCDFGEinfix HBJAFDGCEpostfix HJBFGDECA

VLRLVRLRV

A

prefix BHJinfix HBJ

prefix CDFGEinfix FDGCE

prefix ABHJCDFGEinfix HBJAFDGCE

A

B C

H J D E

F G

prefix ABHJCDFGE

infix HBJAFDGCEk-1

struct BTreeNode BTree_InfixPrefix (AnsiString infix AnsiString prefix)

int k struct BTreeNode node if (infixLength==0) return NULLnode = (struct BTreeNode ) malloc (sizeof(struct BTreeNode))node-gtdata = prefix[1]k = find(prefix[1] infix)node-gtleftchild = BTree_InfixPrefix

(infix[1k-1] prefix[2k])node-gtrightchild = BTree_InfixPrefix(infix[k+1infixLength]prefix[k+1prefixLength])

VLR

LVRk1

k2 k+1~

k+1~

1

struct BTreeNode BTree_InfixPostfix (AnsiString infix AnsiString prefix)

int k struct BTreeNode node if (infixLength==0) return NULLnode = (struct BTreeNode ) malloc (sizeof(struct BTreeNode))node-gtdata = postfix[postfixLength]k = find(postfix[postfixLength] infix)node-gtleftchild = BTree_InfixPrefix

(infix[1k-1] postfix[1k-1])node-gtrightchild = BTree_InfixPrefix(infix[k+1infixLength]postfix[k+1postfixLength-1])

56  二元樹的複製與相等測試程式 5-8 二元樹的複製1 struct BTreeNode2 struct BTreeNode leftchild3 char data4 struct BTreeNode rightchild5 6 struct BTreeNode root7 struct BTreeNode Copy(struct BTreeNode TreeRoot)8 struct BTreeNode CopyRoot9 if (TreeRoot == NULL)10 return NULL11 CopyRoot =(struct BTreeNode ) malloc (sizeof(BTreeNode))12 CopyRoot-gtdata = TreeRoot-gtdata13 CopyRoot-gtleftchild = Copy(TreeRoot-gtleftchild)14 CopyRoot-gtrightchild = Copy(TreeRoot-

gtrightchild)15 return CopyRoot16

程式 5-9 檢測兩棵二元樹是否相等1 struct BTreeNode2 struct BTreeNode leftchild3 char data4 struct BTreeNode rightchild5 6 struct BtreeNode rightchild7 int Equal(struct BTreeNode ustruct BTreeNode v)8 if ((u==NULL) ampamp (v==NULL)) return 19 if ((u=NULL) ampamp (v=NULL)10 ampamp (u-gtdata == v-gtdata )11 ampamp Equal(u-gtleftchildv-gtleftchild)12 ampamp Equal(u-gtrightchildv-gtrightchild))

13 return 114 return 015

Counting Nodes in a Binary Tree

int CountingNodes(struct BTreeNodes node)

if (node == NULL) return 0 int leftchildren = CountingNodes(node-gtleftchild)

int rightchildren= CountingNodes(node-gtrightchild)

return leftchildren+rightchildren

Counting Leaves in a Binary Tree

int CountingLeaves(struct BTreeNodes node)

if (node == NULL) return 0 int leftleaves = CountingLeaves(node-gtleftchild)

int rightchildren= CountingLeaves(node-gtrightchild)

if (leftleaves==0 ampamp leftleaves==0) return 1

return leftleaves + leftleaves

57  引線二元樹bull 在二元樹的鍵結表示中樹葉節點的兩個子樹指標皆指向 NULL 由定理 5-1 得知 n = E+1 其中 n 為節點個數 E 為分支數

bull 而 n 個鍵結節點有 2n 個指標空間每個分支恰佔用一個指標空間共有 E (=n-1) 個指標有用到(不是空的)而共有 n+1 個指標空間是 NULL 比非空的節點還多

bull 有學者提出對這些「放空指標的空間」加以利用的概念mdash與其放空指標不如放指向其它節點的指標稱之為引線 (thread) 使得某些運算(如走訪hellip等)可以加快

bull 這個概念發展出了引線二元樹 (threaded binary tree) 這種資料結構

範例 5-17下圖為加了引線的二元樹

加入的引線將使二元樹的中序走訪更加便利沿著上右圖箭頭所指示的順序走訪即可完成此二元樹的中序走訪GDHBAIECF 為此二元樹的中序表示

bull 範例 5-17 中所加的引線目的在方便二元樹的中序走訪

bull 對樹葉節點 p 而言令其資料為 P 則引線指標的規則如下

(1) p 的左子樹指標指向中序表示中 P 的前一個 元素(前接元素 (predecessor) )

(2) p 的右子樹指標指向中序表示中 P 的後一個 元素(後繼元素 (successor) )

為了區別節點指標放的是一般指標(內部節點)抑或是引線指標(樹葉節點)我們另以 2 個欄位分別區別左和右子樹指標空間存放的對象其節點的記憶體配置有如下圖所示

其中 leftthread (rightthread) 為 0 時表示 leftchild (rightchild) 存放的是一般節點指標而 leftthread (rightthread) 為 1 時表示 leftchild (rightchild) 存放的是引線指標1 struct TBTreeNode2 int leftthread3 struct TBTreeNode leftchild4 char data5 struct TBTreeNode rightchild6 int rightthread7

由右側的引線二元樹可知有最左和最右兩條引線尚無妥善的安排我們另設計一空的引線節點做為樹根如下圖所示

571  引線二元樹的中序走訪決定節點 p 在中序表示中的後繼節點 q (簡稱中序後繼點)

程式 5-10決定節點 node的中序後繼點1 struct TBTreeNode2 int leftthread3 struct TBTreeNode leftchild4 char data5 struct TBTreeNode rightchild6 int rightthread7 8 struct TBTreeNode root9 struct TBTreeNode Next(struct TBTreeNode node)

10 struct TBTreeNode temp11 temp = node-gtrightchild12 if (node-gtrightthead) return temp13 while (temp-gtleftthead) temp=temp-gtleftchild

14 return temp15

程式 5-11引線二元樹的中序表示(接程式 5-10)1 void InorderTBTree()2 struct TBTreeNode node3 for(node=Next(root)node=rootnode=Next(node))

4 cout ltlt node-gtdata5

bull只要 node 是引線二元樹上的任一節點透過呼叫 Next(node) 即可找到其在中序表示中的後繼節點bull(試想若程式 5-10 中的程序 Next 所傳入的參數指標正是 root Next(root) 是否可正常運作請思考)bull於是只須從中序表示中的第一節點起讓每一節點 node 都執行 Next(node) 程序即可得引線二元樹的中序表示

572  在引線二元樹中加入節點在引線二元樹中樹葉節點節點的右子樹指標所指向或內部節點右子樹的最左樹葉為其中序後繼節點而且任一節點若無左子樹則其左子樹指標乃指向其中序前接節點hellip這些性質必須在節點新增進入引線二元樹時也要保持接下來討論節點 new 插入成為引線二元樹的節點 p 的右子節點的情形至於插入成為左子節點的情形則讓各位模擬推敲

572572  在引線二元樹中加入節點 在引線二元樹中加入節點 (( 續續 ))

newnew 會成為會成為 pp 的右子節點的情況共有以下兩種的右子節點的情況共有以下兩種(1) (1) 若若 pp 沒有右子節點 沒有右子節點 ((p-gtrightthread p-gtrightthread 為為1) 1) 如圖如圖 ((a) a) 則 則 new new 插入後應形成如圖插入後應形成如圖 ((b) b) 所示所示

(2)(2) 若若 pp 有右子節點 有右子節點 ((p-gtrightthread p-gtrightthread 為為0) 0) 如圖如圖 ((c) c) 則 則 new new 插入後應形成如插入後應形成如圖圖 ((d) d) 所示所示

程式 5-12  插入節點 new 成為引線二元樹中節點 p的右子節點(上接程式 5-10 5-11 )

1 void InsertRight(struct TBTreeNode p 2 struct TBTreeNode new)3 struct TBTreeNode q4 new-gtrightchild = p-gtrightchild5 new-gtrightthead = p-gtrightthread6 new-gtleftchild = p7 new-gtleftthread = 18 p-gtrightchild = new9 p-gtrightthead = 010 if (new-gtrightthread)11 q = Next(new)12 q-gtleftchild = new13 14

58  堆積bull 「堆積」 (heap) 是一個特殊的完備二元樹節點的資料與其子節點的資料之間有事先定好的大小關係存在bull 定義ndash最大堆疊為一完備二元樹任一節點的資料內容不小於其子節點的資料內容ndash最小堆疊為一完備二元樹任一節點的資料內容不大於其子節點的資料內容

581  插入節點至最大堆積中最大堆積是一完備二元樹所以如圖 (a) 之最大堆積於加入新節點後樹的形狀必須如圖 (b) 所示灰色節點即為新節點若加入的節點資料分別為 1 9 12 則其最大堆積將分別變成如圖 (c) (d) (e) 所示

程式 5-13 新增節點至最大堆積中最大堆積本身是一棵完備二元樹可以用陣列表示之 1 define maxsize 1002 int heap[maxsize]3 int n i4 void InsertHeap(int x)5 if (n == maxsize) HeapFull()6 else7 n++8 i = n9 while ((i gt 1) ampamp (x gt heap[i2]))10 heap[i] = heap[i2]11 i = 212 13 heap[i] = x14 16

程序 InsertHeap 在插入第 n 個資料時需要的時間複雜度為 O(logn)

1 100

2 93

3 84

4 77

5 65

6 81

7 79

8 66

9 70

10 50

100

84

77

93

8165

66 70

79

50

1

2 3

4 5 6 7

8 9 10

1 define maxsize 1002 int heap[maxsize]3 int n i4 void InsertHeap(int x)5 if (n == maxsize) HeapFull()6 else7 n++8 i = n9 while ((i gt 1) ampamp (x gt heap[i2]))10 heap[i] = heap[i2]11 i = 212 13 heap[i] = x14 16

582 刪除最大堆積中的最大資料圖 (a) 中的最大堆積在刪除最大資料後其樹形應變成如圖 (b) 所示將最未一個樹葉節點的資料 6 暫挪至樹根節點內如圖 (c) 再將圖 (c) 中非最大堆積的結構調整成為最大堆積 10 和 8 中選大者與 6對調即成圖 (d)

程式 5-14 刪除最大堆積中的最大資料(上接程式 5-13 )1 int DeleteHeap()2 int x i j3 if (n == 0) HeapEmpty()4 else5 x = heap[1]6 heap[1] = heap[n]7 n--8 i = 19 while (i lt= n2)10 if (heap[2i] gt heap[2i+1])11 j = 2i12 else13 j = 2i+114 if (heap[i]gtheap[j]) break15 swap(heap[i] heap[j])16 i = j17 18 return x19 20

其時間複雜度與其堆積高度相同是為O(logn) 其中 n 為堆積中的元素個數

1 100

2 93

3 84

4 77

5 65

6 81

7 79

8 66

9 70

10 50

100

84

77

93

8165

66 70

79

50

1

2 3

4 5 6 7

8 9 10

1 int DeletHeap()2 int x i j3 if(n == 0) HeapEmpty()4 else5 x = heap[1]6 heap[1] = heap[n]7 n--8 i = 19 while (i lt= n2)10 if ([heap[2i]] gt heap[2i+1])11 j = 2i12 else13 j = 2i+114 if (heap[i]gtheap[j]) break15 swap(heap[i] heap[j])16 i = j17 18 return x19 20

583  優先佇列bull 由於最大堆積(或最小堆積)的結構可提供最大元素(或最小元素)而其維護堆積的成本為 O(logn) n 為總元素個數

bull 堆積常被當成「優先佇列」 (priority queue) 使用mdash此時佇列被組織成完備二元樹若新加入佇列的資料有大小關係優先順序的需求時此優先佇列即以 O(logn) 的代價將新資料放入優先佇列(可能是最大或最小堆積依實際大小關係優先順序的需求而定)中mdash提供依元素的優先順序做為處理順序的服務

bull 一般的佇列為先進先出先到的先行處理優先佇列則是依據其優先順序優先順序高者先行處理與來到的先後無關

範例 5-21bull 在「分時」 (time sharing) 的作業系統中我們不希望使用中央處理器 (Central Processing Unit CPU) 時間短的「處理程序」 (process) 等候過久於是有「最短時間工作優先」 (shortest-job-first) 的排程策略此時等候的處理程序即可組織成之一最小堆積而 CPU 每次即取出存於樹根的處理程序予以處理任何新來的處理程序則以 O(logn) 的時間加入此優先佇列(最小堆積)中

bull 注意之前提過的線性佇列新來的直接加入成為新的尾端元素花O(1) 的時間然線性佇列本身沒有大小的關係每次要取出最大(小)的資料得花O(n) 的代價(在所有元素中挑出最大(小)者) n 為佇列內的元素個數而優先佇列是在新元素加入時要花O(logn) 的時間至於取出最大(小)則只須 O(1) 的時間

59  二元搜尋樹bull 二元搜尋樹是一棵二元樹可能是空二元樹若不為空二元樹則滿足下列性質ndash所有節點內的資料內容是相異的 ndash左子節點的資內容 ( 如果有 ) 比父節點的資料內容小 ndash右子節點的資料內容 ( 如果有 ) 比父節點的資料內容大 ndash以左和右子節點為樹根的左子樹和右子樹也是二元搜尋樹

在二元搜尋樹中搜尋資料二元搜尋樹對資料大小的掌握十分明確若恰與節點 p 資料相等則搜尋成功若欲搜尋的資料 x 比樹節點 p 中的資料小 則只須搜尋節點 p 的左子樹若比節點 p 中的資料大 則只須搜尋 p 的右子樹這樣的判斷可以遞迴地應用在各子樹節點 中

程式 5-15 在二元搜尋樹中搜尋資料(遞迴呼叫)1 struct BSTreeNode2 struct BSTreeNode leftchild3 int data4 struct BSTreeNode rightchild5 6 struct BSTreeNode root7 struct BSTreeNode SearchBST(

struct BSTreeNode tree int x)8 if (tree == NULL) return NULL9 if (x == tree-gtdata) return tree10 if (x lt tree-gtdata)11 return SearchBST(tree-gtleftchild x)

12 return SearchBST(tree-gtrightchild x)13

程式 5-16 搜尋二元搜尋樹的資料(非遞迴呼叫)在二元搜尋樹十分龐大時遞迴呼叫的效率不會太好可利用迴圈來完成搜尋的動作以提高執行效率請看程式 5-16

14 struct BSTreeNode Search BST_iterative15 (struct BSTreeNode node int x)16 while (node = NULL)17 if (x == node-gtdata) return node18 if (x lt node-gtdata)19 node = node-gtleftchild20 else21 node = node-gtrightchild22 23 return NULL24

592  新增資料於二元搜尋樹中欲新增資料進入一二元搜尋樹中得先決定該資料在二元搜尋樹中應出現的位置這可透過上節提到的搜尋動作來解決

在二元搜尋樹定義的性質 (1) 中我們限定了元素必須相異所以搜尋新資料的結果必是 NULL

必須將搜尋的結果改為新增資料所應在位置的節點指標

程式 5-17  在二元搜尋樹中新增資料25 int InsertBST(int x)26 struct BSTreeNode p q27 p = root q = NULL28 while (p = NULL)29 q = p30 if (x == p-gtdata) return 0

若不考慮重複則可省略之31 if (x lt p-gtdata)32 p = p-gtleftchild33 else34 p = p-gtrightchild35

3636 p = (struct BSTreeNode ) p = (struct BSTreeNode ) malloc (sizeof(BSTreeNode))malloc (sizeof(BSTreeNode))

3737 p-gtdata = x p-gtdata = x3838 p-gtleftchild = p-gtrightchild = p-gtleftchild = p-gtrightchild = NULLNULL3939 if (root == NULL) root = p if (root == NULL) root = p4040 else if (x lt q-gtdata) else if (x lt q-gtdata)4141 q-gtleftchild = p q-gtleftchild = p4242 else else4343 q-gtrightchild = p q-gtrightchild = p4444 return 1 return 145 45

程式 程式 5-175-17  在二元搜尋樹中新增資料  在二元搜尋樹中新增資料 (( 續續 ))

範例 5-23 圖 (b) 為圖 (a) 新增整數 6 之後的結果圖 (c) (d) (e) (f) 則分別是逐步再加入 28 18

10 21 的結果

bull若目前的二元搜尋樹高度為 h 則新增資料得花O(h) 的時間找到其所應在的位置遂新增資料至二元搜尋樹中的時間複雜之度為 O(h)

593 自二元搜尋樹中刪除資料欲自二元搜尋樹中刪除資料可能會面臨兩種情況(1) 刪除節點 p 位於樹葉節點上如下圖所示 這是比較容易處理的情況

(2) 刪除的節點 p 不是樹葉刪除節點 p 後原 p 所指的節點可由 q ( q 中包含了 p 左子樹中的最大節點值)或 r ( r 中包含了 p 右子樹中的最小節點值)來取代以維持二元搜尋樹中資料的大小關係其結果分別如圖 (b) 和 (c) 所示而圖 (d) 則標明了指標會改變的節點位置即 p 會消失 father 的左或右子樹指標會異動 k(可能是 q 或 r )會移到原來 p 的位置其父節點 f 的左或右子樹指標會異動其中 father 為 p 父節點 f 為 k 的父節點

程式 5-18 自二元搜尋樹中刪除資料46 int DeleteBST (int x)47 Struct BSTreeNode p father k f48 p = root father = NULL49 while (p = NULL)50 if (x == p-gtdata) 找到 x 所在的節點 p 了51 if ((p-gtleftchild == NULL) ampamp 52 (p-gtrightchild == NULL))53 k = NULL p為樹葉節點54 else if (p-gtleftchild = NULL )x位於非子節

點55 f = p 找出 p左子樹的最右樹葉56 k = p-gtleftchild57 while (k-gtrightchild =NULL)58 f = k59 k = k-gtrightchild60 k將挪至原 p處61 if (p == f)62 f-gtleftchild = k-gtleftchild 63 else64 f-gtrightchild = k-gtleftchild 65

66 else p無左子樹67 f = p 找出 p右子樹的最左樹葉68 k = p-gtrightchild69 while (k-gtleftchild = NULL)70 f = k71 k = k -gtleftchild72 73 if (p == f)74 f-gtrightchild = k-gtrightchild 75 else76 f-gtleftchild = k-gtrightchild77 78 if (k = NULL) k挪至原 p處繼承 p的左右指標79 k-gtleftchild = p-gtleftchild80 k-gtrightchild = p-gtrightchild81 82 if (father == NULL) root = k83 else if (x lt father-gtdata) 決定 k是 father的左

或右兒子84 father-gtleftchild = k85 else86 father-gtrightchild = k87 free(p)88 return 1 成功地刪除 x於此傳回 1返回呼叫處89 51~89行為找到 x時應有的處理步驟

90 else 尚未找到 x繼續往下一階層找

91 father = p92 if (x lt p-gtdata)93 p = p-gtleftchild94 else95 p = p-gtrightchild96 97 98 return 0 未找到 x傳回 099

510  樹林 (forest)

bull 定義樹林(森林)是由 n 棵沒有交集的樹所構成的集合 n 0

下圖為一包含三棵樹的樹林

5101 轉換樹林為二元樹 之前我們以二元樹來表示樹因為二元樹有較多

好用的性質在此我們也將樹林轉換成二元樹看待

在 522 節中我們嘗試了用左子右兄弟的方式來表示樹這個方法事實上已將樹轉換成二元樹

即用左子右兄弟的結構重新組織樹林中的每一棵樹這些樹的樹根將視為兄弟而最左邊的樹則視為樹林二元樹的樹根如此一來樹林即可轉換成二元樹

範例 5-25右下圖為右上圖三棵樹所對應的三棵左子右兄弟二元樹

下圖則為上圖樹林所對應的一棵樹林二元樹

演算法 5-1  轉換樹林成二元樹輸入樹林 F = T1 T2 Tn

輸出 F 的二元樹表示 BT=(T1 T2 Tn)1 if (n == 0) 輸出空二元樹 2 else

3 root(T1 T2 Tn) = T1

4 leftsubtree(T1 T2 Tn) =

BT(T11 T12 T1m)5 BT(T11 T12 T1m)為 T1的子樹6 rightsubtree(T1 T2 Tn) =

BT(T2 T3 Tn)

樹林轉換成二元樹的過程樹林轉換成二元樹的過程

  • Slide 1
  • Slide 2
  • Slide 3
  • Slide 4
  • Slide 5
  • Slide 6
  • Slide 7
  • Slide 8
  • Slide 9
  • Slide 10
  • Slide 11
  • Slide 12
  • Slide 13
  • Slide 14
  • Slide 15
  • Slide 16
  • Slide 17
  • Slide 18
  • Slide 19
  • Slide 20
  • Slide 21
  • Slide 22
  • Slide 23
  • Slide 24
  • Slide 25
  • Slide 26
  • Slide 27
  • Slide 28
  • Slide 29
  • Slide 30
  • Slide 31
  • Slide 32
  • Slide 33
  • Slide 34
  • Slide 35
  • Slide 36
  • Slide 37
  • Slide 38
  • Slide 39
  • Slide 40
  • Slide 41
  • Slide 42
  • Slide 43
  • Slide 44
  • Slide 45
  • Slide 46
  • Slide 47
  • Slide 48
  • Slide 49
  • Slide 50
  • Slide 51
  • Slide 52
  • Slide 53
  • Slide 54
  • Slide 55
  • Slide 56
  • Slide 57
  • Slide 58
  • Slide 59
  • Slide 60
  • Slide 61
  • Slide 62
  • Slide 63
  • Slide 64
  • Slide 65
  • Slide 66
  • Slide 67
  • Slide 68
  • Slide 69
  • Slide 70
  • Slide 71
  • Slide 72
  • Slide 73
  • Slide 74
  • Slide 75
  • Slide 76
  • Slide 77
  • Slide 78
  • Slide 79
  • Slide 80
  • Slide 81
  • Slide 82
  • Slide 83
  • Slide 84
  • Slide 85
  • Slide 86
  • Slide 87
  • Slide 88
  • Slide 89
  • Slide 90
  • Slide 91
  • Slide 92
  • Slide 93
  • Slide 94
  • Slide 95
  • Slide 96
  • Slide 97
  • Slide 98
  • Slide 99
  • Slide 100
  • Slide 101
  • Slide 102
  • Slide 103
Page 10: 第五章 樹

521  一般化的串列表示bull 串列是一個有限且有順序的元素集合

A = (a1 a2 hellip an)

每個元素 ai 1in 皆有相同的資料型態 bull 倘若不限定各個元素都具有相同的資料型態則稱此串列為「一般化串列」 (generalized list) (ai 可以是一個節點或是一棵樹 )

bull 可用一般化串列來表示一棵樹 T T = (R T1 T2 hellip Tn)

其中 R 為 T 的樹根而 T1 T2 hellip Tn 為 R 的子樹 Ti 可能是一個節點也可能是一棵樹(一般化串列)

範例 5-1 以一般化串列表示樹bull (A (B (E K) (F L M) G) (C H) (D (I N) (J

O P)))

(A T1 T2 T3)T1 = (B (E K) (F L M) G)T2 = (C H)T3 = (D (I N) (J O P))

T1 T2T3

程式 5-1 一般化串列鍵結節點的宣告1 struct TreeNode2 int tag3 union4 int data5 struct TreeNode Tlink6 node7 struct TreeNode link8

(A T1 T2 T3)T1 = (B (E K) (F L M) G)T2 = (C H)T3 = (D (I N) (J O P))

522  左子右兄弟表示法

每個節點都有唯一的最左 兒子 (leftmost child) 每個節點都有最靠近它的 右兄弟

523 分支度為 2 的二元樹表示法bull 分支度為 2 的樹又稱為二元樹 (binary tree)

bull 二元樹中任一節點皆有 2個指標分別指向該節點的左子樹和右子樹

bull 二元樹可以表示任何樹事實上二元樹有許多很好的性質我們在一下節中介紹

bull注意樹狀結構中兒子的順序是不重要的試想樹可以樹根為軸心旋轉使得兒子的順序是不固定的

53  二元樹bull 二元樹是一個由節點組成的有限集合可以是空集合或是包含了(1) 一個樹根節點(2) 其它節點分割成兩個沒有交集的二元樹

其一為左子樹 (left subtree) 另一為右子樹 (right subtree) 樹和二元樹之間的差異

(1)樹不允許空節點而空二元樹是允許的

(2) 樹的子樹沒有順序而二元樹的子樹有左右之分

531  樹和二元樹的基本性質

樹或二元樹皆擁有下面的性質

定理 5-1 若一棵樹 T 的總節點數為 V 總邊數為

E 則V = E + 1

證明利用數學歸納法歸納法的基礎當 V=1 時即 T 只有一個樹根節點 E 為 0 所以 V = E + 1 成立歸納法的假設假設當 1 V K 時該式成立歸納法的推論當 V = K + 1 時令此樹的樹根為 R 而 R 有 m 個子樹 T1 T2 hellip

Tm (如圖 5-12 所示)分別有個 V1 V2 hellip Vm 個節點其中 V1 +V2 + hellip+ Vm =K 若 E1 E2 hellip Em 分別為此 m 個子樹的邊數則

E = E1 +E2 + hellip+ Em + m (1)根據歸納法的假設可知

V1 = E1+1V2 = E2+1 Vm = Em+1

所以V1 +V2 + hellip+Vm = E1 + E2 + hellip+ Em +m (2)

然而V = V1 +V2 + hellip+Vm+1 (3)

將式 (2) 代入式 (3) 可得V = E1 + E2 + hellip+ Em + m + 1

引用式 (1) 則可得V = E + 1

由數學歸納法的原理可得證本定理

532 二元樹的性質(a) 稱為歪斜樹 (skew tree) (b) 稱為完備二元樹 (complete binary tree)

( 其樹葉節點皆在相鄰階層完整定義在後 ) 同樣數目的樹節點存放在歪斜樹上會花較多的階層放在完備二元樹上則只須最少的階層

532 二元樹的性質 ( 續 )定理 5-2 (1) 二元樹上階層 i 的節點數目最多為 2i-1 i 1 (2) 深度為 k 的二元樹其節點數目最多為 2k-

1 k1 bull 定理 5-2說明了 (1) 每一階層上可放的最多節點數

(2) 階層數已知時二元樹可放的最多節點數證明 (1) 利用數學歸納法歸納法的基礎當 i=1 時只有樹根一個節點 2i-1=21-1=1 原式成

立歸納法的假設假設 i = k 時該式成立即階層 k 的最大節點數為

2k-1 k 1 歸納法的推論當 i = k+1 時因在階層 k 的每一個節點最多會有

2 個子節點遂在 k+1 階層最多會有 22k-1 = 2k

= 2i-1 節點由數學歸納法的原理可知本定理得證(2) 由 (1) 可知二元樹在階層 i 的最多節點數為 2i-1那麼深度 k 的二元樹最多可有節點如下

k

i

ki

1

1-1- 24212 1-2

)12(1 k= 2k-1

=

bull 對二元樹而言任一節點的分支度可能為 0 1 或 2 分支度為 0 者是為樹葉

bull 樹葉的數目與分支度為 2 的節點數目存在著有趣的關係請見定理 5-3

定理 5-3 若 T 為一非空的二元樹 n0 為樹葉節點數目 n2為分支度為 2 的節點數目則 n0 = n2+1

證明令 n 為所有節點數目而 n1 為分支度為 1 的節點數目可得 n = n0 + n1 +n2 (1)令 B 為二元樹的分支數除了樹根外每一節點皆有一分支指向之所以

n = B+1 (亦可參考定理 5-1 )又 B = n1+2n2

且 n = n1 + 2n2 +1 (2)由式 (1) 和式 (2) 我們得到

n0 = n2 + 1

完滿二元樹 (full binary tree)

定義一個深度為 k 的完滿二元樹即為一深度為 k 且有 2k-1 個節點的二元樹k 0

bull 由定理 5-2(2) 可知深度為 k 的二元樹其最多節點數為 2k-1 這樣的樹會將樹上所有可能存放節點的位置都放滿完滿 (full) 之名因而生成

完備二元樹 (complete binary tree)我們可對二元樹上節點予以編號階層小者先編同一階

層則自左向右編號這樣的編號方式使我們定義出完備(complete) 二元樹

定義深度為 k 節點數為 n 的二元樹稱為完備二元樹若且唯若 (1) k = 1 時樹有一個節點

(2) 當 k 2 且 1iltk 時深度 i 有 2i-1 個節點且第 k 層的節點皆由第 k-1 層的分支由左至右逐一連接而成

完滿或完備二元樹之所以受人注意乃因它們可以將節點以較少的階層數存放

定理 5-4 n 個節點的完備或完滿二元樹其深度為 log2(n+1)

證明 完滿或完備二元樹的節點皆自階層

1 起排放任一階層 i 可排放 2i-1

個節點遂由定理 5-2 得知此 n 個節點所佔的深度為 log2(n+1)

正規二元樹 (formal binary tree)bull 在二元樹中非樹葉的節點又稱為內部節點

(internal node) 若所有內部節點恰有 2 個子節點即成為正規二元樹

定義若二元樹中所有內部節點恰有 2 個子節點則稱之為正規二元樹

正規二元樹常被引用在單淘汰賽正規二元樹常被引用在單淘汰賽制的各項賽程安排樹葉為參賽制的各項賽程安排樹葉為參賽隊伍內部節點為優勝隊伍樹隊伍內部節點為優勝隊伍樹根即為冠軍隊伍根即為冠軍隊伍

在單淘汰賽制中自在單淘汰賽制中自 n n 個隊伍中產個隊伍中產生冠軍須舉辦 生冠軍須舉辦 nn-1-1場比賽場比賽

54  二元樹的表示法541 以陣列表示二元樹定理 5-5 若一個具有 n 個節點的完備二元樹以循序的方式編號並依序存放在陣列 A 中即第 i 個節點放在 A[i] 中 1 i n 則

(1) 節點 i 的父節點位在 A[i2]處 i ne 1( i = 1 時其節點正為樹根無父節點)

(2) 若 2i n 則節點 i 的左兒子節點位在 A[2i] 處若 2i gtn 節點 i 沒有左子節點

(3) 若 2i+1 n 節點 i 的右兒子節點位 A[2i+1]處若 2i+1gtn 節點 i 沒有左子

節點

證明先用數學歸納法證明 (2) bull 歸納法的基礎當 i = 1 時其左子節點確實在 A[2] 處(除非 nlt2 此時節點 1 沒有左子節點)

bull 歸納法的假設假設當 I = k 時其左子節點確在 A[2i] = A[2k] 處(除非 2i 已大於 n 此時節點 i 無左子節點)

bull 歸納法的推論當 i = k+1 時因第 k 節點的左子節點在 A[2k] 處其右子節點則位在 A[2k+1] 處那麼第 k+1 節點的左子節點應緊臨 A[2k+1] 亦即位於 A[2k+2] = A[2(k+1)] 處(除非 2i 已大於 n 此時節點 i 無左子節點)

bull 由數學歸納法的原理得證定理 5-5 (2) (3) 由於 (2) 成立而且節點 i 的右子節點會緊臨

A[2i] 亦即其位於 A[2i+1]處除非 i 根本沒有右子節點得證定理 5-5 (3)

由 (2) 和 (3) 我們可知定理 5-5 (1) 成立

542 以鍵結串列表示二元樹bull 以陣列來表示完備二元樹算是相當方便但ndash一般的二元樹將導致陣列空間的浪費ndash如果二元樹中節點的新增刪除動作頻繁則會造成陣列對應資料的搬動隨而頻繁

bull 以鍵結串列表示二元樹

為方便辦識樹根我們常用 root 指標指向樹根做為樹鍵結串列的起點

陣列表示和鍵結表示的空間需求bull 完備二元樹的陣列表示和鍵結表示皆沒浪費儲存資料的空間

bull 但歪斜樹的陣列表示比起鍵結表示則浪費許多

bull 以 n 個節點的歪斜樹而言陣列表示法可能需要 2n-1 個陣列元素其中 2n-1-n 個陣列元素空間是浪費的而鍵結表示法只需要 n 個節點空間

二元樹節點的宣告程式 5-2

1 struct BTreeNode

2 struct BtreeNode leftchiled

3 int data

4 struct BTreeNode rightchild

5

6 struct BTreeNode root

bull 這樣的二元樹節點要取得兒子節點的指標十分方便但若有取得父節點的需求則嫌不足我們可以加上 parent 欄位指向父節點

55  二元樹的走訪bull 假如一組資料已用二元樹的組織在一起總有需求對其全數資料做動作例如計算所有數目印出所有資料在所有資料中搜尋某項資料hellip等此時即須對此二元樹做走訪 (traversal) 的運算利用走訪二元樹的同時將計算列印或搜尋的動作完成

bull 事實上走訪即在決定二元樹上資料被處理(計算列印或搜尋)的順序我們也希望走訪的演算法對任何節點皆一致是容易撰寫程式實作的

bull 若對一個二元樹上的節點而言 V 表示處理節點上的資料 L 表示走訪其左子樹 R 表示走訪其右子樹

55  二元樹的走訪 ( 續 )bull 因為對稱的緣故我們只考慮先走訪左邊再走訪右邊的情形那麼右圖只剩下三種走訪方式

bull 以 V 所在的相對位置分別對此三種走訪方式取名如下

(1) LVR 中序走訪 (inorder traversal)或中序表示法 (infix notation)

(2) LRV 後序走訪 (postorder traversal) 或後序表示法 (postfix notation)

(3) VLR 前序走訪 (preorder traversal)或前序表示法 (prefix notation)

551  中序走訪bull 利用遞迴的方式撰寫中序走訪的程序希望把二元樹中序走訪的

順序印出來

程式 5-3二元樹的中序走訪1 struct BTreeNode2 struct BTreeNode leftchild3 char data4 struct BTreeNode rightchild5 6 struct BTreeNode root7 void inorder(struct BTreeNode node)8 if (node = NULL)9 inorder(node-gtleftchild)10 cout ltlt node-gtdata11 inorder(node-gtrightchild)12 13

範例 5-12此為運算式二元樹

其對應中序表示運算式為 (A+B)C+D(E-F)

6 struct BTreeNode root7 void inorder(struct BTreeNode node)8 if (node = NULL)9 inorder(node-gtleftchild)10 cout ltlt node-gtdata11 inorder(node-gtrightchild)12 13

6 struct BTreeNode root7 void inorder(struct BTreeNode node)8 if (node = NULL)9 inorder(node-gtleftchild)10 cout ltlt node-gtdata11 inorder(node-gtrightchild)12 13

(A+B)C+D(E-F)

如何加上 ( )

552  後序走訪程式 5-4二元樹的後序走訪14 void postorder(struct BTreeNode node)

15 if (node =NULL)16 postorder(node-gtleftchild)17 postorder(node-gtrightchild)18 cout ltlt node-gtdata19 20

以後序走訪可得到AB+CDEF-+

其正為對應的後序運算式

553  前序走訪程式 5-5二元樹的前序走訪21 void preorder(struct BTreeNode node)

22 if (node = NULL)

23 cout ltlt node-gtdata

24 preorder(node-gtleftchild)

25 preorder(node-gtrightchild)

26

27

以前序走訪可得到++ABCD-EF

其正為對應的前序運算式

554 利用堆疊和迴圈取代遞迴進行二元樹的走訪1 struct BTreeNode 2 struct BTreeNode leftchild3 char data4 struct BtreeNode rightchild5 6 struct BTreeNode root7 struct StackNode8 struct BTreeNode treenode9 struct StackNode next1011 struct StackNode top

12 void push_node(struct BtreeNode node )13 struct StackNode old_top14 old_top = top15 top = (struct StackNode ) malloc

(sizeof(struct StackNode))16 top-gttreenode = node17 top-gtnext = old_top18 19 struct BTreeNode pop_node()20 struct BTreeNode Tnode21 struct StackNode old_top22 if (top == NULL)23 StackEmpty()24 else25 Tnode = top-gttreenode26 old_top = top27 top = top-gtnext28 free(old_top)29 return Tnode30 31

32 void inorder_Stack(struct BtreeNode node)33 do34 while (node = NULL)35 push_node(node)36 node = node-gtleftchild37 push all left children38 if (top = NULL)39 node = pop_node()40 cout ltlt node-gtdata41 node = node-gtrightchild42 inorder printing and check right child43 while ((top=NULL)||(node=NULL))44 top==NULL 堆疊已空 node==NULL沒有右子

6 struct BTreeNode root7 void inorder(struct BTreeNode node)8 if (node = NULL)9 inorder(node-gtleftchild)10 cout ltlt node-gtdata11 inorder(node-gtrightchild)12 13

中序表示為DBGEHAFIC

32 void inorder_Stack()33 struct BTreeNode node34 node = root35 do36 while (node = NULL)37 push_node(node)38 node = node-gtleftchild39 40 if (top = NULL)41 node = pop_node()42 cout ltlt node-gtdata43 node = node-gtrightchild44 45 while(top=NULL)||(node=NULL))46

45 void Preorder_Stack(struct BSTreeNode node)46 do47 while (node = NULL)48 cout ltlt node-gtdata)49 push_node(node)50 node = node-gtleftchild51 push all left children52 if (top = NULL)53 node = pop_node()54 node = node-gtrightchild55 56 while ((top=NULL)||(node=NULL))57

程式 5-5二元樹的前序走訪21 void preorder(struct BTreeNode node)22 if (node = NULL)23 cout ltlt node-gtdata24 preorder(node-gtleftchild)25 preorder(node-gtrightchild)26 27

58 void Postorder_Stack(struct BTreeNode node)59 do60 while (node = NULL)61 cout ltlt node-gtdata62 push_node(node)63 node = node-gtrightchild64 push all left children65 if (top = NULL)66 node = pop_node()67 node = node-gtleftchild68 69 while ((top=NULL)||(node=NULL))70

程式 5-4二元樹的後序走訪14 void postorder(struct BTreeNode node)15 if (node =NULL)16 postorder(node-gtleftchild)17 postorder(node-gtrightchild)18 cout ltlt node-gtdata19 20

555  階層走訪bull 「階層走訪」 (level-order traversal) 是依階層

的順序進行二元樹的走訪先走訪階層小的節點後走訪階層大的節點同一階層者則依自左向右的順序走訪

bull 對下圖的二元樹進行階層走訪的結果為ABCDEFGHI

bull 對同一階層而言先走訪的節點其子節點亦在下一階層中先被走訪

bull 這種「先進先出」的特性恰可用佇列予以儲存與處理

程式 5-7 利用佇列進行二元樹的階層走訪

1 struct BTreeNode2 struct BTreeNode leftchild3 char data4 struct BTreeNode rightchild5 6 struct BTreeNode root7 struct QNode8 struct BTreeNode treenode9 struct QNode next10 11 struct QNode front rear

12 void AddQueue(struct BTreeNode Tnode)13 struct QNode node14 node=(struct QNode )malloc(sizeof(struct QNode))15 node-gttreenode = Tnode16 node-gtnext = NULL17 if (front == NULL) front = node19 else rear-gtnext = node20 rear = node21

22 struct BTreeNode DeleteQueue()23 struct BTreeNode Tnode24 struct QNode old_front25 if (rear == NULL)26 QueueEmpty()27 else28 Tnode = front-gttreenode29 old_front = front30 front = front-gtnext31 free(old_front)32 return Tnode33 34 35 void LevelOrder (struct BTreeNode node)36 AddQueue(node)37 while (front =NULL)38 node = DeleteQueue()39 cout ltlt node-gtdata40 if (node-gtleftchild = NULL)41 AddQueue(node-gtleftchild)42 if (node-gtrightchild = NULL)43 AddQueue(node-gtrightchild)44 45

35 void LevelOrder (struct BTreeNode node)36 AddQueue(node)37 while (front =NULL)38 node = DeleteQueue()39 cout ltlt node-gtdata40 if (node-gtleftchild = NULL)41 AddQueue(node-gtleftchild)42 if (node-gtrightchild = NULL)43 AddQueue(node-gtrightchild)44 45

bull Infix ABCbull Postfix ABC CBA ACB BCA

BAC CAB

B

A CA

B

C

B

A

C

C

B

AB

A

C

二元樹中序與前序式中序與後序式bull 每一棵二元樹皆有唯一的一對中序與前序表示法也有唯一的中序與後序次序

bull 換句話說給予一對中序與前序或中序與後序即可決定一棵二元樹

bull 然而給予前序和後序次序並不能決定唯一的二元樹可能會產生兩棵不同的二元樹

bull 想想階層走訪和中序走訪順序可否決定唯一的二元樹

A

B C

H J D E

F G

prefix ABHJCDFGEinfix HBJAFDGCEpostfix HJBFGDECA

VLRLVRLRV

A

prefix BHJinfix HBJ

prefix CDFGEinfix FDGCE

prefix ABHJCDFGEinfix HBJAFDGCE

A

B C

H J D E

F G

prefix ABHJCDFGE

infix HBJAFDGCEk-1

struct BTreeNode BTree_InfixPrefix (AnsiString infix AnsiString prefix)

int k struct BTreeNode node if (infixLength==0) return NULLnode = (struct BTreeNode ) malloc (sizeof(struct BTreeNode))node-gtdata = prefix[1]k = find(prefix[1] infix)node-gtleftchild = BTree_InfixPrefix

(infix[1k-1] prefix[2k])node-gtrightchild = BTree_InfixPrefix(infix[k+1infixLength]prefix[k+1prefixLength])

VLR

LVRk1

k2 k+1~

k+1~

1

struct BTreeNode BTree_InfixPostfix (AnsiString infix AnsiString prefix)

int k struct BTreeNode node if (infixLength==0) return NULLnode = (struct BTreeNode ) malloc (sizeof(struct BTreeNode))node-gtdata = postfix[postfixLength]k = find(postfix[postfixLength] infix)node-gtleftchild = BTree_InfixPrefix

(infix[1k-1] postfix[1k-1])node-gtrightchild = BTree_InfixPrefix(infix[k+1infixLength]postfix[k+1postfixLength-1])

56  二元樹的複製與相等測試程式 5-8 二元樹的複製1 struct BTreeNode2 struct BTreeNode leftchild3 char data4 struct BTreeNode rightchild5 6 struct BTreeNode root7 struct BTreeNode Copy(struct BTreeNode TreeRoot)8 struct BTreeNode CopyRoot9 if (TreeRoot == NULL)10 return NULL11 CopyRoot =(struct BTreeNode ) malloc (sizeof(BTreeNode))12 CopyRoot-gtdata = TreeRoot-gtdata13 CopyRoot-gtleftchild = Copy(TreeRoot-gtleftchild)14 CopyRoot-gtrightchild = Copy(TreeRoot-

gtrightchild)15 return CopyRoot16

程式 5-9 檢測兩棵二元樹是否相等1 struct BTreeNode2 struct BTreeNode leftchild3 char data4 struct BTreeNode rightchild5 6 struct BtreeNode rightchild7 int Equal(struct BTreeNode ustruct BTreeNode v)8 if ((u==NULL) ampamp (v==NULL)) return 19 if ((u=NULL) ampamp (v=NULL)10 ampamp (u-gtdata == v-gtdata )11 ampamp Equal(u-gtleftchildv-gtleftchild)12 ampamp Equal(u-gtrightchildv-gtrightchild))

13 return 114 return 015

Counting Nodes in a Binary Tree

int CountingNodes(struct BTreeNodes node)

if (node == NULL) return 0 int leftchildren = CountingNodes(node-gtleftchild)

int rightchildren= CountingNodes(node-gtrightchild)

return leftchildren+rightchildren

Counting Leaves in a Binary Tree

int CountingLeaves(struct BTreeNodes node)

if (node == NULL) return 0 int leftleaves = CountingLeaves(node-gtleftchild)

int rightchildren= CountingLeaves(node-gtrightchild)

if (leftleaves==0 ampamp leftleaves==0) return 1

return leftleaves + leftleaves

57  引線二元樹bull 在二元樹的鍵結表示中樹葉節點的兩個子樹指標皆指向 NULL 由定理 5-1 得知 n = E+1 其中 n 為節點個數 E 為分支數

bull 而 n 個鍵結節點有 2n 個指標空間每個分支恰佔用一個指標空間共有 E (=n-1) 個指標有用到(不是空的)而共有 n+1 個指標空間是 NULL 比非空的節點還多

bull 有學者提出對這些「放空指標的空間」加以利用的概念mdash與其放空指標不如放指向其它節點的指標稱之為引線 (thread) 使得某些運算(如走訪hellip等)可以加快

bull 這個概念發展出了引線二元樹 (threaded binary tree) 這種資料結構

範例 5-17下圖為加了引線的二元樹

加入的引線將使二元樹的中序走訪更加便利沿著上右圖箭頭所指示的順序走訪即可完成此二元樹的中序走訪GDHBAIECF 為此二元樹的中序表示

bull 範例 5-17 中所加的引線目的在方便二元樹的中序走訪

bull 對樹葉節點 p 而言令其資料為 P 則引線指標的規則如下

(1) p 的左子樹指標指向中序表示中 P 的前一個 元素(前接元素 (predecessor) )

(2) p 的右子樹指標指向中序表示中 P 的後一個 元素(後繼元素 (successor) )

為了區別節點指標放的是一般指標(內部節點)抑或是引線指標(樹葉節點)我們另以 2 個欄位分別區別左和右子樹指標空間存放的對象其節點的記憶體配置有如下圖所示

其中 leftthread (rightthread) 為 0 時表示 leftchild (rightchild) 存放的是一般節點指標而 leftthread (rightthread) 為 1 時表示 leftchild (rightchild) 存放的是引線指標1 struct TBTreeNode2 int leftthread3 struct TBTreeNode leftchild4 char data5 struct TBTreeNode rightchild6 int rightthread7

由右側的引線二元樹可知有最左和最右兩條引線尚無妥善的安排我們另設計一空的引線節點做為樹根如下圖所示

571  引線二元樹的中序走訪決定節點 p 在中序表示中的後繼節點 q (簡稱中序後繼點)

程式 5-10決定節點 node的中序後繼點1 struct TBTreeNode2 int leftthread3 struct TBTreeNode leftchild4 char data5 struct TBTreeNode rightchild6 int rightthread7 8 struct TBTreeNode root9 struct TBTreeNode Next(struct TBTreeNode node)

10 struct TBTreeNode temp11 temp = node-gtrightchild12 if (node-gtrightthead) return temp13 while (temp-gtleftthead) temp=temp-gtleftchild

14 return temp15

程式 5-11引線二元樹的中序表示(接程式 5-10)1 void InorderTBTree()2 struct TBTreeNode node3 for(node=Next(root)node=rootnode=Next(node))

4 cout ltlt node-gtdata5

bull只要 node 是引線二元樹上的任一節點透過呼叫 Next(node) 即可找到其在中序表示中的後繼節點bull(試想若程式 5-10 中的程序 Next 所傳入的參數指標正是 root Next(root) 是否可正常運作請思考)bull於是只須從中序表示中的第一節點起讓每一節點 node 都執行 Next(node) 程序即可得引線二元樹的中序表示

572  在引線二元樹中加入節點在引線二元樹中樹葉節點節點的右子樹指標所指向或內部節點右子樹的最左樹葉為其中序後繼節點而且任一節點若無左子樹則其左子樹指標乃指向其中序前接節點hellip這些性質必須在節點新增進入引線二元樹時也要保持接下來討論節點 new 插入成為引線二元樹的節點 p 的右子節點的情形至於插入成為左子節點的情形則讓各位模擬推敲

572572  在引線二元樹中加入節點 在引線二元樹中加入節點 (( 續續 ))

newnew 會成為會成為 pp 的右子節點的情況共有以下兩種的右子節點的情況共有以下兩種(1) (1) 若若 pp 沒有右子節點 沒有右子節點 ((p-gtrightthread p-gtrightthread 為為1) 1) 如圖如圖 ((a) a) 則 則 new new 插入後應形成如圖插入後應形成如圖 ((b) b) 所示所示

(2)(2) 若若 pp 有右子節點 有右子節點 ((p-gtrightthread p-gtrightthread 為為0) 0) 如圖如圖 ((c) c) 則 則 new new 插入後應形成如插入後應形成如圖圖 ((d) d) 所示所示

程式 5-12  插入節點 new 成為引線二元樹中節點 p的右子節點(上接程式 5-10 5-11 )

1 void InsertRight(struct TBTreeNode p 2 struct TBTreeNode new)3 struct TBTreeNode q4 new-gtrightchild = p-gtrightchild5 new-gtrightthead = p-gtrightthread6 new-gtleftchild = p7 new-gtleftthread = 18 p-gtrightchild = new9 p-gtrightthead = 010 if (new-gtrightthread)11 q = Next(new)12 q-gtleftchild = new13 14

58  堆積bull 「堆積」 (heap) 是一個特殊的完備二元樹節點的資料與其子節點的資料之間有事先定好的大小關係存在bull 定義ndash最大堆疊為一完備二元樹任一節點的資料內容不小於其子節點的資料內容ndash最小堆疊為一完備二元樹任一節點的資料內容不大於其子節點的資料內容

581  插入節點至最大堆積中最大堆積是一完備二元樹所以如圖 (a) 之最大堆積於加入新節點後樹的形狀必須如圖 (b) 所示灰色節點即為新節點若加入的節點資料分別為 1 9 12 則其最大堆積將分別變成如圖 (c) (d) (e) 所示

程式 5-13 新增節點至最大堆積中最大堆積本身是一棵完備二元樹可以用陣列表示之 1 define maxsize 1002 int heap[maxsize]3 int n i4 void InsertHeap(int x)5 if (n == maxsize) HeapFull()6 else7 n++8 i = n9 while ((i gt 1) ampamp (x gt heap[i2]))10 heap[i] = heap[i2]11 i = 212 13 heap[i] = x14 16

程序 InsertHeap 在插入第 n 個資料時需要的時間複雜度為 O(logn)

1 100

2 93

3 84

4 77

5 65

6 81

7 79

8 66

9 70

10 50

100

84

77

93

8165

66 70

79

50

1

2 3

4 5 6 7

8 9 10

1 define maxsize 1002 int heap[maxsize]3 int n i4 void InsertHeap(int x)5 if (n == maxsize) HeapFull()6 else7 n++8 i = n9 while ((i gt 1) ampamp (x gt heap[i2]))10 heap[i] = heap[i2]11 i = 212 13 heap[i] = x14 16

582 刪除最大堆積中的最大資料圖 (a) 中的最大堆積在刪除最大資料後其樹形應變成如圖 (b) 所示將最未一個樹葉節點的資料 6 暫挪至樹根節點內如圖 (c) 再將圖 (c) 中非最大堆積的結構調整成為最大堆積 10 和 8 中選大者與 6對調即成圖 (d)

程式 5-14 刪除最大堆積中的最大資料(上接程式 5-13 )1 int DeleteHeap()2 int x i j3 if (n == 0) HeapEmpty()4 else5 x = heap[1]6 heap[1] = heap[n]7 n--8 i = 19 while (i lt= n2)10 if (heap[2i] gt heap[2i+1])11 j = 2i12 else13 j = 2i+114 if (heap[i]gtheap[j]) break15 swap(heap[i] heap[j])16 i = j17 18 return x19 20

其時間複雜度與其堆積高度相同是為O(logn) 其中 n 為堆積中的元素個數

1 100

2 93

3 84

4 77

5 65

6 81

7 79

8 66

9 70

10 50

100

84

77

93

8165

66 70

79

50

1

2 3

4 5 6 7

8 9 10

1 int DeletHeap()2 int x i j3 if(n == 0) HeapEmpty()4 else5 x = heap[1]6 heap[1] = heap[n]7 n--8 i = 19 while (i lt= n2)10 if ([heap[2i]] gt heap[2i+1])11 j = 2i12 else13 j = 2i+114 if (heap[i]gtheap[j]) break15 swap(heap[i] heap[j])16 i = j17 18 return x19 20

583  優先佇列bull 由於最大堆積(或最小堆積)的結構可提供最大元素(或最小元素)而其維護堆積的成本為 O(logn) n 為總元素個數

bull 堆積常被當成「優先佇列」 (priority queue) 使用mdash此時佇列被組織成完備二元樹若新加入佇列的資料有大小關係優先順序的需求時此優先佇列即以 O(logn) 的代價將新資料放入優先佇列(可能是最大或最小堆積依實際大小關係優先順序的需求而定)中mdash提供依元素的優先順序做為處理順序的服務

bull 一般的佇列為先進先出先到的先行處理優先佇列則是依據其優先順序優先順序高者先行處理與來到的先後無關

範例 5-21bull 在「分時」 (time sharing) 的作業系統中我們不希望使用中央處理器 (Central Processing Unit CPU) 時間短的「處理程序」 (process) 等候過久於是有「最短時間工作優先」 (shortest-job-first) 的排程策略此時等候的處理程序即可組織成之一最小堆積而 CPU 每次即取出存於樹根的處理程序予以處理任何新來的處理程序則以 O(logn) 的時間加入此優先佇列(最小堆積)中

bull 注意之前提過的線性佇列新來的直接加入成為新的尾端元素花O(1) 的時間然線性佇列本身沒有大小的關係每次要取出最大(小)的資料得花O(n) 的代價(在所有元素中挑出最大(小)者) n 為佇列內的元素個數而優先佇列是在新元素加入時要花O(logn) 的時間至於取出最大(小)則只須 O(1) 的時間

59  二元搜尋樹bull 二元搜尋樹是一棵二元樹可能是空二元樹若不為空二元樹則滿足下列性質ndash所有節點內的資料內容是相異的 ndash左子節點的資內容 ( 如果有 ) 比父節點的資料內容小 ndash右子節點的資料內容 ( 如果有 ) 比父節點的資料內容大 ndash以左和右子節點為樹根的左子樹和右子樹也是二元搜尋樹

在二元搜尋樹中搜尋資料二元搜尋樹對資料大小的掌握十分明確若恰與節點 p 資料相等則搜尋成功若欲搜尋的資料 x 比樹節點 p 中的資料小 則只須搜尋節點 p 的左子樹若比節點 p 中的資料大 則只須搜尋 p 的右子樹這樣的判斷可以遞迴地應用在各子樹節點 中

程式 5-15 在二元搜尋樹中搜尋資料(遞迴呼叫)1 struct BSTreeNode2 struct BSTreeNode leftchild3 int data4 struct BSTreeNode rightchild5 6 struct BSTreeNode root7 struct BSTreeNode SearchBST(

struct BSTreeNode tree int x)8 if (tree == NULL) return NULL9 if (x == tree-gtdata) return tree10 if (x lt tree-gtdata)11 return SearchBST(tree-gtleftchild x)

12 return SearchBST(tree-gtrightchild x)13

程式 5-16 搜尋二元搜尋樹的資料(非遞迴呼叫)在二元搜尋樹十分龐大時遞迴呼叫的效率不會太好可利用迴圈來完成搜尋的動作以提高執行效率請看程式 5-16

14 struct BSTreeNode Search BST_iterative15 (struct BSTreeNode node int x)16 while (node = NULL)17 if (x == node-gtdata) return node18 if (x lt node-gtdata)19 node = node-gtleftchild20 else21 node = node-gtrightchild22 23 return NULL24

592  新增資料於二元搜尋樹中欲新增資料進入一二元搜尋樹中得先決定該資料在二元搜尋樹中應出現的位置這可透過上節提到的搜尋動作來解決

在二元搜尋樹定義的性質 (1) 中我們限定了元素必須相異所以搜尋新資料的結果必是 NULL

必須將搜尋的結果改為新增資料所應在位置的節點指標

程式 5-17  在二元搜尋樹中新增資料25 int InsertBST(int x)26 struct BSTreeNode p q27 p = root q = NULL28 while (p = NULL)29 q = p30 if (x == p-gtdata) return 0

若不考慮重複則可省略之31 if (x lt p-gtdata)32 p = p-gtleftchild33 else34 p = p-gtrightchild35

3636 p = (struct BSTreeNode ) p = (struct BSTreeNode ) malloc (sizeof(BSTreeNode))malloc (sizeof(BSTreeNode))

3737 p-gtdata = x p-gtdata = x3838 p-gtleftchild = p-gtrightchild = p-gtleftchild = p-gtrightchild = NULLNULL3939 if (root == NULL) root = p if (root == NULL) root = p4040 else if (x lt q-gtdata) else if (x lt q-gtdata)4141 q-gtleftchild = p q-gtleftchild = p4242 else else4343 q-gtrightchild = p q-gtrightchild = p4444 return 1 return 145 45

程式 程式 5-175-17  在二元搜尋樹中新增資料  在二元搜尋樹中新增資料 (( 續續 ))

範例 5-23 圖 (b) 為圖 (a) 新增整數 6 之後的結果圖 (c) (d) (e) (f) 則分別是逐步再加入 28 18

10 21 的結果

bull若目前的二元搜尋樹高度為 h 則新增資料得花O(h) 的時間找到其所應在的位置遂新增資料至二元搜尋樹中的時間複雜之度為 O(h)

593 自二元搜尋樹中刪除資料欲自二元搜尋樹中刪除資料可能會面臨兩種情況(1) 刪除節點 p 位於樹葉節點上如下圖所示 這是比較容易處理的情況

(2) 刪除的節點 p 不是樹葉刪除節點 p 後原 p 所指的節點可由 q ( q 中包含了 p 左子樹中的最大節點值)或 r ( r 中包含了 p 右子樹中的最小節點值)來取代以維持二元搜尋樹中資料的大小關係其結果分別如圖 (b) 和 (c) 所示而圖 (d) 則標明了指標會改變的節點位置即 p 會消失 father 的左或右子樹指標會異動 k(可能是 q 或 r )會移到原來 p 的位置其父節點 f 的左或右子樹指標會異動其中 father 為 p 父節點 f 為 k 的父節點

程式 5-18 自二元搜尋樹中刪除資料46 int DeleteBST (int x)47 Struct BSTreeNode p father k f48 p = root father = NULL49 while (p = NULL)50 if (x == p-gtdata) 找到 x 所在的節點 p 了51 if ((p-gtleftchild == NULL) ampamp 52 (p-gtrightchild == NULL))53 k = NULL p為樹葉節點54 else if (p-gtleftchild = NULL )x位於非子節

點55 f = p 找出 p左子樹的最右樹葉56 k = p-gtleftchild57 while (k-gtrightchild =NULL)58 f = k59 k = k-gtrightchild60 k將挪至原 p處61 if (p == f)62 f-gtleftchild = k-gtleftchild 63 else64 f-gtrightchild = k-gtleftchild 65

66 else p無左子樹67 f = p 找出 p右子樹的最左樹葉68 k = p-gtrightchild69 while (k-gtleftchild = NULL)70 f = k71 k = k -gtleftchild72 73 if (p == f)74 f-gtrightchild = k-gtrightchild 75 else76 f-gtleftchild = k-gtrightchild77 78 if (k = NULL) k挪至原 p處繼承 p的左右指標79 k-gtleftchild = p-gtleftchild80 k-gtrightchild = p-gtrightchild81 82 if (father == NULL) root = k83 else if (x lt father-gtdata) 決定 k是 father的左

或右兒子84 father-gtleftchild = k85 else86 father-gtrightchild = k87 free(p)88 return 1 成功地刪除 x於此傳回 1返回呼叫處89 51~89行為找到 x時應有的處理步驟

90 else 尚未找到 x繼續往下一階層找

91 father = p92 if (x lt p-gtdata)93 p = p-gtleftchild94 else95 p = p-gtrightchild96 97 98 return 0 未找到 x傳回 099

510  樹林 (forest)

bull 定義樹林(森林)是由 n 棵沒有交集的樹所構成的集合 n 0

下圖為一包含三棵樹的樹林

5101 轉換樹林為二元樹 之前我們以二元樹來表示樹因為二元樹有較多

好用的性質在此我們也將樹林轉換成二元樹看待

在 522 節中我們嘗試了用左子右兄弟的方式來表示樹這個方法事實上已將樹轉換成二元樹

即用左子右兄弟的結構重新組織樹林中的每一棵樹這些樹的樹根將視為兄弟而最左邊的樹則視為樹林二元樹的樹根如此一來樹林即可轉換成二元樹

範例 5-25右下圖為右上圖三棵樹所對應的三棵左子右兄弟二元樹

下圖則為上圖樹林所對應的一棵樹林二元樹

演算法 5-1  轉換樹林成二元樹輸入樹林 F = T1 T2 Tn

輸出 F 的二元樹表示 BT=(T1 T2 Tn)1 if (n == 0) 輸出空二元樹 2 else

3 root(T1 T2 Tn) = T1

4 leftsubtree(T1 T2 Tn) =

BT(T11 T12 T1m)5 BT(T11 T12 T1m)為 T1的子樹6 rightsubtree(T1 T2 Tn) =

BT(T2 T3 Tn)

樹林轉換成二元樹的過程樹林轉換成二元樹的過程

  • Slide 1
  • Slide 2
  • Slide 3
  • Slide 4
  • Slide 5
  • Slide 6
  • Slide 7
  • Slide 8
  • Slide 9
  • Slide 10
  • Slide 11
  • Slide 12
  • Slide 13
  • Slide 14
  • Slide 15
  • Slide 16
  • Slide 17
  • Slide 18
  • Slide 19
  • Slide 20
  • Slide 21
  • Slide 22
  • Slide 23
  • Slide 24
  • Slide 25
  • Slide 26
  • Slide 27
  • Slide 28
  • Slide 29
  • Slide 30
  • Slide 31
  • Slide 32
  • Slide 33
  • Slide 34
  • Slide 35
  • Slide 36
  • Slide 37
  • Slide 38
  • Slide 39
  • Slide 40
  • Slide 41
  • Slide 42
  • Slide 43
  • Slide 44
  • Slide 45
  • Slide 46
  • Slide 47
  • Slide 48
  • Slide 49
  • Slide 50
  • Slide 51
  • Slide 52
  • Slide 53
  • Slide 54
  • Slide 55
  • Slide 56
  • Slide 57
  • Slide 58
  • Slide 59
  • Slide 60
  • Slide 61
  • Slide 62
  • Slide 63
  • Slide 64
  • Slide 65
  • Slide 66
  • Slide 67
  • Slide 68
  • Slide 69
  • Slide 70
  • Slide 71
  • Slide 72
  • Slide 73
  • Slide 74
  • Slide 75
  • Slide 76
  • Slide 77
  • Slide 78
  • Slide 79
  • Slide 80
  • Slide 81
  • Slide 82
  • Slide 83
  • Slide 84
  • Slide 85
  • Slide 86
  • Slide 87
  • Slide 88
  • Slide 89
  • Slide 90
  • Slide 91
  • Slide 92
  • Slide 93
  • Slide 94
  • Slide 95
  • Slide 96
  • Slide 97
  • Slide 98
  • Slide 99
  • Slide 100
  • Slide 101
  • Slide 102
  • Slide 103
Page 11: 第五章 樹

範例 5-1 以一般化串列表示樹bull (A (B (E K) (F L M) G) (C H) (D (I N) (J

O P)))

(A T1 T2 T3)T1 = (B (E K) (F L M) G)T2 = (C H)T3 = (D (I N) (J O P))

T1 T2T3

程式 5-1 一般化串列鍵結節點的宣告1 struct TreeNode2 int tag3 union4 int data5 struct TreeNode Tlink6 node7 struct TreeNode link8

(A T1 T2 T3)T1 = (B (E K) (F L M) G)T2 = (C H)T3 = (D (I N) (J O P))

522  左子右兄弟表示法

每個節點都有唯一的最左 兒子 (leftmost child) 每個節點都有最靠近它的 右兄弟

523 分支度為 2 的二元樹表示法bull 分支度為 2 的樹又稱為二元樹 (binary tree)

bull 二元樹中任一節點皆有 2個指標分別指向該節點的左子樹和右子樹

bull 二元樹可以表示任何樹事實上二元樹有許多很好的性質我們在一下節中介紹

bull注意樹狀結構中兒子的順序是不重要的試想樹可以樹根為軸心旋轉使得兒子的順序是不固定的

53  二元樹bull 二元樹是一個由節點組成的有限集合可以是空集合或是包含了(1) 一個樹根節點(2) 其它節點分割成兩個沒有交集的二元樹

其一為左子樹 (left subtree) 另一為右子樹 (right subtree) 樹和二元樹之間的差異

(1)樹不允許空節點而空二元樹是允許的

(2) 樹的子樹沒有順序而二元樹的子樹有左右之分

531  樹和二元樹的基本性質

樹或二元樹皆擁有下面的性質

定理 5-1 若一棵樹 T 的總節點數為 V 總邊數為

E 則V = E + 1

證明利用數學歸納法歸納法的基礎當 V=1 時即 T 只有一個樹根節點 E 為 0 所以 V = E + 1 成立歸納法的假設假設當 1 V K 時該式成立歸納法的推論當 V = K + 1 時令此樹的樹根為 R 而 R 有 m 個子樹 T1 T2 hellip

Tm (如圖 5-12 所示)分別有個 V1 V2 hellip Vm 個節點其中 V1 +V2 + hellip+ Vm =K 若 E1 E2 hellip Em 分別為此 m 個子樹的邊數則

E = E1 +E2 + hellip+ Em + m (1)根據歸納法的假設可知

V1 = E1+1V2 = E2+1 Vm = Em+1

所以V1 +V2 + hellip+Vm = E1 + E2 + hellip+ Em +m (2)

然而V = V1 +V2 + hellip+Vm+1 (3)

將式 (2) 代入式 (3) 可得V = E1 + E2 + hellip+ Em + m + 1

引用式 (1) 則可得V = E + 1

由數學歸納法的原理可得證本定理

532 二元樹的性質(a) 稱為歪斜樹 (skew tree) (b) 稱為完備二元樹 (complete binary tree)

( 其樹葉節點皆在相鄰階層完整定義在後 ) 同樣數目的樹節點存放在歪斜樹上會花較多的階層放在完備二元樹上則只須最少的階層

532 二元樹的性質 ( 續 )定理 5-2 (1) 二元樹上階層 i 的節點數目最多為 2i-1 i 1 (2) 深度為 k 的二元樹其節點數目最多為 2k-

1 k1 bull 定理 5-2說明了 (1) 每一階層上可放的最多節點數

(2) 階層數已知時二元樹可放的最多節點數證明 (1) 利用數學歸納法歸納法的基礎當 i=1 時只有樹根一個節點 2i-1=21-1=1 原式成

立歸納法的假設假設 i = k 時該式成立即階層 k 的最大節點數為

2k-1 k 1 歸納法的推論當 i = k+1 時因在階層 k 的每一個節點最多會有

2 個子節點遂在 k+1 階層最多會有 22k-1 = 2k

= 2i-1 節點由數學歸納法的原理可知本定理得證(2) 由 (1) 可知二元樹在階層 i 的最多節點數為 2i-1那麼深度 k 的二元樹最多可有節點如下

k

i

ki

1

1-1- 24212 1-2

)12(1 k= 2k-1

=

bull 對二元樹而言任一節點的分支度可能為 0 1 或 2 分支度為 0 者是為樹葉

bull 樹葉的數目與分支度為 2 的節點數目存在著有趣的關係請見定理 5-3

定理 5-3 若 T 為一非空的二元樹 n0 為樹葉節點數目 n2為分支度為 2 的節點數目則 n0 = n2+1

證明令 n 為所有節點數目而 n1 為分支度為 1 的節點數目可得 n = n0 + n1 +n2 (1)令 B 為二元樹的分支數除了樹根外每一節點皆有一分支指向之所以

n = B+1 (亦可參考定理 5-1 )又 B = n1+2n2

且 n = n1 + 2n2 +1 (2)由式 (1) 和式 (2) 我們得到

n0 = n2 + 1

完滿二元樹 (full binary tree)

定義一個深度為 k 的完滿二元樹即為一深度為 k 且有 2k-1 個節點的二元樹k 0

bull 由定理 5-2(2) 可知深度為 k 的二元樹其最多節點數為 2k-1 這樣的樹會將樹上所有可能存放節點的位置都放滿完滿 (full) 之名因而生成

完備二元樹 (complete binary tree)我們可對二元樹上節點予以編號階層小者先編同一階

層則自左向右編號這樣的編號方式使我們定義出完備(complete) 二元樹

定義深度為 k 節點數為 n 的二元樹稱為完備二元樹若且唯若 (1) k = 1 時樹有一個節點

(2) 當 k 2 且 1iltk 時深度 i 有 2i-1 個節點且第 k 層的節點皆由第 k-1 層的分支由左至右逐一連接而成

完滿或完備二元樹之所以受人注意乃因它們可以將節點以較少的階層數存放

定理 5-4 n 個節點的完備或完滿二元樹其深度為 log2(n+1)

證明 完滿或完備二元樹的節點皆自階層

1 起排放任一階層 i 可排放 2i-1

個節點遂由定理 5-2 得知此 n 個節點所佔的深度為 log2(n+1)

正規二元樹 (formal binary tree)bull 在二元樹中非樹葉的節點又稱為內部節點

(internal node) 若所有內部節點恰有 2 個子節點即成為正規二元樹

定義若二元樹中所有內部節點恰有 2 個子節點則稱之為正規二元樹

正規二元樹常被引用在單淘汰賽正規二元樹常被引用在單淘汰賽制的各項賽程安排樹葉為參賽制的各項賽程安排樹葉為參賽隊伍內部節點為優勝隊伍樹隊伍內部節點為優勝隊伍樹根即為冠軍隊伍根即為冠軍隊伍

在單淘汰賽制中自在單淘汰賽制中自 n n 個隊伍中產個隊伍中產生冠軍須舉辦 生冠軍須舉辦 nn-1-1場比賽場比賽

54  二元樹的表示法541 以陣列表示二元樹定理 5-5 若一個具有 n 個節點的完備二元樹以循序的方式編號並依序存放在陣列 A 中即第 i 個節點放在 A[i] 中 1 i n 則

(1) 節點 i 的父節點位在 A[i2]處 i ne 1( i = 1 時其節點正為樹根無父節點)

(2) 若 2i n 則節點 i 的左兒子節點位在 A[2i] 處若 2i gtn 節點 i 沒有左子節點

(3) 若 2i+1 n 節點 i 的右兒子節點位 A[2i+1]處若 2i+1gtn 節點 i 沒有左子

節點

證明先用數學歸納法證明 (2) bull 歸納法的基礎當 i = 1 時其左子節點確實在 A[2] 處(除非 nlt2 此時節點 1 沒有左子節點)

bull 歸納法的假設假設當 I = k 時其左子節點確在 A[2i] = A[2k] 處(除非 2i 已大於 n 此時節點 i 無左子節點)

bull 歸納法的推論當 i = k+1 時因第 k 節點的左子節點在 A[2k] 處其右子節點則位在 A[2k+1] 處那麼第 k+1 節點的左子節點應緊臨 A[2k+1] 亦即位於 A[2k+2] = A[2(k+1)] 處(除非 2i 已大於 n 此時節點 i 無左子節點)

bull 由數學歸納法的原理得證定理 5-5 (2) (3) 由於 (2) 成立而且節點 i 的右子節點會緊臨

A[2i] 亦即其位於 A[2i+1]處除非 i 根本沒有右子節點得證定理 5-5 (3)

由 (2) 和 (3) 我們可知定理 5-5 (1) 成立

542 以鍵結串列表示二元樹bull 以陣列來表示完備二元樹算是相當方便但ndash一般的二元樹將導致陣列空間的浪費ndash如果二元樹中節點的新增刪除動作頻繁則會造成陣列對應資料的搬動隨而頻繁

bull 以鍵結串列表示二元樹

為方便辦識樹根我們常用 root 指標指向樹根做為樹鍵結串列的起點

陣列表示和鍵結表示的空間需求bull 完備二元樹的陣列表示和鍵結表示皆沒浪費儲存資料的空間

bull 但歪斜樹的陣列表示比起鍵結表示則浪費許多

bull 以 n 個節點的歪斜樹而言陣列表示法可能需要 2n-1 個陣列元素其中 2n-1-n 個陣列元素空間是浪費的而鍵結表示法只需要 n 個節點空間

二元樹節點的宣告程式 5-2

1 struct BTreeNode

2 struct BtreeNode leftchiled

3 int data

4 struct BTreeNode rightchild

5

6 struct BTreeNode root

bull 這樣的二元樹節點要取得兒子節點的指標十分方便但若有取得父節點的需求則嫌不足我們可以加上 parent 欄位指向父節點

55  二元樹的走訪bull 假如一組資料已用二元樹的組織在一起總有需求對其全數資料做動作例如計算所有數目印出所有資料在所有資料中搜尋某項資料hellip等此時即須對此二元樹做走訪 (traversal) 的運算利用走訪二元樹的同時將計算列印或搜尋的動作完成

bull 事實上走訪即在決定二元樹上資料被處理(計算列印或搜尋)的順序我們也希望走訪的演算法對任何節點皆一致是容易撰寫程式實作的

bull 若對一個二元樹上的節點而言 V 表示處理節點上的資料 L 表示走訪其左子樹 R 表示走訪其右子樹

55  二元樹的走訪 ( 續 )bull 因為對稱的緣故我們只考慮先走訪左邊再走訪右邊的情形那麼右圖只剩下三種走訪方式

bull 以 V 所在的相對位置分別對此三種走訪方式取名如下

(1) LVR 中序走訪 (inorder traversal)或中序表示法 (infix notation)

(2) LRV 後序走訪 (postorder traversal) 或後序表示法 (postfix notation)

(3) VLR 前序走訪 (preorder traversal)或前序表示法 (prefix notation)

551  中序走訪bull 利用遞迴的方式撰寫中序走訪的程序希望把二元樹中序走訪的

順序印出來

程式 5-3二元樹的中序走訪1 struct BTreeNode2 struct BTreeNode leftchild3 char data4 struct BTreeNode rightchild5 6 struct BTreeNode root7 void inorder(struct BTreeNode node)8 if (node = NULL)9 inorder(node-gtleftchild)10 cout ltlt node-gtdata11 inorder(node-gtrightchild)12 13

範例 5-12此為運算式二元樹

其對應中序表示運算式為 (A+B)C+D(E-F)

6 struct BTreeNode root7 void inorder(struct BTreeNode node)8 if (node = NULL)9 inorder(node-gtleftchild)10 cout ltlt node-gtdata11 inorder(node-gtrightchild)12 13

6 struct BTreeNode root7 void inorder(struct BTreeNode node)8 if (node = NULL)9 inorder(node-gtleftchild)10 cout ltlt node-gtdata11 inorder(node-gtrightchild)12 13

(A+B)C+D(E-F)

如何加上 ( )

552  後序走訪程式 5-4二元樹的後序走訪14 void postorder(struct BTreeNode node)

15 if (node =NULL)16 postorder(node-gtleftchild)17 postorder(node-gtrightchild)18 cout ltlt node-gtdata19 20

以後序走訪可得到AB+CDEF-+

其正為對應的後序運算式

553  前序走訪程式 5-5二元樹的前序走訪21 void preorder(struct BTreeNode node)

22 if (node = NULL)

23 cout ltlt node-gtdata

24 preorder(node-gtleftchild)

25 preorder(node-gtrightchild)

26

27

以前序走訪可得到++ABCD-EF

其正為對應的前序運算式

554 利用堆疊和迴圈取代遞迴進行二元樹的走訪1 struct BTreeNode 2 struct BTreeNode leftchild3 char data4 struct BtreeNode rightchild5 6 struct BTreeNode root7 struct StackNode8 struct BTreeNode treenode9 struct StackNode next1011 struct StackNode top

12 void push_node(struct BtreeNode node )13 struct StackNode old_top14 old_top = top15 top = (struct StackNode ) malloc

(sizeof(struct StackNode))16 top-gttreenode = node17 top-gtnext = old_top18 19 struct BTreeNode pop_node()20 struct BTreeNode Tnode21 struct StackNode old_top22 if (top == NULL)23 StackEmpty()24 else25 Tnode = top-gttreenode26 old_top = top27 top = top-gtnext28 free(old_top)29 return Tnode30 31

32 void inorder_Stack(struct BtreeNode node)33 do34 while (node = NULL)35 push_node(node)36 node = node-gtleftchild37 push all left children38 if (top = NULL)39 node = pop_node()40 cout ltlt node-gtdata41 node = node-gtrightchild42 inorder printing and check right child43 while ((top=NULL)||(node=NULL))44 top==NULL 堆疊已空 node==NULL沒有右子

6 struct BTreeNode root7 void inorder(struct BTreeNode node)8 if (node = NULL)9 inorder(node-gtleftchild)10 cout ltlt node-gtdata11 inorder(node-gtrightchild)12 13

中序表示為DBGEHAFIC

32 void inorder_Stack()33 struct BTreeNode node34 node = root35 do36 while (node = NULL)37 push_node(node)38 node = node-gtleftchild39 40 if (top = NULL)41 node = pop_node()42 cout ltlt node-gtdata43 node = node-gtrightchild44 45 while(top=NULL)||(node=NULL))46

45 void Preorder_Stack(struct BSTreeNode node)46 do47 while (node = NULL)48 cout ltlt node-gtdata)49 push_node(node)50 node = node-gtleftchild51 push all left children52 if (top = NULL)53 node = pop_node()54 node = node-gtrightchild55 56 while ((top=NULL)||(node=NULL))57

程式 5-5二元樹的前序走訪21 void preorder(struct BTreeNode node)22 if (node = NULL)23 cout ltlt node-gtdata24 preorder(node-gtleftchild)25 preorder(node-gtrightchild)26 27

58 void Postorder_Stack(struct BTreeNode node)59 do60 while (node = NULL)61 cout ltlt node-gtdata62 push_node(node)63 node = node-gtrightchild64 push all left children65 if (top = NULL)66 node = pop_node()67 node = node-gtleftchild68 69 while ((top=NULL)||(node=NULL))70

程式 5-4二元樹的後序走訪14 void postorder(struct BTreeNode node)15 if (node =NULL)16 postorder(node-gtleftchild)17 postorder(node-gtrightchild)18 cout ltlt node-gtdata19 20

555  階層走訪bull 「階層走訪」 (level-order traversal) 是依階層

的順序進行二元樹的走訪先走訪階層小的節點後走訪階層大的節點同一階層者則依自左向右的順序走訪

bull 對下圖的二元樹進行階層走訪的結果為ABCDEFGHI

bull 對同一階層而言先走訪的節點其子節點亦在下一階層中先被走訪

bull 這種「先進先出」的特性恰可用佇列予以儲存與處理

程式 5-7 利用佇列進行二元樹的階層走訪

1 struct BTreeNode2 struct BTreeNode leftchild3 char data4 struct BTreeNode rightchild5 6 struct BTreeNode root7 struct QNode8 struct BTreeNode treenode9 struct QNode next10 11 struct QNode front rear

12 void AddQueue(struct BTreeNode Tnode)13 struct QNode node14 node=(struct QNode )malloc(sizeof(struct QNode))15 node-gttreenode = Tnode16 node-gtnext = NULL17 if (front == NULL) front = node19 else rear-gtnext = node20 rear = node21

22 struct BTreeNode DeleteQueue()23 struct BTreeNode Tnode24 struct QNode old_front25 if (rear == NULL)26 QueueEmpty()27 else28 Tnode = front-gttreenode29 old_front = front30 front = front-gtnext31 free(old_front)32 return Tnode33 34 35 void LevelOrder (struct BTreeNode node)36 AddQueue(node)37 while (front =NULL)38 node = DeleteQueue()39 cout ltlt node-gtdata40 if (node-gtleftchild = NULL)41 AddQueue(node-gtleftchild)42 if (node-gtrightchild = NULL)43 AddQueue(node-gtrightchild)44 45

35 void LevelOrder (struct BTreeNode node)36 AddQueue(node)37 while (front =NULL)38 node = DeleteQueue()39 cout ltlt node-gtdata40 if (node-gtleftchild = NULL)41 AddQueue(node-gtleftchild)42 if (node-gtrightchild = NULL)43 AddQueue(node-gtrightchild)44 45

bull Infix ABCbull Postfix ABC CBA ACB BCA

BAC CAB

B

A CA

B

C

B

A

C

C

B

AB

A

C

二元樹中序與前序式中序與後序式bull 每一棵二元樹皆有唯一的一對中序與前序表示法也有唯一的中序與後序次序

bull 換句話說給予一對中序與前序或中序與後序即可決定一棵二元樹

bull 然而給予前序和後序次序並不能決定唯一的二元樹可能會產生兩棵不同的二元樹

bull 想想階層走訪和中序走訪順序可否決定唯一的二元樹

A

B C

H J D E

F G

prefix ABHJCDFGEinfix HBJAFDGCEpostfix HJBFGDECA

VLRLVRLRV

A

prefix BHJinfix HBJ

prefix CDFGEinfix FDGCE

prefix ABHJCDFGEinfix HBJAFDGCE

A

B C

H J D E

F G

prefix ABHJCDFGE

infix HBJAFDGCEk-1

struct BTreeNode BTree_InfixPrefix (AnsiString infix AnsiString prefix)

int k struct BTreeNode node if (infixLength==0) return NULLnode = (struct BTreeNode ) malloc (sizeof(struct BTreeNode))node-gtdata = prefix[1]k = find(prefix[1] infix)node-gtleftchild = BTree_InfixPrefix

(infix[1k-1] prefix[2k])node-gtrightchild = BTree_InfixPrefix(infix[k+1infixLength]prefix[k+1prefixLength])

VLR

LVRk1

k2 k+1~

k+1~

1

struct BTreeNode BTree_InfixPostfix (AnsiString infix AnsiString prefix)

int k struct BTreeNode node if (infixLength==0) return NULLnode = (struct BTreeNode ) malloc (sizeof(struct BTreeNode))node-gtdata = postfix[postfixLength]k = find(postfix[postfixLength] infix)node-gtleftchild = BTree_InfixPrefix

(infix[1k-1] postfix[1k-1])node-gtrightchild = BTree_InfixPrefix(infix[k+1infixLength]postfix[k+1postfixLength-1])

56  二元樹的複製與相等測試程式 5-8 二元樹的複製1 struct BTreeNode2 struct BTreeNode leftchild3 char data4 struct BTreeNode rightchild5 6 struct BTreeNode root7 struct BTreeNode Copy(struct BTreeNode TreeRoot)8 struct BTreeNode CopyRoot9 if (TreeRoot == NULL)10 return NULL11 CopyRoot =(struct BTreeNode ) malloc (sizeof(BTreeNode))12 CopyRoot-gtdata = TreeRoot-gtdata13 CopyRoot-gtleftchild = Copy(TreeRoot-gtleftchild)14 CopyRoot-gtrightchild = Copy(TreeRoot-

gtrightchild)15 return CopyRoot16

程式 5-9 檢測兩棵二元樹是否相等1 struct BTreeNode2 struct BTreeNode leftchild3 char data4 struct BTreeNode rightchild5 6 struct BtreeNode rightchild7 int Equal(struct BTreeNode ustruct BTreeNode v)8 if ((u==NULL) ampamp (v==NULL)) return 19 if ((u=NULL) ampamp (v=NULL)10 ampamp (u-gtdata == v-gtdata )11 ampamp Equal(u-gtleftchildv-gtleftchild)12 ampamp Equal(u-gtrightchildv-gtrightchild))

13 return 114 return 015

Counting Nodes in a Binary Tree

int CountingNodes(struct BTreeNodes node)

if (node == NULL) return 0 int leftchildren = CountingNodes(node-gtleftchild)

int rightchildren= CountingNodes(node-gtrightchild)

return leftchildren+rightchildren

Counting Leaves in a Binary Tree

int CountingLeaves(struct BTreeNodes node)

if (node == NULL) return 0 int leftleaves = CountingLeaves(node-gtleftchild)

int rightchildren= CountingLeaves(node-gtrightchild)

if (leftleaves==0 ampamp leftleaves==0) return 1

return leftleaves + leftleaves

57  引線二元樹bull 在二元樹的鍵結表示中樹葉節點的兩個子樹指標皆指向 NULL 由定理 5-1 得知 n = E+1 其中 n 為節點個數 E 為分支數

bull 而 n 個鍵結節點有 2n 個指標空間每個分支恰佔用一個指標空間共有 E (=n-1) 個指標有用到(不是空的)而共有 n+1 個指標空間是 NULL 比非空的節點還多

bull 有學者提出對這些「放空指標的空間」加以利用的概念mdash與其放空指標不如放指向其它節點的指標稱之為引線 (thread) 使得某些運算(如走訪hellip等)可以加快

bull 這個概念發展出了引線二元樹 (threaded binary tree) 這種資料結構

範例 5-17下圖為加了引線的二元樹

加入的引線將使二元樹的中序走訪更加便利沿著上右圖箭頭所指示的順序走訪即可完成此二元樹的中序走訪GDHBAIECF 為此二元樹的中序表示

bull 範例 5-17 中所加的引線目的在方便二元樹的中序走訪

bull 對樹葉節點 p 而言令其資料為 P 則引線指標的規則如下

(1) p 的左子樹指標指向中序表示中 P 的前一個 元素(前接元素 (predecessor) )

(2) p 的右子樹指標指向中序表示中 P 的後一個 元素(後繼元素 (successor) )

為了區別節點指標放的是一般指標(內部節點)抑或是引線指標(樹葉節點)我們另以 2 個欄位分別區別左和右子樹指標空間存放的對象其節點的記憶體配置有如下圖所示

其中 leftthread (rightthread) 為 0 時表示 leftchild (rightchild) 存放的是一般節點指標而 leftthread (rightthread) 為 1 時表示 leftchild (rightchild) 存放的是引線指標1 struct TBTreeNode2 int leftthread3 struct TBTreeNode leftchild4 char data5 struct TBTreeNode rightchild6 int rightthread7

由右側的引線二元樹可知有最左和最右兩條引線尚無妥善的安排我們另設計一空的引線節點做為樹根如下圖所示

571  引線二元樹的中序走訪決定節點 p 在中序表示中的後繼節點 q (簡稱中序後繼點)

程式 5-10決定節點 node的中序後繼點1 struct TBTreeNode2 int leftthread3 struct TBTreeNode leftchild4 char data5 struct TBTreeNode rightchild6 int rightthread7 8 struct TBTreeNode root9 struct TBTreeNode Next(struct TBTreeNode node)

10 struct TBTreeNode temp11 temp = node-gtrightchild12 if (node-gtrightthead) return temp13 while (temp-gtleftthead) temp=temp-gtleftchild

14 return temp15

程式 5-11引線二元樹的中序表示(接程式 5-10)1 void InorderTBTree()2 struct TBTreeNode node3 for(node=Next(root)node=rootnode=Next(node))

4 cout ltlt node-gtdata5

bull只要 node 是引線二元樹上的任一節點透過呼叫 Next(node) 即可找到其在中序表示中的後繼節點bull(試想若程式 5-10 中的程序 Next 所傳入的參數指標正是 root Next(root) 是否可正常運作請思考)bull於是只須從中序表示中的第一節點起讓每一節點 node 都執行 Next(node) 程序即可得引線二元樹的中序表示

572  在引線二元樹中加入節點在引線二元樹中樹葉節點節點的右子樹指標所指向或內部節點右子樹的最左樹葉為其中序後繼節點而且任一節點若無左子樹則其左子樹指標乃指向其中序前接節點hellip這些性質必須在節點新增進入引線二元樹時也要保持接下來討論節點 new 插入成為引線二元樹的節點 p 的右子節點的情形至於插入成為左子節點的情形則讓各位模擬推敲

572572  在引線二元樹中加入節點 在引線二元樹中加入節點 (( 續續 ))

newnew 會成為會成為 pp 的右子節點的情況共有以下兩種的右子節點的情況共有以下兩種(1) (1) 若若 pp 沒有右子節點 沒有右子節點 ((p-gtrightthread p-gtrightthread 為為1) 1) 如圖如圖 ((a) a) 則 則 new new 插入後應形成如圖插入後應形成如圖 ((b) b) 所示所示

(2)(2) 若若 pp 有右子節點 有右子節點 ((p-gtrightthread p-gtrightthread 為為0) 0) 如圖如圖 ((c) c) 則 則 new new 插入後應形成如插入後應形成如圖圖 ((d) d) 所示所示

程式 5-12  插入節點 new 成為引線二元樹中節點 p的右子節點(上接程式 5-10 5-11 )

1 void InsertRight(struct TBTreeNode p 2 struct TBTreeNode new)3 struct TBTreeNode q4 new-gtrightchild = p-gtrightchild5 new-gtrightthead = p-gtrightthread6 new-gtleftchild = p7 new-gtleftthread = 18 p-gtrightchild = new9 p-gtrightthead = 010 if (new-gtrightthread)11 q = Next(new)12 q-gtleftchild = new13 14

58  堆積bull 「堆積」 (heap) 是一個特殊的完備二元樹節點的資料與其子節點的資料之間有事先定好的大小關係存在bull 定義ndash最大堆疊為一完備二元樹任一節點的資料內容不小於其子節點的資料內容ndash最小堆疊為一完備二元樹任一節點的資料內容不大於其子節點的資料內容

581  插入節點至最大堆積中最大堆積是一完備二元樹所以如圖 (a) 之最大堆積於加入新節點後樹的形狀必須如圖 (b) 所示灰色節點即為新節點若加入的節點資料分別為 1 9 12 則其最大堆積將分別變成如圖 (c) (d) (e) 所示

程式 5-13 新增節點至最大堆積中最大堆積本身是一棵完備二元樹可以用陣列表示之 1 define maxsize 1002 int heap[maxsize]3 int n i4 void InsertHeap(int x)5 if (n == maxsize) HeapFull()6 else7 n++8 i = n9 while ((i gt 1) ampamp (x gt heap[i2]))10 heap[i] = heap[i2]11 i = 212 13 heap[i] = x14 16

程序 InsertHeap 在插入第 n 個資料時需要的時間複雜度為 O(logn)

1 100

2 93

3 84

4 77

5 65

6 81

7 79

8 66

9 70

10 50

100

84

77

93

8165

66 70

79

50

1

2 3

4 5 6 7

8 9 10

1 define maxsize 1002 int heap[maxsize]3 int n i4 void InsertHeap(int x)5 if (n == maxsize) HeapFull()6 else7 n++8 i = n9 while ((i gt 1) ampamp (x gt heap[i2]))10 heap[i] = heap[i2]11 i = 212 13 heap[i] = x14 16

582 刪除最大堆積中的最大資料圖 (a) 中的最大堆積在刪除最大資料後其樹形應變成如圖 (b) 所示將最未一個樹葉節點的資料 6 暫挪至樹根節點內如圖 (c) 再將圖 (c) 中非最大堆積的結構調整成為最大堆積 10 和 8 中選大者與 6對調即成圖 (d)

程式 5-14 刪除最大堆積中的最大資料(上接程式 5-13 )1 int DeleteHeap()2 int x i j3 if (n == 0) HeapEmpty()4 else5 x = heap[1]6 heap[1] = heap[n]7 n--8 i = 19 while (i lt= n2)10 if (heap[2i] gt heap[2i+1])11 j = 2i12 else13 j = 2i+114 if (heap[i]gtheap[j]) break15 swap(heap[i] heap[j])16 i = j17 18 return x19 20

其時間複雜度與其堆積高度相同是為O(logn) 其中 n 為堆積中的元素個數

1 100

2 93

3 84

4 77

5 65

6 81

7 79

8 66

9 70

10 50

100

84

77

93

8165

66 70

79

50

1

2 3

4 5 6 7

8 9 10

1 int DeletHeap()2 int x i j3 if(n == 0) HeapEmpty()4 else5 x = heap[1]6 heap[1] = heap[n]7 n--8 i = 19 while (i lt= n2)10 if ([heap[2i]] gt heap[2i+1])11 j = 2i12 else13 j = 2i+114 if (heap[i]gtheap[j]) break15 swap(heap[i] heap[j])16 i = j17 18 return x19 20

583  優先佇列bull 由於最大堆積(或最小堆積)的結構可提供最大元素(或最小元素)而其維護堆積的成本為 O(logn) n 為總元素個數

bull 堆積常被當成「優先佇列」 (priority queue) 使用mdash此時佇列被組織成完備二元樹若新加入佇列的資料有大小關係優先順序的需求時此優先佇列即以 O(logn) 的代價將新資料放入優先佇列(可能是最大或最小堆積依實際大小關係優先順序的需求而定)中mdash提供依元素的優先順序做為處理順序的服務

bull 一般的佇列為先進先出先到的先行處理優先佇列則是依據其優先順序優先順序高者先行處理與來到的先後無關

範例 5-21bull 在「分時」 (time sharing) 的作業系統中我們不希望使用中央處理器 (Central Processing Unit CPU) 時間短的「處理程序」 (process) 等候過久於是有「最短時間工作優先」 (shortest-job-first) 的排程策略此時等候的處理程序即可組織成之一最小堆積而 CPU 每次即取出存於樹根的處理程序予以處理任何新來的處理程序則以 O(logn) 的時間加入此優先佇列(最小堆積)中

bull 注意之前提過的線性佇列新來的直接加入成為新的尾端元素花O(1) 的時間然線性佇列本身沒有大小的關係每次要取出最大(小)的資料得花O(n) 的代價(在所有元素中挑出最大(小)者) n 為佇列內的元素個數而優先佇列是在新元素加入時要花O(logn) 的時間至於取出最大(小)則只須 O(1) 的時間

59  二元搜尋樹bull 二元搜尋樹是一棵二元樹可能是空二元樹若不為空二元樹則滿足下列性質ndash所有節點內的資料內容是相異的 ndash左子節點的資內容 ( 如果有 ) 比父節點的資料內容小 ndash右子節點的資料內容 ( 如果有 ) 比父節點的資料內容大 ndash以左和右子節點為樹根的左子樹和右子樹也是二元搜尋樹

在二元搜尋樹中搜尋資料二元搜尋樹對資料大小的掌握十分明確若恰與節點 p 資料相等則搜尋成功若欲搜尋的資料 x 比樹節點 p 中的資料小 則只須搜尋節點 p 的左子樹若比節點 p 中的資料大 則只須搜尋 p 的右子樹這樣的判斷可以遞迴地應用在各子樹節點 中

程式 5-15 在二元搜尋樹中搜尋資料(遞迴呼叫)1 struct BSTreeNode2 struct BSTreeNode leftchild3 int data4 struct BSTreeNode rightchild5 6 struct BSTreeNode root7 struct BSTreeNode SearchBST(

struct BSTreeNode tree int x)8 if (tree == NULL) return NULL9 if (x == tree-gtdata) return tree10 if (x lt tree-gtdata)11 return SearchBST(tree-gtleftchild x)

12 return SearchBST(tree-gtrightchild x)13

程式 5-16 搜尋二元搜尋樹的資料(非遞迴呼叫)在二元搜尋樹十分龐大時遞迴呼叫的效率不會太好可利用迴圈來完成搜尋的動作以提高執行效率請看程式 5-16

14 struct BSTreeNode Search BST_iterative15 (struct BSTreeNode node int x)16 while (node = NULL)17 if (x == node-gtdata) return node18 if (x lt node-gtdata)19 node = node-gtleftchild20 else21 node = node-gtrightchild22 23 return NULL24

592  新增資料於二元搜尋樹中欲新增資料進入一二元搜尋樹中得先決定該資料在二元搜尋樹中應出現的位置這可透過上節提到的搜尋動作來解決

在二元搜尋樹定義的性質 (1) 中我們限定了元素必須相異所以搜尋新資料的結果必是 NULL

必須將搜尋的結果改為新增資料所應在位置的節點指標

程式 5-17  在二元搜尋樹中新增資料25 int InsertBST(int x)26 struct BSTreeNode p q27 p = root q = NULL28 while (p = NULL)29 q = p30 if (x == p-gtdata) return 0

若不考慮重複則可省略之31 if (x lt p-gtdata)32 p = p-gtleftchild33 else34 p = p-gtrightchild35

3636 p = (struct BSTreeNode ) p = (struct BSTreeNode ) malloc (sizeof(BSTreeNode))malloc (sizeof(BSTreeNode))

3737 p-gtdata = x p-gtdata = x3838 p-gtleftchild = p-gtrightchild = p-gtleftchild = p-gtrightchild = NULLNULL3939 if (root == NULL) root = p if (root == NULL) root = p4040 else if (x lt q-gtdata) else if (x lt q-gtdata)4141 q-gtleftchild = p q-gtleftchild = p4242 else else4343 q-gtrightchild = p q-gtrightchild = p4444 return 1 return 145 45

程式 程式 5-175-17  在二元搜尋樹中新增資料  在二元搜尋樹中新增資料 (( 續續 ))

範例 5-23 圖 (b) 為圖 (a) 新增整數 6 之後的結果圖 (c) (d) (e) (f) 則分別是逐步再加入 28 18

10 21 的結果

bull若目前的二元搜尋樹高度為 h 則新增資料得花O(h) 的時間找到其所應在的位置遂新增資料至二元搜尋樹中的時間複雜之度為 O(h)

593 自二元搜尋樹中刪除資料欲自二元搜尋樹中刪除資料可能會面臨兩種情況(1) 刪除節點 p 位於樹葉節點上如下圖所示 這是比較容易處理的情況

(2) 刪除的節點 p 不是樹葉刪除節點 p 後原 p 所指的節點可由 q ( q 中包含了 p 左子樹中的最大節點值)或 r ( r 中包含了 p 右子樹中的最小節點值)來取代以維持二元搜尋樹中資料的大小關係其結果分別如圖 (b) 和 (c) 所示而圖 (d) 則標明了指標會改變的節點位置即 p 會消失 father 的左或右子樹指標會異動 k(可能是 q 或 r )會移到原來 p 的位置其父節點 f 的左或右子樹指標會異動其中 father 為 p 父節點 f 為 k 的父節點

程式 5-18 自二元搜尋樹中刪除資料46 int DeleteBST (int x)47 Struct BSTreeNode p father k f48 p = root father = NULL49 while (p = NULL)50 if (x == p-gtdata) 找到 x 所在的節點 p 了51 if ((p-gtleftchild == NULL) ampamp 52 (p-gtrightchild == NULL))53 k = NULL p為樹葉節點54 else if (p-gtleftchild = NULL )x位於非子節

點55 f = p 找出 p左子樹的最右樹葉56 k = p-gtleftchild57 while (k-gtrightchild =NULL)58 f = k59 k = k-gtrightchild60 k將挪至原 p處61 if (p == f)62 f-gtleftchild = k-gtleftchild 63 else64 f-gtrightchild = k-gtleftchild 65

66 else p無左子樹67 f = p 找出 p右子樹的最左樹葉68 k = p-gtrightchild69 while (k-gtleftchild = NULL)70 f = k71 k = k -gtleftchild72 73 if (p == f)74 f-gtrightchild = k-gtrightchild 75 else76 f-gtleftchild = k-gtrightchild77 78 if (k = NULL) k挪至原 p處繼承 p的左右指標79 k-gtleftchild = p-gtleftchild80 k-gtrightchild = p-gtrightchild81 82 if (father == NULL) root = k83 else if (x lt father-gtdata) 決定 k是 father的左

或右兒子84 father-gtleftchild = k85 else86 father-gtrightchild = k87 free(p)88 return 1 成功地刪除 x於此傳回 1返回呼叫處89 51~89行為找到 x時應有的處理步驟

90 else 尚未找到 x繼續往下一階層找

91 father = p92 if (x lt p-gtdata)93 p = p-gtleftchild94 else95 p = p-gtrightchild96 97 98 return 0 未找到 x傳回 099

510  樹林 (forest)

bull 定義樹林(森林)是由 n 棵沒有交集的樹所構成的集合 n 0

下圖為一包含三棵樹的樹林

5101 轉換樹林為二元樹 之前我們以二元樹來表示樹因為二元樹有較多

好用的性質在此我們也將樹林轉換成二元樹看待

在 522 節中我們嘗試了用左子右兄弟的方式來表示樹這個方法事實上已將樹轉換成二元樹

即用左子右兄弟的結構重新組織樹林中的每一棵樹這些樹的樹根將視為兄弟而最左邊的樹則視為樹林二元樹的樹根如此一來樹林即可轉換成二元樹

範例 5-25右下圖為右上圖三棵樹所對應的三棵左子右兄弟二元樹

下圖則為上圖樹林所對應的一棵樹林二元樹

演算法 5-1  轉換樹林成二元樹輸入樹林 F = T1 T2 Tn

輸出 F 的二元樹表示 BT=(T1 T2 Tn)1 if (n == 0) 輸出空二元樹 2 else

3 root(T1 T2 Tn) = T1

4 leftsubtree(T1 T2 Tn) =

BT(T11 T12 T1m)5 BT(T11 T12 T1m)為 T1的子樹6 rightsubtree(T1 T2 Tn) =

BT(T2 T3 Tn)

樹林轉換成二元樹的過程樹林轉換成二元樹的過程

  • Slide 1
  • Slide 2
  • Slide 3
  • Slide 4
  • Slide 5
  • Slide 6
  • Slide 7
  • Slide 8
  • Slide 9
  • Slide 10
  • Slide 11
  • Slide 12
  • Slide 13
  • Slide 14
  • Slide 15
  • Slide 16
  • Slide 17
  • Slide 18
  • Slide 19
  • Slide 20
  • Slide 21
  • Slide 22
  • Slide 23
  • Slide 24
  • Slide 25
  • Slide 26
  • Slide 27
  • Slide 28
  • Slide 29
  • Slide 30
  • Slide 31
  • Slide 32
  • Slide 33
  • Slide 34
  • Slide 35
  • Slide 36
  • Slide 37
  • Slide 38
  • Slide 39
  • Slide 40
  • Slide 41
  • Slide 42
  • Slide 43
  • Slide 44
  • Slide 45
  • Slide 46
  • Slide 47
  • Slide 48
  • Slide 49
  • Slide 50
  • Slide 51
  • Slide 52
  • Slide 53
  • Slide 54
  • Slide 55
  • Slide 56
  • Slide 57
  • Slide 58
  • Slide 59
  • Slide 60
  • Slide 61
  • Slide 62
  • Slide 63
  • Slide 64
  • Slide 65
  • Slide 66
  • Slide 67
  • Slide 68
  • Slide 69
  • Slide 70
  • Slide 71
  • Slide 72
  • Slide 73
  • Slide 74
  • Slide 75
  • Slide 76
  • Slide 77
  • Slide 78
  • Slide 79
  • Slide 80
  • Slide 81
  • Slide 82
  • Slide 83
  • Slide 84
  • Slide 85
  • Slide 86
  • Slide 87
  • Slide 88
  • Slide 89
  • Slide 90
  • Slide 91
  • Slide 92
  • Slide 93
  • Slide 94
  • Slide 95
  • Slide 96
  • Slide 97
  • Slide 98
  • Slide 99
  • Slide 100
  • Slide 101
  • Slide 102
  • Slide 103
Page 12: 第五章 樹

程式 5-1 一般化串列鍵結節點的宣告1 struct TreeNode2 int tag3 union4 int data5 struct TreeNode Tlink6 node7 struct TreeNode link8

(A T1 T2 T3)T1 = (B (E K) (F L M) G)T2 = (C H)T3 = (D (I N) (J O P))

522  左子右兄弟表示法

每個節點都有唯一的最左 兒子 (leftmost child) 每個節點都有最靠近它的 右兄弟

523 分支度為 2 的二元樹表示法bull 分支度為 2 的樹又稱為二元樹 (binary tree)

bull 二元樹中任一節點皆有 2個指標分別指向該節點的左子樹和右子樹

bull 二元樹可以表示任何樹事實上二元樹有許多很好的性質我們在一下節中介紹

bull注意樹狀結構中兒子的順序是不重要的試想樹可以樹根為軸心旋轉使得兒子的順序是不固定的

53  二元樹bull 二元樹是一個由節點組成的有限集合可以是空集合或是包含了(1) 一個樹根節點(2) 其它節點分割成兩個沒有交集的二元樹

其一為左子樹 (left subtree) 另一為右子樹 (right subtree) 樹和二元樹之間的差異

(1)樹不允許空節點而空二元樹是允許的

(2) 樹的子樹沒有順序而二元樹的子樹有左右之分

531  樹和二元樹的基本性質

樹或二元樹皆擁有下面的性質

定理 5-1 若一棵樹 T 的總節點數為 V 總邊數為

E 則V = E + 1

證明利用數學歸納法歸納法的基礎當 V=1 時即 T 只有一個樹根節點 E 為 0 所以 V = E + 1 成立歸納法的假設假設當 1 V K 時該式成立歸納法的推論當 V = K + 1 時令此樹的樹根為 R 而 R 有 m 個子樹 T1 T2 hellip

Tm (如圖 5-12 所示)分別有個 V1 V2 hellip Vm 個節點其中 V1 +V2 + hellip+ Vm =K 若 E1 E2 hellip Em 分別為此 m 個子樹的邊數則

E = E1 +E2 + hellip+ Em + m (1)根據歸納法的假設可知

V1 = E1+1V2 = E2+1 Vm = Em+1

所以V1 +V2 + hellip+Vm = E1 + E2 + hellip+ Em +m (2)

然而V = V1 +V2 + hellip+Vm+1 (3)

將式 (2) 代入式 (3) 可得V = E1 + E2 + hellip+ Em + m + 1

引用式 (1) 則可得V = E + 1

由數學歸納法的原理可得證本定理

532 二元樹的性質(a) 稱為歪斜樹 (skew tree) (b) 稱為完備二元樹 (complete binary tree)

( 其樹葉節點皆在相鄰階層完整定義在後 ) 同樣數目的樹節點存放在歪斜樹上會花較多的階層放在完備二元樹上則只須最少的階層

532 二元樹的性質 ( 續 )定理 5-2 (1) 二元樹上階層 i 的節點數目最多為 2i-1 i 1 (2) 深度為 k 的二元樹其節點數目最多為 2k-

1 k1 bull 定理 5-2說明了 (1) 每一階層上可放的最多節點數

(2) 階層數已知時二元樹可放的最多節點數證明 (1) 利用數學歸納法歸納法的基礎當 i=1 時只有樹根一個節點 2i-1=21-1=1 原式成

立歸納法的假設假設 i = k 時該式成立即階層 k 的最大節點數為

2k-1 k 1 歸納法的推論當 i = k+1 時因在階層 k 的每一個節點最多會有

2 個子節點遂在 k+1 階層最多會有 22k-1 = 2k

= 2i-1 節點由數學歸納法的原理可知本定理得證(2) 由 (1) 可知二元樹在階層 i 的最多節點數為 2i-1那麼深度 k 的二元樹最多可有節點如下

k

i

ki

1

1-1- 24212 1-2

)12(1 k= 2k-1

=

bull 對二元樹而言任一節點的分支度可能為 0 1 或 2 分支度為 0 者是為樹葉

bull 樹葉的數目與分支度為 2 的節點數目存在著有趣的關係請見定理 5-3

定理 5-3 若 T 為一非空的二元樹 n0 為樹葉節點數目 n2為分支度為 2 的節點數目則 n0 = n2+1

證明令 n 為所有節點數目而 n1 為分支度為 1 的節點數目可得 n = n0 + n1 +n2 (1)令 B 為二元樹的分支數除了樹根外每一節點皆有一分支指向之所以

n = B+1 (亦可參考定理 5-1 )又 B = n1+2n2

且 n = n1 + 2n2 +1 (2)由式 (1) 和式 (2) 我們得到

n0 = n2 + 1

完滿二元樹 (full binary tree)

定義一個深度為 k 的完滿二元樹即為一深度為 k 且有 2k-1 個節點的二元樹k 0

bull 由定理 5-2(2) 可知深度為 k 的二元樹其最多節點數為 2k-1 這樣的樹會將樹上所有可能存放節點的位置都放滿完滿 (full) 之名因而生成

完備二元樹 (complete binary tree)我們可對二元樹上節點予以編號階層小者先編同一階

層則自左向右編號這樣的編號方式使我們定義出完備(complete) 二元樹

定義深度為 k 節點數為 n 的二元樹稱為完備二元樹若且唯若 (1) k = 1 時樹有一個節點

(2) 當 k 2 且 1iltk 時深度 i 有 2i-1 個節點且第 k 層的節點皆由第 k-1 層的分支由左至右逐一連接而成

完滿或完備二元樹之所以受人注意乃因它們可以將節點以較少的階層數存放

定理 5-4 n 個節點的完備或完滿二元樹其深度為 log2(n+1)

證明 完滿或完備二元樹的節點皆自階層

1 起排放任一階層 i 可排放 2i-1

個節點遂由定理 5-2 得知此 n 個節點所佔的深度為 log2(n+1)

正規二元樹 (formal binary tree)bull 在二元樹中非樹葉的節點又稱為內部節點

(internal node) 若所有內部節點恰有 2 個子節點即成為正規二元樹

定義若二元樹中所有內部節點恰有 2 個子節點則稱之為正規二元樹

正規二元樹常被引用在單淘汰賽正規二元樹常被引用在單淘汰賽制的各項賽程安排樹葉為參賽制的各項賽程安排樹葉為參賽隊伍內部節點為優勝隊伍樹隊伍內部節點為優勝隊伍樹根即為冠軍隊伍根即為冠軍隊伍

在單淘汰賽制中自在單淘汰賽制中自 n n 個隊伍中產個隊伍中產生冠軍須舉辦 生冠軍須舉辦 nn-1-1場比賽場比賽

54  二元樹的表示法541 以陣列表示二元樹定理 5-5 若一個具有 n 個節點的完備二元樹以循序的方式編號並依序存放在陣列 A 中即第 i 個節點放在 A[i] 中 1 i n 則

(1) 節點 i 的父節點位在 A[i2]處 i ne 1( i = 1 時其節點正為樹根無父節點)

(2) 若 2i n 則節點 i 的左兒子節點位在 A[2i] 處若 2i gtn 節點 i 沒有左子節點

(3) 若 2i+1 n 節點 i 的右兒子節點位 A[2i+1]處若 2i+1gtn 節點 i 沒有左子

節點

證明先用數學歸納法證明 (2) bull 歸納法的基礎當 i = 1 時其左子節點確實在 A[2] 處(除非 nlt2 此時節點 1 沒有左子節點)

bull 歸納法的假設假設當 I = k 時其左子節點確在 A[2i] = A[2k] 處(除非 2i 已大於 n 此時節點 i 無左子節點)

bull 歸納法的推論當 i = k+1 時因第 k 節點的左子節點在 A[2k] 處其右子節點則位在 A[2k+1] 處那麼第 k+1 節點的左子節點應緊臨 A[2k+1] 亦即位於 A[2k+2] = A[2(k+1)] 處(除非 2i 已大於 n 此時節點 i 無左子節點)

bull 由數學歸納法的原理得證定理 5-5 (2) (3) 由於 (2) 成立而且節點 i 的右子節點會緊臨

A[2i] 亦即其位於 A[2i+1]處除非 i 根本沒有右子節點得證定理 5-5 (3)

由 (2) 和 (3) 我們可知定理 5-5 (1) 成立

542 以鍵結串列表示二元樹bull 以陣列來表示完備二元樹算是相當方便但ndash一般的二元樹將導致陣列空間的浪費ndash如果二元樹中節點的新增刪除動作頻繁則會造成陣列對應資料的搬動隨而頻繁

bull 以鍵結串列表示二元樹

為方便辦識樹根我們常用 root 指標指向樹根做為樹鍵結串列的起點

陣列表示和鍵結表示的空間需求bull 完備二元樹的陣列表示和鍵結表示皆沒浪費儲存資料的空間

bull 但歪斜樹的陣列表示比起鍵結表示則浪費許多

bull 以 n 個節點的歪斜樹而言陣列表示法可能需要 2n-1 個陣列元素其中 2n-1-n 個陣列元素空間是浪費的而鍵結表示法只需要 n 個節點空間

二元樹節點的宣告程式 5-2

1 struct BTreeNode

2 struct BtreeNode leftchiled

3 int data

4 struct BTreeNode rightchild

5

6 struct BTreeNode root

bull 這樣的二元樹節點要取得兒子節點的指標十分方便但若有取得父節點的需求則嫌不足我們可以加上 parent 欄位指向父節點

55  二元樹的走訪bull 假如一組資料已用二元樹的組織在一起總有需求對其全數資料做動作例如計算所有數目印出所有資料在所有資料中搜尋某項資料hellip等此時即須對此二元樹做走訪 (traversal) 的運算利用走訪二元樹的同時將計算列印或搜尋的動作完成

bull 事實上走訪即在決定二元樹上資料被處理(計算列印或搜尋)的順序我們也希望走訪的演算法對任何節點皆一致是容易撰寫程式實作的

bull 若對一個二元樹上的節點而言 V 表示處理節點上的資料 L 表示走訪其左子樹 R 表示走訪其右子樹

55  二元樹的走訪 ( 續 )bull 因為對稱的緣故我們只考慮先走訪左邊再走訪右邊的情形那麼右圖只剩下三種走訪方式

bull 以 V 所在的相對位置分別對此三種走訪方式取名如下

(1) LVR 中序走訪 (inorder traversal)或中序表示法 (infix notation)

(2) LRV 後序走訪 (postorder traversal) 或後序表示法 (postfix notation)

(3) VLR 前序走訪 (preorder traversal)或前序表示法 (prefix notation)

551  中序走訪bull 利用遞迴的方式撰寫中序走訪的程序希望把二元樹中序走訪的

順序印出來

程式 5-3二元樹的中序走訪1 struct BTreeNode2 struct BTreeNode leftchild3 char data4 struct BTreeNode rightchild5 6 struct BTreeNode root7 void inorder(struct BTreeNode node)8 if (node = NULL)9 inorder(node-gtleftchild)10 cout ltlt node-gtdata11 inorder(node-gtrightchild)12 13

範例 5-12此為運算式二元樹

其對應中序表示運算式為 (A+B)C+D(E-F)

6 struct BTreeNode root7 void inorder(struct BTreeNode node)8 if (node = NULL)9 inorder(node-gtleftchild)10 cout ltlt node-gtdata11 inorder(node-gtrightchild)12 13

6 struct BTreeNode root7 void inorder(struct BTreeNode node)8 if (node = NULL)9 inorder(node-gtleftchild)10 cout ltlt node-gtdata11 inorder(node-gtrightchild)12 13

(A+B)C+D(E-F)

如何加上 ( )

552  後序走訪程式 5-4二元樹的後序走訪14 void postorder(struct BTreeNode node)

15 if (node =NULL)16 postorder(node-gtleftchild)17 postorder(node-gtrightchild)18 cout ltlt node-gtdata19 20

以後序走訪可得到AB+CDEF-+

其正為對應的後序運算式

553  前序走訪程式 5-5二元樹的前序走訪21 void preorder(struct BTreeNode node)

22 if (node = NULL)

23 cout ltlt node-gtdata

24 preorder(node-gtleftchild)

25 preorder(node-gtrightchild)

26

27

以前序走訪可得到++ABCD-EF

其正為對應的前序運算式

554 利用堆疊和迴圈取代遞迴進行二元樹的走訪1 struct BTreeNode 2 struct BTreeNode leftchild3 char data4 struct BtreeNode rightchild5 6 struct BTreeNode root7 struct StackNode8 struct BTreeNode treenode9 struct StackNode next1011 struct StackNode top

12 void push_node(struct BtreeNode node )13 struct StackNode old_top14 old_top = top15 top = (struct StackNode ) malloc

(sizeof(struct StackNode))16 top-gttreenode = node17 top-gtnext = old_top18 19 struct BTreeNode pop_node()20 struct BTreeNode Tnode21 struct StackNode old_top22 if (top == NULL)23 StackEmpty()24 else25 Tnode = top-gttreenode26 old_top = top27 top = top-gtnext28 free(old_top)29 return Tnode30 31

32 void inorder_Stack(struct BtreeNode node)33 do34 while (node = NULL)35 push_node(node)36 node = node-gtleftchild37 push all left children38 if (top = NULL)39 node = pop_node()40 cout ltlt node-gtdata41 node = node-gtrightchild42 inorder printing and check right child43 while ((top=NULL)||(node=NULL))44 top==NULL 堆疊已空 node==NULL沒有右子

6 struct BTreeNode root7 void inorder(struct BTreeNode node)8 if (node = NULL)9 inorder(node-gtleftchild)10 cout ltlt node-gtdata11 inorder(node-gtrightchild)12 13

中序表示為DBGEHAFIC

32 void inorder_Stack()33 struct BTreeNode node34 node = root35 do36 while (node = NULL)37 push_node(node)38 node = node-gtleftchild39 40 if (top = NULL)41 node = pop_node()42 cout ltlt node-gtdata43 node = node-gtrightchild44 45 while(top=NULL)||(node=NULL))46

45 void Preorder_Stack(struct BSTreeNode node)46 do47 while (node = NULL)48 cout ltlt node-gtdata)49 push_node(node)50 node = node-gtleftchild51 push all left children52 if (top = NULL)53 node = pop_node()54 node = node-gtrightchild55 56 while ((top=NULL)||(node=NULL))57

程式 5-5二元樹的前序走訪21 void preorder(struct BTreeNode node)22 if (node = NULL)23 cout ltlt node-gtdata24 preorder(node-gtleftchild)25 preorder(node-gtrightchild)26 27

58 void Postorder_Stack(struct BTreeNode node)59 do60 while (node = NULL)61 cout ltlt node-gtdata62 push_node(node)63 node = node-gtrightchild64 push all left children65 if (top = NULL)66 node = pop_node()67 node = node-gtleftchild68 69 while ((top=NULL)||(node=NULL))70

程式 5-4二元樹的後序走訪14 void postorder(struct BTreeNode node)15 if (node =NULL)16 postorder(node-gtleftchild)17 postorder(node-gtrightchild)18 cout ltlt node-gtdata19 20

555  階層走訪bull 「階層走訪」 (level-order traversal) 是依階層

的順序進行二元樹的走訪先走訪階層小的節點後走訪階層大的節點同一階層者則依自左向右的順序走訪

bull 對下圖的二元樹進行階層走訪的結果為ABCDEFGHI

bull 對同一階層而言先走訪的節點其子節點亦在下一階層中先被走訪

bull 這種「先進先出」的特性恰可用佇列予以儲存與處理

程式 5-7 利用佇列進行二元樹的階層走訪

1 struct BTreeNode2 struct BTreeNode leftchild3 char data4 struct BTreeNode rightchild5 6 struct BTreeNode root7 struct QNode8 struct BTreeNode treenode9 struct QNode next10 11 struct QNode front rear

12 void AddQueue(struct BTreeNode Tnode)13 struct QNode node14 node=(struct QNode )malloc(sizeof(struct QNode))15 node-gttreenode = Tnode16 node-gtnext = NULL17 if (front == NULL) front = node19 else rear-gtnext = node20 rear = node21

22 struct BTreeNode DeleteQueue()23 struct BTreeNode Tnode24 struct QNode old_front25 if (rear == NULL)26 QueueEmpty()27 else28 Tnode = front-gttreenode29 old_front = front30 front = front-gtnext31 free(old_front)32 return Tnode33 34 35 void LevelOrder (struct BTreeNode node)36 AddQueue(node)37 while (front =NULL)38 node = DeleteQueue()39 cout ltlt node-gtdata40 if (node-gtleftchild = NULL)41 AddQueue(node-gtleftchild)42 if (node-gtrightchild = NULL)43 AddQueue(node-gtrightchild)44 45

35 void LevelOrder (struct BTreeNode node)36 AddQueue(node)37 while (front =NULL)38 node = DeleteQueue()39 cout ltlt node-gtdata40 if (node-gtleftchild = NULL)41 AddQueue(node-gtleftchild)42 if (node-gtrightchild = NULL)43 AddQueue(node-gtrightchild)44 45

bull Infix ABCbull Postfix ABC CBA ACB BCA

BAC CAB

B

A CA

B

C

B

A

C

C

B

AB

A

C

二元樹中序與前序式中序與後序式bull 每一棵二元樹皆有唯一的一對中序與前序表示法也有唯一的中序與後序次序

bull 換句話說給予一對中序與前序或中序與後序即可決定一棵二元樹

bull 然而給予前序和後序次序並不能決定唯一的二元樹可能會產生兩棵不同的二元樹

bull 想想階層走訪和中序走訪順序可否決定唯一的二元樹

A

B C

H J D E

F G

prefix ABHJCDFGEinfix HBJAFDGCEpostfix HJBFGDECA

VLRLVRLRV

A

prefix BHJinfix HBJ

prefix CDFGEinfix FDGCE

prefix ABHJCDFGEinfix HBJAFDGCE

A

B C

H J D E

F G

prefix ABHJCDFGE

infix HBJAFDGCEk-1

struct BTreeNode BTree_InfixPrefix (AnsiString infix AnsiString prefix)

int k struct BTreeNode node if (infixLength==0) return NULLnode = (struct BTreeNode ) malloc (sizeof(struct BTreeNode))node-gtdata = prefix[1]k = find(prefix[1] infix)node-gtleftchild = BTree_InfixPrefix

(infix[1k-1] prefix[2k])node-gtrightchild = BTree_InfixPrefix(infix[k+1infixLength]prefix[k+1prefixLength])

VLR

LVRk1

k2 k+1~

k+1~

1

struct BTreeNode BTree_InfixPostfix (AnsiString infix AnsiString prefix)

int k struct BTreeNode node if (infixLength==0) return NULLnode = (struct BTreeNode ) malloc (sizeof(struct BTreeNode))node-gtdata = postfix[postfixLength]k = find(postfix[postfixLength] infix)node-gtleftchild = BTree_InfixPrefix

(infix[1k-1] postfix[1k-1])node-gtrightchild = BTree_InfixPrefix(infix[k+1infixLength]postfix[k+1postfixLength-1])

56  二元樹的複製與相等測試程式 5-8 二元樹的複製1 struct BTreeNode2 struct BTreeNode leftchild3 char data4 struct BTreeNode rightchild5 6 struct BTreeNode root7 struct BTreeNode Copy(struct BTreeNode TreeRoot)8 struct BTreeNode CopyRoot9 if (TreeRoot == NULL)10 return NULL11 CopyRoot =(struct BTreeNode ) malloc (sizeof(BTreeNode))12 CopyRoot-gtdata = TreeRoot-gtdata13 CopyRoot-gtleftchild = Copy(TreeRoot-gtleftchild)14 CopyRoot-gtrightchild = Copy(TreeRoot-

gtrightchild)15 return CopyRoot16

程式 5-9 檢測兩棵二元樹是否相等1 struct BTreeNode2 struct BTreeNode leftchild3 char data4 struct BTreeNode rightchild5 6 struct BtreeNode rightchild7 int Equal(struct BTreeNode ustruct BTreeNode v)8 if ((u==NULL) ampamp (v==NULL)) return 19 if ((u=NULL) ampamp (v=NULL)10 ampamp (u-gtdata == v-gtdata )11 ampamp Equal(u-gtleftchildv-gtleftchild)12 ampamp Equal(u-gtrightchildv-gtrightchild))

13 return 114 return 015

Counting Nodes in a Binary Tree

int CountingNodes(struct BTreeNodes node)

if (node == NULL) return 0 int leftchildren = CountingNodes(node-gtleftchild)

int rightchildren= CountingNodes(node-gtrightchild)

return leftchildren+rightchildren

Counting Leaves in a Binary Tree

int CountingLeaves(struct BTreeNodes node)

if (node == NULL) return 0 int leftleaves = CountingLeaves(node-gtleftchild)

int rightchildren= CountingLeaves(node-gtrightchild)

if (leftleaves==0 ampamp leftleaves==0) return 1

return leftleaves + leftleaves

57  引線二元樹bull 在二元樹的鍵結表示中樹葉節點的兩個子樹指標皆指向 NULL 由定理 5-1 得知 n = E+1 其中 n 為節點個數 E 為分支數

bull 而 n 個鍵結節點有 2n 個指標空間每個分支恰佔用一個指標空間共有 E (=n-1) 個指標有用到(不是空的)而共有 n+1 個指標空間是 NULL 比非空的節點還多

bull 有學者提出對這些「放空指標的空間」加以利用的概念mdash與其放空指標不如放指向其它節點的指標稱之為引線 (thread) 使得某些運算(如走訪hellip等)可以加快

bull 這個概念發展出了引線二元樹 (threaded binary tree) 這種資料結構

範例 5-17下圖為加了引線的二元樹

加入的引線將使二元樹的中序走訪更加便利沿著上右圖箭頭所指示的順序走訪即可完成此二元樹的中序走訪GDHBAIECF 為此二元樹的中序表示

bull 範例 5-17 中所加的引線目的在方便二元樹的中序走訪

bull 對樹葉節點 p 而言令其資料為 P 則引線指標的規則如下

(1) p 的左子樹指標指向中序表示中 P 的前一個 元素(前接元素 (predecessor) )

(2) p 的右子樹指標指向中序表示中 P 的後一個 元素(後繼元素 (successor) )

為了區別節點指標放的是一般指標(內部節點)抑或是引線指標(樹葉節點)我們另以 2 個欄位分別區別左和右子樹指標空間存放的對象其節點的記憶體配置有如下圖所示

其中 leftthread (rightthread) 為 0 時表示 leftchild (rightchild) 存放的是一般節點指標而 leftthread (rightthread) 為 1 時表示 leftchild (rightchild) 存放的是引線指標1 struct TBTreeNode2 int leftthread3 struct TBTreeNode leftchild4 char data5 struct TBTreeNode rightchild6 int rightthread7

由右側的引線二元樹可知有最左和最右兩條引線尚無妥善的安排我們另設計一空的引線節點做為樹根如下圖所示

571  引線二元樹的中序走訪決定節點 p 在中序表示中的後繼節點 q (簡稱中序後繼點)

程式 5-10決定節點 node的中序後繼點1 struct TBTreeNode2 int leftthread3 struct TBTreeNode leftchild4 char data5 struct TBTreeNode rightchild6 int rightthread7 8 struct TBTreeNode root9 struct TBTreeNode Next(struct TBTreeNode node)

10 struct TBTreeNode temp11 temp = node-gtrightchild12 if (node-gtrightthead) return temp13 while (temp-gtleftthead) temp=temp-gtleftchild

14 return temp15

程式 5-11引線二元樹的中序表示(接程式 5-10)1 void InorderTBTree()2 struct TBTreeNode node3 for(node=Next(root)node=rootnode=Next(node))

4 cout ltlt node-gtdata5

bull只要 node 是引線二元樹上的任一節點透過呼叫 Next(node) 即可找到其在中序表示中的後繼節點bull(試想若程式 5-10 中的程序 Next 所傳入的參數指標正是 root Next(root) 是否可正常運作請思考)bull於是只須從中序表示中的第一節點起讓每一節點 node 都執行 Next(node) 程序即可得引線二元樹的中序表示

572  在引線二元樹中加入節點在引線二元樹中樹葉節點節點的右子樹指標所指向或內部節點右子樹的最左樹葉為其中序後繼節點而且任一節點若無左子樹則其左子樹指標乃指向其中序前接節點hellip這些性質必須在節點新增進入引線二元樹時也要保持接下來討論節點 new 插入成為引線二元樹的節點 p 的右子節點的情形至於插入成為左子節點的情形則讓各位模擬推敲

572572  在引線二元樹中加入節點 在引線二元樹中加入節點 (( 續續 ))

newnew 會成為會成為 pp 的右子節點的情況共有以下兩種的右子節點的情況共有以下兩種(1) (1) 若若 pp 沒有右子節點 沒有右子節點 ((p-gtrightthread p-gtrightthread 為為1) 1) 如圖如圖 ((a) a) 則 則 new new 插入後應形成如圖插入後應形成如圖 ((b) b) 所示所示

(2)(2) 若若 pp 有右子節點 有右子節點 ((p-gtrightthread p-gtrightthread 為為0) 0) 如圖如圖 ((c) c) 則 則 new new 插入後應形成如插入後應形成如圖圖 ((d) d) 所示所示

程式 5-12  插入節點 new 成為引線二元樹中節點 p的右子節點(上接程式 5-10 5-11 )

1 void InsertRight(struct TBTreeNode p 2 struct TBTreeNode new)3 struct TBTreeNode q4 new-gtrightchild = p-gtrightchild5 new-gtrightthead = p-gtrightthread6 new-gtleftchild = p7 new-gtleftthread = 18 p-gtrightchild = new9 p-gtrightthead = 010 if (new-gtrightthread)11 q = Next(new)12 q-gtleftchild = new13 14

58  堆積bull 「堆積」 (heap) 是一個特殊的完備二元樹節點的資料與其子節點的資料之間有事先定好的大小關係存在bull 定義ndash最大堆疊為一完備二元樹任一節點的資料內容不小於其子節點的資料內容ndash最小堆疊為一完備二元樹任一節點的資料內容不大於其子節點的資料內容

581  插入節點至最大堆積中最大堆積是一完備二元樹所以如圖 (a) 之最大堆積於加入新節點後樹的形狀必須如圖 (b) 所示灰色節點即為新節點若加入的節點資料分別為 1 9 12 則其最大堆積將分別變成如圖 (c) (d) (e) 所示

程式 5-13 新增節點至最大堆積中最大堆積本身是一棵完備二元樹可以用陣列表示之 1 define maxsize 1002 int heap[maxsize]3 int n i4 void InsertHeap(int x)5 if (n == maxsize) HeapFull()6 else7 n++8 i = n9 while ((i gt 1) ampamp (x gt heap[i2]))10 heap[i] = heap[i2]11 i = 212 13 heap[i] = x14 16

程序 InsertHeap 在插入第 n 個資料時需要的時間複雜度為 O(logn)

1 100

2 93

3 84

4 77

5 65

6 81

7 79

8 66

9 70

10 50

100

84

77

93

8165

66 70

79

50

1

2 3

4 5 6 7

8 9 10

1 define maxsize 1002 int heap[maxsize]3 int n i4 void InsertHeap(int x)5 if (n == maxsize) HeapFull()6 else7 n++8 i = n9 while ((i gt 1) ampamp (x gt heap[i2]))10 heap[i] = heap[i2]11 i = 212 13 heap[i] = x14 16

582 刪除最大堆積中的最大資料圖 (a) 中的最大堆積在刪除最大資料後其樹形應變成如圖 (b) 所示將最未一個樹葉節點的資料 6 暫挪至樹根節點內如圖 (c) 再將圖 (c) 中非最大堆積的結構調整成為最大堆積 10 和 8 中選大者與 6對調即成圖 (d)

程式 5-14 刪除最大堆積中的最大資料(上接程式 5-13 )1 int DeleteHeap()2 int x i j3 if (n == 0) HeapEmpty()4 else5 x = heap[1]6 heap[1] = heap[n]7 n--8 i = 19 while (i lt= n2)10 if (heap[2i] gt heap[2i+1])11 j = 2i12 else13 j = 2i+114 if (heap[i]gtheap[j]) break15 swap(heap[i] heap[j])16 i = j17 18 return x19 20

其時間複雜度與其堆積高度相同是為O(logn) 其中 n 為堆積中的元素個數

1 100

2 93

3 84

4 77

5 65

6 81

7 79

8 66

9 70

10 50

100

84

77

93

8165

66 70

79

50

1

2 3

4 5 6 7

8 9 10

1 int DeletHeap()2 int x i j3 if(n == 0) HeapEmpty()4 else5 x = heap[1]6 heap[1] = heap[n]7 n--8 i = 19 while (i lt= n2)10 if ([heap[2i]] gt heap[2i+1])11 j = 2i12 else13 j = 2i+114 if (heap[i]gtheap[j]) break15 swap(heap[i] heap[j])16 i = j17 18 return x19 20

583  優先佇列bull 由於最大堆積(或最小堆積)的結構可提供最大元素(或最小元素)而其維護堆積的成本為 O(logn) n 為總元素個數

bull 堆積常被當成「優先佇列」 (priority queue) 使用mdash此時佇列被組織成完備二元樹若新加入佇列的資料有大小關係優先順序的需求時此優先佇列即以 O(logn) 的代價將新資料放入優先佇列(可能是最大或最小堆積依實際大小關係優先順序的需求而定)中mdash提供依元素的優先順序做為處理順序的服務

bull 一般的佇列為先進先出先到的先行處理優先佇列則是依據其優先順序優先順序高者先行處理與來到的先後無關

範例 5-21bull 在「分時」 (time sharing) 的作業系統中我們不希望使用中央處理器 (Central Processing Unit CPU) 時間短的「處理程序」 (process) 等候過久於是有「最短時間工作優先」 (shortest-job-first) 的排程策略此時等候的處理程序即可組織成之一最小堆積而 CPU 每次即取出存於樹根的處理程序予以處理任何新來的處理程序則以 O(logn) 的時間加入此優先佇列(最小堆積)中

bull 注意之前提過的線性佇列新來的直接加入成為新的尾端元素花O(1) 的時間然線性佇列本身沒有大小的關係每次要取出最大(小)的資料得花O(n) 的代價(在所有元素中挑出最大(小)者) n 為佇列內的元素個數而優先佇列是在新元素加入時要花O(logn) 的時間至於取出最大(小)則只須 O(1) 的時間

59  二元搜尋樹bull 二元搜尋樹是一棵二元樹可能是空二元樹若不為空二元樹則滿足下列性質ndash所有節點內的資料內容是相異的 ndash左子節點的資內容 ( 如果有 ) 比父節點的資料內容小 ndash右子節點的資料內容 ( 如果有 ) 比父節點的資料內容大 ndash以左和右子節點為樹根的左子樹和右子樹也是二元搜尋樹

在二元搜尋樹中搜尋資料二元搜尋樹對資料大小的掌握十分明確若恰與節點 p 資料相等則搜尋成功若欲搜尋的資料 x 比樹節點 p 中的資料小 則只須搜尋節點 p 的左子樹若比節點 p 中的資料大 則只須搜尋 p 的右子樹這樣的判斷可以遞迴地應用在各子樹節點 中

程式 5-15 在二元搜尋樹中搜尋資料(遞迴呼叫)1 struct BSTreeNode2 struct BSTreeNode leftchild3 int data4 struct BSTreeNode rightchild5 6 struct BSTreeNode root7 struct BSTreeNode SearchBST(

struct BSTreeNode tree int x)8 if (tree == NULL) return NULL9 if (x == tree-gtdata) return tree10 if (x lt tree-gtdata)11 return SearchBST(tree-gtleftchild x)

12 return SearchBST(tree-gtrightchild x)13

程式 5-16 搜尋二元搜尋樹的資料(非遞迴呼叫)在二元搜尋樹十分龐大時遞迴呼叫的效率不會太好可利用迴圈來完成搜尋的動作以提高執行效率請看程式 5-16

14 struct BSTreeNode Search BST_iterative15 (struct BSTreeNode node int x)16 while (node = NULL)17 if (x == node-gtdata) return node18 if (x lt node-gtdata)19 node = node-gtleftchild20 else21 node = node-gtrightchild22 23 return NULL24

592  新增資料於二元搜尋樹中欲新增資料進入一二元搜尋樹中得先決定該資料在二元搜尋樹中應出現的位置這可透過上節提到的搜尋動作來解決

在二元搜尋樹定義的性質 (1) 中我們限定了元素必須相異所以搜尋新資料的結果必是 NULL

必須將搜尋的結果改為新增資料所應在位置的節點指標

程式 5-17  在二元搜尋樹中新增資料25 int InsertBST(int x)26 struct BSTreeNode p q27 p = root q = NULL28 while (p = NULL)29 q = p30 if (x == p-gtdata) return 0

若不考慮重複則可省略之31 if (x lt p-gtdata)32 p = p-gtleftchild33 else34 p = p-gtrightchild35

3636 p = (struct BSTreeNode ) p = (struct BSTreeNode ) malloc (sizeof(BSTreeNode))malloc (sizeof(BSTreeNode))

3737 p-gtdata = x p-gtdata = x3838 p-gtleftchild = p-gtrightchild = p-gtleftchild = p-gtrightchild = NULLNULL3939 if (root == NULL) root = p if (root == NULL) root = p4040 else if (x lt q-gtdata) else if (x lt q-gtdata)4141 q-gtleftchild = p q-gtleftchild = p4242 else else4343 q-gtrightchild = p q-gtrightchild = p4444 return 1 return 145 45

程式 程式 5-175-17  在二元搜尋樹中新增資料  在二元搜尋樹中新增資料 (( 續續 ))

範例 5-23 圖 (b) 為圖 (a) 新增整數 6 之後的結果圖 (c) (d) (e) (f) 則分別是逐步再加入 28 18

10 21 的結果

bull若目前的二元搜尋樹高度為 h 則新增資料得花O(h) 的時間找到其所應在的位置遂新增資料至二元搜尋樹中的時間複雜之度為 O(h)

593 自二元搜尋樹中刪除資料欲自二元搜尋樹中刪除資料可能會面臨兩種情況(1) 刪除節點 p 位於樹葉節點上如下圖所示 這是比較容易處理的情況

(2) 刪除的節點 p 不是樹葉刪除節點 p 後原 p 所指的節點可由 q ( q 中包含了 p 左子樹中的最大節點值)或 r ( r 中包含了 p 右子樹中的最小節點值)來取代以維持二元搜尋樹中資料的大小關係其結果分別如圖 (b) 和 (c) 所示而圖 (d) 則標明了指標會改變的節點位置即 p 會消失 father 的左或右子樹指標會異動 k(可能是 q 或 r )會移到原來 p 的位置其父節點 f 的左或右子樹指標會異動其中 father 為 p 父節點 f 為 k 的父節點

程式 5-18 自二元搜尋樹中刪除資料46 int DeleteBST (int x)47 Struct BSTreeNode p father k f48 p = root father = NULL49 while (p = NULL)50 if (x == p-gtdata) 找到 x 所在的節點 p 了51 if ((p-gtleftchild == NULL) ampamp 52 (p-gtrightchild == NULL))53 k = NULL p為樹葉節點54 else if (p-gtleftchild = NULL )x位於非子節

點55 f = p 找出 p左子樹的最右樹葉56 k = p-gtleftchild57 while (k-gtrightchild =NULL)58 f = k59 k = k-gtrightchild60 k將挪至原 p處61 if (p == f)62 f-gtleftchild = k-gtleftchild 63 else64 f-gtrightchild = k-gtleftchild 65

66 else p無左子樹67 f = p 找出 p右子樹的最左樹葉68 k = p-gtrightchild69 while (k-gtleftchild = NULL)70 f = k71 k = k -gtleftchild72 73 if (p == f)74 f-gtrightchild = k-gtrightchild 75 else76 f-gtleftchild = k-gtrightchild77 78 if (k = NULL) k挪至原 p處繼承 p的左右指標79 k-gtleftchild = p-gtleftchild80 k-gtrightchild = p-gtrightchild81 82 if (father == NULL) root = k83 else if (x lt father-gtdata) 決定 k是 father的左

或右兒子84 father-gtleftchild = k85 else86 father-gtrightchild = k87 free(p)88 return 1 成功地刪除 x於此傳回 1返回呼叫處89 51~89行為找到 x時應有的處理步驟

90 else 尚未找到 x繼續往下一階層找

91 father = p92 if (x lt p-gtdata)93 p = p-gtleftchild94 else95 p = p-gtrightchild96 97 98 return 0 未找到 x傳回 099

510  樹林 (forest)

bull 定義樹林(森林)是由 n 棵沒有交集的樹所構成的集合 n 0

下圖為一包含三棵樹的樹林

5101 轉換樹林為二元樹 之前我們以二元樹來表示樹因為二元樹有較多

好用的性質在此我們也將樹林轉換成二元樹看待

在 522 節中我們嘗試了用左子右兄弟的方式來表示樹這個方法事實上已將樹轉換成二元樹

即用左子右兄弟的結構重新組織樹林中的每一棵樹這些樹的樹根將視為兄弟而最左邊的樹則視為樹林二元樹的樹根如此一來樹林即可轉換成二元樹

範例 5-25右下圖為右上圖三棵樹所對應的三棵左子右兄弟二元樹

下圖則為上圖樹林所對應的一棵樹林二元樹

演算法 5-1  轉換樹林成二元樹輸入樹林 F = T1 T2 Tn

輸出 F 的二元樹表示 BT=(T1 T2 Tn)1 if (n == 0) 輸出空二元樹 2 else

3 root(T1 T2 Tn) = T1

4 leftsubtree(T1 T2 Tn) =

BT(T11 T12 T1m)5 BT(T11 T12 T1m)為 T1的子樹6 rightsubtree(T1 T2 Tn) =

BT(T2 T3 Tn)

樹林轉換成二元樹的過程樹林轉換成二元樹的過程

  • Slide 1
  • Slide 2
  • Slide 3
  • Slide 4
  • Slide 5
  • Slide 6
  • Slide 7
  • Slide 8
  • Slide 9
  • Slide 10
  • Slide 11
  • Slide 12
  • Slide 13
  • Slide 14
  • Slide 15
  • Slide 16
  • Slide 17
  • Slide 18
  • Slide 19
  • Slide 20
  • Slide 21
  • Slide 22
  • Slide 23
  • Slide 24
  • Slide 25
  • Slide 26
  • Slide 27
  • Slide 28
  • Slide 29
  • Slide 30
  • Slide 31
  • Slide 32
  • Slide 33
  • Slide 34
  • Slide 35
  • Slide 36
  • Slide 37
  • Slide 38
  • Slide 39
  • Slide 40
  • Slide 41
  • Slide 42
  • Slide 43
  • Slide 44
  • Slide 45
  • Slide 46
  • Slide 47
  • Slide 48
  • Slide 49
  • Slide 50
  • Slide 51
  • Slide 52
  • Slide 53
  • Slide 54
  • Slide 55
  • Slide 56
  • Slide 57
  • Slide 58
  • Slide 59
  • Slide 60
  • Slide 61
  • Slide 62
  • Slide 63
  • Slide 64
  • Slide 65
  • Slide 66
  • Slide 67
  • Slide 68
  • Slide 69
  • Slide 70
  • Slide 71
  • Slide 72
  • Slide 73
  • Slide 74
  • Slide 75
  • Slide 76
  • Slide 77
  • Slide 78
  • Slide 79
  • Slide 80
  • Slide 81
  • Slide 82
  • Slide 83
  • Slide 84
  • Slide 85
  • Slide 86
  • Slide 87
  • Slide 88
  • Slide 89
  • Slide 90
  • Slide 91
  • Slide 92
  • Slide 93
  • Slide 94
  • Slide 95
  • Slide 96
  • Slide 97
  • Slide 98
  • Slide 99
  • Slide 100
  • Slide 101
  • Slide 102
  • Slide 103
Page 13: 第五章 樹

(A T1 T2 T3)T1 = (B (E K) (F L M) G)T2 = (C H)T3 = (D (I N) (J O P))

522  左子右兄弟表示法

每個節點都有唯一的最左 兒子 (leftmost child) 每個節點都有最靠近它的 右兄弟

523 分支度為 2 的二元樹表示法bull 分支度為 2 的樹又稱為二元樹 (binary tree)

bull 二元樹中任一節點皆有 2個指標分別指向該節點的左子樹和右子樹

bull 二元樹可以表示任何樹事實上二元樹有許多很好的性質我們在一下節中介紹

bull注意樹狀結構中兒子的順序是不重要的試想樹可以樹根為軸心旋轉使得兒子的順序是不固定的

53  二元樹bull 二元樹是一個由節點組成的有限集合可以是空集合或是包含了(1) 一個樹根節點(2) 其它節點分割成兩個沒有交集的二元樹

其一為左子樹 (left subtree) 另一為右子樹 (right subtree) 樹和二元樹之間的差異

(1)樹不允許空節點而空二元樹是允許的

(2) 樹的子樹沒有順序而二元樹的子樹有左右之分

531  樹和二元樹的基本性質

樹或二元樹皆擁有下面的性質

定理 5-1 若一棵樹 T 的總節點數為 V 總邊數為

E 則V = E + 1

證明利用數學歸納法歸納法的基礎當 V=1 時即 T 只有一個樹根節點 E 為 0 所以 V = E + 1 成立歸納法的假設假設當 1 V K 時該式成立歸納法的推論當 V = K + 1 時令此樹的樹根為 R 而 R 有 m 個子樹 T1 T2 hellip

Tm (如圖 5-12 所示)分別有個 V1 V2 hellip Vm 個節點其中 V1 +V2 + hellip+ Vm =K 若 E1 E2 hellip Em 分別為此 m 個子樹的邊數則

E = E1 +E2 + hellip+ Em + m (1)根據歸納法的假設可知

V1 = E1+1V2 = E2+1 Vm = Em+1

所以V1 +V2 + hellip+Vm = E1 + E2 + hellip+ Em +m (2)

然而V = V1 +V2 + hellip+Vm+1 (3)

將式 (2) 代入式 (3) 可得V = E1 + E2 + hellip+ Em + m + 1

引用式 (1) 則可得V = E + 1

由數學歸納法的原理可得證本定理

532 二元樹的性質(a) 稱為歪斜樹 (skew tree) (b) 稱為完備二元樹 (complete binary tree)

( 其樹葉節點皆在相鄰階層完整定義在後 ) 同樣數目的樹節點存放在歪斜樹上會花較多的階層放在完備二元樹上則只須最少的階層

532 二元樹的性質 ( 續 )定理 5-2 (1) 二元樹上階層 i 的節點數目最多為 2i-1 i 1 (2) 深度為 k 的二元樹其節點數目最多為 2k-

1 k1 bull 定理 5-2說明了 (1) 每一階層上可放的最多節點數

(2) 階層數已知時二元樹可放的最多節點數證明 (1) 利用數學歸納法歸納法的基礎當 i=1 時只有樹根一個節點 2i-1=21-1=1 原式成

立歸納法的假設假設 i = k 時該式成立即階層 k 的最大節點數為

2k-1 k 1 歸納法的推論當 i = k+1 時因在階層 k 的每一個節點最多會有

2 個子節點遂在 k+1 階層最多會有 22k-1 = 2k

= 2i-1 節點由數學歸納法的原理可知本定理得證(2) 由 (1) 可知二元樹在階層 i 的最多節點數為 2i-1那麼深度 k 的二元樹最多可有節點如下

k

i

ki

1

1-1- 24212 1-2

)12(1 k= 2k-1

=

bull 對二元樹而言任一節點的分支度可能為 0 1 或 2 分支度為 0 者是為樹葉

bull 樹葉的數目與分支度為 2 的節點數目存在著有趣的關係請見定理 5-3

定理 5-3 若 T 為一非空的二元樹 n0 為樹葉節點數目 n2為分支度為 2 的節點數目則 n0 = n2+1

證明令 n 為所有節點數目而 n1 為分支度為 1 的節點數目可得 n = n0 + n1 +n2 (1)令 B 為二元樹的分支數除了樹根外每一節點皆有一分支指向之所以

n = B+1 (亦可參考定理 5-1 )又 B = n1+2n2

且 n = n1 + 2n2 +1 (2)由式 (1) 和式 (2) 我們得到

n0 = n2 + 1

完滿二元樹 (full binary tree)

定義一個深度為 k 的完滿二元樹即為一深度為 k 且有 2k-1 個節點的二元樹k 0

bull 由定理 5-2(2) 可知深度為 k 的二元樹其最多節點數為 2k-1 這樣的樹會將樹上所有可能存放節點的位置都放滿完滿 (full) 之名因而生成

完備二元樹 (complete binary tree)我們可對二元樹上節點予以編號階層小者先編同一階

層則自左向右編號這樣的編號方式使我們定義出完備(complete) 二元樹

定義深度為 k 節點數為 n 的二元樹稱為完備二元樹若且唯若 (1) k = 1 時樹有一個節點

(2) 當 k 2 且 1iltk 時深度 i 有 2i-1 個節點且第 k 層的節點皆由第 k-1 層的分支由左至右逐一連接而成

完滿或完備二元樹之所以受人注意乃因它們可以將節點以較少的階層數存放

定理 5-4 n 個節點的完備或完滿二元樹其深度為 log2(n+1)

證明 完滿或完備二元樹的節點皆自階層

1 起排放任一階層 i 可排放 2i-1

個節點遂由定理 5-2 得知此 n 個節點所佔的深度為 log2(n+1)

正規二元樹 (formal binary tree)bull 在二元樹中非樹葉的節點又稱為內部節點

(internal node) 若所有內部節點恰有 2 個子節點即成為正規二元樹

定義若二元樹中所有內部節點恰有 2 個子節點則稱之為正規二元樹

正規二元樹常被引用在單淘汰賽正規二元樹常被引用在單淘汰賽制的各項賽程安排樹葉為參賽制的各項賽程安排樹葉為參賽隊伍內部節點為優勝隊伍樹隊伍內部節點為優勝隊伍樹根即為冠軍隊伍根即為冠軍隊伍

在單淘汰賽制中自在單淘汰賽制中自 n n 個隊伍中產個隊伍中產生冠軍須舉辦 生冠軍須舉辦 nn-1-1場比賽場比賽

54  二元樹的表示法541 以陣列表示二元樹定理 5-5 若一個具有 n 個節點的完備二元樹以循序的方式編號並依序存放在陣列 A 中即第 i 個節點放在 A[i] 中 1 i n 則

(1) 節點 i 的父節點位在 A[i2]處 i ne 1( i = 1 時其節點正為樹根無父節點)

(2) 若 2i n 則節點 i 的左兒子節點位在 A[2i] 處若 2i gtn 節點 i 沒有左子節點

(3) 若 2i+1 n 節點 i 的右兒子節點位 A[2i+1]處若 2i+1gtn 節點 i 沒有左子

節點

證明先用數學歸納法證明 (2) bull 歸納法的基礎當 i = 1 時其左子節點確實在 A[2] 處(除非 nlt2 此時節點 1 沒有左子節點)

bull 歸納法的假設假設當 I = k 時其左子節點確在 A[2i] = A[2k] 處(除非 2i 已大於 n 此時節點 i 無左子節點)

bull 歸納法的推論當 i = k+1 時因第 k 節點的左子節點在 A[2k] 處其右子節點則位在 A[2k+1] 處那麼第 k+1 節點的左子節點應緊臨 A[2k+1] 亦即位於 A[2k+2] = A[2(k+1)] 處(除非 2i 已大於 n 此時節點 i 無左子節點)

bull 由數學歸納法的原理得證定理 5-5 (2) (3) 由於 (2) 成立而且節點 i 的右子節點會緊臨

A[2i] 亦即其位於 A[2i+1]處除非 i 根本沒有右子節點得證定理 5-5 (3)

由 (2) 和 (3) 我們可知定理 5-5 (1) 成立

542 以鍵結串列表示二元樹bull 以陣列來表示完備二元樹算是相當方便但ndash一般的二元樹將導致陣列空間的浪費ndash如果二元樹中節點的新增刪除動作頻繁則會造成陣列對應資料的搬動隨而頻繁

bull 以鍵結串列表示二元樹

為方便辦識樹根我們常用 root 指標指向樹根做為樹鍵結串列的起點

陣列表示和鍵結表示的空間需求bull 完備二元樹的陣列表示和鍵結表示皆沒浪費儲存資料的空間

bull 但歪斜樹的陣列表示比起鍵結表示則浪費許多

bull 以 n 個節點的歪斜樹而言陣列表示法可能需要 2n-1 個陣列元素其中 2n-1-n 個陣列元素空間是浪費的而鍵結表示法只需要 n 個節點空間

二元樹節點的宣告程式 5-2

1 struct BTreeNode

2 struct BtreeNode leftchiled

3 int data

4 struct BTreeNode rightchild

5

6 struct BTreeNode root

bull 這樣的二元樹節點要取得兒子節點的指標十分方便但若有取得父節點的需求則嫌不足我們可以加上 parent 欄位指向父節點

55  二元樹的走訪bull 假如一組資料已用二元樹的組織在一起總有需求對其全數資料做動作例如計算所有數目印出所有資料在所有資料中搜尋某項資料hellip等此時即須對此二元樹做走訪 (traversal) 的運算利用走訪二元樹的同時將計算列印或搜尋的動作完成

bull 事實上走訪即在決定二元樹上資料被處理(計算列印或搜尋)的順序我們也希望走訪的演算法對任何節點皆一致是容易撰寫程式實作的

bull 若對一個二元樹上的節點而言 V 表示處理節點上的資料 L 表示走訪其左子樹 R 表示走訪其右子樹

55  二元樹的走訪 ( 續 )bull 因為對稱的緣故我們只考慮先走訪左邊再走訪右邊的情形那麼右圖只剩下三種走訪方式

bull 以 V 所在的相對位置分別對此三種走訪方式取名如下

(1) LVR 中序走訪 (inorder traversal)或中序表示法 (infix notation)

(2) LRV 後序走訪 (postorder traversal) 或後序表示法 (postfix notation)

(3) VLR 前序走訪 (preorder traversal)或前序表示法 (prefix notation)

551  中序走訪bull 利用遞迴的方式撰寫中序走訪的程序希望把二元樹中序走訪的

順序印出來

程式 5-3二元樹的中序走訪1 struct BTreeNode2 struct BTreeNode leftchild3 char data4 struct BTreeNode rightchild5 6 struct BTreeNode root7 void inorder(struct BTreeNode node)8 if (node = NULL)9 inorder(node-gtleftchild)10 cout ltlt node-gtdata11 inorder(node-gtrightchild)12 13

範例 5-12此為運算式二元樹

其對應中序表示運算式為 (A+B)C+D(E-F)

6 struct BTreeNode root7 void inorder(struct BTreeNode node)8 if (node = NULL)9 inorder(node-gtleftchild)10 cout ltlt node-gtdata11 inorder(node-gtrightchild)12 13

6 struct BTreeNode root7 void inorder(struct BTreeNode node)8 if (node = NULL)9 inorder(node-gtleftchild)10 cout ltlt node-gtdata11 inorder(node-gtrightchild)12 13

(A+B)C+D(E-F)

如何加上 ( )

552  後序走訪程式 5-4二元樹的後序走訪14 void postorder(struct BTreeNode node)

15 if (node =NULL)16 postorder(node-gtleftchild)17 postorder(node-gtrightchild)18 cout ltlt node-gtdata19 20

以後序走訪可得到AB+CDEF-+

其正為對應的後序運算式

553  前序走訪程式 5-5二元樹的前序走訪21 void preorder(struct BTreeNode node)

22 if (node = NULL)

23 cout ltlt node-gtdata

24 preorder(node-gtleftchild)

25 preorder(node-gtrightchild)

26

27

以前序走訪可得到++ABCD-EF

其正為對應的前序運算式

554 利用堆疊和迴圈取代遞迴進行二元樹的走訪1 struct BTreeNode 2 struct BTreeNode leftchild3 char data4 struct BtreeNode rightchild5 6 struct BTreeNode root7 struct StackNode8 struct BTreeNode treenode9 struct StackNode next1011 struct StackNode top

12 void push_node(struct BtreeNode node )13 struct StackNode old_top14 old_top = top15 top = (struct StackNode ) malloc

(sizeof(struct StackNode))16 top-gttreenode = node17 top-gtnext = old_top18 19 struct BTreeNode pop_node()20 struct BTreeNode Tnode21 struct StackNode old_top22 if (top == NULL)23 StackEmpty()24 else25 Tnode = top-gttreenode26 old_top = top27 top = top-gtnext28 free(old_top)29 return Tnode30 31

32 void inorder_Stack(struct BtreeNode node)33 do34 while (node = NULL)35 push_node(node)36 node = node-gtleftchild37 push all left children38 if (top = NULL)39 node = pop_node()40 cout ltlt node-gtdata41 node = node-gtrightchild42 inorder printing and check right child43 while ((top=NULL)||(node=NULL))44 top==NULL 堆疊已空 node==NULL沒有右子

6 struct BTreeNode root7 void inorder(struct BTreeNode node)8 if (node = NULL)9 inorder(node-gtleftchild)10 cout ltlt node-gtdata11 inorder(node-gtrightchild)12 13

中序表示為DBGEHAFIC

32 void inorder_Stack()33 struct BTreeNode node34 node = root35 do36 while (node = NULL)37 push_node(node)38 node = node-gtleftchild39 40 if (top = NULL)41 node = pop_node()42 cout ltlt node-gtdata43 node = node-gtrightchild44 45 while(top=NULL)||(node=NULL))46

45 void Preorder_Stack(struct BSTreeNode node)46 do47 while (node = NULL)48 cout ltlt node-gtdata)49 push_node(node)50 node = node-gtleftchild51 push all left children52 if (top = NULL)53 node = pop_node()54 node = node-gtrightchild55 56 while ((top=NULL)||(node=NULL))57

程式 5-5二元樹的前序走訪21 void preorder(struct BTreeNode node)22 if (node = NULL)23 cout ltlt node-gtdata24 preorder(node-gtleftchild)25 preorder(node-gtrightchild)26 27

58 void Postorder_Stack(struct BTreeNode node)59 do60 while (node = NULL)61 cout ltlt node-gtdata62 push_node(node)63 node = node-gtrightchild64 push all left children65 if (top = NULL)66 node = pop_node()67 node = node-gtleftchild68 69 while ((top=NULL)||(node=NULL))70

程式 5-4二元樹的後序走訪14 void postorder(struct BTreeNode node)15 if (node =NULL)16 postorder(node-gtleftchild)17 postorder(node-gtrightchild)18 cout ltlt node-gtdata19 20

555  階層走訪bull 「階層走訪」 (level-order traversal) 是依階層

的順序進行二元樹的走訪先走訪階層小的節點後走訪階層大的節點同一階層者則依自左向右的順序走訪

bull 對下圖的二元樹進行階層走訪的結果為ABCDEFGHI

bull 對同一階層而言先走訪的節點其子節點亦在下一階層中先被走訪

bull 這種「先進先出」的特性恰可用佇列予以儲存與處理

程式 5-7 利用佇列進行二元樹的階層走訪

1 struct BTreeNode2 struct BTreeNode leftchild3 char data4 struct BTreeNode rightchild5 6 struct BTreeNode root7 struct QNode8 struct BTreeNode treenode9 struct QNode next10 11 struct QNode front rear

12 void AddQueue(struct BTreeNode Tnode)13 struct QNode node14 node=(struct QNode )malloc(sizeof(struct QNode))15 node-gttreenode = Tnode16 node-gtnext = NULL17 if (front == NULL) front = node19 else rear-gtnext = node20 rear = node21

22 struct BTreeNode DeleteQueue()23 struct BTreeNode Tnode24 struct QNode old_front25 if (rear == NULL)26 QueueEmpty()27 else28 Tnode = front-gttreenode29 old_front = front30 front = front-gtnext31 free(old_front)32 return Tnode33 34 35 void LevelOrder (struct BTreeNode node)36 AddQueue(node)37 while (front =NULL)38 node = DeleteQueue()39 cout ltlt node-gtdata40 if (node-gtleftchild = NULL)41 AddQueue(node-gtleftchild)42 if (node-gtrightchild = NULL)43 AddQueue(node-gtrightchild)44 45

35 void LevelOrder (struct BTreeNode node)36 AddQueue(node)37 while (front =NULL)38 node = DeleteQueue()39 cout ltlt node-gtdata40 if (node-gtleftchild = NULL)41 AddQueue(node-gtleftchild)42 if (node-gtrightchild = NULL)43 AddQueue(node-gtrightchild)44 45

bull Infix ABCbull Postfix ABC CBA ACB BCA

BAC CAB

B

A CA

B

C

B

A

C

C

B

AB

A

C

二元樹中序與前序式中序與後序式bull 每一棵二元樹皆有唯一的一對中序與前序表示法也有唯一的中序與後序次序

bull 換句話說給予一對中序與前序或中序與後序即可決定一棵二元樹

bull 然而給予前序和後序次序並不能決定唯一的二元樹可能會產生兩棵不同的二元樹

bull 想想階層走訪和中序走訪順序可否決定唯一的二元樹

A

B C

H J D E

F G

prefix ABHJCDFGEinfix HBJAFDGCEpostfix HJBFGDECA

VLRLVRLRV

A

prefix BHJinfix HBJ

prefix CDFGEinfix FDGCE

prefix ABHJCDFGEinfix HBJAFDGCE

A

B C

H J D E

F G

prefix ABHJCDFGE

infix HBJAFDGCEk-1

struct BTreeNode BTree_InfixPrefix (AnsiString infix AnsiString prefix)

int k struct BTreeNode node if (infixLength==0) return NULLnode = (struct BTreeNode ) malloc (sizeof(struct BTreeNode))node-gtdata = prefix[1]k = find(prefix[1] infix)node-gtleftchild = BTree_InfixPrefix

(infix[1k-1] prefix[2k])node-gtrightchild = BTree_InfixPrefix(infix[k+1infixLength]prefix[k+1prefixLength])

VLR

LVRk1

k2 k+1~

k+1~

1

struct BTreeNode BTree_InfixPostfix (AnsiString infix AnsiString prefix)

int k struct BTreeNode node if (infixLength==0) return NULLnode = (struct BTreeNode ) malloc (sizeof(struct BTreeNode))node-gtdata = postfix[postfixLength]k = find(postfix[postfixLength] infix)node-gtleftchild = BTree_InfixPrefix

(infix[1k-1] postfix[1k-1])node-gtrightchild = BTree_InfixPrefix(infix[k+1infixLength]postfix[k+1postfixLength-1])

56  二元樹的複製與相等測試程式 5-8 二元樹的複製1 struct BTreeNode2 struct BTreeNode leftchild3 char data4 struct BTreeNode rightchild5 6 struct BTreeNode root7 struct BTreeNode Copy(struct BTreeNode TreeRoot)8 struct BTreeNode CopyRoot9 if (TreeRoot == NULL)10 return NULL11 CopyRoot =(struct BTreeNode ) malloc (sizeof(BTreeNode))12 CopyRoot-gtdata = TreeRoot-gtdata13 CopyRoot-gtleftchild = Copy(TreeRoot-gtleftchild)14 CopyRoot-gtrightchild = Copy(TreeRoot-

gtrightchild)15 return CopyRoot16

程式 5-9 檢測兩棵二元樹是否相等1 struct BTreeNode2 struct BTreeNode leftchild3 char data4 struct BTreeNode rightchild5 6 struct BtreeNode rightchild7 int Equal(struct BTreeNode ustruct BTreeNode v)8 if ((u==NULL) ampamp (v==NULL)) return 19 if ((u=NULL) ampamp (v=NULL)10 ampamp (u-gtdata == v-gtdata )11 ampamp Equal(u-gtleftchildv-gtleftchild)12 ampamp Equal(u-gtrightchildv-gtrightchild))

13 return 114 return 015

Counting Nodes in a Binary Tree

int CountingNodes(struct BTreeNodes node)

if (node == NULL) return 0 int leftchildren = CountingNodes(node-gtleftchild)

int rightchildren= CountingNodes(node-gtrightchild)

return leftchildren+rightchildren

Counting Leaves in a Binary Tree

int CountingLeaves(struct BTreeNodes node)

if (node == NULL) return 0 int leftleaves = CountingLeaves(node-gtleftchild)

int rightchildren= CountingLeaves(node-gtrightchild)

if (leftleaves==0 ampamp leftleaves==0) return 1

return leftleaves + leftleaves

57  引線二元樹bull 在二元樹的鍵結表示中樹葉節點的兩個子樹指標皆指向 NULL 由定理 5-1 得知 n = E+1 其中 n 為節點個數 E 為分支數

bull 而 n 個鍵結節點有 2n 個指標空間每個分支恰佔用一個指標空間共有 E (=n-1) 個指標有用到(不是空的)而共有 n+1 個指標空間是 NULL 比非空的節點還多

bull 有學者提出對這些「放空指標的空間」加以利用的概念mdash與其放空指標不如放指向其它節點的指標稱之為引線 (thread) 使得某些運算(如走訪hellip等)可以加快

bull 這個概念發展出了引線二元樹 (threaded binary tree) 這種資料結構

範例 5-17下圖為加了引線的二元樹

加入的引線將使二元樹的中序走訪更加便利沿著上右圖箭頭所指示的順序走訪即可完成此二元樹的中序走訪GDHBAIECF 為此二元樹的中序表示

bull 範例 5-17 中所加的引線目的在方便二元樹的中序走訪

bull 對樹葉節點 p 而言令其資料為 P 則引線指標的規則如下

(1) p 的左子樹指標指向中序表示中 P 的前一個 元素(前接元素 (predecessor) )

(2) p 的右子樹指標指向中序表示中 P 的後一個 元素(後繼元素 (successor) )

為了區別節點指標放的是一般指標(內部節點)抑或是引線指標(樹葉節點)我們另以 2 個欄位分別區別左和右子樹指標空間存放的對象其節點的記憶體配置有如下圖所示

其中 leftthread (rightthread) 為 0 時表示 leftchild (rightchild) 存放的是一般節點指標而 leftthread (rightthread) 為 1 時表示 leftchild (rightchild) 存放的是引線指標1 struct TBTreeNode2 int leftthread3 struct TBTreeNode leftchild4 char data5 struct TBTreeNode rightchild6 int rightthread7

由右側的引線二元樹可知有最左和最右兩條引線尚無妥善的安排我們另設計一空的引線節點做為樹根如下圖所示

571  引線二元樹的中序走訪決定節點 p 在中序表示中的後繼節點 q (簡稱中序後繼點)

程式 5-10決定節點 node的中序後繼點1 struct TBTreeNode2 int leftthread3 struct TBTreeNode leftchild4 char data5 struct TBTreeNode rightchild6 int rightthread7 8 struct TBTreeNode root9 struct TBTreeNode Next(struct TBTreeNode node)

10 struct TBTreeNode temp11 temp = node-gtrightchild12 if (node-gtrightthead) return temp13 while (temp-gtleftthead) temp=temp-gtleftchild

14 return temp15

程式 5-11引線二元樹的中序表示(接程式 5-10)1 void InorderTBTree()2 struct TBTreeNode node3 for(node=Next(root)node=rootnode=Next(node))

4 cout ltlt node-gtdata5

bull只要 node 是引線二元樹上的任一節點透過呼叫 Next(node) 即可找到其在中序表示中的後繼節點bull(試想若程式 5-10 中的程序 Next 所傳入的參數指標正是 root Next(root) 是否可正常運作請思考)bull於是只須從中序表示中的第一節點起讓每一節點 node 都執行 Next(node) 程序即可得引線二元樹的中序表示

572  在引線二元樹中加入節點在引線二元樹中樹葉節點節點的右子樹指標所指向或內部節點右子樹的最左樹葉為其中序後繼節點而且任一節點若無左子樹則其左子樹指標乃指向其中序前接節點hellip這些性質必須在節點新增進入引線二元樹時也要保持接下來討論節點 new 插入成為引線二元樹的節點 p 的右子節點的情形至於插入成為左子節點的情形則讓各位模擬推敲

572572  在引線二元樹中加入節點 在引線二元樹中加入節點 (( 續續 ))

newnew 會成為會成為 pp 的右子節點的情況共有以下兩種的右子節點的情況共有以下兩種(1) (1) 若若 pp 沒有右子節點 沒有右子節點 ((p-gtrightthread p-gtrightthread 為為1) 1) 如圖如圖 ((a) a) 則 則 new new 插入後應形成如圖插入後應形成如圖 ((b) b) 所示所示

(2)(2) 若若 pp 有右子節點 有右子節點 ((p-gtrightthread p-gtrightthread 為為0) 0) 如圖如圖 ((c) c) 則 則 new new 插入後應形成如插入後應形成如圖圖 ((d) d) 所示所示

程式 5-12  插入節點 new 成為引線二元樹中節點 p的右子節點(上接程式 5-10 5-11 )

1 void InsertRight(struct TBTreeNode p 2 struct TBTreeNode new)3 struct TBTreeNode q4 new-gtrightchild = p-gtrightchild5 new-gtrightthead = p-gtrightthread6 new-gtleftchild = p7 new-gtleftthread = 18 p-gtrightchild = new9 p-gtrightthead = 010 if (new-gtrightthread)11 q = Next(new)12 q-gtleftchild = new13 14

58  堆積bull 「堆積」 (heap) 是一個特殊的完備二元樹節點的資料與其子節點的資料之間有事先定好的大小關係存在bull 定義ndash最大堆疊為一完備二元樹任一節點的資料內容不小於其子節點的資料內容ndash最小堆疊為一完備二元樹任一節點的資料內容不大於其子節點的資料內容

581  插入節點至最大堆積中最大堆積是一完備二元樹所以如圖 (a) 之最大堆積於加入新節點後樹的形狀必須如圖 (b) 所示灰色節點即為新節點若加入的節點資料分別為 1 9 12 則其最大堆積將分別變成如圖 (c) (d) (e) 所示

程式 5-13 新增節點至最大堆積中最大堆積本身是一棵完備二元樹可以用陣列表示之 1 define maxsize 1002 int heap[maxsize]3 int n i4 void InsertHeap(int x)5 if (n == maxsize) HeapFull()6 else7 n++8 i = n9 while ((i gt 1) ampamp (x gt heap[i2]))10 heap[i] = heap[i2]11 i = 212 13 heap[i] = x14 16

程序 InsertHeap 在插入第 n 個資料時需要的時間複雜度為 O(logn)

1 100

2 93

3 84

4 77

5 65

6 81

7 79

8 66

9 70

10 50

100

84

77

93

8165

66 70

79

50

1

2 3

4 5 6 7

8 9 10

1 define maxsize 1002 int heap[maxsize]3 int n i4 void InsertHeap(int x)5 if (n == maxsize) HeapFull()6 else7 n++8 i = n9 while ((i gt 1) ampamp (x gt heap[i2]))10 heap[i] = heap[i2]11 i = 212 13 heap[i] = x14 16

582 刪除最大堆積中的最大資料圖 (a) 中的最大堆積在刪除最大資料後其樹形應變成如圖 (b) 所示將最未一個樹葉節點的資料 6 暫挪至樹根節點內如圖 (c) 再將圖 (c) 中非最大堆積的結構調整成為最大堆積 10 和 8 中選大者與 6對調即成圖 (d)

程式 5-14 刪除最大堆積中的最大資料(上接程式 5-13 )1 int DeleteHeap()2 int x i j3 if (n == 0) HeapEmpty()4 else5 x = heap[1]6 heap[1] = heap[n]7 n--8 i = 19 while (i lt= n2)10 if (heap[2i] gt heap[2i+1])11 j = 2i12 else13 j = 2i+114 if (heap[i]gtheap[j]) break15 swap(heap[i] heap[j])16 i = j17 18 return x19 20

其時間複雜度與其堆積高度相同是為O(logn) 其中 n 為堆積中的元素個數

1 100

2 93

3 84

4 77

5 65

6 81

7 79

8 66

9 70

10 50

100

84

77

93

8165

66 70

79

50

1

2 3

4 5 6 7

8 9 10

1 int DeletHeap()2 int x i j3 if(n == 0) HeapEmpty()4 else5 x = heap[1]6 heap[1] = heap[n]7 n--8 i = 19 while (i lt= n2)10 if ([heap[2i]] gt heap[2i+1])11 j = 2i12 else13 j = 2i+114 if (heap[i]gtheap[j]) break15 swap(heap[i] heap[j])16 i = j17 18 return x19 20

583  優先佇列bull 由於最大堆積(或最小堆積)的結構可提供最大元素(或最小元素)而其維護堆積的成本為 O(logn) n 為總元素個數

bull 堆積常被當成「優先佇列」 (priority queue) 使用mdash此時佇列被組織成完備二元樹若新加入佇列的資料有大小關係優先順序的需求時此優先佇列即以 O(logn) 的代價將新資料放入優先佇列(可能是最大或最小堆積依實際大小關係優先順序的需求而定)中mdash提供依元素的優先順序做為處理順序的服務

bull 一般的佇列為先進先出先到的先行處理優先佇列則是依據其優先順序優先順序高者先行處理與來到的先後無關

範例 5-21bull 在「分時」 (time sharing) 的作業系統中我們不希望使用中央處理器 (Central Processing Unit CPU) 時間短的「處理程序」 (process) 等候過久於是有「最短時間工作優先」 (shortest-job-first) 的排程策略此時等候的處理程序即可組織成之一最小堆積而 CPU 每次即取出存於樹根的處理程序予以處理任何新來的處理程序則以 O(logn) 的時間加入此優先佇列(最小堆積)中

bull 注意之前提過的線性佇列新來的直接加入成為新的尾端元素花O(1) 的時間然線性佇列本身沒有大小的關係每次要取出最大(小)的資料得花O(n) 的代價(在所有元素中挑出最大(小)者) n 為佇列內的元素個數而優先佇列是在新元素加入時要花O(logn) 的時間至於取出最大(小)則只須 O(1) 的時間

59  二元搜尋樹bull 二元搜尋樹是一棵二元樹可能是空二元樹若不為空二元樹則滿足下列性質ndash所有節點內的資料內容是相異的 ndash左子節點的資內容 ( 如果有 ) 比父節點的資料內容小 ndash右子節點的資料內容 ( 如果有 ) 比父節點的資料內容大 ndash以左和右子節點為樹根的左子樹和右子樹也是二元搜尋樹

在二元搜尋樹中搜尋資料二元搜尋樹對資料大小的掌握十分明確若恰與節點 p 資料相等則搜尋成功若欲搜尋的資料 x 比樹節點 p 中的資料小 則只須搜尋節點 p 的左子樹若比節點 p 中的資料大 則只須搜尋 p 的右子樹這樣的判斷可以遞迴地應用在各子樹節點 中

程式 5-15 在二元搜尋樹中搜尋資料(遞迴呼叫)1 struct BSTreeNode2 struct BSTreeNode leftchild3 int data4 struct BSTreeNode rightchild5 6 struct BSTreeNode root7 struct BSTreeNode SearchBST(

struct BSTreeNode tree int x)8 if (tree == NULL) return NULL9 if (x == tree-gtdata) return tree10 if (x lt tree-gtdata)11 return SearchBST(tree-gtleftchild x)

12 return SearchBST(tree-gtrightchild x)13

程式 5-16 搜尋二元搜尋樹的資料(非遞迴呼叫)在二元搜尋樹十分龐大時遞迴呼叫的效率不會太好可利用迴圈來完成搜尋的動作以提高執行效率請看程式 5-16

14 struct BSTreeNode Search BST_iterative15 (struct BSTreeNode node int x)16 while (node = NULL)17 if (x == node-gtdata) return node18 if (x lt node-gtdata)19 node = node-gtleftchild20 else21 node = node-gtrightchild22 23 return NULL24

592  新增資料於二元搜尋樹中欲新增資料進入一二元搜尋樹中得先決定該資料在二元搜尋樹中應出現的位置這可透過上節提到的搜尋動作來解決

在二元搜尋樹定義的性質 (1) 中我們限定了元素必須相異所以搜尋新資料的結果必是 NULL

必須將搜尋的結果改為新增資料所應在位置的節點指標

程式 5-17  在二元搜尋樹中新增資料25 int InsertBST(int x)26 struct BSTreeNode p q27 p = root q = NULL28 while (p = NULL)29 q = p30 if (x == p-gtdata) return 0

若不考慮重複則可省略之31 if (x lt p-gtdata)32 p = p-gtleftchild33 else34 p = p-gtrightchild35

3636 p = (struct BSTreeNode ) p = (struct BSTreeNode ) malloc (sizeof(BSTreeNode))malloc (sizeof(BSTreeNode))

3737 p-gtdata = x p-gtdata = x3838 p-gtleftchild = p-gtrightchild = p-gtleftchild = p-gtrightchild = NULLNULL3939 if (root == NULL) root = p if (root == NULL) root = p4040 else if (x lt q-gtdata) else if (x lt q-gtdata)4141 q-gtleftchild = p q-gtleftchild = p4242 else else4343 q-gtrightchild = p q-gtrightchild = p4444 return 1 return 145 45

程式 程式 5-175-17  在二元搜尋樹中新增資料  在二元搜尋樹中新增資料 (( 續續 ))

範例 5-23 圖 (b) 為圖 (a) 新增整數 6 之後的結果圖 (c) (d) (e) (f) 則分別是逐步再加入 28 18

10 21 的結果

bull若目前的二元搜尋樹高度為 h 則新增資料得花O(h) 的時間找到其所應在的位置遂新增資料至二元搜尋樹中的時間複雜之度為 O(h)

593 自二元搜尋樹中刪除資料欲自二元搜尋樹中刪除資料可能會面臨兩種情況(1) 刪除節點 p 位於樹葉節點上如下圖所示 這是比較容易處理的情況

(2) 刪除的節點 p 不是樹葉刪除節點 p 後原 p 所指的節點可由 q ( q 中包含了 p 左子樹中的最大節點值)或 r ( r 中包含了 p 右子樹中的最小節點值)來取代以維持二元搜尋樹中資料的大小關係其結果分別如圖 (b) 和 (c) 所示而圖 (d) 則標明了指標會改變的節點位置即 p 會消失 father 的左或右子樹指標會異動 k(可能是 q 或 r )會移到原來 p 的位置其父節點 f 的左或右子樹指標會異動其中 father 為 p 父節點 f 為 k 的父節點

程式 5-18 自二元搜尋樹中刪除資料46 int DeleteBST (int x)47 Struct BSTreeNode p father k f48 p = root father = NULL49 while (p = NULL)50 if (x == p-gtdata) 找到 x 所在的節點 p 了51 if ((p-gtleftchild == NULL) ampamp 52 (p-gtrightchild == NULL))53 k = NULL p為樹葉節點54 else if (p-gtleftchild = NULL )x位於非子節

點55 f = p 找出 p左子樹的最右樹葉56 k = p-gtleftchild57 while (k-gtrightchild =NULL)58 f = k59 k = k-gtrightchild60 k將挪至原 p處61 if (p == f)62 f-gtleftchild = k-gtleftchild 63 else64 f-gtrightchild = k-gtleftchild 65

66 else p無左子樹67 f = p 找出 p右子樹的最左樹葉68 k = p-gtrightchild69 while (k-gtleftchild = NULL)70 f = k71 k = k -gtleftchild72 73 if (p == f)74 f-gtrightchild = k-gtrightchild 75 else76 f-gtleftchild = k-gtrightchild77 78 if (k = NULL) k挪至原 p處繼承 p的左右指標79 k-gtleftchild = p-gtleftchild80 k-gtrightchild = p-gtrightchild81 82 if (father == NULL) root = k83 else if (x lt father-gtdata) 決定 k是 father的左

或右兒子84 father-gtleftchild = k85 else86 father-gtrightchild = k87 free(p)88 return 1 成功地刪除 x於此傳回 1返回呼叫處89 51~89行為找到 x時應有的處理步驟

90 else 尚未找到 x繼續往下一階層找

91 father = p92 if (x lt p-gtdata)93 p = p-gtleftchild94 else95 p = p-gtrightchild96 97 98 return 0 未找到 x傳回 099

510  樹林 (forest)

bull 定義樹林(森林)是由 n 棵沒有交集的樹所構成的集合 n 0

下圖為一包含三棵樹的樹林

5101 轉換樹林為二元樹 之前我們以二元樹來表示樹因為二元樹有較多

好用的性質在此我們也將樹林轉換成二元樹看待

在 522 節中我們嘗試了用左子右兄弟的方式來表示樹這個方法事實上已將樹轉換成二元樹

即用左子右兄弟的結構重新組織樹林中的每一棵樹這些樹的樹根將視為兄弟而最左邊的樹則視為樹林二元樹的樹根如此一來樹林即可轉換成二元樹

範例 5-25右下圖為右上圖三棵樹所對應的三棵左子右兄弟二元樹

下圖則為上圖樹林所對應的一棵樹林二元樹

演算法 5-1  轉換樹林成二元樹輸入樹林 F = T1 T2 Tn

輸出 F 的二元樹表示 BT=(T1 T2 Tn)1 if (n == 0) 輸出空二元樹 2 else

3 root(T1 T2 Tn) = T1

4 leftsubtree(T1 T2 Tn) =

BT(T11 T12 T1m)5 BT(T11 T12 T1m)為 T1的子樹6 rightsubtree(T1 T2 Tn) =

BT(T2 T3 Tn)

樹林轉換成二元樹的過程樹林轉換成二元樹的過程

  • Slide 1
  • Slide 2
  • Slide 3
  • Slide 4
  • Slide 5
  • Slide 6
  • Slide 7
  • Slide 8
  • Slide 9
  • Slide 10
  • Slide 11
  • Slide 12
  • Slide 13
  • Slide 14
  • Slide 15
  • Slide 16
  • Slide 17
  • Slide 18
  • Slide 19
  • Slide 20
  • Slide 21
  • Slide 22
  • Slide 23
  • Slide 24
  • Slide 25
  • Slide 26
  • Slide 27
  • Slide 28
  • Slide 29
  • Slide 30
  • Slide 31
  • Slide 32
  • Slide 33
  • Slide 34
  • Slide 35
  • Slide 36
  • Slide 37
  • Slide 38
  • Slide 39
  • Slide 40
  • Slide 41
  • Slide 42
  • Slide 43
  • Slide 44
  • Slide 45
  • Slide 46
  • Slide 47
  • Slide 48
  • Slide 49
  • Slide 50
  • Slide 51
  • Slide 52
  • Slide 53
  • Slide 54
  • Slide 55
  • Slide 56
  • Slide 57
  • Slide 58
  • Slide 59
  • Slide 60
  • Slide 61
  • Slide 62
  • Slide 63
  • Slide 64
  • Slide 65
  • Slide 66
  • Slide 67
  • Slide 68
  • Slide 69
  • Slide 70
  • Slide 71
  • Slide 72
  • Slide 73
  • Slide 74
  • Slide 75
  • Slide 76
  • Slide 77
  • Slide 78
  • Slide 79
  • Slide 80
  • Slide 81
  • Slide 82
  • Slide 83
  • Slide 84
  • Slide 85
  • Slide 86
  • Slide 87
  • Slide 88
  • Slide 89
  • Slide 90
  • Slide 91
  • Slide 92
  • Slide 93
  • Slide 94
  • Slide 95
  • Slide 96
  • Slide 97
  • Slide 98
  • Slide 99
  • Slide 100
  • Slide 101
  • Slide 102
  • Slide 103
Page 14: 第五章 樹

522  左子右兄弟表示法

每個節點都有唯一的最左 兒子 (leftmost child) 每個節點都有最靠近它的 右兄弟

523 分支度為 2 的二元樹表示法bull 分支度為 2 的樹又稱為二元樹 (binary tree)

bull 二元樹中任一節點皆有 2個指標分別指向該節點的左子樹和右子樹

bull 二元樹可以表示任何樹事實上二元樹有許多很好的性質我們在一下節中介紹

bull注意樹狀結構中兒子的順序是不重要的試想樹可以樹根為軸心旋轉使得兒子的順序是不固定的

53  二元樹bull 二元樹是一個由節點組成的有限集合可以是空集合或是包含了(1) 一個樹根節點(2) 其它節點分割成兩個沒有交集的二元樹

其一為左子樹 (left subtree) 另一為右子樹 (right subtree) 樹和二元樹之間的差異

(1)樹不允許空節點而空二元樹是允許的

(2) 樹的子樹沒有順序而二元樹的子樹有左右之分

531  樹和二元樹的基本性質

樹或二元樹皆擁有下面的性質

定理 5-1 若一棵樹 T 的總節點數為 V 總邊數為

E 則V = E + 1

證明利用數學歸納法歸納法的基礎當 V=1 時即 T 只有一個樹根節點 E 為 0 所以 V = E + 1 成立歸納法的假設假設當 1 V K 時該式成立歸納法的推論當 V = K + 1 時令此樹的樹根為 R 而 R 有 m 個子樹 T1 T2 hellip

Tm (如圖 5-12 所示)分別有個 V1 V2 hellip Vm 個節點其中 V1 +V2 + hellip+ Vm =K 若 E1 E2 hellip Em 分別為此 m 個子樹的邊數則

E = E1 +E2 + hellip+ Em + m (1)根據歸納法的假設可知

V1 = E1+1V2 = E2+1 Vm = Em+1

所以V1 +V2 + hellip+Vm = E1 + E2 + hellip+ Em +m (2)

然而V = V1 +V2 + hellip+Vm+1 (3)

將式 (2) 代入式 (3) 可得V = E1 + E2 + hellip+ Em + m + 1

引用式 (1) 則可得V = E + 1

由數學歸納法的原理可得證本定理

532 二元樹的性質(a) 稱為歪斜樹 (skew tree) (b) 稱為完備二元樹 (complete binary tree)

( 其樹葉節點皆在相鄰階層完整定義在後 ) 同樣數目的樹節點存放在歪斜樹上會花較多的階層放在完備二元樹上則只須最少的階層

532 二元樹的性質 ( 續 )定理 5-2 (1) 二元樹上階層 i 的節點數目最多為 2i-1 i 1 (2) 深度為 k 的二元樹其節點數目最多為 2k-

1 k1 bull 定理 5-2說明了 (1) 每一階層上可放的最多節點數

(2) 階層數已知時二元樹可放的最多節點數證明 (1) 利用數學歸納法歸納法的基礎當 i=1 時只有樹根一個節點 2i-1=21-1=1 原式成

立歸納法的假設假設 i = k 時該式成立即階層 k 的最大節點數為

2k-1 k 1 歸納法的推論當 i = k+1 時因在階層 k 的每一個節點最多會有

2 個子節點遂在 k+1 階層最多會有 22k-1 = 2k

= 2i-1 節點由數學歸納法的原理可知本定理得證(2) 由 (1) 可知二元樹在階層 i 的最多節點數為 2i-1那麼深度 k 的二元樹最多可有節點如下

k

i

ki

1

1-1- 24212 1-2

)12(1 k= 2k-1

=

bull 對二元樹而言任一節點的分支度可能為 0 1 或 2 分支度為 0 者是為樹葉

bull 樹葉的數目與分支度為 2 的節點數目存在著有趣的關係請見定理 5-3

定理 5-3 若 T 為一非空的二元樹 n0 為樹葉節點數目 n2為分支度為 2 的節點數目則 n0 = n2+1

證明令 n 為所有節點數目而 n1 為分支度為 1 的節點數目可得 n = n0 + n1 +n2 (1)令 B 為二元樹的分支數除了樹根外每一節點皆有一分支指向之所以

n = B+1 (亦可參考定理 5-1 )又 B = n1+2n2

且 n = n1 + 2n2 +1 (2)由式 (1) 和式 (2) 我們得到

n0 = n2 + 1

完滿二元樹 (full binary tree)

定義一個深度為 k 的完滿二元樹即為一深度為 k 且有 2k-1 個節點的二元樹k 0

bull 由定理 5-2(2) 可知深度為 k 的二元樹其最多節點數為 2k-1 這樣的樹會將樹上所有可能存放節點的位置都放滿完滿 (full) 之名因而生成

完備二元樹 (complete binary tree)我們可對二元樹上節點予以編號階層小者先編同一階

層則自左向右編號這樣的編號方式使我們定義出完備(complete) 二元樹

定義深度為 k 節點數為 n 的二元樹稱為完備二元樹若且唯若 (1) k = 1 時樹有一個節點

(2) 當 k 2 且 1iltk 時深度 i 有 2i-1 個節點且第 k 層的節點皆由第 k-1 層的分支由左至右逐一連接而成

完滿或完備二元樹之所以受人注意乃因它們可以將節點以較少的階層數存放

定理 5-4 n 個節點的完備或完滿二元樹其深度為 log2(n+1)

證明 完滿或完備二元樹的節點皆自階層

1 起排放任一階層 i 可排放 2i-1

個節點遂由定理 5-2 得知此 n 個節點所佔的深度為 log2(n+1)

正規二元樹 (formal binary tree)bull 在二元樹中非樹葉的節點又稱為內部節點

(internal node) 若所有內部節點恰有 2 個子節點即成為正規二元樹

定義若二元樹中所有內部節點恰有 2 個子節點則稱之為正規二元樹

正規二元樹常被引用在單淘汰賽正規二元樹常被引用在單淘汰賽制的各項賽程安排樹葉為參賽制的各項賽程安排樹葉為參賽隊伍內部節點為優勝隊伍樹隊伍內部節點為優勝隊伍樹根即為冠軍隊伍根即為冠軍隊伍

在單淘汰賽制中自在單淘汰賽制中自 n n 個隊伍中產個隊伍中產生冠軍須舉辦 生冠軍須舉辦 nn-1-1場比賽場比賽

54  二元樹的表示法541 以陣列表示二元樹定理 5-5 若一個具有 n 個節點的完備二元樹以循序的方式編號並依序存放在陣列 A 中即第 i 個節點放在 A[i] 中 1 i n 則

(1) 節點 i 的父節點位在 A[i2]處 i ne 1( i = 1 時其節點正為樹根無父節點)

(2) 若 2i n 則節點 i 的左兒子節點位在 A[2i] 處若 2i gtn 節點 i 沒有左子節點

(3) 若 2i+1 n 節點 i 的右兒子節點位 A[2i+1]處若 2i+1gtn 節點 i 沒有左子

節點

證明先用數學歸納法證明 (2) bull 歸納法的基礎當 i = 1 時其左子節點確實在 A[2] 處(除非 nlt2 此時節點 1 沒有左子節點)

bull 歸納法的假設假設當 I = k 時其左子節點確在 A[2i] = A[2k] 處(除非 2i 已大於 n 此時節點 i 無左子節點)

bull 歸納法的推論當 i = k+1 時因第 k 節點的左子節點在 A[2k] 處其右子節點則位在 A[2k+1] 處那麼第 k+1 節點的左子節點應緊臨 A[2k+1] 亦即位於 A[2k+2] = A[2(k+1)] 處(除非 2i 已大於 n 此時節點 i 無左子節點)

bull 由數學歸納法的原理得證定理 5-5 (2) (3) 由於 (2) 成立而且節點 i 的右子節點會緊臨

A[2i] 亦即其位於 A[2i+1]處除非 i 根本沒有右子節點得證定理 5-5 (3)

由 (2) 和 (3) 我們可知定理 5-5 (1) 成立

542 以鍵結串列表示二元樹bull 以陣列來表示完備二元樹算是相當方便但ndash一般的二元樹將導致陣列空間的浪費ndash如果二元樹中節點的新增刪除動作頻繁則會造成陣列對應資料的搬動隨而頻繁

bull 以鍵結串列表示二元樹

為方便辦識樹根我們常用 root 指標指向樹根做為樹鍵結串列的起點

陣列表示和鍵結表示的空間需求bull 完備二元樹的陣列表示和鍵結表示皆沒浪費儲存資料的空間

bull 但歪斜樹的陣列表示比起鍵結表示則浪費許多

bull 以 n 個節點的歪斜樹而言陣列表示法可能需要 2n-1 個陣列元素其中 2n-1-n 個陣列元素空間是浪費的而鍵結表示法只需要 n 個節點空間

二元樹節點的宣告程式 5-2

1 struct BTreeNode

2 struct BtreeNode leftchiled

3 int data

4 struct BTreeNode rightchild

5

6 struct BTreeNode root

bull 這樣的二元樹節點要取得兒子節點的指標十分方便但若有取得父節點的需求則嫌不足我們可以加上 parent 欄位指向父節點

55  二元樹的走訪bull 假如一組資料已用二元樹的組織在一起總有需求對其全數資料做動作例如計算所有數目印出所有資料在所有資料中搜尋某項資料hellip等此時即須對此二元樹做走訪 (traversal) 的運算利用走訪二元樹的同時將計算列印或搜尋的動作完成

bull 事實上走訪即在決定二元樹上資料被處理(計算列印或搜尋)的順序我們也希望走訪的演算法對任何節點皆一致是容易撰寫程式實作的

bull 若對一個二元樹上的節點而言 V 表示處理節點上的資料 L 表示走訪其左子樹 R 表示走訪其右子樹

55  二元樹的走訪 ( 續 )bull 因為對稱的緣故我們只考慮先走訪左邊再走訪右邊的情形那麼右圖只剩下三種走訪方式

bull 以 V 所在的相對位置分別對此三種走訪方式取名如下

(1) LVR 中序走訪 (inorder traversal)或中序表示法 (infix notation)

(2) LRV 後序走訪 (postorder traversal) 或後序表示法 (postfix notation)

(3) VLR 前序走訪 (preorder traversal)或前序表示法 (prefix notation)

551  中序走訪bull 利用遞迴的方式撰寫中序走訪的程序希望把二元樹中序走訪的

順序印出來

程式 5-3二元樹的中序走訪1 struct BTreeNode2 struct BTreeNode leftchild3 char data4 struct BTreeNode rightchild5 6 struct BTreeNode root7 void inorder(struct BTreeNode node)8 if (node = NULL)9 inorder(node-gtleftchild)10 cout ltlt node-gtdata11 inorder(node-gtrightchild)12 13

範例 5-12此為運算式二元樹

其對應中序表示運算式為 (A+B)C+D(E-F)

6 struct BTreeNode root7 void inorder(struct BTreeNode node)8 if (node = NULL)9 inorder(node-gtleftchild)10 cout ltlt node-gtdata11 inorder(node-gtrightchild)12 13

6 struct BTreeNode root7 void inorder(struct BTreeNode node)8 if (node = NULL)9 inorder(node-gtleftchild)10 cout ltlt node-gtdata11 inorder(node-gtrightchild)12 13

(A+B)C+D(E-F)

如何加上 ( )

552  後序走訪程式 5-4二元樹的後序走訪14 void postorder(struct BTreeNode node)

15 if (node =NULL)16 postorder(node-gtleftchild)17 postorder(node-gtrightchild)18 cout ltlt node-gtdata19 20

以後序走訪可得到AB+CDEF-+

其正為對應的後序運算式

553  前序走訪程式 5-5二元樹的前序走訪21 void preorder(struct BTreeNode node)

22 if (node = NULL)

23 cout ltlt node-gtdata

24 preorder(node-gtleftchild)

25 preorder(node-gtrightchild)

26

27

以前序走訪可得到++ABCD-EF

其正為對應的前序運算式

554 利用堆疊和迴圈取代遞迴進行二元樹的走訪1 struct BTreeNode 2 struct BTreeNode leftchild3 char data4 struct BtreeNode rightchild5 6 struct BTreeNode root7 struct StackNode8 struct BTreeNode treenode9 struct StackNode next1011 struct StackNode top

12 void push_node(struct BtreeNode node )13 struct StackNode old_top14 old_top = top15 top = (struct StackNode ) malloc

(sizeof(struct StackNode))16 top-gttreenode = node17 top-gtnext = old_top18 19 struct BTreeNode pop_node()20 struct BTreeNode Tnode21 struct StackNode old_top22 if (top == NULL)23 StackEmpty()24 else25 Tnode = top-gttreenode26 old_top = top27 top = top-gtnext28 free(old_top)29 return Tnode30 31

32 void inorder_Stack(struct BtreeNode node)33 do34 while (node = NULL)35 push_node(node)36 node = node-gtleftchild37 push all left children38 if (top = NULL)39 node = pop_node()40 cout ltlt node-gtdata41 node = node-gtrightchild42 inorder printing and check right child43 while ((top=NULL)||(node=NULL))44 top==NULL 堆疊已空 node==NULL沒有右子

6 struct BTreeNode root7 void inorder(struct BTreeNode node)8 if (node = NULL)9 inorder(node-gtleftchild)10 cout ltlt node-gtdata11 inorder(node-gtrightchild)12 13

中序表示為DBGEHAFIC

32 void inorder_Stack()33 struct BTreeNode node34 node = root35 do36 while (node = NULL)37 push_node(node)38 node = node-gtleftchild39 40 if (top = NULL)41 node = pop_node()42 cout ltlt node-gtdata43 node = node-gtrightchild44 45 while(top=NULL)||(node=NULL))46

45 void Preorder_Stack(struct BSTreeNode node)46 do47 while (node = NULL)48 cout ltlt node-gtdata)49 push_node(node)50 node = node-gtleftchild51 push all left children52 if (top = NULL)53 node = pop_node()54 node = node-gtrightchild55 56 while ((top=NULL)||(node=NULL))57

程式 5-5二元樹的前序走訪21 void preorder(struct BTreeNode node)22 if (node = NULL)23 cout ltlt node-gtdata24 preorder(node-gtleftchild)25 preorder(node-gtrightchild)26 27

58 void Postorder_Stack(struct BTreeNode node)59 do60 while (node = NULL)61 cout ltlt node-gtdata62 push_node(node)63 node = node-gtrightchild64 push all left children65 if (top = NULL)66 node = pop_node()67 node = node-gtleftchild68 69 while ((top=NULL)||(node=NULL))70

程式 5-4二元樹的後序走訪14 void postorder(struct BTreeNode node)15 if (node =NULL)16 postorder(node-gtleftchild)17 postorder(node-gtrightchild)18 cout ltlt node-gtdata19 20

555  階層走訪bull 「階層走訪」 (level-order traversal) 是依階層

的順序進行二元樹的走訪先走訪階層小的節點後走訪階層大的節點同一階層者則依自左向右的順序走訪

bull 對下圖的二元樹進行階層走訪的結果為ABCDEFGHI

bull 對同一階層而言先走訪的節點其子節點亦在下一階層中先被走訪

bull 這種「先進先出」的特性恰可用佇列予以儲存與處理

程式 5-7 利用佇列進行二元樹的階層走訪

1 struct BTreeNode2 struct BTreeNode leftchild3 char data4 struct BTreeNode rightchild5 6 struct BTreeNode root7 struct QNode8 struct BTreeNode treenode9 struct QNode next10 11 struct QNode front rear

12 void AddQueue(struct BTreeNode Tnode)13 struct QNode node14 node=(struct QNode )malloc(sizeof(struct QNode))15 node-gttreenode = Tnode16 node-gtnext = NULL17 if (front == NULL) front = node19 else rear-gtnext = node20 rear = node21

22 struct BTreeNode DeleteQueue()23 struct BTreeNode Tnode24 struct QNode old_front25 if (rear == NULL)26 QueueEmpty()27 else28 Tnode = front-gttreenode29 old_front = front30 front = front-gtnext31 free(old_front)32 return Tnode33 34 35 void LevelOrder (struct BTreeNode node)36 AddQueue(node)37 while (front =NULL)38 node = DeleteQueue()39 cout ltlt node-gtdata40 if (node-gtleftchild = NULL)41 AddQueue(node-gtleftchild)42 if (node-gtrightchild = NULL)43 AddQueue(node-gtrightchild)44 45

35 void LevelOrder (struct BTreeNode node)36 AddQueue(node)37 while (front =NULL)38 node = DeleteQueue()39 cout ltlt node-gtdata40 if (node-gtleftchild = NULL)41 AddQueue(node-gtleftchild)42 if (node-gtrightchild = NULL)43 AddQueue(node-gtrightchild)44 45

bull Infix ABCbull Postfix ABC CBA ACB BCA

BAC CAB

B

A CA

B

C

B

A

C

C

B

AB

A

C

二元樹中序與前序式中序與後序式bull 每一棵二元樹皆有唯一的一對中序與前序表示法也有唯一的中序與後序次序

bull 換句話說給予一對中序與前序或中序與後序即可決定一棵二元樹

bull 然而給予前序和後序次序並不能決定唯一的二元樹可能會產生兩棵不同的二元樹

bull 想想階層走訪和中序走訪順序可否決定唯一的二元樹

A

B C

H J D E

F G

prefix ABHJCDFGEinfix HBJAFDGCEpostfix HJBFGDECA

VLRLVRLRV

A

prefix BHJinfix HBJ

prefix CDFGEinfix FDGCE

prefix ABHJCDFGEinfix HBJAFDGCE

A

B C

H J D E

F G

prefix ABHJCDFGE

infix HBJAFDGCEk-1

struct BTreeNode BTree_InfixPrefix (AnsiString infix AnsiString prefix)

int k struct BTreeNode node if (infixLength==0) return NULLnode = (struct BTreeNode ) malloc (sizeof(struct BTreeNode))node-gtdata = prefix[1]k = find(prefix[1] infix)node-gtleftchild = BTree_InfixPrefix

(infix[1k-1] prefix[2k])node-gtrightchild = BTree_InfixPrefix(infix[k+1infixLength]prefix[k+1prefixLength])

VLR

LVRk1

k2 k+1~

k+1~

1

struct BTreeNode BTree_InfixPostfix (AnsiString infix AnsiString prefix)

int k struct BTreeNode node if (infixLength==0) return NULLnode = (struct BTreeNode ) malloc (sizeof(struct BTreeNode))node-gtdata = postfix[postfixLength]k = find(postfix[postfixLength] infix)node-gtleftchild = BTree_InfixPrefix

(infix[1k-1] postfix[1k-1])node-gtrightchild = BTree_InfixPrefix(infix[k+1infixLength]postfix[k+1postfixLength-1])

56  二元樹的複製與相等測試程式 5-8 二元樹的複製1 struct BTreeNode2 struct BTreeNode leftchild3 char data4 struct BTreeNode rightchild5 6 struct BTreeNode root7 struct BTreeNode Copy(struct BTreeNode TreeRoot)8 struct BTreeNode CopyRoot9 if (TreeRoot == NULL)10 return NULL11 CopyRoot =(struct BTreeNode ) malloc (sizeof(BTreeNode))12 CopyRoot-gtdata = TreeRoot-gtdata13 CopyRoot-gtleftchild = Copy(TreeRoot-gtleftchild)14 CopyRoot-gtrightchild = Copy(TreeRoot-

gtrightchild)15 return CopyRoot16

程式 5-9 檢測兩棵二元樹是否相等1 struct BTreeNode2 struct BTreeNode leftchild3 char data4 struct BTreeNode rightchild5 6 struct BtreeNode rightchild7 int Equal(struct BTreeNode ustruct BTreeNode v)8 if ((u==NULL) ampamp (v==NULL)) return 19 if ((u=NULL) ampamp (v=NULL)10 ampamp (u-gtdata == v-gtdata )11 ampamp Equal(u-gtleftchildv-gtleftchild)12 ampamp Equal(u-gtrightchildv-gtrightchild))

13 return 114 return 015

Counting Nodes in a Binary Tree

int CountingNodes(struct BTreeNodes node)

if (node == NULL) return 0 int leftchildren = CountingNodes(node-gtleftchild)

int rightchildren= CountingNodes(node-gtrightchild)

return leftchildren+rightchildren

Counting Leaves in a Binary Tree

int CountingLeaves(struct BTreeNodes node)

if (node == NULL) return 0 int leftleaves = CountingLeaves(node-gtleftchild)

int rightchildren= CountingLeaves(node-gtrightchild)

if (leftleaves==0 ampamp leftleaves==0) return 1

return leftleaves + leftleaves

57  引線二元樹bull 在二元樹的鍵結表示中樹葉節點的兩個子樹指標皆指向 NULL 由定理 5-1 得知 n = E+1 其中 n 為節點個數 E 為分支數

bull 而 n 個鍵結節點有 2n 個指標空間每個分支恰佔用一個指標空間共有 E (=n-1) 個指標有用到(不是空的)而共有 n+1 個指標空間是 NULL 比非空的節點還多

bull 有學者提出對這些「放空指標的空間」加以利用的概念mdash與其放空指標不如放指向其它節點的指標稱之為引線 (thread) 使得某些運算(如走訪hellip等)可以加快

bull 這個概念發展出了引線二元樹 (threaded binary tree) 這種資料結構

範例 5-17下圖為加了引線的二元樹

加入的引線將使二元樹的中序走訪更加便利沿著上右圖箭頭所指示的順序走訪即可完成此二元樹的中序走訪GDHBAIECF 為此二元樹的中序表示

bull 範例 5-17 中所加的引線目的在方便二元樹的中序走訪

bull 對樹葉節點 p 而言令其資料為 P 則引線指標的規則如下

(1) p 的左子樹指標指向中序表示中 P 的前一個 元素(前接元素 (predecessor) )

(2) p 的右子樹指標指向中序表示中 P 的後一個 元素(後繼元素 (successor) )

為了區別節點指標放的是一般指標(內部節點)抑或是引線指標(樹葉節點)我們另以 2 個欄位分別區別左和右子樹指標空間存放的對象其節點的記憶體配置有如下圖所示

其中 leftthread (rightthread) 為 0 時表示 leftchild (rightchild) 存放的是一般節點指標而 leftthread (rightthread) 為 1 時表示 leftchild (rightchild) 存放的是引線指標1 struct TBTreeNode2 int leftthread3 struct TBTreeNode leftchild4 char data5 struct TBTreeNode rightchild6 int rightthread7

由右側的引線二元樹可知有最左和最右兩條引線尚無妥善的安排我們另設計一空的引線節點做為樹根如下圖所示

571  引線二元樹的中序走訪決定節點 p 在中序表示中的後繼節點 q (簡稱中序後繼點)

程式 5-10決定節點 node的中序後繼點1 struct TBTreeNode2 int leftthread3 struct TBTreeNode leftchild4 char data5 struct TBTreeNode rightchild6 int rightthread7 8 struct TBTreeNode root9 struct TBTreeNode Next(struct TBTreeNode node)

10 struct TBTreeNode temp11 temp = node-gtrightchild12 if (node-gtrightthead) return temp13 while (temp-gtleftthead) temp=temp-gtleftchild

14 return temp15

程式 5-11引線二元樹的中序表示(接程式 5-10)1 void InorderTBTree()2 struct TBTreeNode node3 for(node=Next(root)node=rootnode=Next(node))

4 cout ltlt node-gtdata5

bull只要 node 是引線二元樹上的任一節點透過呼叫 Next(node) 即可找到其在中序表示中的後繼節點bull(試想若程式 5-10 中的程序 Next 所傳入的參數指標正是 root Next(root) 是否可正常運作請思考)bull於是只須從中序表示中的第一節點起讓每一節點 node 都執行 Next(node) 程序即可得引線二元樹的中序表示

572  在引線二元樹中加入節點在引線二元樹中樹葉節點節點的右子樹指標所指向或內部節點右子樹的最左樹葉為其中序後繼節點而且任一節點若無左子樹則其左子樹指標乃指向其中序前接節點hellip這些性質必須在節點新增進入引線二元樹時也要保持接下來討論節點 new 插入成為引線二元樹的節點 p 的右子節點的情形至於插入成為左子節點的情形則讓各位模擬推敲

572572  在引線二元樹中加入節點 在引線二元樹中加入節點 (( 續續 ))

newnew 會成為會成為 pp 的右子節點的情況共有以下兩種的右子節點的情況共有以下兩種(1) (1) 若若 pp 沒有右子節點 沒有右子節點 ((p-gtrightthread p-gtrightthread 為為1) 1) 如圖如圖 ((a) a) 則 則 new new 插入後應形成如圖插入後應形成如圖 ((b) b) 所示所示

(2)(2) 若若 pp 有右子節點 有右子節點 ((p-gtrightthread p-gtrightthread 為為0) 0) 如圖如圖 ((c) c) 則 則 new new 插入後應形成如插入後應形成如圖圖 ((d) d) 所示所示

程式 5-12  插入節點 new 成為引線二元樹中節點 p的右子節點(上接程式 5-10 5-11 )

1 void InsertRight(struct TBTreeNode p 2 struct TBTreeNode new)3 struct TBTreeNode q4 new-gtrightchild = p-gtrightchild5 new-gtrightthead = p-gtrightthread6 new-gtleftchild = p7 new-gtleftthread = 18 p-gtrightchild = new9 p-gtrightthead = 010 if (new-gtrightthread)11 q = Next(new)12 q-gtleftchild = new13 14

58  堆積bull 「堆積」 (heap) 是一個特殊的完備二元樹節點的資料與其子節點的資料之間有事先定好的大小關係存在bull 定義ndash最大堆疊為一完備二元樹任一節點的資料內容不小於其子節點的資料內容ndash最小堆疊為一完備二元樹任一節點的資料內容不大於其子節點的資料內容

581  插入節點至最大堆積中最大堆積是一完備二元樹所以如圖 (a) 之最大堆積於加入新節點後樹的形狀必須如圖 (b) 所示灰色節點即為新節點若加入的節點資料分別為 1 9 12 則其最大堆積將分別變成如圖 (c) (d) (e) 所示

程式 5-13 新增節點至最大堆積中最大堆積本身是一棵完備二元樹可以用陣列表示之 1 define maxsize 1002 int heap[maxsize]3 int n i4 void InsertHeap(int x)5 if (n == maxsize) HeapFull()6 else7 n++8 i = n9 while ((i gt 1) ampamp (x gt heap[i2]))10 heap[i] = heap[i2]11 i = 212 13 heap[i] = x14 16

程序 InsertHeap 在插入第 n 個資料時需要的時間複雜度為 O(logn)

1 100

2 93

3 84

4 77

5 65

6 81

7 79

8 66

9 70

10 50

100

84

77

93

8165

66 70

79

50

1

2 3

4 5 6 7

8 9 10

1 define maxsize 1002 int heap[maxsize]3 int n i4 void InsertHeap(int x)5 if (n == maxsize) HeapFull()6 else7 n++8 i = n9 while ((i gt 1) ampamp (x gt heap[i2]))10 heap[i] = heap[i2]11 i = 212 13 heap[i] = x14 16

582 刪除最大堆積中的最大資料圖 (a) 中的最大堆積在刪除最大資料後其樹形應變成如圖 (b) 所示將最未一個樹葉節點的資料 6 暫挪至樹根節點內如圖 (c) 再將圖 (c) 中非最大堆積的結構調整成為最大堆積 10 和 8 中選大者與 6對調即成圖 (d)

程式 5-14 刪除最大堆積中的最大資料(上接程式 5-13 )1 int DeleteHeap()2 int x i j3 if (n == 0) HeapEmpty()4 else5 x = heap[1]6 heap[1] = heap[n]7 n--8 i = 19 while (i lt= n2)10 if (heap[2i] gt heap[2i+1])11 j = 2i12 else13 j = 2i+114 if (heap[i]gtheap[j]) break15 swap(heap[i] heap[j])16 i = j17 18 return x19 20

其時間複雜度與其堆積高度相同是為O(logn) 其中 n 為堆積中的元素個數

1 100

2 93

3 84

4 77

5 65

6 81

7 79

8 66

9 70

10 50

100

84

77

93

8165

66 70

79

50

1

2 3

4 5 6 7

8 9 10

1 int DeletHeap()2 int x i j3 if(n == 0) HeapEmpty()4 else5 x = heap[1]6 heap[1] = heap[n]7 n--8 i = 19 while (i lt= n2)10 if ([heap[2i]] gt heap[2i+1])11 j = 2i12 else13 j = 2i+114 if (heap[i]gtheap[j]) break15 swap(heap[i] heap[j])16 i = j17 18 return x19 20

583  優先佇列bull 由於最大堆積(或最小堆積)的結構可提供最大元素(或最小元素)而其維護堆積的成本為 O(logn) n 為總元素個數

bull 堆積常被當成「優先佇列」 (priority queue) 使用mdash此時佇列被組織成完備二元樹若新加入佇列的資料有大小關係優先順序的需求時此優先佇列即以 O(logn) 的代價將新資料放入優先佇列(可能是最大或最小堆積依實際大小關係優先順序的需求而定)中mdash提供依元素的優先順序做為處理順序的服務

bull 一般的佇列為先進先出先到的先行處理優先佇列則是依據其優先順序優先順序高者先行處理與來到的先後無關

範例 5-21bull 在「分時」 (time sharing) 的作業系統中我們不希望使用中央處理器 (Central Processing Unit CPU) 時間短的「處理程序」 (process) 等候過久於是有「最短時間工作優先」 (shortest-job-first) 的排程策略此時等候的處理程序即可組織成之一最小堆積而 CPU 每次即取出存於樹根的處理程序予以處理任何新來的處理程序則以 O(logn) 的時間加入此優先佇列(最小堆積)中

bull 注意之前提過的線性佇列新來的直接加入成為新的尾端元素花O(1) 的時間然線性佇列本身沒有大小的關係每次要取出最大(小)的資料得花O(n) 的代價(在所有元素中挑出最大(小)者) n 為佇列內的元素個數而優先佇列是在新元素加入時要花O(logn) 的時間至於取出最大(小)則只須 O(1) 的時間

59  二元搜尋樹bull 二元搜尋樹是一棵二元樹可能是空二元樹若不為空二元樹則滿足下列性質ndash所有節點內的資料內容是相異的 ndash左子節點的資內容 ( 如果有 ) 比父節點的資料內容小 ndash右子節點的資料內容 ( 如果有 ) 比父節點的資料內容大 ndash以左和右子節點為樹根的左子樹和右子樹也是二元搜尋樹

在二元搜尋樹中搜尋資料二元搜尋樹對資料大小的掌握十分明確若恰與節點 p 資料相等則搜尋成功若欲搜尋的資料 x 比樹節點 p 中的資料小 則只須搜尋節點 p 的左子樹若比節點 p 中的資料大 則只須搜尋 p 的右子樹這樣的判斷可以遞迴地應用在各子樹節點 中

程式 5-15 在二元搜尋樹中搜尋資料(遞迴呼叫)1 struct BSTreeNode2 struct BSTreeNode leftchild3 int data4 struct BSTreeNode rightchild5 6 struct BSTreeNode root7 struct BSTreeNode SearchBST(

struct BSTreeNode tree int x)8 if (tree == NULL) return NULL9 if (x == tree-gtdata) return tree10 if (x lt tree-gtdata)11 return SearchBST(tree-gtleftchild x)

12 return SearchBST(tree-gtrightchild x)13

程式 5-16 搜尋二元搜尋樹的資料(非遞迴呼叫)在二元搜尋樹十分龐大時遞迴呼叫的效率不會太好可利用迴圈來完成搜尋的動作以提高執行效率請看程式 5-16

14 struct BSTreeNode Search BST_iterative15 (struct BSTreeNode node int x)16 while (node = NULL)17 if (x == node-gtdata) return node18 if (x lt node-gtdata)19 node = node-gtleftchild20 else21 node = node-gtrightchild22 23 return NULL24

592  新增資料於二元搜尋樹中欲新增資料進入一二元搜尋樹中得先決定該資料在二元搜尋樹中應出現的位置這可透過上節提到的搜尋動作來解決

在二元搜尋樹定義的性質 (1) 中我們限定了元素必須相異所以搜尋新資料的結果必是 NULL

必須將搜尋的結果改為新增資料所應在位置的節點指標

程式 5-17  在二元搜尋樹中新增資料25 int InsertBST(int x)26 struct BSTreeNode p q27 p = root q = NULL28 while (p = NULL)29 q = p30 if (x == p-gtdata) return 0

若不考慮重複則可省略之31 if (x lt p-gtdata)32 p = p-gtleftchild33 else34 p = p-gtrightchild35

3636 p = (struct BSTreeNode ) p = (struct BSTreeNode ) malloc (sizeof(BSTreeNode))malloc (sizeof(BSTreeNode))

3737 p-gtdata = x p-gtdata = x3838 p-gtleftchild = p-gtrightchild = p-gtleftchild = p-gtrightchild = NULLNULL3939 if (root == NULL) root = p if (root == NULL) root = p4040 else if (x lt q-gtdata) else if (x lt q-gtdata)4141 q-gtleftchild = p q-gtleftchild = p4242 else else4343 q-gtrightchild = p q-gtrightchild = p4444 return 1 return 145 45

程式 程式 5-175-17  在二元搜尋樹中新增資料  在二元搜尋樹中新增資料 (( 續續 ))

範例 5-23 圖 (b) 為圖 (a) 新增整數 6 之後的結果圖 (c) (d) (e) (f) 則分別是逐步再加入 28 18

10 21 的結果

bull若目前的二元搜尋樹高度為 h 則新增資料得花O(h) 的時間找到其所應在的位置遂新增資料至二元搜尋樹中的時間複雜之度為 O(h)

593 自二元搜尋樹中刪除資料欲自二元搜尋樹中刪除資料可能會面臨兩種情況(1) 刪除節點 p 位於樹葉節點上如下圖所示 這是比較容易處理的情況

(2) 刪除的節點 p 不是樹葉刪除節點 p 後原 p 所指的節點可由 q ( q 中包含了 p 左子樹中的最大節點值)或 r ( r 中包含了 p 右子樹中的最小節點值)來取代以維持二元搜尋樹中資料的大小關係其結果分別如圖 (b) 和 (c) 所示而圖 (d) 則標明了指標會改變的節點位置即 p 會消失 father 的左或右子樹指標會異動 k(可能是 q 或 r )會移到原來 p 的位置其父節點 f 的左或右子樹指標會異動其中 father 為 p 父節點 f 為 k 的父節點

程式 5-18 自二元搜尋樹中刪除資料46 int DeleteBST (int x)47 Struct BSTreeNode p father k f48 p = root father = NULL49 while (p = NULL)50 if (x == p-gtdata) 找到 x 所在的節點 p 了51 if ((p-gtleftchild == NULL) ampamp 52 (p-gtrightchild == NULL))53 k = NULL p為樹葉節點54 else if (p-gtleftchild = NULL )x位於非子節

點55 f = p 找出 p左子樹的最右樹葉56 k = p-gtleftchild57 while (k-gtrightchild =NULL)58 f = k59 k = k-gtrightchild60 k將挪至原 p處61 if (p == f)62 f-gtleftchild = k-gtleftchild 63 else64 f-gtrightchild = k-gtleftchild 65

66 else p無左子樹67 f = p 找出 p右子樹的最左樹葉68 k = p-gtrightchild69 while (k-gtleftchild = NULL)70 f = k71 k = k -gtleftchild72 73 if (p == f)74 f-gtrightchild = k-gtrightchild 75 else76 f-gtleftchild = k-gtrightchild77 78 if (k = NULL) k挪至原 p處繼承 p的左右指標79 k-gtleftchild = p-gtleftchild80 k-gtrightchild = p-gtrightchild81 82 if (father == NULL) root = k83 else if (x lt father-gtdata) 決定 k是 father的左

或右兒子84 father-gtleftchild = k85 else86 father-gtrightchild = k87 free(p)88 return 1 成功地刪除 x於此傳回 1返回呼叫處89 51~89行為找到 x時應有的處理步驟

90 else 尚未找到 x繼續往下一階層找

91 father = p92 if (x lt p-gtdata)93 p = p-gtleftchild94 else95 p = p-gtrightchild96 97 98 return 0 未找到 x傳回 099

510  樹林 (forest)

bull 定義樹林(森林)是由 n 棵沒有交集的樹所構成的集合 n 0

下圖為一包含三棵樹的樹林

5101 轉換樹林為二元樹 之前我們以二元樹來表示樹因為二元樹有較多

好用的性質在此我們也將樹林轉換成二元樹看待

在 522 節中我們嘗試了用左子右兄弟的方式來表示樹這個方法事實上已將樹轉換成二元樹

即用左子右兄弟的結構重新組織樹林中的每一棵樹這些樹的樹根將視為兄弟而最左邊的樹則視為樹林二元樹的樹根如此一來樹林即可轉換成二元樹

範例 5-25右下圖為右上圖三棵樹所對應的三棵左子右兄弟二元樹

下圖則為上圖樹林所對應的一棵樹林二元樹

演算法 5-1  轉換樹林成二元樹輸入樹林 F = T1 T2 Tn

輸出 F 的二元樹表示 BT=(T1 T2 Tn)1 if (n == 0) 輸出空二元樹 2 else

3 root(T1 T2 Tn) = T1

4 leftsubtree(T1 T2 Tn) =

BT(T11 T12 T1m)5 BT(T11 T12 T1m)為 T1的子樹6 rightsubtree(T1 T2 Tn) =

BT(T2 T3 Tn)

樹林轉換成二元樹的過程樹林轉換成二元樹的過程

  • Slide 1
  • Slide 2
  • Slide 3
  • Slide 4
  • Slide 5
  • Slide 6
  • Slide 7
  • Slide 8
  • Slide 9
  • Slide 10
  • Slide 11
  • Slide 12
  • Slide 13
  • Slide 14
  • Slide 15
  • Slide 16
  • Slide 17
  • Slide 18
  • Slide 19
  • Slide 20
  • Slide 21
  • Slide 22
  • Slide 23
  • Slide 24
  • Slide 25
  • Slide 26
  • Slide 27
  • Slide 28
  • Slide 29
  • Slide 30
  • Slide 31
  • Slide 32
  • Slide 33
  • Slide 34
  • Slide 35
  • Slide 36
  • Slide 37
  • Slide 38
  • Slide 39
  • Slide 40
  • Slide 41
  • Slide 42
  • Slide 43
  • Slide 44
  • Slide 45
  • Slide 46
  • Slide 47
  • Slide 48
  • Slide 49
  • Slide 50
  • Slide 51
  • Slide 52
  • Slide 53
  • Slide 54
  • Slide 55
  • Slide 56
  • Slide 57
  • Slide 58
  • Slide 59
  • Slide 60
  • Slide 61
  • Slide 62
  • Slide 63
  • Slide 64
  • Slide 65
  • Slide 66
  • Slide 67
  • Slide 68
  • Slide 69
  • Slide 70
  • Slide 71
  • Slide 72
  • Slide 73
  • Slide 74
  • Slide 75
  • Slide 76
  • Slide 77
  • Slide 78
  • Slide 79
  • Slide 80
  • Slide 81
  • Slide 82
  • Slide 83
  • Slide 84
  • Slide 85
  • Slide 86
  • Slide 87
  • Slide 88
  • Slide 89
  • Slide 90
  • Slide 91
  • Slide 92
  • Slide 93
  • Slide 94
  • Slide 95
  • Slide 96
  • Slide 97
  • Slide 98
  • Slide 99
  • Slide 100
  • Slide 101
  • Slide 102
  • Slide 103
Page 15: 第五章 樹

523 分支度為 2 的二元樹表示法bull 分支度為 2 的樹又稱為二元樹 (binary tree)

bull 二元樹中任一節點皆有 2個指標分別指向該節點的左子樹和右子樹

bull 二元樹可以表示任何樹事實上二元樹有許多很好的性質我們在一下節中介紹

bull注意樹狀結構中兒子的順序是不重要的試想樹可以樹根為軸心旋轉使得兒子的順序是不固定的

53  二元樹bull 二元樹是一個由節點組成的有限集合可以是空集合或是包含了(1) 一個樹根節點(2) 其它節點分割成兩個沒有交集的二元樹

其一為左子樹 (left subtree) 另一為右子樹 (right subtree) 樹和二元樹之間的差異

(1)樹不允許空節點而空二元樹是允許的

(2) 樹的子樹沒有順序而二元樹的子樹有左右之分

531  樹和二元樹的基本性質

樹或二元樹皆擁有下面的性質

定理 5-1 若一棵樹 T 的總節點數為 V 總邊數為

E 則V = E + 1

證明利用數學歸納法歸納法的基礎當 V=1 時即 T 只有一個樹根節點 E 為 0 所以 V = E + 1 成立歸納法的假設假設當 1 V K 時該式成立歸納法的推論當 V = K + 1 時令此樹的樹根為 R 而 R 有 m 個子樹 T1 T2 hellip

Tm (如圖 5-12 所示)分別有個 V1 V2 hellip Vm 個節點其中 V1 +V2 + hellip+ Vm =K 若 E1 E2 hellip Em 分別為此 m 個子樹的邊數則

E = E1 +E2 + hellip+ Em + m (1)根據歸納法的假設可知

V1 = E1+1V2 = E2+1 Vm = Em+1

所以V1 +V2 + hellip+Vm = E1 + E2 + hellip+ Em +m (2)

然而V = V1 +V2 + hellip+Vm+1 (3)

將式 (2) 代入式 (3) 可得V = E1 + E2 + hellip+ Em + m + 1

引用式 (1) 則可得V = E + 1

由數學歸納法的原理可得證本定理

532 二元樹的性質(a) 稱為歪斜樹 (skew tree) (b) 稱為完備二元樹 (complete binary tree)

( 其樹葉節點皆在相鄰階層完整定義在後 ) 同樣數目的樹節點存放在歪斜樹上會花較多的階層放在完備二元樹上則只須最少的階層

532 二元樹的性質 ( 續 )定理 5-2 (1) 二元樹上階層 i 的節點數目最多為 2i-1 i 1 (2) 深度為 k 的二元樹其節點數目最多為 2k-

1 k1 bull 定理 5-2說明了 (1) 每一階層上可放的最多節點數

(2) 階層數已知時二元樹可放的最多節點數證明 (1) 利用數學歸納法歸納法的基礎當 i=1 時只有樹根一個節點 2i-1=21-1=1 原式成

立歸納法的假設假設 i = k 時該式成立即階層 k 的最大節點數為

2k-1 k 1 歸納法的推論當 i = k+1 時因在階層 k 的每一個節點最多會有

2 個子節點遂在 k+1 階層最多會有 22k-1 = 2k

= 2i-1 節點由數學歸納法的原理可知本定理得證(2) 由 (1) 可知二元樹在階層 i 的最多節點數為 2i-1那麼深度 k 的二元樹最多可有節點如下

k

i

ki

1

1-1- 24212 1-2

)12(1 k= 2k-1

=

bull 對二元樹而言任一節點的分支度可能為 0 1 或 2 分支度為 0 者是為樹葉

bull 樹葉的數目與分支度為 2 的節點數目存在著有趣的關係請見定理 5-3

定理 5-3 若 T 為一非空的二元樹 n0 為樹葉節點數目 n2為分支度為 2 的節點數目則 n0 = n2+1

證明令 n 為所有節點數目而 n1 為分支度為 1 的節點數目可得 n = n0 + n1 +n2 (1)令 B 為二元樹的分支數除了樹根外每一節點皆有一分支指向之所以

n = B+1 (亦可參考定理 5-1 )又 B = n1+2n2

且 n = n1 + 2n2 +1 (2)由式 (1) 和式 (2) 我們得到

n0 = n2 + 1

完滿二元樹 (full binary tree)

定義一個深度為 k 的完滿二元樹即為一深度為 k 且有 2k-1 個節點的二元樹k 0

bull 由定理 5-2(2) 可知深度為 k 的二元樹其最多節點數為 2k-1 這樣的樹會將樹上所有可能存放節點的位置都放滿完滿 (full) 之名因而生成

完備二元樹 (complete binary tree)我們可對二元樹上節點予以編號階層小者先編同一階

層則自左向右編號這樣的編號方式使我們定義出完備(complete) 二元樹

定義深度為 k 節點數為 n 的二元樹稱為完備二元樹若且唯若 (1) k = 1 時樹有一個節點

(2) 當 k 2 且 1iltk 時深度 i 有 2i-1 個節點且第 k 層的節點皆由第 k-1 層的分支由左至右逐一連接而成

完滿或完備二元樹之所以受人注意乃因它們可以將節點以較少的階層數存放

定理 5-4 n 個節點的完備或完滿二元樹其深度為 log2(n+1)

證明 完滿或完備二元樹的節點皆自階層

1 起排放任一階層 i 可排放 2i-1

個節點遂由定理 5-2 得知此 n 個節點所佔的深度為 log2(n+1)

正規二元樹 (formal binary tree)bull 在二元樹中非樹葉的節點又稱為內部節點

(internal node) 若所有內部節點恰有 2 個子節點即成為正規二元樹

定義若二元樹中所有內部節點恰有 2 個子節點則稱之為正規二元樹

正規二元樹常被引用在單淘汰賽正規二元樹常被引用在單淘汰賽制的各項賽程安排樹葉為參賽制的各項賽程安排樹葉為參賽隊伍內部節點為優勝隊伍樹隊伍內部節點為優勝隊伍樹根即為冠軍隊伍根即為冠軍隊伍

在單淘汰賽制中自在單淘汰賽制中自 n n 個隊伍中產個隊伍中產生冠軍須舉辦 生冠軍須舉辦 nn-1-1場比賽場比賽

54  二元樹的表示法541 以陣列表示二元樹定理 5-5 若一個具有 n 個節點的完備二元樹以循序的方式編號並依序存放在陣列 A 中即第 i 個節點放在 A[i] 中 1 i n 則

(1) 節點 i 的父節點位在 A[i2]處 i ne 1( i = 1 時其節點正為樹根無父節點)

(2) 若 2i n 則節點 i 的左兒子節點位在 A[2i] 處若 2i gtn 節點 i 沒有左子節點

(3) 若 2i+1 n 節點 i 的右兒子節點位 A[2i+1]處若 2i+1gtn 節點 i 沒有左子

節點

證明先用數學歸納法證明 (2) bull 歸納法的基礎當 i = 1 時其左子節點確實在 A[2] 處(除非 nlt2 此時節點 1 沒有左子節點)

bull 歸納法的假設假設當 I = k 時其左子節點確在 A[2i] = A[2k] 處(除非 2i 已大於 n 此時節點 i 無左子節點)

bull 歸納法的推論當 i = k+1 時因第 k 節點的左子節點在 A[2k] 處其右子節點則位在 A[2k+1] 處那麼第 k+1 節點的左子節點應緊臨 A[2k+1] 亦即位於 A[2k+2] = A[2(k+1)] 處(除非 2i 已大於 n 此時節點 i 無左子節點)

bull 由數學歸納法的原理得證定理 5-5 (2) (3) 由於 (2) 成立而且節點 i 的右子節點會緊臨

A[2i] 亦即其位於 A[2i+1]處除非 i 根本沒有右子節點得證定理 5-5 (3)

由 (2) 和 (3) 我們可知定理 5-5 (1) 成立

542 以鍵結串列表示二元樹bull 以陣列來表示完備二元樹算是相當方便但ndash一般的二元樹將導致陣列空間的浪費ndash如果二元樹中節點的新增刪除動作頻繁則會造成陣列對應資料的搬動隨而頻繁

bull 以鍵結串列表示二元樹

為方便辦識樹根我們常用 root 指標指向樹根做為樹鍵結串列的起點

陣列表示和鍵結表示的空間需求bull 完備二元樹的陣列表示和鍵結表示皆沒浪費儲存資料的空間

bull 但歪斜樹的陣列表示比起鍵結表示則浪費許多

bull 以 n 個節點的歪斜樹而言陣列表示法可能需要 2n-1 個陣列元素其中 2n-1-n 個陣列元素空間是浪費的而鍵結表示法只需要 n 個節點空間

二元樹節點的宣告程式 5-2

1 struct BTreeNode

2 struct BtreeNode leftchiled

3 int data

4 struct BTreeNode rightchild

5

6 struct BTreeNode root

bull 這樣的二元樹節點要取得兒子節點的指標十分方便但若有取得父節點的需求則嫌不足我們可以加上 parent 欄位指向父節點

55  二元樹的走訪bull 假如一組資料已用二元樹的組織在一起總有需求對其全數資料做動作例如計算所有數目印出所有資料在所有資料中搜尋某項資料hellip等此時即須對此二元樹做走訪 (traversal) 的運算利用走訪二元樹的同時將計算列印或搜尋的動作完成

bull 事實上走訪即在決定二元樹上資料被處理(計算列印或搜尋)的順序我們也希望走訪的演算法對任何節點皆一致是容易撰寫程式實作的

bull 若對一個二元樹上的節點而言 V 表示處理節點上的資料 L 表示走訪其左子樹 R 表示走訪其右子樹

55  二元樹的走訪 ( 續 )bull 因為對稱的緣故我們只考慮先走訪左邊再走訪右邊的情形那麼右圖只剩下三種走訪方式

bull 以 V 所在的相對位置分別對此三種走訪方式取名如下

(1) LVR 中序走訪 (inorder traversal)或中序表示法 (infix notation)

(2) LRV 後序走訪 (postorder traversal) 或後序表示法 (postfix notation)

(3) VLR 前序走訪 (preorder traversal)或前序表示法 (prefix notation)

551  中序走訪bull 利用遞迴的方式撰寫中序走訪的程序希望把二元樹中序走訪的

順序印出來

程式 5-3二元樹的中序走訪1 struct BTreeNode2 struct BTreeNode leftchild3 char data4 struct BTreeNode rightchild5 6 struct BTreeNode root7 void inorder(struct BTreeNode node)8 if (node = NULL)9 inorder(node-gtleftchild)10 cout ltlt node-gtdata11 inorder(node-gtrightchild)12 13

範例 5-12此為運算式二元樹

其對應中序表示運算式為 (A+B)C+D(E-F)

6 struct BTreeNode root7 void inorder(struct BTreeNode node)8 if (node = NULL)9 inorder(node-gtleftchild)10 cout ltlt node-gtdata11 inorder(node-gtrightchild)12 13

6 struct BTreeNode root7 void inorder(struct BTreeNode node)8 if (node = NULL)9 inorder(node-gtleftchild)10 cout ltlt node-gtdata11 inorder(node-gtrightchild)12 13

(A+B)C+D(E-F)

如何加上 ( )

552  後序走訪程式 5-4二元樹的後序走訪14 void postorder(struct BTreeNode node)

15 if (node =NULL)16 postorder(node-gtleftchild)17 postorder(node-gtrightchild)18 cout ltlt node-gtdata19 20

以後序走訪可得到AB+CDEF-+

其正為對應的後序運算式

553  前序走訪程式 5-5二元樹的前序走訪21 void preorder(struct BTreeNode node)

22 if (node = NULL)

23 cout ltlt node-gtdata

24 preorder(node-gtleftchild)

25 preorder(node-gtrightchild)

26

27

以前序走訪可得到++ABCD-EF

其正為對應的前序運算式

554 利用堆疊和迴圈取代遞迴進行二元樹的走訪1 struct BTreeNode 2 struct BTreeNode leftchild3 char data4 struct BtreeNode rightchild5 6 struct BTreeNode root7 struct StackNode8 struct BTreeNode treenode9 struct StackNode next1011 struct StackNode top

12 void push_node(struct BtreeNode node )13 struct StackNode old_top14 old_top = top15 top = (struct StackNode ) malloc

(sizeof(struct StackNode))16 top-gttreenode = node17 top-gtnext = old_top18 19 struct BTreeNode pop_node()20 struct BTreeNode Tnode21 struct StackNode old_top22 if (top == NULL)23 StackEmpty()24 else25 Tnode = top-gttreenode26 old_top = top27 top = top-gtnext28 free(old_top)29 return Tnode30 31

32 void inorder_Stack(struct BtreeNode node)33 do34 while (node = NULL)35 push_node(node)36 node = node-gtleftchild37 push all left children38 if (top = NULL)39 node = pop_node()40 cout ltlt node-gtdata41 node = node-gtrightchild42 inorder printing and check right child43 while ((top=NULL)||(node=NULL))44 top==NULL 堆疊已空 node==NULL沒有右子

6 struct BTreeNode root7 void inorder(struct BTreeNode node)8 if (node = NULL)9 inorder(node-gtleftchild)10 cout ltlt node-gtdata11 inorder(node-gtrightchild)12 13

中序表示為DBGEHAFIC

32 void inorder_Stack()33 struct BTreeNode node34 node = root35 do36 while (node = NULL)37 push_node(node)38 node = node-gtleftchild39 40 if (top = NULL)41 node = pop_node()42 cout ltlt node-gtdata43 node = node-gtrightchild44 45 while(top=NULL)||(node=NULL))46

45 void Preorder_Stack(struct BSTreeNode node)46 do47 while (node = NULL)48 cout ltlt node-gtdata)49 push_node(node)50 node = node-gtleftchild51 push all left children52 if (top = NULL)53 node = pop_node()54 node = node-gtrightchild55 56 while ((top=NULL)||(node=NULL))57

程式 5-5二元樹的前序走訪21 void preorder(struct BTreeNode node)22 if (node = NULL)23 cout ltlt node-gtdata24 preorder(node-gtleftchild)25 preorder(node-gtrightchild)26 27

58 void Postorder_Stack(struct BTreeNode node)59 do60 while (node = NULL)61 cout ltlt node-gtdata62 push_node(node)63 node = node-gtrightchild64 push all left children65 if (top = NULL)66 node = pop_node()67 node = node-gtleftchild68 69 while ((top=NULL)||(node=NULL))70

程式 5-4二元樹的後序走訪14 void postorder(struct BTreeNode node)15 if (node =NULL)16 postorder(node-gtleftchild)17 postorder(node-gtrightchild)18 cout ltlt node-gtdata19 20

555  階層走訪bull 「階層走訪」 (level-order traversal) 是依階層

的順序進行二元樹的走訪先走訪階層小的節點後走訪階層大的節點同一階層者則依自左向右的順序走訪

bull 對下圖的二元樹進行階層走訪的結果為ABCDEFGHI

bull 對同一階層而言先走訪的節點其子節點亦在下一階層中先被走訪

bull 這種「先進先出」的特性恰可用佇列予以儲存與處理

程式 5-7 利用佇列進行二元樹的階層走訪

1 struct BTreeNode2 struct BTreeNode leftchild3 char data4 struct BTreeNode rightchild5 6 struct BTreeNode root7 struct QNode8 struct BTreeNode treenode9 struct QNode next10 11 struct QNode front rear

12 void AddQueue(struct BTreeNode Tnode)13 struct QNode node14 node=(struct QNode )malloc(sizeof(struct QNode))15 node-gttreenode = Tnode16 node-gtnext = NULL17 if (front == NULL) front = node19 else rear-gtnext = node20 rear = node21

22 struct BTreeNode DeleteQueue()23 struct BTreeNode Tnode24 struct QNode old_front25 if (rear == NULL)26 QueueEmpty()27 else28 Tnode = front-gttreenode29 old_front = front30 front = front-gtnext31 free(old_front)32 return Tnode33 34 35 void LevelOrder (struct BTreeNode node)36 AddQueue(node)37 while (front =NULL)38 node = DeleteQueue()39 cout ltlt node-gtdata40 if (node-gtleftchild = NULL)41 AddQueue(node-gtleftchild)42 if (node-gtrightchild = NULL)43 AddQueue(node-gtrightchild)44 45

35 void LevelOrder (struct BTreeNode node)36 AddQueue(node)37 while (front =NULL)38 node = DeleteQueue()39 cout ltlt node-gtdata40 if (node-gtleftchild = NULL)41 AddQueue(node-gtleftchild)42 if (node-gtrightchild = NULL)43 AddQueue(node-gtrightchild)44 45

bull Infix ABCbull Postfix ABC CBA ACB BCA

BAC CAB

B

A CA

B

C

B

A

C

C

B

AB

A

C

二元樹中序與前序式中序與後序式bull 每一棵二元樹皆有唯一的一對中序與前序表示法也有唯一的中序與後序次序

bull 換句話說給予一對中序與前序或中序與後序即可決定一棵二元樹

bull 然而給予前序和後序次序並不能決定唯一的二元樹可能會產生兩棵不同的二元樹

bull 想想階層走訪和中序走訪順序可否決定唯一的二元樹

A

B C

H J D E

F G

prefix ABHJCDFGEinfix HBJAFDGCEpostfix HJBFGDECA

VLRLVRLRV

A

prefix BHJinfix HBJ

prefix CDFGEinfix FDGCE

prefix ABHJCDFGEinfix HBJAFDGCE

A

B C

H J D E

F G

prefix ABHJCDFGE

infix HBJAFDGCEk-1

struct BTreeNode BTree_InfixPrefix (AnsiString infix AnsiString prefix)

int k struct BTreeNode node if (infixLength==0) return NULLnode = (struct BTreeNode ) malloc (sizeof(struct BTreeNode))node-gtdata = prefix[1]k = find(prefix[1] infix)node-gtleftchild = BTree_InfixPrefix

(infix[1k-1] prefix[2k])node-gtrightchild = BTree_InfixPrefix(infix[k+1infixLength]prefix[k+1prefixLength])

VLR

LVRk1

k2 k+1~

k+1~

1

struct BTreeNode BTree_InfixPostfix (AnsiString infix AnsiString prefix)

int k struct BTreeNode node if (infixLength==0) return NULLnode = (struct BTreeNode ) malloc (sizeof(struct BTreeNode))node-gtdata = postfix[postfixLength]k = find(postfix[postfixLength] infix)node-gtleftchild = BTree_InfixPrefix

(infix[1k-1] postfix[1k-1])node-gtrightchild = BTree_InfixPrefix(infix[k+1infixLength]postfix[k+1postfixLength-1])

56  二元樹的複製與相等測試程式 5-8 二元樹的複製1 struct BTreeNode2 struct BTreeNode leftchild3 char data4 struct BTreeNode rightchild5 6 struct BTreeNode root7 struct BTreeNode Copy(struct BTreeNode TreeRoot)8 struct BTreeNode CopyRoot9 if (TreeRoot == NULL)10 return NULL11 CopyRoot =(struct BTreeNode ) malloc (sizeof(BTreeNode))12 CopyRoot-gtdata = TreeRoot-gtdata13 CopyRoot-gtleftchild = Copy(TreeRoot-gtleftchild)14 CopyRoot-gtrightchild = Copy(TreeRoot-

gtrightchild)15 return CopyRoot16

程式 5-9 檢測兩棵二元樹是否相等1 struct BTreeNode2 struct BTreeNode leftchild3 char data4 struct BTreeNode rightchild5 6 struct BtreeNode rightchild7 int Equal(struct BTreeNode ustruct BTreeNode v)8 if ((u==NULL) ampamp (v==NULL)) return 19 if ((u=NULL) ampamp (v=NULL)10 ampamp (u-gtdata == v-gtdata )11 ampamp Equal(u-gtleftchildv-gtleftchild)12 ampamp Equal(u-gtrightchildv-gtrightchild))

13 return 114 return 015

Counting Nodes in a Binary Tree

int CountingNodes(struct BTreeNodes node)

if (node == NULL) return 0 int leftchildren = CountingNodes(node-gtleftchild)

int rightchildren= CountingNodes(node-gtrightchild)

return leftchildren+rightchildren

Counting Leaves in a Binary Tree

int CountingLeaves(struct BTreeNodes node)

if (node == NULL) return 0 int leftleaves = CountingLeaves(node-gtleftchild)

int rightchildren= CountingLeaves(node-gtrightchild)

if (leftleaves==0 ampamp leftleaves==0) return 1

return leftleaves + leftleaves

57  引線二元樹bull 在二元樹的鍵結表示中樹葉節點的兩個子樹指標皆指向 NULL 由定理 5-1 得知 n = E+1 其中 n 為節點個數 E 為分支數

bull 而 n 個鍵結節點有 2n 個指標空間每個分支恰佔用一個指標空間共有 E (=n-1) 個指標有用到(不是空的)而共有 n+1 個指標空間是 NULL 比非空的節點還多

bull 有學者提出對這些「放空指標的空間」加以利用的概念mdash與其放空指標不如放指向其它節點的指標稱之為引線 (thread) 使得某些運算(如走訪hellip等)可以加快

bull 這個概念發展出了引線二元樹 (threaded binary tree) 這種資料結構

範例 5-17下圖為加了引線的二元樹

加入的引線將使二元樹的中序走訪更加便利沿著上右圖箭頭所指示的順序走訪即可完成此二元樹的中序走訪GDHBAIECF 為此二元樹的中序表示

bull 範例 5-17 中所加的引線目的在方便二元樹的中序走訪

bull 對樹葉節點 p 而言令其資料為 P 則引線指標的規則如下

(1) p 的左子樹指標指向中序表示中 P 的前一個 元素(前接元素 (predecessor) )

(2) p 的右子樹指標指向中序表示中 P 的後一個 元素(後繼元素 (successor) )

為了區別節點指標放的是一般指標(內部節點)抑或是引線指標(樹葉節點)我們另以 2 個欄位分別區別左和右子樹指標空間存放的對象其節點的記憶體配置有如下圖所示

其中 leftthread (rightthread) 為 0 時表示 leftchild (rightchild) 存放的是一般節點指標而 leftthread (rightthread) 為 1 時表示 leftchild (rightchild) 存放的是引線指標1 struct TBTreeNode2 int leftthread3 struct TBTreeNode leftchild4 char data5 struct TBTreeNode rightchild6 int rightthread7

由右側的引線二元樹可知有最左和最右兩條引線尚無妥善的安排我們另設計一空的引線節點做為樹根如下圖所示

571  引線二元樹的中序走訪決定節點 p 在中序表示中的後繼節點 q (簡稱中序後繼點)

程式 5-10決定節點 node的中序後繼點1 struct TBTreeNode2 int leftthread3 struct TBTreeNode leftchild4 char data5 struct TBTreeNode rightchild6 int rightthread7 8 struct TBTreeNode root9 struct TBTreeNode Next(struct TBTreeNode node)

10 struct TBTreeNode temp11 temp = node-gtrightchild12 if (node-gtrightthead) return temp13 while (temp-gtleftthead) temp=temp-gtleftchild

14 return temp15

程式 5-11引線二元樹的中序表示(接程式 5-10)1 void InorderTBTree()2 struct TBTreeNode node3 for(node=Next(root)node=rootnode=Next(node))

4 cout ltlt node-gtdata5

bull只要 node 是引線二元樹上的任一節點透過呼叫 Next(node) 即可找到其在中序表示中的後繼節點bull(試想若程式 5-10 中的程序 Next 所傳入的參數指標正是 root Next(root) 是否可正常運作請思考)bull於是只須從中序表示中的第一節點起讓每一節點 node 都執行 Next(node) 程序即可得引線二元樹的中序表示

572  在引線二元樹中加入節點在引線二元樹中樹葉節點節點的右子樹指標所指向或內部節點右子樹的最左樹葉為其中序後繼節點而且任一節點若無左子樹則其左子樹指標乃指向其中序前接節點hellip這些性質必須在節點新增進入引線二元樹時也要保持接下來討論節點 new 插入成為引線二元樹的節點 p 的右子節點的情形至於插入成為左子節點的情形則讓各位模擬推敲

572572  在引線二元樹中加入節點 在引線二元樹中加入節點 (( 續續 ))

newnew 會成為會成為 pp 的右子節點的情況共有以下兩種的右子節點的情況共有以下兩種(1) (1) 若若 pp 沒有右子節點 沒有右子節點 ((p-gtrightthread p-gtrightthread 為為1) 1) 如圖如圖 ((a) a) 則 則 new new 插入後應形成如圖插入後應形成如圖 ((b) b) 所示所示

(2)(2) 若若 pp 有右子節點 有右子節點 ((p-gtrightthread p-gtrightthread 為為0) 0) 如圖如圖 ((c) c) 則 則 new new 插入後應形成如插入後應形成如圖圖 ((d) d) 所示所示

程式 5-12  插入節點 new 成為引線二元樹中節點 p的右子節點(上接程式 5-10 5-11 )

1 void InsertRight(struct TBTreeNode p 2 struct TBTreeNode new)3 struct TBTreeNode q4 new-gtrightchild = p-gtrightchild5 new-gtrightthead = p-gtrightthread6 new-gtleftchild = p7 new-gtleftthread = 18 p-gtrightchild = new9 p-gtrightthead = 010 if (new-gtrightthread)11 q = Next(new)12 q-gtleftchild = new13 14

58  堆積bull 「堆積」 (heap) 是一個特殊的完備二元樹節點的資料與其子節點的資料之間有事先定好的大小關係存在bull 定義ndash最大堆疊為一完備二元樹任一節點的資料內容不小於其子節點的資料內容ndash最小堆疊為一完備二元樹任一節點的資料內容不大於其子節點的資料內容

581  插入節點至最大堆積中最大堆積是一完備二元樹所以如圖 (a) 之最大堆積於加入新節點後樹的形狀必須如圖 (b) 所示灰色節點即為新節點若加入的節點資料分別為 1 9 12 則其最大堆積將分別變成如圖 (c) (d) (e) 所示

程式 5-13 新增節點至最大堆積中最大堆積本身是一棵完備二元樹可以用陣列表示之 1 define maxsize 1002 int heap[maxsize]3 int n i4 void InsertHeap(int x)5 if (n == maxsize) HeapFull()6 else7 n++8 i = n9 while ((i gt 1) ampamp (x gt heap[i2]))10 heap[i] = heap[i2]11 i = 212 13 heap[i] = x14 16

程序 InsertHeap 在插入第 n 個資料時需要的時間複雜度為 O(logn)

1 100

2 93

3 84

4 77

5 65

6 81

7 79

8 66

9 70

10 50

100

84

77

93

8165

66 70

79

50

1

2 3

4 5 6 7

8 9 10

1 define maxsize 1002 int heap[maxsize]3 int n i4 void InsertHeap(int x)5 if (n == maxsize) HeapFull()6 else7 n++8 i = n9 while ((i gt 1) ampamp (x gt heap[i2]))10 heap[i] = heap[i2]11 i = 212 13 heap[i] = x14 16

582 刪除最大堆積中的最大資料圖 (a) 中的最大堆積在刪除最大資料後其樹形應變成如圖 (b) 所示將最未一個樹葉節點的資料 6 暫挪至樹根節點內如圖 (c) 再將圖 (c) 中非最大堆積的結構調整成為最大堆積 10 和 8 中選大者與 6對調即成圖 (d)

程式 5-14 刪除最大堆積中的最大資料(上接程式 5-13 )1 int DeleteHeap()2 int x i j3 if (n == 0) HeapEmpty()4 else5 x = heap[1]6 heap[1] = heap[n]7 n--8 i = 19 while (i lt= n2)10 if (heap[2i] gt heap[2i+1])11 j = 2i12 else13 j = 2i+114 if (heap[i]gtheap[j]) break15 swap(heap[i] heap[j])16 i = j17 18 return x19 20

其時間複雜度與其堆積高度相同是為O(logn) 其中 n 為堆積中的元素個數

1 100

2 93

3 84

4 77

5 65

6 81

7 79

8 66

9 70

10 50

100

84

77

93

8165

66 70

79

50

1

2 3

4 5 6 7

8 9 10

1 int DeletHeap()2 int x i j3 if(n == 0) HeapEmpty()4 else5 x = heap[1]6 heap[1] = heap[n]7 n--8 i = 19 while (i lt= n2)10 if ([heap[2i]] gt heap[2i+1])11 j = 2i12 else13 j = 2i+114 if (heap[i]gtheap[j]) break15 swap(heap[i] heap[j])16 i = j17 18 return x19 20

583  優先佇列bull 由於最大堆積(或最小堆積)的結構可提供最大元素(或最小元素)而其維護堆積的成本為 O(logn) n 為總元素個數

bull 堆積常被當成「優先佇列」 (priority queue) 使用mdash此時佇列被組織成完備二元樹若新加入佇列的資料有大小關係優先順序的需求時此優先佇列即以 O(logn) 的代價將新資料放入優先佇列(可能是最大或最小堆積依實際大小關係優先順序的需求而定)中mdash提供依元素的優先順序做為處理順序的服務

bull 一般的佇列為先進先出先到的先行處理優先佇列則是依據其優先順序優先順序高者先行處理與來到的先後無關

範例 5-21bull 在「分時」 (time sharing) 的作業系統中我們不希望使用中央處理器 (Central Processing Unit CPU) 時間短的「處理程序」 (process) 等候過久於是有「最短時間工作優先」 (shortest-job-first) 的排程策略此時等候的處理程序即可組織成之一最小堆積而 CPU 每次即取出存於樹根的處理程序予以處理任何新來的處理程序則以 O(logn) 的時間加入此優先佇列(最小堆積)中

bull 注意之前提過的線性佇列新來的直接加入成為新的尾端元素花O(1) 的時間然線性佇列本身沒有大小的關係每次要取出最大(小)的資料得花O(n) 的代價(在所有元素中挑出最大(小)者) n 為佇列內的元素個數而優先佇列是在新元素加入時要花O(logn) 的時間至於取出最大(小)則只須 O(1) 的時間

59  二元搜尋樹bull 二元搜尋樹是一棵二元樹可能是空二元樹若不為空二元樹則滿足下列性質ndash所有節點內的資料內容是相異的 ndash左子節點的資內容 ( 如果有 ) 比父節點的資料內容小 ndash右子節點的資料內容 ( 如果有 ) 比父節點的資料內容大 ndash以左和右子節點為樹根的左子樹和右子樹也是二元搜尋樹

在二元搜尋樹中搜尋資料二元搜尋樹對資料大小的掌握十分明確若恰與節點 p 資料相等則搜尋成功若欲搜尋的資料 x 比樹節點 p 中的資料小 則只須搜尋節點 p 的左子樹若比節點 p 中的資料大 則只須搜尋 p 的右子樹這樣的判斷可以遞迴地應用在各子樹節點 中

程式 5-15 在二元搜尋樹中搜尋資料(遞迴呼叫)1 struct BSTreeNode2 struct BSTreeNode leftchild3 int data4 struct BSTreeNode rightchild5 6 struct BSTreeNode root7 struct BSTreeNode SearchBST(

struct BSTreeNode tree int x)8 if (tree == NULL) return NULL9 if (x == tree-gtdata) return tree10 if (x lt tree-gtdata)11 return SearchBST(tree-gtleftchild x)

12 return SearchBST(tree-gtrightchild x)13

程式 5-16 搜尋二元搜尋樹的資料(非遞迴呼叫)在二元搜尋樹十分龐大時遞迴呼叫的效率不會太好可利用迴圈來完成搜尋的動作以提高執行效率請看程式 5-16

14 struct BSTreeNode Search BST_iterative15 (struct BSTreeNode node int x)16 while (node = NULL)17 if (x == node-gtdata) return node18 if (x lt node-gtdata)19 node = node-gtleftchild20 else21 node = node-gtrightchild22 23 return NULL24

592  新增資料於二元搜尋樹中欲新增資料進入一二元搜尋樹中得先決定該資料在二元搜尋樹中應出現的位置這可透過上節提到的搜尋動作來解決

在二元搜尋樹定義的性質 (1) 中我們限定了元素必須相異所以搜尋新資料的結果必是 NULL

必須將搜尋的結果改為新增資料所應在位置的節點指標

程式 5-17  在二元搜尋樹中新增資料25 int InsertBST(int x)26 struct BSTreeNode p q27 p = root q = NULL28 while (p = NULL)29 q = p30 if (x == p-gtdata) return 0

若不考慮重複則可省略之31 if (x lt p-gtdata)32 p = p-gtleftchild33 else34 p = p-gtrightchild35

3636 p = (struct BSTreeNode ) p = (struct BSTreeNode ) malloc (sizeof(BSTreeNode))malloc (sizeof(BSTreeNode))

3737 p-gtdata = x p-gtdata = x3838 p-gtleftchild = p-gtrightchild = p-gtleftchild = p-gtrightchild = NULLNULL3939 if (root == NULL) root = p if (root == NULL) root = p4040 else if (x lt q-gtdata) else if (x lt q-gtdata)4141 q-gtleftchild = p q-gtleftchild = p4242 else else4343 q-gtrightchild = p q-gtrightchild = p4444 return 1 return 145 45

程式 程式 5-175-17  在二元搜尋樹中新增資料  在二元搜尋樹中新增資料 (( 續續 ))

範例 5-23 圖 (b) 為圖 (a) 新增整數 6 之後的結果圖 (c) (d) (e) (f) 則分別是逐步再加入 28 18

10 21 的結果

bull若目前的二元搜尋樹高度為 h 則新增資料得花O(h) 的時間找到其所應在的位置遂新增資料至二元搜尋樹中的時間複雜之度為 O(h)

593 自二元搜尋樹中刪除資料欲自二元搜尋樹中刪除資料可能會面臨兩種情況(1) 刪除節點 p 位於樹葉節點上如下圖所示 這是比較容易處理的情況

(2) 刪除的節點 p 不是樹葉刪除節點 p 後原 p 所指的節點可由 q ( q 中包含了 p 左子樹中的最大節點值)或 r ( r 中包含了 p 右子樹中的最小節點值)來取代以維持二元搜尋樹中資料的大小關係其結果分別如圖 (b) 和 (c) 所示而圖 (d) 則標明了指標會改變的節點位置即 p 會消失 father 的左或右子樹指標會異動 k(可能是 q 或 r )會移到原來 p 的位置其父節點 f 的左或右子樹指標會異動其中 father 為 p 父節點 f 為 k 的父節點

程式 5-18 自二元搜尋樹中刪除資料46 int DeleteBST (int x)47 Struct BSTreeNode p father k f48 p = root father = NULL49 while (p = NULL)50 if (x == p-gtdata) 找到 x 所在的節點 p 了51 if ((p-gtleftchild == NULL) ampamp 52 (p-gtrightchild == NULL))53 k = NULL p為樹葉節點54 else if (p-gtleftchild = NULL )x位於非子節

點55 f = p 找出 p左子樹的最右樹葉56 k = p-gtleftchild57 while (k-gtrightchild =NULL)58 f = k59 k = k-gtrightchild60 k將挪至原 p處61 if (p == f)62 f-gtleftchild = k-gtleftchild 63 else64 f-gtrightchild = k-gtleftchild 65

66 else p無左子樹67 f = p 找出 p右子樹的最左樹葉68 k = p-gtrightchild69 while (k-gtleftchild = NULL)70 f = k71 k = k -gtleftchild72 73 if (p == f)74 f-gtrightchild = k-gtrightchild 75 else76 f-gtleftchild = k-gtrightchild77 78 if (k = NULL) k挪至原 p處繼承 p的左右指標79 k-gtleftchild = p-gtleftchild80 k-gtrightchild = p-gtrightchild81 82 if (father == NULL) root = k83 else if (x lt father-gtdata) 決定 k是 father的左

或右兒子84 father-gtleftchild = k85 else86 father-gtrightchild = k87 free(p)88 return 1 成功地刪除 x於此傳回 1返回呼叫處89 51~89行為找到 x時應有的處理步驟

90 else 尚未找到 x繼續往下一階層找

91 father = p92 if (x lt p-gtdata)93 p = p-gtleftchild94 else95 p = p-gtrightchild96 97 98 return 0 未找到 x傳回 099

510  樹林 (forest)

bull 定義樹林(森林)是由 n 棵沒有交集的樹所構成的集合 n 0

下圖為一包含三棵樹的樹林

5101 轉換樹林為二元樹 之前我們以二元樹來表示樹因為二元樹有較多

好用的性質在此我們也將樹林轉換成二元樹看待

在 522 節中我們嘗試了用左子右兄弟的方式來表示樹這個方法事實上已將樹轉換成二元樹

即用左子右兄弟的結構重新組織樹林中的每一棵樹這些樹的樹根將視為兄弟而最左邊的樹則視為樹林二元樹的樹根如此一來樹林即可轉換成二元樹

範例 5-25右下圖為右上圖三棵樹所對應的三棵左子右兄弟二元樹

下圖則為上圖樹林所對應的一棵樹林二元樹

演算法 5-1  轉換樹林成二元樹輸入樹林 F = T1 T2 Tn

輸出 F 的二元樹表示 BT=(T1 T2 Tn)1 if (n == 0) 輸出空二元樹 2 else

3 root(T1 T2 Tn) = T1

4 leftsubtree(T1 T2 Tn) =

BT(T11 T12 T1m)5 BT(T11 T12 T1m)為 T1的子樹6 rightsubtree(T1 T2 Tn) =

BT(T2 T3 Tn)

樹林轉換成二元樹的過程樹林轉換成二元樹的過程

  • Slide 1
  • Slide 2
  • Slide 3
  • Slide 4
  • Slide 5
  • Slide 6
  • Slide 7
  • Slide 8
  • Slide 9
  • Slide 10
  • Slide 11
  • Slide 12
  • Slide 13
  • Slide 14
  • Slide 15
  • Slide 16
  • Slide 17
  • Slide 18
  • Slide 19
  • Slide 20
  • Slide 21
  • Slide 22
  • Slide 23
  • Slide 24
  • Slide 25
  • Slide 26
  • Slide 27
  • Slide 28
  • Slide 29
  • Slide 30
  • Slide 31
  • Slide 32
  • Slide 33
  • Slide 34
  • Slide 35
  • Slide 36
  • Slide 37
  • Slide 38
  • Slide 39
  • Slide 40
  • Slide 41
  • Slide 42
  • Slide 43
  • Slide 44
  • Slide 45
  • Slide 46
  • Slide 47
  • Slide 48
  • Slide 49
  • Slide 50
  • Slide 51
  • Slide 52
  • Slide 53
  • Slide 54
  • Slide 55
  • Slide 56
  • Slide 57
  • Slide 58
  • Slide 59
  • Slide 60
  • Slide 61
  • Slide 62
  • Slide 63
  • Slide 64
  • Slide 65
  • Slide 66
  • Slide 67
  • Slide 68
  • Slide 69
  • Slide 70
  • Slide 71
  • Slide 72
  • Slide 73
  • Slide 74
  • Slide 75
  • Slide 76
  • Slide 77
  • Slide 78
  • Slide 79
  • Slide 80
  • Slide 81
  • Slide 82
  • Slide 83
  • Slide 84
  • Slide 85
  • Slide 86
  • Slide 87
  • Slide 88
  • Slide 89
  • Slide 90
  • Slide 91
  • Slide 92
  • Slide 93
  • Slide 94
  • Slide 95
  • Slide 96
  • Slide 97
  • Slide 98
  • Slide 99
  • Slide 100
  • Slide 101
  • Slide 102
  • Slide 103
Page 16: 第五章 樹

bull注意樹狀結構中兒子的順序是不重要的試想樹可以樹根為軸心旋轉使得兒子的順序是不固定的

53  二元樹bull 二元樹是一個由節點組成的有限集合可以是空集合或是包含了(1) 一個樹根節點(2) 其它節點分割成兩個沒有交集的二元樹

其一為左子樹 (left subtree) 另一為右子樹 (right subtree) 樹和二元樹之間的差異

(1)樹不允許空節點而空二元樹是允許的

(2) 樹的子樹沒有順序而二元樹的子樹有左右之分

531  樹和二元樹的基本性質

樹或二元樹皆擁有下面的性質

定理 5-1 若一棵樹 T 的總節點數為 V 總邊數為

E 則V = E + 1

證明利用數學歸納法歸納法的基礎當 V=1 時即 T 只有一個樹根節點 E 為 0 所以 V = E + 1 成立歸納法的假設假設當 1 V K 時該式成立歸納法的推論當 V = K + 1 時令此樹的樹根為 R 而 R 有 m 個子樹 T1 T2 hellip

Tm (如圖 5-12 所示)分別有個 V1 V2 hellip Vm 個節點其中 V1 +V2 + hellip+ Vm =K 若 E1 E2 hellip Em 分別為此 m 個子樹的邊數則

E = E1 +E2 + hellip+ Em + m (1)根據歸納法的假設可知

V1 = E1+1V2 = E2+1 Vm = Em+1

所以V1 +V2 + hellip+Vm = E1 + E2 + hellip+ Em +m (2)

然而V = V1 +V2 + hellip+Vm+1 (3)

將式 (2) 代入式 (3) 可得V = E1 + E2 + hellip+ Em + m + 1

引用式 (1) 則可得V = E + 1

由數學歸納法的原理可得證本定理

532 二元樹的性質(a) 稱為歪斜樹 (skew tree) (b) 稱為完備二元樹 (complete binary tree)

( 其樹葉節點皆在相鄰階層完整定義在後 ) 同樣數目的樹節點存放在歪斜樹上會花較多的階層放在完備二元樹上則只須最少的階層

532 二元樹的性質 ( 續 )定理 5-2 (1) 二元樹上階層 i 的節點數目最多為 2i-1 i 1 (2) 深度為 k 的二元樹其節點數目最多為 2k-

1 k1 bull 定理 5-2說明了 (1) 每一階層上可放的最多節點數

(2) 階層數已知時二元樹可放的最多節點數證明 (1) 利用數學歸納法歸納法的基礎當 i=1 時只有樹根一個節點 2i-1=21-1=1 原式成

立歸納法的假設假設 i = k 時該式成立即階層 k 的最大節點數為

2k-1 k 1 歸納法的推論當 i = k+1 時因在階層 k 的每一個節點最多會有

2 個子節點遂在 k+1 階層最多會有 22k-1 = 2k

= 2i-1 節點由數學歸納法的原理可知本定理得證(2) 由 (1) 可知二元樹在階層 i 的最多節點數為 2i-1那麼深度 k 的二元樹最多可有節點如下

k

i

ki

1

1-1- 24212 1-2

)12(1 k= 2k-1

=

bull 對二元樹而言任一節點的分支度可能為 0 1 或 2 分支度為 0 者是為樹葉

bull 樹葉的數目與分支度為 2 的節點數目存在著有趣的關係請見定理 5-3

定理 5-3 若 T 為一非空的二元樹 n0 為樹葉節點數目 n2為分支度為 2 的節點數目則 n0 = n2+1

證明令 n 為所有節點數目而 n1 為分支度為 1 的節點數目可得 n = n0 + n1 +n2 (1)令 B 為二元樹的分支數除了樹根外每一節點皆有一分支指向之所以

n = B+1 (亦可參考定理 5-1 )又 B = n1+2n2

且 n = n1 + 2n2 +1 (2)由式 (1) 和式 (2) 我們得到

n0 = n2 + 1

完滿二元樹 (full binary tree)

定義一個深度為 k 的完滿二元樹即為一深度為 k 且有 2k-1 個節點的二元樹k 0

bull 由定理 5-2(2) 可知深度為 k 的二元樹其最多節點數為 2k-1 這樣的樹會將樹上所有可能存放節點的位置都放滿完滿 (full) 之名因而生成

完備二元樹 (complete binary tree)我們可對二元樹上節點予以編號階層小者先編同一階

層則自左向右編號這樣的編號方式使我們定義出完備(complete) 二元樹

定義深度為 k 節點數為 n 的二元樹稱為完備二元樹若且唯若 (1) k = 1 時樹有一個節點

(2) 當 k 2 且 1iltk 時深度 i 有 2i-1 個節點且第 k 層的節點皆由第 k-1 層的分支由左至右逐一連接而成

完滿或完備二元樹之所以受人注意乃因它們可以將節點以較少的階層數存放

定理 5-4 n 個節點的完備或完滿二元樹其深度為 log2(n+1)

證明 完滿或完備二元樹的節點皆自階層

1 起排放任一階層 i 可排放 2i-1

個節點遂由定理 5-2 得知此 n 個節點所佔的深度為 log2(n+1)

正規二元樹 (formal binary tree)bull 在二元樹中非樹葉的節點又稱為內部節點

(internal node) 若所有內部節點恰有 2 個子節點即成為正規二元樹

定義若二元樹中所有內部節點恰有 2 個子節點則稱之為正規二元樹

正規二元樹常被引用在單淘汰賽正規二元樹常被引用在單淘汰賽制的各項賽程安排樹葉為參賽制的各項賽程安排樹葉為參賽隊伍內部節點為優勝隊伍樹隊伍內部節點為優勝隊伍樹根即為冠軍隊伍根即為冠軍隊伍

在單淘汰賽制中自在單淘汰賽制中自 n n 個隊伍中產個隊伍中產生冠軍須舉辦 生冠軍須舉辦 nn-1-1場比賽場比賽

54  二元樹的表示法541 以陣列表示二元樹定理 5-5 若一個具有 n 個節點的完備二元樹以循序的方式編號並依序存放在陣列 A 中即第 i 個節點放在 A[i] 中 1 i n 則

(1) 節點 i 的父節點位在 A[i2]處 i ne 1( i = 1 時其節點正為樹根無父節點)

(2) 若 2i n 則節點 i 的左兒子節點位在 A[2i] 處若 2i gtn 節點 i 沒有左子節點

(3) 若 2i+1 n 節點 i 的右兒子節點位 A[2i+1]處若 2i+1gtn 節點 i 沒有左子

節點

證明先用數學歸納法證明 (2) bull 歸納法的基礎當 i = 1 時其左子節點確實在 A[2] 處(除非 nlt2 此時節點 1 沒有左子節點)

bull 歸納法的假設假設當 I = k 時其左子節點確在 A[2i] = A[2k] 處(除非 2i 已大於 n 此時節點 i 無左子節點)

bull 歸納法的推論當 i = k+1 時因第 k 節點的左子節點在 A[2k] 處其右子節點則位在 A[2k+1] 處那麼第 k+1 節點的左子節點應緊臨 A[2k+1] 亦即位於 A[2k+2] = A[2(k+1)] 處(除非 2i 已大於 n 此時節點 i 無左子節點)

bull 由數學歸納法的原理得證定理 5-5 (2) (3) 由於 (2) 成立而且節點 i 的右子節點會緊臨

A[2i] 亦即其位於 A[2i+1]處除非 i 根本沒有右子節點得證定理 5-5 (3)

由 (2) 和 (3) 我們可知定理 5-5 (1) 成立

542 以鍵結串列表示二元樹bull 以陣列來表示完備二元樹算是相當方便但ndash一般的二元樹將導致陣列空間的浪費ndash如果二元樹中節點的新增刪除動作頻繁則會造成陣列對應資料的搬動隨而頻繁

bull 以鍵結串列表示二元樹

為方便辦識樹根我們常用 root 指標指向樹根做為樹鍵結串列的起點

陣列表示和鍵結表示的空間需求bull 完備二元樹的陣列表示和鍵結表示皆沒浪費儲存資料的空間

bull 但歪斜樹的陣列表示比起鍵結表示則浪費許多

bull 以 n 個節點的歪斜樹而言陣列表示法可能需要 2n-1 個陣列元素其中 2n-1-n 個陣列元素空間是浪費的而鍵結表示法只需要 n 個節點空間

二元樹節點的宣告程式 5-2

1 struct BTreeNode

2 struct BtreeNode leftchiled

3 int data

4 struct BTreeNode rightchild

5

6 struct BTreeNode root

bull 這樣的二元樹節點要取得兒子節點的指標十分方便但若有取得父節點的需求則嫌不足我們可以加上 parent 欄位指向父節點

55  二元樹的走訪bull 假如一組資料已用二元樹的組織在一起總有需求對其全數資料做動作例如計算所有數目印出所有資料在所有資料中搜尋某項資料hellip等此時即須對此二元樹做走訪 (traversal) 的運算利用走訪二元樹的同時將計算列印或搜尋的動作完成

bull 事實上走訪即在決定二元樹上資料被處理(計算列印或搜尋)的順序我們也希望走訪的演算法對任何節點皆一致是容易撰寫程式實作的

bull 若對一個二元樹上的節點而言 V 表示處理節點上的資料 L 表示走訪其左子樹 R 表示走訪其右子樹

55  二元樹的走訪 ( 續 )bull 因為對稱的緣故我們只考慮先走訪左邊再走訪右邊的情形那麼右圖只剩下三種走訪方式

bull 以 V 所在的相對位置分別對此三種走訪方式取名如下

(1) LVR 中序走訪 (inorder traversal)或中序表示法 (infix notation)

(2) LRV 後序走訪 (postorder traversal) 或後序表示法 (postfix notation)

(3) VLR 前序走訪 (preorder traversal)或前序表示法 (prefix notation)

551  中序走訪bull 利用遞迴的方式撰寫中序走訪的程序希望把二元樹中序走訪的

順序印出來

程式 5-3二元樹的中序走訪1 struct BTreeNode2 struct BTreeNode leftchild3 char data4 struct BTreeNode rightchild5 6 struct BTreeNode root7 void inorder(struct BTreeNode node)8 if (node = NULL)9 inorder(node-gtleftchild)10 cout ltlt node-gtdata11 inorder(node-gtrightchild)12 13

範例 5-12此為運算式二元樹

其對應中序表示運算式為 (A+B)C+D(E-F)

6 struct BTreeNode root7 void inorder(struct BTreeNode node)8 if (node = NULL)9 inorder(node-gtleftchild)10 cout ltlt node-gtdata11 inorder(node-gtrightchild)12 13

6 struct BTreeNode root7 void inorder(struct BTreeNode node)8 if (node = NULL)9 inorder(node-gtleftchild)10 cout ltlt node-gtdata11 inorder(node-gtrightchild)12 13

(A+B)C+D(E-F)

如何加上 ( )

552  後序走訪程式 5-4二元樹的後序走訪14 void postorder(struct BTreeNode node)

15 if (node =NULL)16 postorder(node-gtleftchild)17 postorder(node-gtrightchild)18 cout ltlt node-gtdata19 20

以後序走訪可得到AB+CDEF-+

其正為對應的後序運算式

553  前序走訪程式 5-5二元樹的前序走訪21 void preorder(struct BTreeNode node)

22 if (node = NULL)

23 cout ltlt node-gtdata

24 preorder(node-gtleftchild)

25 preorder(node-gtrightchild)

26

27

以前序走訪可得到++ABCD-EF

其正為對應的前序運算式

554 利用堆疊和迴圈取代遞迴進行二元樹的走訪1 struct BTreeNode 2 struct BTreeNode leftchild3 char data4 struct BtreeNode rightchild5 6 struct BTreeNode root7 struct StackNode8 struct BTreeNode treenode9 struct StackNode next1011 struct StackNode top

12 void push_node(struct BtreeNode node )13 struct StackNode old_top14 old_top = top15 top = (struct StackNode ) malloc

(sizeof(struct StackNode))16 top-gttreenode = node17 top-gtnext = old_top18 19 struct BTreeNode pop_node()20 struct BTreeNode Tnode21 struct StackNode old_top22 if (top == NULL)23 StackEmpty()24 else25 Tnode = top-gttreenode26 old_top = top27 top = top-gtnext28 free(old_top)29 return Tnode30 31

32 void inorder_Stack(struct BtreeNode node)33 do34 while (node = NULL)35 push_node(node)36 node = node-gtleftchild37 push all left children38 if (top = NULL)39 node = pop_node()40 cout ltlt node-gtdata41 node = node-gtrightchild42 inorder printing and check right child43 while ((top=NULL)||(node=NULL))44 top==NULL 堆疊已空 node==NULL沒有右子

6 struct BTreeNode root7 void inorder(struct BTreeNode node)8 if (node = NULL)9 inorder(node-gtleftchild)10 cout ltlt node-gtdata11 inorder(node-gtrightchild)12 13

中序表示為DBGEHAFIC

32 void inorder_Stack()33 struct BTreeNode node34 node = root35 do36 while (node = NULL)37 push_node(node)38 node = node-gtleftchild39 40 if (top = NULL)41 node = pop_node()42 cout ltlt node-gtdata43 node = node-gtrightchild44 45 while(top=NULL)||(node=NULL))46

45 void Preorder_Stack(struct BSTreeNode node)46 do47 while (node = NULL)48 cout ltlt node-gtdata)49 push_node(node)50 node = node-gtleftchild51 push all left children52 if (top = NULL)53 node = pop_node()54 node = node-gtrightchild55 56 while ((top=NULL)||(node=NULL))57

程式 5-5二元樹的前序走訪21 void preorder(struct BTreeNode node)22 if (node = NULL)23 cout ltlt node-gtdata24 preorder(node-gtleftchild)25 preorder(node-gtrightchild)26 27

58 void Postorder_Stack(struct BTreeNode node)59 do60 while (node = NULL)61 cout ltlt node-gtdata62 push_node(node)63 node = node-gtrightchild64 push all left children65 if (top = NULL)66 node = pop_node()67 node = node-gtleftchild68 69 while ((top=NULL)||(node=NULL))70

程式 5-4二元樹的後序走訪14 void postorder(struct BTreeNode node)15 if (node =NULL)16 postorder(node-gtleftchild)17 postorder(node-gtrightchild)18 cout ltlt node-gtdata19 20

555  階層走訪bull 「階層走訪」 (level-order traversal) 是依階層

的順序進行二元樹的走訪先走訪階層小的節點後走訪階層大的節點同一階層者則依自左向右的順序走訪

bull 對下圖的二元樹進行階層走訪的結果為ABCDEFGHI

bull 對同一階層而言先走訪的節點其子節點亦在下一階層中先被走訪

bull 這種「先進先出」的特性恰可用佇列予以儲存與處理

程式 5-7 利用佇列進行二元樹的階層走訪

1 struct BTreeNode2 struct BTreeNode leftchild3 char data4 struct BTreeNode rightchild5 6 struct BTreeNode root7 struct QNode8 struct BTreeNode treenode9 struct QNode next10 11 struct QNode front rear

12 void AddQueue(struct BTreeNode Tnode)13 struct QNode node14 node=(struct QNode )malloc(sizeof(struct QNode))15 node-gttreenode = Tnode16 node-gtnext = NULL17 if (front == NULL) front = node19 else rear-gtnext = node20 rear = node21

22 struct BTreeNode DeleteQueue()23 struct BTreeNode Tnode24 struct QNode old_front25 if (rear == NULL)26 QueueEmpty()27 else28 Tnode = front-gttreenode29 old_front = front30 front = front-gtnext31 free(old_front)32 return Tnode33 34 35 void LevelOrder (struct BTreeNode node)36 AddQueue(node)37 while (front =NULL)38 node = DeleteQueue()39 cout ltlt node-gtdata40 if (node-gtleftchild = NULL)41 AddQueue(node-gtleftchild)42 if (node-gtrightchild = NULL)43 AddQueue(node-gtrightchild)44 45

35 void LevelOrder (struct BTreeNode node)36 AddQueue(node)37 while (front =NULL)38 node = DeleteQueue()39 cout ltlt node-gtdata40 if (node-gtleftchild = NULL)41 AddQueue(node-gtleftchild)42 if (node-gtrightchild = NULL)43 AddQueue(node-gtrightchild)44 45

bull Infix ABCbull Postfix ABC CBA ACB BCA

BAC CAB

B

A CA

B

C

B

A

C

C

B

AB

A

C

二元樹中序與前序式中序與後序式bull 每一棵二元樹皆有唯一的一對中序與前序表示法也有唯一的中序與後序次序

bull 換句話說給予一對中序與前序或中序與後序即可決定一棵二元樹

bull 然而給予前序和後序次序並不能決定唯一的二元樹可能會產生兩棵不同的二元樹

bull 想想階層走訪和中序走訪順序可否決定唯一的二元樹

A

B C

H J D E

F G

prefix ABHJCDFGEinfix HBJAFDGCEpostfix HJBFGDECA

VLRLVRLRV

A

prefix BHJinfix HBJ

prefix CDFGEinfix FDGCE

prefix ABHJCDFGEinfix HBJAFDGCE

A

B C

H J D E

F G

prefix ABHJCDFGE

infix HBJAFDGCEk-1

struct BTreeNode BTree_InfixPrefix (AnsiString infix AnsiString prefix)

int k struct BTreeNode node if (infixLength==0) return NULLnode = (struct BTreeNode ) malloc (sizeof(struct BTreeNode))node-gtdata = prefix[1]k = find(prefix[1] infix)node-gtleftchild = BTree_InfixPrefix

(infix[1k-1] prefix[2k])node-gtrightchild = BTree_InfixPrefix(infix[k+1infixLength]prefix[k+1prefixLength])

VLR

LVRk1

k2 k+1~

k+1~

1

struct BTreeNode BTree_InfixPostfix (AnsiString infix AnsiString prefix)

int k struct BTreeNode node if (infixLength==0) return NULLnode = (struct BTreeNode ) malloc (sizeof(struct BTreeNode))node-gtdata = postfix[postfixLength]k = find(postfix[postfixLength] infix)node-gtleftchild = BTree_InfixPrefix

(infix[1k-1] postfix[1k-1])node-gtrightchild = BTree_InfixPrefix(infix[k+1infixLength]postfix[k+1postfixLength-1])

56  二元樹的複製與相等測試程式 5-8 二元樹的複製1 struct BTreeNode2 struct BTreeNode leftchild3 char data4 struct BTreeNode rightchild5 6 struct BTreeNode root7 struct BTreeNode Copy(struct BTreeNode TreeRoot)8 struct BTreeNode CopyRoot9 if (TreeRoot == NULL)10 return NULL11 CopyRoot =(struct BTreeNode ) malloc (sizeof(BTreeNode))12 CopyRoot-gtdata = TreeRoot-gtdata13 CopyRoot-gtleftchild = Copy(TreeRoot-gtleftchild)14 CopyRoot-gtrightchild = Copy(TreeRoot-

gtrightchild)15 return CopyRoot16

程式 5-9 檢測兩棵二元樹是否相等1 struct BTreeNode2 struct BTreeNode leftchild3 char data4 struct BTreeNode rightchild5 6 struct BtreeNode rightchild7 int Equal(struct BTreeNode ustruct BTreeNode v)8 if ((u==NULL) ampamp (v==NULL)) return 19 if ((u=NULL) ampamp (v=NULL)10 ampamp (u-gtdata == v-gtdata )11 ampamp Equal(u-gtleftchildv-gtleftchild)12 ampamp Equal(u-gtrightchildv-gtrightchild))

13 return 114 return 015

Counting Nodes in a Binary Tree

int CountingNodes(struct BTreeNodes node)

if (node == NULL) return 0 int leftchildren = CountingNodes(node-gtleftchild)

int rightchildren= CountingNodes(node-gtrightchild)

return leftchildren+rightchildren

Counting Leaves in a Binary Tree

int CountingLeaves(struct BTreeNodes node)

if (node == NULL) return 0 int leftleaves = CountingLeaves(node-gtleftchild)

int rightchildren= CountingLeaves(node-gtrightchild)

if (leftleaves==0 ampamp leftleaves==0) return 1

return leftleaves + leftleaves

57  引線二元樹bull 在二元樹的鍵結表示中樹葉節點的兩個子樹指標皆指向 NULL 由定理 5-1 得知 n = E+1 其中 n 為節點個數 E 為分支數

bull 而 n 個鍵結節點有 2n 個指標空間每個分支恰佔用一個指標空間共有 E (=n-1) 個指標有用到(不是空的)而共有 n+1 個指標空間是 NULL 比非空的節點還多

bull 有學者提出對這些「放空指標的空間」加以利用的概念mdash與其放空指標不如放指向其它節點的指標稱之為引線 (thread) 使得某些運算(如走訪hellip等)可以加快

bull 這個概念發展出了引線二元樹 (threaded binary tree) 這種資料結構

範例 5-17下圖為加了引線的二元樹

加入的引線將使二元樹的中序走訪更加便利沿著上右圖箭頭所指示的順序走訪即可完成此二元樹的中序走訪GDHBAIECF 為此二元樹的中序表示

bull 範例 5-17 中所加的引線目的在方便二元樹的中序走訪

bull 對樹葉節點 p 而言令其資料為 P 則引線指標的規則如下

(1) p 的左子樹指標指向中序表示中 P 的前一個 元素(前接元素 (predecessor) )

(2) p 的右子樹指標指向中序表示中 P 的後一個 元素(後繼元素 (successor) )

為了區別節點指標放的是一般指標(內部節點)抑或是引線指標(樹葉節點)我們另以 2 個欄位分別區別左和右子樹指標空間存放的對象其節點的記憶體配置有如下圖所示

其中 leftthread (rightthread) 為 0 時表示 leftchild (rightchild) 存放的是一般節點指標而 leftthread (rightthread) 為 1 時表示 leftchild (rightchild) 存放的是引線指標1 struct TBTreeNode2 int leftthread3 struct TBTreeNode leftchild4 char data5 struct TBTreeNode rightchild6 int rightthread7

由右側的引線二元樹可知有最左和最右兩條引線尚無妥善的安排我們另設計一空的引線節點做為樹根如下圖所示

571  引線二元樹的中序走訪決定節點 p 在中序表示中的後繼節點 q (簡稱中序後繼點)

程式 5-10決定節點 node的中序後繼點1 struct TBTreeNode2 int leftthread3 struct TBTreeNode leftchild4 char data5 struct TBTreeNode rightchild6 int rightthread7 8 struct TBTreeNode root9 struct TBTreeNode Next(struct TBTreeNode node)

10 struct TBTreeNode temp11 temp = node-gtrightchild12 if (node-gtrightthead) return temp13 while (temp-gtleftthead) temp=temp-gtleftchild

14 return temp15

程式 5-11引線二元樹的中序表示(接程式 5-10)1 void InorderTBTree()2 struct TBTreeNode node3 for(node=Next(root)node=rootnode=Next(node))

4 cout ltlt node-gtdata5

bull只要 node 是引線二元樹上的任一節點透過呼叫 Next(node) 即可找到其在中序表示中的後繼節點bull(試想若程式 5-10 中的程序 Next 所傳入的參數指標正是 root Next(root) 是否可正常運作請思考)bull於是只須從中序表示中的第一節點起讓每一節點 node 都執行 Next(node) 程序即可得引線二元樹的中序表示

572  在引線二元樹中加入節點在引線二元樹中樹葉節點節點的右子樹指標所指向或內部節點右子樹的最左樹葉為其中序後繼節點而且任一節點若無左子樹則其左子樹指標乃指向其中序前接節點hellip這些性質必須在節點新增進入引線二元樹時也要保持接下來討論節點 new 插入成為引線二元樹的節點 p 的右子節點的情形至於插入成為左子節點的情形則讓各位模擬推敲

572572  在引線二元樹中加入節點 在引線二元樹中加入節點 (( 續續 ))

newnew 會成為會成為 pp 的右子節點的情況共有以下兩種的右子節點的情況共有以下兩種(1) (1) 若若 pp 沒有右子節點 沒有右子節點 ((p-gtrightthread p-gtrightthread 為為1) 1) 如圖如圖 ((a) a) 則 則 new new 插入後應形成如圖插入後應形成如圖 ((b) b) 所示所示

(2)(2) 若若 pp 有右子節點 有右子節點 ((p-gtrightthread p-gtrightthread 為為0) 0) 如圖如圖 ((c) c) 則 則 new new 插入後應形成如插入後應形成如圖圖 ((d) d) 所示所示

程式 5-12  插入節點 new 成為引線二元樹中節點 p的右子節點(上接程式 5-10 5-11 )

1 void InsertRight(struct TBTreeNode p 2 struct TBTreeNode new)3 struct TBTreeNode q4 new-gtrightchild = p-gtrightchild5 new-gtrightthead = p-gtrightthread6 new-gtleftchild = p7 new-gtleftthread = 18 p-gtrightchild = new9 p-gtrightthead = 010 if (new-gtrightthread)11 q = Next(new)12 q-gtleftchild = new13 14

58  堆積bull 「堆積」 (heap) 是一個特殊的完備二元樹節點的資料與其子節點的資料之間有事先定好的大小關係存在bull 定義ndash最大堆疊為一完備二元樹任一節點的資料內容不小於其子節點的資料內容ndash最小堆疊為一完備二元樹任一節點的資料內容不大於其子節點的資料內容

581  插入節點至最大堆積中最大堆積是一完備二元樹所以如圖 (a) 之最大堆積於加入新節點後樹的形狀必須如圖 (b) 所示灰色節點即為新節點若加入的節點資料分別為 1 9 12 則其最大堆積將分別變成如圖 (c) (d) (e) 所示

程式 5-13 新增節點至最大堆積中最大堆積本身是一棵完備二元樹可以用陣列表示之 1 define maxsize 1002 int heap[maxsize]3 int n i4 void InsertHeap(int x)5 if (n == maxsize) HeapFull()6 else7 n++8 i = n9 while ((i gt 1) ampamp (x gt heap[i2]))10 heap[i] = heap[i2]11 i = 212 13 heap[i] = x14 16

程序 InsertHeap 在插入第 n 個資料時需要的時間複雜度為 O(logn)

1 100

2 93

3 84

4 77

5 65

6 81

7 79

8 66

9 70

10 50

100

84

77

93

8165

66 70

79

50

1

2 3

4 5 6 7

8 9 10

1 define maxsize 1002 int heap[maxsize]3 int n i4 void InsertHeap(int x)5 if (n == maxsize) HeapFull()6 else7 n++8 i = n9 while ((i gt 1) ampamp (x gt heap[i2]))10 heap[i] = heap[i2]11 i = 212 13 heap[i] = x14 16

582 刪除最大堆積中的最大資料圖 (a) 中的最大堆積在刪除最大資料後其樹形應變成如圖 (b) 所示將最未一個樹葉節點的資料 6 暫挪至樹根節點內如圖 (c) 再將圖 (c) 中非最大堆積的結構調整成為最大堆積 10 和 8 中選大者與 6對調即成圖 (d)

程式 5-14 刪除最大堆積中的最大資料(上接程式 5-13 )1 int DeleteHeap()2 int x i j3 if (n == 0) HeapEmpty()4 else5 x = heap[1]6 heap[1] = heap[n]7 n--8 i = 19 while (i lt= n2)10 if (heap[2i] gt heap[2i+1])11 j = 2i12 else13 j = 2i+114 if (heap[i]gtheap[j]) break15 swap(heap[i] heap[j])16 i = j17 18 return x19 20

其時間複雜度與其堆積高度相同是為O(logn) 其中 n 為堆積中的元素個數

1 100

2 93

3 84

4 77

5 65

6 81

7 79

8 66

9 70

10 50

100

84

77

93

8165

66 70

79

50

1

2 3

4 5 6 7

8 9 10

1 int DeletHeap()2 int x i j3 if(n == 0) HeapEmpty()4 else5 x = heap[1]6 heap[1] = heap[n]7 n--8 i = 19 while (i lt= n2)10 if ([heap[2i]] gt heap[2i+1])11 j = 2i12 else13 j = 2i+114 if (heap[i]gtheap[j]) break15 swap(heap[i] heap[j])16 i = j17 18 return x19 20

583  優先佇列bull 由於最大堆積(或最小堆積)的結構可提供最大元素(或最小元素)而其維護堆積的成本為 O(logn) n 為總元素個數

bull 堆積常被當成「優先佇列」 (priority queue) 使用mdash此時佇列被組織成完備二元樹若新加入佇列的資料有大小關係優先順序的需求時此優先佇列即以 O(logn) 的代價將新資料放入優先佇列(可能是最大或最小堆積依實際大小關係優先順序的需求而定)中mdash提供依元素的優先順序做為處理順序的服務

bull 一般的佇列為先進先出先到的先行處理優先佇列則是依據其優先順序優先順序高者先行處理與來到的先後無關

範例 5-21bull 在「分時」 (time sharing) 的作業系統中我們不希望使用中央處理器 (Central Processing Unit CPU) 時間短的「處理程序」 (process) 等候過久於是有「最短時間工作優先」 (shortest-job-first) 的排程策略此時等候的處理程序即可組織成之一最小堆積而 CPU 每次即取出存於樹根的處理程序予以處理任何新來的處理程序則以 O(logn) 的時間加入此優先佇列(最小堆積)中

bull 注意之前提過的線性佇列新來的直接加入成為新的尾端元素花O(1) 的時間然線性佇列本身沒有大小的關係每次要取出最大(小)的資料得花O(n) 的代價(在所有元素中挑出最大(小)者) n 為佇列內的元素個數而優先佇列是在新元素加入時要花O(logn) 的時間至於取出最大(小)則只須 O(1) 的時間

59  二元搜尋樹bull 二元搜尋樹是一棵二元樹可能是空二元樹若不為空二元樹則滿足下列性質ndash所有節點內的資料內容是相異的 ndash左子節點的資內容 ( 如果有 ) 比父節點的資料內容小 ndash右子節點的資料內容 ( 如果有 ) 比父節點的資料內容大 ndash以左和右子節點為樹根的左子樹和右子樹也是二元搜尋樹

在二元搜尋樹中搜尋資料二元搜尋樹對資料大小的掌握十分明確若恰與節點 p 資料相等則搜尋成功若欲搜尋的資料 x 比樹節點 p 中的資料小 則只須搜尋節點 p 的左子樹若比節點 p 中的資料大 則只須搜尋 p 的右子樹這樣的判斷可以遞迴地應用在各子樹節點 中

程式 5-15 在二元搜尋樹中搜尋資料(遞迴呼叫)1 struct BSTreeNode2 struct BSTreeNode leftchild3 int data4 struct BSTreeNode rightchild5 6 struct BSTreeNode root7 struct BSTreeNode SearchBST(

struct BSTreeNode tree int x)8 if (tree == NULL) return NULL9 if (x == tree-gtdata) return tree10 if (x lt tree-gtdata)11 return SearchBST(tree-gtleftchild x)

12 return SearchBST(tree-gtrightchild x)13

程式 5-16 搜尋二元搜尋樹的資料(非遞迴呼叫)在二元搜尋樹十分龐大時遞迴呼叫的效率不會太好可利用迴圈來完成搜尋的動作以提高執行效率請看程式 5-16

14 struct BSTreeNode Search BST_iterative15 (struct BSTreeNode node int x)16 while (node = NULL)17 if (x == node-gtdata) return node18 if (x lt node-gtdata)19 node = node-gtleftchild20 else21 node = node-gtrightchild22 23 return NULL24

592  新增資料於二元搜尋樹中欲新增資料進入一二元搜尋樹中得先決定該資料在二元搜尋樹中應出現的位置這可透過上節提到的搜尋動作來解決

在二元搜尋樹定義的性質 (1) 中我們限定了元素必須相異所以搜尋新資料的結果必是 NULL

必須將搜尋的結果改為新增資料所應在位置的節點指標

程式 5-17  在二元搜尋樹中新增資料25 int InsertBST(int x)26 struct BSTreeNode p q27 p = root q = NULL28 while (p = NULL)29 q = p30 if (x == p-gtdata) return 0

若不考慮重複則可省略之31 if (x lt p-gtdata)32 p = p-gtleftchild33 else34 p = p-gtrightchild35

3636 p = (struct BSTreeNode ) p = (struct BSTreeNode ) malloc (sizeof(BSTreeNode))malloc (sizeof(BSTreeNode))

3737 p-gtdata = x p-gtdata = x3838 p-gtleftchild = p-gtrightchild = p-gtleftchild = p-gtrightchild = NULLNULL3939 if (root == NULL) root = p if (root == NULL) root = p4040 else if (x lt q-gtdata) else if (x lt q-gtdata)4141 q-gtleftchild = p q-gtleftchild = p4242 else else4343 q-gtrightchild = p q-gtrightchild = p4444 return 1 return 145 45

程式 程式 5-175-17  在二元搜尋樹中新增資料  在二元搜尋樹中新增資料 (( 續續 ))

範例 5-23 圖 (b) 為圖 (a) 新增整數 6 之後的結果圖 (c) (d) (e) (f) 則分別是逐步再加入 28 18

10 21 的結果

bull若目前的二元搜尋樹高度為 h 則新增資料得花O(h) 的時間找到其所應在的位置遂新增資料至二元搜尋樹中的時間複雜之度為 O(h)

593 自二元搜尋樹中刪除資料欲自二元搜尋樹中刪除資料可能會面臨兩種情況(1) 刪除節點 p 位於樹葉節點上如下圖所示 這是比較容易處理的情況

(2) 刪除的節點 p 不是樹葉刪除節點 p 後原 p 所指的節點可由 q ( q 中包含了 p 左子樹中的最大節點值)或 r ( r 中包含了 p 右子樹中的最小節點值)來取代以維持二元搜尋樹中資料的大小關係其結果分別如圖 (b) 和 (c) 所示而圖 (d) 則標明了指標會改變的節點位置即 p 會消失 father 的左或右子樹指標會異動 k(可能是 q 或 r )會移到原來 p 的位置其父節點 f 的左或右子樹指標會異動其中 father 為 p 父節點 f 為 k 的父節點

程式 5-18 自二元搜尋樹中刪除資料46 int DeleteBST (int x)47 Struct BSTreeNode p father k f48 p = root father = NULL49 while (p = NULL)50 if (x == p-gtdata) 找到 x 所在的節點 p 了51 if ((p-gtleftchild == NULL) ampamp 52 (p-gtrightchild == NULL))53 k = NULL p為樹葉節點54 else if (p-gtleftchild = NULL )x位於非子節

點55 f = p 找出 p左子樹的最右樹葉56 k = p-gtleftchild57 while (k-gtrightchild =NULL)58 f = k59 k = k-gtrightchild60 k將挪至原 p處61 if (p == f)62 f-gtleftchild = k-gtleftchild 63 else64 f-gtrightchild = k-gtleftchild 65

66 else p無左子樹67 f = p 找出 p右子樹的最左樹葉68 k = p-gtrightchild69 while (k-gtleftchild = NULL)70 f = k71 k = k -gtleftchild72 73 if (p == f)74 f-gtrightchild = k-gtrightchild 75 else76 f-gtleftchild = k-gtrightchild77 78 if (k = NULL) k挪至原 p處繼承 p的左右指標79 k-gtleftchild = p-gtleftchild80 k-gtrightchild = p-gtrightchild81 82 if (father == NULL) root = k83 else if (x lt father-gtdata) 決定 k是 father的左

或右兒子84 father-gtleftchild = k85 else86 father-gtrightchild = k87 free(p)88 return 1 成功地刪除 x於此傳回 1返回呼叫處89 51~89行為找到 x時應有的處理步驟

90 else 尚未找到 x繼續往下一階層找

91 father = p92 if (x lt p-gtdata)93 p = p-gtleftchild94 else95 p = p-gtrightchild96 97 98 return 0 未找到 x傳回 099

510  樹林 (forest)

bull 定義樹林(森林)是由 n 棵沒有交集的樹所構成的集合 n 0

下圖為一包含三棵樹的樹林

5101 轉換樹林為二元樹 之前我們以二元樹來表示樹因為二元樹有較多

好用的性質在此我們也將樹林轉換成二元樹看待

在 522 節中我們嘗試了用左子右兄弟的方式來表示樹這個方法事實上已將樹轉換成二元樹

即用左子右兄弟的結構重新組織樹林中的每一棵樹這些樹的樹根將視為兄弟而最左邊的樹則視為樹林二元樹的樹根如此一來樹林即可轉換成二元樹

範例 5-25右下圖為右上圖三棵樹所對應的三棵左子右兄弟二元樹

下圖則為上圖樹林所對應的一棵樹林二元樹

演算法 5-1  轉換樹林成二元樹輸入樹林 F = T1 T2 Tn

輸出 F 的二元樹表示 BT=(T1 T2 Tn)1 if (n == 0) 輸出空二元樹 2 else

3 root(T1 T2 Tn) = T1

4 leftsubtree(T1 T2 Tn) =

BT(T11 T12 T1m)5 BT(T11 T12 T1m)為 T1的子樹6 rightsubtree(T1 T2 Tn) =

BT(T2 T3 Tn)

樹林轉換成二元樹的過程樹林轉換成二元樹的過程

  • Slide 1
  • Slide 2
  • Slide 3
  • Slide 4
  • Slide 5
  • Slide 6
  • Slide 7
  • Slide 8
  • Slide 9
  • Slide 10
  • Slide 11
  • Slide 12
  • Slide 13
  • Slide 14
  • Slide 15
  • Slide 16
  • Slide 17
  • Slide 18
  • Slide 19
  • Slide 20
  • Slide 21
  • Slide 22
  • Slide 23
  • Slide 24
  • Slide 25
  • Slide 26
  • Slide 27
  • Slide 28
  • Slide 29
  • Slide 30
  • Slide 31
  • Slide 32
  • Slide 33
  • Slide 34
  • Slide 35
  • Slide 36
  • Slide 37
  • Slide 38
  • Slide 39
  • Slide 40
  • Slide 41
  • Slide 42
  • Slide 43
  • Slide 44
  • Slide 45
  • Slide 46
  • Slide 47
  • Slide 48
  • Slide 49
  • Slide 50
  • Slide 51
  • Slide 52
  • Slide 53
  • Slide 54
  • Slide 55
  • Slide 56
  • Slide 57
  • Slide 58
  • Slide 59
  • Slide 60
  • Slide 61
  • Slide 62
  • Slide 63
  • Slide 64
  • Slide 65
  • Slide 66
  • Slide 67
  • Slide 68
  • Slide 69
  • Slide 70
  • Slide 71
  • Slide 72
  • Slide 73
  • Slide 74
  • Slide 75
  • Slide 76
  • Slide 77
  • Slide 78
  • Slide 79
  • Slide 80
  • Slide 81
  • Slide 82
  • Slide 83
  • Slide 84
  • Slide 85
  • Slide 86
  • Slide 87
  • Slide 88
  • Slide 89
  • Slide 90
  • Slide 91
  • Slide 92
  • Slide 93
  • Slide 94
  • Slide 95
  • Slide 96
  • Slide 97
  • Slide 98
  • Slide 99
  • Slide 100
  • Slide 101
  • Slide 102
  • Slide 103
Page 17: 第五章 樹

53  二元樹bull 二元樹是一個由節點組成的有限集合可以是空集合或是包含了(1) 一個樹根節點(2) 其它節點分割成兩個沒有交集的二元樹

其一為左子樹 (left subtree) 另一為右子樹 (right subtree) 樹和二元樹之間的差異

(1)樹不允許空節點而空二元樹是允許的

(2) 樹的子樹沒有順序而二元樹的子樹有左右之分

531  樹和二元樹的基本性質

樹或二元樹皆擁有下面的性質

定理 5-1 若一棵樹 T 的總節點數為 V 總邊數為

E 則V = E + 1

證明利用數學歸納法歸納法的基礎當 V=1 時即 T 只有一個樹根節點 E 為 0 所以 V = E + 1 成立歸納法的假設假設當 1 V K 時該式成立歸納法的推論當 V = K + 1 時令此樹的樹根為 R 而 R 有 m 個子樹 T1 T2 hellip

Tm (如圖 5-12 所示)分別有個 V1 V2 hellip Vm 個節點其中 V1 +V2 + hellip+ Vm =K 若 E1 E2 hellip Em 分別為此 m 個子樹的邊數則

E = E1 +E2 + hellip+ Em + m (1)根據歸納法的假設可知

V1 = E1+1V2 = E2+1 Vm = Em+1

所以V1 +V2 + hellip+Vm = E1 + E2 + hellip+ Em +m (2)

然而V = V1 +V2 + hellip+Vm+1 (3)

將式 (2) 代入式 (3) 可得V = E1 + E2 + hellip+ Em + m + 1

引用式 (1) 則可得V = E + 1

由數學歸納法的原理可得證本定理

532 二元樹的性質(a) 稱為歪斜樹 (skew tree) (b) 稱為完備二元樹 (complete binary tree)

( 其樹葉節點皆在相鄰階層完整定義在後 ) 同樣數目的樹節點存放在歪斜樹上會花較多的階層放在完備二元樹上則只須最少的階層

532 二元樹的性質 ( 續 )定理 5-2 (1) 二元樹上階層 i 的節點數目最多為 2i-1 i 1 (2) 深度為 k 的二元樹其節點數目最多為 2k-

1 k1 bull 定理 5-2說明了 (1) 每一階層上可放的最多節點數

(2) 階層數已知時二元樹可放的最多節點數證明 (1) 利用數學歸納法歸納法的基礎當 i=1 時只有樹根一個節點 2i-1=21-1=1 原式成

立歸納法的假設假設 i = k 時該式成立即階層 k 的最大節點數為

2k-1 k 1 歸納法的推論當 i = k+1 時因在階層 k 的每一個節點最多會有

2 個子節點遂在 k+1 階層最多會有 22k-1 = 2k

= 2i-1 節點由數學歸納法的原理可知本定理得證(2) 由 (1) 可知二元樹在階層 i 的最多節點數為 2i-1那麼深度 k 的二元樹最多可有節點如下

k

i

ki

1

1-1- 24212 1-2

)12(1 k= 2k-1

=

bull 對二元樹而言任一節點的分支度可能為 0 1 或 2 分支度為 0 者是為樹葉

bull 樹葉的數目與分支度為 2 的節點數目存在著有趣的關係請見定理 5-3

定理 5-3 若 T 為一非空的二元樹 n0 為樹葉節點數目 n2為分支度為 2 的節點數目則 n0 = n2+1

證明令 n 為所有節點數目而 n1 為分支度為 1 的節點數目可得 n = n0 + n1 +n2 (1)令 B 為二元樹的分支數除了樹根外每一節點皆有一分支指向之所以

n = B+1 (亦可參考定理 5-1 )又 B = n1+2n2

且 n = n1 + 2n2 +1 (2)由式 (1) 和式 (2) 我們得到

n0 = n2 + 1

完滿二元樹 (full binary tree)

定義一個深度為 k 的完滿二元樹即為一深度為 k 且有 2k-1 個節點的二元樹k 0

bull 由定理 5-2(2) 可知深度為 k 的二元樹其最多節點數為 2k-1 這樣的樹會將樹上所有可能存放節點的位置都放滿完滿 (full) 之名因而生成

完備二元樹 (complete binary tree)我們可對二元樹上節點予以編號階層小者先編同一階

層則自左向右編號這樣的編號方式使我們定義出完備(complete) 二元樹

定義深度為 k 節點數為 n 的二元樹稱為完備二元樹若且唯若 (1) k = 1 時樹有一個節點

(2) 當 k 2 且 1iltk 時深度 i 有 2i-1 個節點且第 k 層的節點皆由第 k-1 層的分支由左至右逐一連接而成

完滿或完備二元樹之所以受人注意乃因它們可以將節點以較少的階層數存放

定理 5-4 n 個節點的完備或完滿二元樹其深度為 log2(n+1)

證明 完滿或完備二元樹的節點皆自階層

1 起排放任一階層 i 可排放 2i-1

個節點遂由定理 5-2 得知此 n 個節點所佔的深度為 log2(n+1)

正規二元樹 (formal binary tree)bull 在二元樹中非樹葉的節點又稱為內部節點

(internal node) 若所有內部節點恰有 2 個子節點即成為正規二元樹

定義若二元樹中所有內部節點恰有 2 個子節點則稱之為正規二元樹

正規二元樹常被引用在單淘汰賽正規二元樹常被引用在單淘汰賽制的各項賽程安排樹葉為參賽制的各項賽程安排樹葉為參賽隊伍內部節點為優勝隊伍樹隊伍內部節點為優勝隊伍樹根即為冠軍隊伍根即為冠軍隊伍

在單淘汰賽制中自在單淘汰賽制中自 n n 個隊伍中產個隊伍中產生冠軍須舉辦 生冠軍須舉辦 nn-1-1場比賽場比賽

54  二元樹的表示法541 以陣列表示二元樹定理 5-5 若一個具有 n 個節點的完備二元樹以循序的方式編號並依序存放在陣列 A 中即第 i 個節點放在 A[i] 中 1 i n 則

(1) 節點 i 的父節點位在 A[i2]處 i ne 1( i = 1 時其節點正為樹根無父節點)

(2) 若 2i n 則節點 i 的左兒子節點位在 A[2i] 處若 2i gtn 節點 i 沒有左子節點

(3) 若 2i+1 n 節點 i 的右兒子節點位 A[2i+1]處若 2i+1gtn 節點 i 沒有左子

節點

證明先用數學歸納法證明 (2) bull 歸納法的基礎當 i = 1 時其左子節點確實在 A[2] 處(除非 nlt2 此時節點 1 沒有左子節點)

bull 歸納法的假設假設當 I = k 時其左子節點確在 A[2i] = A[2k] 處(除非 2i 已大於 n 此時節點 i 無左子節點)

bull 歸納法的推論當 i = k+1 時因第 k 節點的左子節點在 A[2k] 處其右子節點則位在 A[2k+1] 處那麼第 k+1 節點的左子節點應緊臨 A[2k+1] 亦即位於 A[2k+2] = A[2(k+1)] 處(除非 2i 已大於 n 此時節點 i 無左子節點)

bull 由數學歸納法的原理得證定理 5-5 (2) (3) 由於 (2) 成立而且節點 i 的右子節點會緊臨

A[2i] 亦即其位於 A[2i+1]處除非 i 根本沒有右子節點得證定理 5-5 (3)

由 (2) 和 (3) 我們可知定理 5-5 (1) 成立

542 以鍵結串列表示二元樹bull 以陣列來表示完備二元樹算是相當方便但ndash一般的二元樹將導致陣列空間的浪費ndash如果二元樹中節點的新增刪除動作頻繁則會造成陣列對應資料的搬動隨而頻繁

bull 以鍵結串列表示二元樹

為方便辦識樹根我們常用 root 指標指向樹根做為樹鍵結串列的起點

陣列表示和鍵結表示的空間需求bull 完備二元樹的陣列表示和鍵結表示皆沒浪費儲存資料的空間

bull 但歪斜樹的陣列表示比起鍵結表示則浪費許多

bull 以 n 個節點的歪斜樹而言陣列表示法可能需要 2n-1 個陣列元素其中 2n-1-n 個陣列元素空間是浪費的而鍵結表示法只需要 n 個節點空間

二元樹節點的宣告程式 5-2

1 struct BTreeNode

2 struct BtreeNode leftchiled

3 int data

4 struct BTreeNode rightchild

5

6 struct BTreeNode root

bull 這樣的二元樹節點要取得兒子節點的指標十分方便但若有取得父節點的需求則嫌不足我們可以加上 parent 欄位指向父節點

55  二元樹的走訪bull 假如一組資料已用二元樹的組織在一起總有需求對其全數資料做動作例如計算所有數目印出所有資料在所有資料中搜尋某項資料hellip等此時即須對此二元樹做走訪 (traversal) 的運算利用走訪二元樹的同時將計算列印或搜尋的動作完成

bull 事實上走訪即在決定二元樹上資料被處理(計算列印或搜尋)的順序我們也希望走訪的演算法對任何節點皆一致是容易撰寫程式實作的

bull 若對一個二元樹上的節點而言 V 表示處理節點上的資料 L 表示走訪其左子樹 R 表示走訪其右子樹

55  二元樹的走訪 ( 續 )bull 因為對稱的緣故我們只考慮先走訪左邊再走訪右邊的情形那麼右圖只剩下三種走訪方式

bull 以 V 所在的相對位置分別對此三種走訪方式取名如下

(1) LVR 中序走訪 (inorder traversal)或中序表示法 (infix notation)

(2) LRV 後序走訪 (postorder traversal) 或後序表示法 (postfix notation)

(3) VLR 前序走訪 (preorder traversal)或前序表示法 (prefix notation)

551  中序走訪bull 利用遞迴的方式撰寫中序走訪的程序希望把二元樹中序走訪的

順序印出來

程式 5-3二元樹的中序走訪1 struct BTreeNode2 struct BTreeNode leftchild3 char data4 struct BTreeNode rightchild5 6 struct BTreeNode root7 void inorder(struct BTreeNode node)8 if (node = NULL)9 inorder(node-gtleftchild)10 cout ltlt node-gtdata11 inorder(node-gtrightchild)12 13

範例 5-12此為運算式二元樹

其對應中序表示運算式為 (A+B)C+D(E-F)

6 struct BTreeNode root7 void inorder(struct BTreeNode node)8 if (node = NULL)9 inorder(node-gtleftchild)10 cout ltlt node-gtdata11 inorder(node-gtrightchild)12 13

6 struct BTreeNode root7 void inorder(struct BTreeNode node)8 if (node = NULL)9 inorder(node-gtleftchild)10 cout ltlt node-gtdata11 inorder(node-gtrightchild)12 13

(A+B)C+D(E-F)

如何加上 ( )

552  後序走訪程式 5-4二元樹的後序走訪14 void postorder(struct BTreeNode node)

15 if (node =NULL)16 postorder(node-gtleftchild)17 postorder(node-gtrightchild)18 cout ltlt node-gtdata19 20

以後序走訪可得到AB+CDEF-+

其正為對應的後序運算式

553  前序走訪程式 5-5二元樹的前序走訪21 void preorder(struct BTreeNode node)

22 if (node = NULL)

23 cout ltlt node-gtdata

24 preorder(node-gtleftchild)

25 preorder(node-gtrightchild)

26

27

以前序走訪可得到++ABCD-EF

其正為對應的前序運算式

554 利用堆疊和迴圈取代遞迴進行二元樹的走訪1 struct BTreeNode 2 struct BTreeNode leftchild3 char data4 struct BtreeNode rightchild5 6 struct BTreeNode root7 struct StackNode8 struct BTreeNode treenode9 struct StackNode next1011 struct StackNode top

12 void push_node(struct BtreeNode node )13 struct StackNode old_top14 old_top = top15 top = (struct StackNode ) malloc

(sizeof(struct StackNode))16 top-gttreenode = node17 top-gtnext = old_top18 19 struct BTreeNode pop_node()20 struct BTreeNode Tnode21 struct StackNode old_top22 if (top == NULL)23 StackEmpty()24 else25 Tnode = top-gttreenode26 old_top = top27 top = top-gtnext28 free(old_top)29 return Tnode30 31

32 void inorder_Stack(struct BtreeNode node)33 do34 while (node = NULL)35 push_node(node)36 node = node-gtleftchild37 push all left children38 if (top = NULL)39 node = pop_node()40 cout ltlt node-gtdata41 node = node-gtrightchild42 inorder printing and check right child43 while ((top=NULL)||(node=NULL))44 top==NULL 堆疊已空 node==NULL沒有右子

6 struct BTreeNode root7 void inorder(struct BTreeNode node)8 if (node = NULL)9 inorder(node-gtleftchild)10 cout ltlt node-gtdata11 inorder(node-gtrightchild)12 13

中序表示為DBGEHAFIC

32 void inorder_Stack()33 struct BTreeNode node34 node = root35 do36 while (node = NULL)37 push_node(node)38 node = node-gtleftchild39 40 if (top = NULL)41 node = pop_node()42 cout ltlt node-gtdata43 node = node-gtrightchild44 45 while(top=NULL)||(node=NULL))46

45 void Preorder_Stack(struct BSTreeNode node)46 do47 while (node = NULL)48 cout ltlt node-gtdata)49 push_node(node)50 node = node-gtleftchild51 push all left children52 if (top = NULL)53 node = pop_node()54 node = node-gtrightchild55 56 while ((top=NULL)||(node=NULL))57

程式 5-5二元樹的前序走訪21 void preorder(struct BTreeNode node)22 if (node = NULL)23 cout ltlt node-gtdata24 preorder(node-gtleftchild)25 preorder(node-gtrightchild)26 27

58 void Postorder_Stack(struct BTreeNode node)59 do60 while (node = NULL)61 cout ltlt node-gtdata62 push_node(node)63 node = node-gtrightchild64 push all left children65 if (top = NULL)66 node = pop_node()67 node = node-gtleftchild68 69 while ((top=NULL)||(node=NULL))70

程式 5-4二元樹的後序走訪14 void postorder(struct BTreeNode node)15 if (node =NULL)16 postorder(node-gtleftchild)17 postorder(node-gtrightchild)18 cout ltlt node-gtdata19 20

555  階層走訪bull 「階層走訪」 (level-order traversal) 是依階層

的順序進行二元樹的走訪先走訪階層小的節點後走訪階層大的節點同一階層者則依自左向右的順序走訪

bull 對下圖的二元樹進行階層走訪的結果為ABCDEFGHI

bull 對同一階層而言先走訪的節點其子節點亦在下一階層中先被走訪

bull 這種「先進先出」的特性恰可用佇列予以儲存與處理

程式 5-7 利用佇列進行二元樹的階層走訪

1 struct BTreeNode2 struct BTreeNode leftchild3 char data4 struct BTreeNode rightchild5 6 struct BTreeNode root7 struct QNode8 struct BTreeNode treenode9 struct QNode next10 11 struct QNode front rear

12 void AddQueue(struct BTreeNode Tnode)13 struct QNode node14 node=(struct QNode )malloc(sizeof(struct QNode))15 node-gttreenode = Tnode16 node-gtnext = NULL17 if (front == NULL) front = node19 else rear-gtnext = node20 rear = node21

22 struct BTreeNode DeleteQueue()23 struct BTreeNode Tnode24 struct QNode old_front25 if (rear == NULL)26 QueueEmpty()27 else28 Tnode = front-gttreenode29 old_front = front30 front = front-gtnext31 free(old_front)32 return Tnode33 34 35 void LevelOrder (struct BTreeNode node)36 AddQueue(node)37 while (front =NULL)38 node = DeleteQueue()39 cout ltlt node-gtdata40 if (node-gtleftchild = NULL)41 AddQueue(node-gtleftchild)42 if (node-gtrightchild = NULL)43 AddQueue(node-gtrightchild)44 45

35 void LevelOrder (struct BTreeNode node)36 AddQueue(node)37 while (front =NULL)38 node = DeleteQueue()39 cout ltlt node-gtdata40 if (node-gtleftchild = NULL)41 AddQueue(node-gtleftchild)42 if (node-gtrightchild = NULL)43 AddQueue(node-gtrightchild)44 45

bull Infix ABCbull Postfix ABC CBA ACB BCA

BAC CAB

B

A CA

B

C

B

A

C

C

B

AB

A

C

二元樹中序與前序式中序與後序式bull 每一棵二元樹皆有唯一的一對中序與前序表示法也有唯一的中序與後序次序

bull 換句話說給予一對中序與前序或中序與後序即可決定一棵二元樹

bull 然而給予前序和後序次序並不能決定唯一的二元樹可能會產生兩棵不同的二元樹

bull 想想階層走訪和中序走訪順序可否決定唯一的二元樹

A

B C

H J D E

F G

prefix ABHJCDFGEinfix HBJAFDGCEpostfix HJBFGDECA

VLRLVRLRV

A

prefix BHJinfix HBJ

prefix CDFGEinfix FDGCE

prefix ABHJCDFGEinfix HBJAFDGCE

A

B C

H J D E

F G

prefix ABHJCDFGE

infix HBJAFDGCEk-1

struct BTreeNode BTree_InfixPrefix (AnsiString infix AnsiString prefix)

int k struct BTreeNode node if (infixLength==0) return NULLnode = (struct BTreeNode ) malloc (sizeof(struct BTreeNode))node-gtdata = prefix[1]k = find(prefix[1] infix)node-gtleftchild = BTree_InfixPrefix

(infix[1k-1] prefix[2k])node-gtrightchild = BTree_InfixPrefix(infix[k+1infixLength]prefix[k+1prefixLength])

VLR

LVRk1

k2 k+1~

k+1~

1

struct BTreeNode BTree_InfixPostfix (AnsiString infix AnsiString prefix)

int k struct BTreeNode node if (infixLength==0) return NULLnode = (struct BTreeNode ) malloc (sizeof(struct BTreeNode))node-gtdata = postfix[postfixLength]k = find(postfix[postfixLength] infix)node-gtleftchild = BTree_InfixPrefix

(infix[1k-1] postfix[1k-1])node-gtrightchild = BTree_InfixPrefix(infix[k+1infixLength]postfix[k+1postfixLength-1])

56  二元樹的複製與相等測試程式 5-8 二元樹的複製1 struct BTreeNode2 struct BTreeNode leftchild3 char data4 struct BTreeNode rightchild5 6 struct BTreeNode root7 struct BTreeNode Copy(struct BTreeNode TreeRoot)8 struct BTreeNode CopyRoot9 if (TreeRoot == NULL)10 return NULL11 CopyRoot =(struct BTreeNode ) malloc (sizeof(BTreeNode))12 CopyRoot-gtdata = TreeRoot-gtdata13 CopyRoot-gtleftchild = Copy(TreeRoot-gtleftchild)14 CopyRoot-gtrightchild = Copy(TreeRoot-

gtrightchild)15 return CopyRoot16

程式 5-9 檢測兩棵二元樹是否相等1 struct BTreeNode2 struct BTreeNode leftchild3 char data4 struct BTreeNode rightchild5 6 struct BtreeNode rightchild7 int Equal(struct BTreeNode ustruct BTreeNode v)8 if ((u==NULL) ampamp (v==NULL)) return 19 if ((u=NULL) ampamp (v=NULL)10 ampamp (u-gtdata == v-gtdata )11 ampamp Equal(u-gtleftchildv-gtleftchild)12 ampamp Equal(u-gtrightchildv-gtrightchild))

13 return 114 return 015

Counting Nodes in a Binary Tree

int CountingNodes(struct BTreeNodes node)

if (node == NULL) return 0 int leftchildren = CountingNodes(node-gtleftchild)

int rightchildren= CountingNodes(node-gtrightchild)

return leftchildren+rightchildren

Counting Leaves in a Binary Tree

int CountingLeaves(struct BTreeNodes node)

if (node == NULL) return 0 int leftleaves = CountingLeaves(node-gtleftchild)

int rightchildren= CountingLeaves(node-gtrightchild)

if (leftleaves==0 ampamp leftleaves==0) return 1

return leftleaves + leftleaves

57  引線二元樹bull 在二元樹的鍵結表示中樹葉節點的兩個子樹指標皆指向 NULL 由定理 5-1 得知 n = E+1 其中 n 為節點個數 E 為分支數

bull 而 n 個鍵結節點有 2n 個指標空間每個分支恰佔用一個指標空間共有 E (=n-1) 個指標有用到(不是空的)而共有 n+1 個指標空間是 NULL 比非空的節點還多

bull 有學者提出對這些「放空指標的空間」加以利用的概念mdash與其放空指標不如放指向其它節點的指標稱之為引線 (thread) 使得某些運算(如走訪hellip等)可以加快

bull 這個概念發展出了引線二元樹 (threaded binary tree) 這種資料結構

範例 5-17下圖為加了引線的二元樹

加入的引線將使二元樹的中序走訪更加便利沿著上右圖箭頭所指示的順序走訪即可完成此二元樹的中序走訪GDHBAIECF 為此二元樹的中序表示

bull 範例 5-17 中所加的引線目的在方便二元樹的中序走訪

bull 對樹葉節點 p 而言令其資料為 P 則引線指標的規則如下

(1) p 的左子樹指標指向中序表示中 P 的前一個 元素(前接元素 (predecessor) )

(2) p 的右子樹指標指向中序表示中 P 的後一個 元素(後繼元素 (successor) )

為了區別節點指標放的是一般指標(內部節點)抑或是引線指標(樹葉節點)我們另以 2 個欄位分別區別左和右子樹指標空間存放的對象其節點的記憶體配置有如下圖所示

其中 leftthread (rightthread) 為 0 時表示 leftchild (rightchild) 存放的是一般節點指標而 leftthread (rightthread) 為 1 時表示 leftchild (rightchild) 存放的是引線指標1 struct TBTreeNode2 int leftthread3 struct TBTreeNode leftchild4 char data5 struct TBTreeNode rightchild6 int rightthread7

由右側的引線二元樹可知有最左和最右兩條引線尚無妥善的安排我們另設計一空的引線節點做為樹根如下圖所示

571  引線二元樹的中序走訪決定節點 p 在中序表示中的後繼節點 q (簡稱中序後繼點)

程式 5-10決定節點 node的中序後繼點1 struct TBTreeNode2 int leftthread3 struct TBTreeNode leftchild4 char data5 struct TBTreeNode rightchild6 int rightthread7 8 struct TBTreeNode root9 struct TBTreeNode Next(struct TBTreeNode node)

10 struct TBTreeNode temp11 temp = node-gtrightchild12 if (node-gtrightthead) return temp13 while (temp-gtleftthead) temp=temp-gtleftchild

14 return temp15

程式 5-11引線二元樹的中序表示(接程式 5-10)1 void InorderTBTree()2 struct TBTreeNode node3 for(node=Next(root)node=rootnode=Next(node))

4 cout ltlt node-gtdata5

bull只要 node 是引線二元樹上的任一節點透過呼叫 Next(node) 即可找到其在中序表示中的後繼節點bull(試想若程式 5-10 中的程序 Next 所傳入的參數指標正是 root Next(root) 是否可正常運作請思考)bull於是只須從中序表示中的第一節點起讓每一節點 node 都執行 Next(node) 程序即可得引線二元樹的中序表示

572  在引線二元樹中加入節點在引線二元樹中樹葉節點節點的右子樹指標所指向或內部節點右子樹的最左樹葉為其中序後繼節點而且任一節點若無左子樹則其左子樹指標乃指向其中序前接節點hellip這些性質必須在節點新增進入引線二元樹時也要保持接下來討論節點 new 插入成為引線二元樹的節點 p 的右子節點的情形至於插入成為左子節點的情形則讓各位模擬推敲

572572  在引線二元樹中加入節點 在引線二元樹中加入節點 (( 續續 ))

newnew 會成為會成為 pp 的右子節點的情況共有以下兩種的右子節點的情況共有以下兩種(1) (1) 若若 pp 沒有右子節點 沒有右子節點 ((p-gtrightthread p-gtrightthread 為為1) 1) 如圖如圖 ((a) a) 則 則 new new 插入後應形成如圖插入後應形成如圖 ((b) b) 所示所示

(2)(2) 若若 pp 有右子節點 有右子節點 ((p-gtrightthread p-gtrightthread 為為0) 0) 如圖如圖 ((c) c) 則 則 new new 插入後應形成如插入後應形成如圖圖 ((d) d) 所示所示

程式 5-12  插入節點 new 成為引線二元樹中節點 p的右子節點(上接程式 5-10 5-11 )

1 void InsertRight(struct TBTreeNode p 2 struct TBTreeNode new)3 struct TBTreeNode q4 new-gtrightchild = p-gtrightchild5 new-gtrightthead = p-gtrightthread6 new-gtleftchild = p7 new-gtleftthread = 18 p-gtrightchild = new9 p-gtrightthead = 010 if (new-gtrightthread)11 q = Next(new)12 q-gtleftchild = new13 14

58  堆積bull 「堆積」 (heap) 是一個特殊的完備二元樹節點的資料與其子節點的資料之間有事先定好的大小關係存在bull 定義ndash最大堆疊為一完備二元樹任一節點的資料內容不小於其子節點的資料內容ndash最小堆疊為一完備二元樹任一節點的資料內容不大於其子節點的資料內容

581  插入節點至最大堆積中最大堆積是一完備二元樹所以如圖 (a) 之最大堆積於加入新節點後樹的形狀必須如圖 (b) 所示灰色節點即為新節點若加入的節點資料分別為 1 9 12 則其最大堆積將分別變成如圖 (c) (d) (e) 所示

程式 5-13 新增節點至最大堆積中最大堆積本身是一棵完備二元樹可以用陣列表示之 1 define maxsize 1002 int heap[maxsize]3 int n i4 void InsertHeap(int x)5 if (n == maxsize) HeapFull()6 else7 n++8 i = n9 while ((i gt 1) ampamp (x gt heap[i2]))10 heap[i] = heap[i2]11 i = 212 13 heap[i] = x14 16

程序 InsertHeap 在插入第 n 個資料時需要的時間複雜度為 O(logn)

1 100

2 93

3 84

4 77

5 65

6 81

7 79

8 66

9 70

10 50

100

84

77

93

8165

66 70

79

50

1

2 3

4 5 6 7

8 9 10

1 define maxsize 1002 int heap[maxsize]3 int n i4 void InsertHeap(int x)5 if (n == maxsize) HeapFull()6 else7 n++8 i = n9 while ((i gt 1) ampamp (x gt heap[i2]))10 heap[i] = heap[i2]11 i = 212 13 heap[i] = x14 16

582 刪除最大堆積中的最大資料圖 (a) 中的最大堆積在刪除最大資料後其樹形應變成如圖 (b) 所示將最未一個樹葉節點的資料 6 暫挪至樹根節點內如圖 (c) 再將圖 (c) 中非最大堆積的結構調整成為最大堆積 10 和 8 中選大者與 6對調即成圖 (d)

程式 5-14 刪除最大堆積中的最大資料(上接程式 5-13 )1 int DeleteHeap()2 int x i j3 if (n == 0) HeapEmpty()4 else5 x = heap[1]6 heap[1] = heap[n]7 n--8 i = 19 while (i lt= n2)10 if (heap[2i] gt heap[2i+1])11 j = 2i12 else13 j = 2i+114 if (heap[i]gtheap[j]) break15 swap(heap[i] heap[j])16 i = j17 18 return x19 20

其時間複雜度與其堆積高度相同是為O(logn) 其中 n 為堆積中的元素個數

1 100

2 93

3 84

4 77

5 65

6 81

7 79

8 66

9 70

10 50

100

84

77

93

8165

66 70

79

50

1

2 3

4 5 6 7

8 9 10

1 int DeletHeap()2 int x i j3 if(n == 0) HeapEmpty()4 else5 x = heap[1]6 heap[1] = heap[n]7 n--8 i = 19 while (i lt= n2)10 if ([heap[2i]] gt heap[2i+1])11 j = 2i12 else13 j = 2i+114 if (heap[i]gtheap[j]) break15 swap(heap[i] heap[j])16 i = j17 18 return x19 20

583  優先佇列bull 由於最大堆積(或最小堆積)的結構可提供最大元素(或最小元素)而其維護堆積的成本為 O(logn) n 為總元素個數

bull 堆積常被當成「優先佇列」 (priority queue) 使用mdash此時佇列被組織成完備二元樹若新加入佇列的資料有大小關係優先順序的需求時此優先佇列即以 O(logn) 的代價將新資料放入優先佇列(可能是最大或最小堆積依實際大小關係優先順序的需求而定)中mdash提供依元素的優先順序做為處理順序的服務

bull 一般的佇列為先進先出先到的先行處理優先佇列則是依據其優先順序優先順序高者先行處理與來到的先後無關

範例 5-21bull 在「分時」 (time sharing) 的作業系統中我們不希望使用中央處理器 (Central Processing Unit CPU) 時間短的「處理程序」 (process) 等候過久於是有「最短時間工作優先」 (shortest-job-first) 的排程策略此時等候的處理程序即可組織成之一最小堆積而 CPU 每次即取出存於樹根的處理程序予以處理任何新來的處理程序則以 O(logn) 的時間加入此優先佇列(最小堆積)中

bull 注意之前提過的線性佇列新來的直接加入成為新的尾端元素花O(1) 的時間然線性佇列本身沒有大小的關係每次要取出最大(小)的資料得花O(n) 的代價(在所有元素中挑出最大(小)者) n 為佇列內的元素個數而優先佇列是在新元素加入時要花O(logn) 的時間至於取出最大(小)則只須 O(1) 的時間

59  二元搜尋樹bull 二元搜尋樹是一棵二元樹可能是空二元樹若不為空二元樹則滿足下列性質ndash所有節點內的資料內容是相異的 ndash左子節點的資內容 ( 如果有 ) 比父節點的資料內容小 ndash右子節點的資料內容 ( 如果有 ) 比父節點的資料內容大 ndash以左和右子節點為樹根的左子樹和右子樹也是二元搜尋樹

在二元搜尋樹中搜尋資料二元搜尋樹對資料大小的掌握十分明確若恰與節點 p 資料相等則搜尋成功若欲搜尋的資料 x 比樹節點 p 中的資料小 則只須搜尋節點 p 的左子樹若比節點 p 中的資料大 則只須搜尋 p 的右子樹這樣的判斷可以遞迴地應用在各子樹節點 中

程式 5-15 在二元搜尋樹中搜尋資料(遞迴呼叫)1 struct BSTreeNode2 struct BSTreeNode leftchild3 int data4 struct BSTreeNode rightchild5 6 struct BSTreeNode root7 struct BSTreeNode SearchBST(

struct BSTreeNode tree int x)8 if (tree == NULL) return NULL9 if (x == tree-gtdata) return tree10 if (x lt tree-gtdata)11 return SearchBST(tree-gtleftchild x)

12 return SearchBST(tree-gtrightchild x)13

程式 5-16 搜尋二元搜尋樹的資料(非遞迴呼叫)在二元搜尋樹十分龐大時遞迴呼叫的效率不會太好可利用迴圈來完成搜尋的動作以提高執行效率請看程式 5-16

14 struct BSTreeNode Search BST_iterative15 (struct BSTreeNode node int x)16 while (node = NULL)17 if (x == node-gtdata) return node18 if (x lt node-gtdata)19 node = node-gtleftchild20 else21 node = node-gtrightchild22 23 return NULL24

592  新增資料於二元搜尋樹中欲新增資料進入一二元搜尋樹中得先決定該資料在二元搜尋樹中應出現的位置這可透過上節提到的搜尋動作來解決

在二元搜尋樹定義的性質 (1) 中我們限定了元素必須相異所以搜尋新資料的結果必是 NULL

必須將搜尋的結果改為新增資料所應在位置的節點指標

程式 5-17  在二元搜尋樹中新增資料25 int InsertBST(int x)26 struct BSTreeNode p q27 p = root q = NULL28 while (p = NULL)29 q = p30 if (x == p-gtdata) return 0

若不考慮重複則可省略之31 if (x lt p-gtdata)32 p = p-gtleftchild33 else34 p = p-gtrightchild35

3636 p = (struct BSTreeNode ) p = (struct BSTreeNode ) malloc (sizeof(BSTreeNode))malloc (sizeof(BSTreeNode))

3737 p-gtdata = x p-gtdata = x3838 p-gtleftchild = p-gtrightchild = p-gtleftchild = p-gtrightchild = NULLNULL3939 if (root == NULL) root = p if (root == NULL) root = p4040 else if (x lt q-gtdata) else if (x lt q-gtdata)4141 q-gtleftchild = p q-gtleftchild = p4242 else else4343 q-gtrightchild = p q-gtrightchild = p4444 return 1 return 145 45

程式 程式 5-175-17  在二元搜尋樹中新增資料  在二元搜尋樹中新增資料 (( 續續 ))

範例 5-23 圖 (b) 為圖 (a) 新增整數 6 之後的結果圖 (c) (d) (e) (f) 則分別是逐步再加入 28 18

10 21 的結果

bull若目前的二元搜尋樹高度為 h 則新增資料得花O(h) 的時間找到其所應在的位置遂新增資料至二元搜尋樹中的時間複雜之度為 O(h)

593 自二元搜尋樹中刪除資料欲自二元搜尋樹中刪除資料可能會面臨兩種情況(1) 刪除節點 p 位於樹葉節點上如下圖所示 這是比較容易處理的情況

(2) 刪除的節點 p 不是樹葉刪除節點 p 後原 p 所指的節點可由 q ( q 中包含了 p 左子樹中的最大節點值)或 r ( r 中包含了 p 右子樹中的最小節點值)來取代以維持二元搜尋樹中資料的大小關係其結果分別如圖 (b) 和 (c) 所示而圖 (d) 則標明了指標會改變的節點位置即 p 會消失 father 的左或右子樹指標會異動 k(可能是 q 或 r )會移到原來 p 的位置其父節點 f 的左或右子樹指標會異動其中 father 為 p 父節點 f 為 k 的父節點

程式 5-18 自二元搜尋樹中刪除資料46 int DeleteBST (int x)47 Struct BSTreeNode p father k f48 p = root father = NULL49 while (p = NULL)50 if (x == p-gtdata) 找到 x 所在的節點 p 了51 if ((p-gtleftchild == NULL) ampamp 52 (p-gtrightchild == NULL))53 k = NULL p為樹葉節點54 else if (p-gtleftchild = NULL )x位於非子節

點55 f = p 找出 p左子樹的最右樹葉56 k = p-gtleftchild57 while (k-gtrightchild =NULL)58 f = k59 k = k-gtrightchild60 k將挪至原 p處61 if (p == f)62 f-gtleftchild = k-gtleftchild 63 else64 f-gtrightchild = k-gtleftchild 65

66 else p無左子樹67 f = p 找出 p右子樹的最左樹葉68 k = p-gtrightchild69 while (k-gtleftchild = NULL)70 f = k71 k = k -gtleftchild72 73 if (p == f)74 f-gtrightchild = k-gtrightchild 75 else76 f-gtleftchild = k-gtrightchild77 78 if (k = NULL) k挪至原 p處繼承 p的左右指標79 k-gtleftchild = p-gtleftchild80 k-gtrightchild = p-gtrightchild81 82 if (father == NULL) root = k83 else if (x lt father-gtdata) 決定 k是 father的左

或右兒子84 father-gtleftchild = k85 else86 father-gtrightchild = k87 free(p)88 return 1 成功地刪除 x於此傳回 1返回呼叫處89 51~89行為找到 x時應有的處理步驟

90 else 尚未找到 x繼續往下一階層找

91 father = p92 if (x lt p-gtdata)93 p = p-gtleftchild94 else95 p = p-gtrightchild96 97 98 return 0 未找到 x傳回 099

510  樹林 (forest)

bull 定義樹林(森林)是由 n 棵沒有交集的樹所構成的集合 n 0

下圖為一包含三棵樹的樹林

5101 轉換樹林為二元樹 之前我們以二元樹來表示樹因為二元樹有較多

好用的性質在此我們也將樹林轉換成二元樹看待

在 522 節中我們嘗試了用左子右兄弟的方式來表示樹這個方法事實上已將樹轉換成二元樹

即用左子右兄弟的結構重新組織樹林中的每一棵樹這些樹的樹根將視為兄弟而最左邊的樹則視為樹林二元樹的樹根如此一來樹林即可轉換成二元樹

範例 5-25右下圖為右上圖三棵樹所對應的三棵左子右兄弟二元樹

下圖則為上圖樹林所對應的一棵樹林二元樹

演算法 5-1  轉換樹林成二元樹輸入樹林 F = T1 T2 Tn

輸出 F 的二元樹表示 BT=(T1 T2 Tn)1 if (n == 0) 輸出空二元樹 2 else

3 root(T1 T2 Tn) = T1

4 leftsubtree(T1 T2 Tn) =

BT(T11 T12 T1m)5 BT(T11 T12 T1m)為 T1的子樹6 rightsubtree(T1 T2 Tn) =

BT(T2 T3 Tn)

樹林轉換成二元樹的過程樹林轉換成二元樹的過程

  • Slide 1
  • Slide 2
  • Slide 3
  • Slide 4
  • Slide 5
  • Slide 6
  • Slide 7
  • Slide 8
  • Slide 9
  • Slide 10
  • Slide 11
  • Slide 12
  • Slide 13
  • Slide 14
  • Slide 15
  • Slide 16
  • Slide 17
  • Slide 18
  • Slide 19
  • Slide 20
  • Slide 21
  • Slide 22
  • Slide 23
  • Slide 24
  • Slide 25
  • Slide 26
  • Slide 27
  • Slide 28
  • Slide 29
  • Slide 30
  • Slide 31
  • Slide 32
  • Slide 33
  • Slide 34
  • Slide 35
  • Slide 36
  • Slide 37
  • Slide 38
  • Slide 39
  • Slide 40
  • Slide 41
  • Slide 42
  • Slide 43
  • Slide 44
  • Slide 45
  • Slide 46
  • Slide 47
  • Slide 48
  • Slide 49
  • Slide 50
  • Slide 51
  • Slide 52
  • Slide 53
  • Slide 54
  • Slide 55
  • Slide 56
  • Slide 57
  • Slide 58
  • Slide 59
  • Slide 60
  • Slide 61
  • Slide 62
  • Slide 63
  • Slide 64
  • Slide 65
  • Slide 66
  • Slide 67
  • Slide 68
  • Slide 69
  • Slide 70
  • Slide 71
  • Slide 72
  • Slide 73
  • Slide 74
  • Slide 75
  • Slide 76
  • Slide 77
  • Slide 78
  • Slide 79
  • Slide 80
  • Slide 81
  • Slide 82
  • Slide 83
  • Slide 84
  • Slide 85
  • Slide 86
  • Slide 87
  • Slide 88
  • Slide 89
  • Slide 90
  • Slide 91
  • Slide 92
  • Slide 93
  • Slide 94
  • Slide 95
  • Slide 96
  • Slide 97
  • Slide 98
  • Slide 99
  • Slide 100
  • Slide 101
  • Slide 102
  • Slide 103
Page 18: 第五章 樹

531  樹和二元樹的基本性質

樹或二元樹皆擁有下面的性質

定理 5-1 若一棵樹 T 的總節點數為 V 總邊數為

E 則V = E + 1

證明利用數學歸納法歸納法的基礎當 V=1 時即 T 只有一個樹根節點 E 為 0 所以 V = E + 1 成立歸納法的假設假設當 1 V K 時該式成立歸納法的推論當 V = K + 1 時令此樹的樹根為 R 而 R 有 m 個子樹 T1 T2 hellip

Tm (如圖 5-12 所示)分別有個 V1 V2 hellip Vm 個節點其中 V1 +V2 + hellip+ Vm =K 若 E1 E2 hellip Em 分別為此 m 個子樹的邊數則

E = E1 +E2 + hellip+ Em + m (1)根據歸納法的假設可知

V1 = E1+1V2 = E2+1 Vm = Em+1

所以V1 +V2 + hellip+Vm = E1 + E2 + hellip+ Em +m (2)

然而V = V1 +V2 + hellip+Vm+1 (3)

將式 (2) 代入式 (3) 可得V = E1 + E2 + hellip+ Em + m + 1

引用式 (1) 則可得V = E + 1

由數學歸納法的原理可得證本定理

532 二元樹的性質(a) 稱為歪斜樹 (skew tree) (b) 稱為完備二元樹 (complete binary tree)

( 其樹葉節點皆在相鄰階層完整定義在後 ) 同樣數目的樹節點存放在歪斜樹上會花較多的階層放在完備二元樹上則只須最少的階層

532 二元樹的性質 ( 續 )定理 5-2 (1) 二元樹上階層 i 的節點數目最多為 2i-1 i 1 (2) 深度為 k 的二元樹其節點數目最多為 2k-

1 k1 bull 定理 5-2說明了 (1) 每一階層上可放的最多節點數

(2) 階層數已知時二元樹可放的最多節點數證明 (1) 利用數學歸納法歸納法的基礎當 i=1 時只有樹根一個節點 2i-1=21-1=1 原式成

立歸納法的假設假設 i = k 時該式成立即階層 k 的最大節點數為

2k-1 k 1 歸納法的推論當 i = k+1 時因在階層 k 的每一個節點最多會有

2 個子節點遂在 k+1 階層最多會有 22k-1 = 2k

= 2i-1 節點由數學歸納法的原理可知本定理得證(2) 由 (1) 可知二元樹在階層 i 的最多節點數為 2i-1那麼深度 k 的二元樹最多可有節點如下

k

i

ki

1

1-1- 24212 1-2

)12(1 k= 2k-1

=

bull 對二元樹而言任一節點的分支度可能為 0 1 或 2 分支度為 0 者是為樹葉

bull 樹葉的數目與分支度為 2 的節點數目存在著有趣的關係請見定理 5-3

定理 5-3 若 T 為一非空的二元樹 n0 為樹葉節點數目 n2為分支度為 2 的節點數目則 n0 = n2+1

證明令 n 為所有節點數目而 n1 為分支度為 1 的節點數目可得 n = n0 + n1 +n2 (1)令 B 為二元樹的分支數除了樹根外每一節點皆有一分支指向之所以

n = B+1 (亦可參考定理 5-1 )又 B = n1+2n2

且 n = n1 + 2n2 +1 (2)由式 (1) 和式 (2) 我們得到

n0 = n2 + 1

完滿二元樹 (full binary tree)

定義一個深度為 k 的完滿二元樹即為一深度為 k 且有 2k-1 個節點的二元樹k 0

bull 由定理 5-2(2) 可知深度為 k 的二元樹其最多節點數為 2k-1 這樣的樹會將樹上所有可能存放節點的位置都放滿完滿 (full) 之名因而生成

完備二元樹 (complete binary tree)我們可對二元樹上節點予以編號階層小者先編同一階

層則自左向右編號這樣的編號方式使我們定義出完備(complete) 二元樹

定義深度為 k 節點數為 n 的二元樹稱為完備二元樹若且唯若 (1) k = 1 時樹有一個節點

(2) 當 k 2 且 1iltk 時深度 i 有 2i-1 個節點且第 k 層的節點皆由第 k-1 層的分支由左至右逐一連接而成

完滿或完備二元樹之所以受人注意乃因它們可以將節點以較少的階層數存放

定理 5-4 n 個節點的完備或完滿二元樹其深度為 log2(n+1)

證明 完滿或完備二元樹的節點皆自階層

1 起排放任一階層 i 可排放 2i-1

個節點遂由定理 5-2 得知此 n 個節點所佔的深度為 log2(n+1)

正規二元樹 (formal binary tree)bull 在二元樹中非樹葉的節點又稱為內部節點

(internal node) 若所有內部節點恰有 2 個子節點即成為正規二元樹

定義若二元樹中所有內部節點恰有 2 個子節點則稱之為正規二元樹

正規二元樹常被引用在單淘汰賽正規二元樹常被引用在單淘汰賽制的各項賽程安排樹葉為參賽制的各項賽程安排樹葉為參賽隊伍內部節點為優勝隊伍樹隊伍內部節點為優勝隊伍樹根即為冠軍隊伍根即為冠軍隊伍

在單淘汰賽制中自在單淘汰賽制中自 n n 個隊伍中產個隊伍中產生冠軍須舉辦 生冠軍須舉辦 nn-1-1場比賽場比賽

54  二元樹的表示法541 以陣列表示二元樹定理 5-5 若一個具有 n 個節點的完備二元樹以循序的方式編號並依序存放在陣列 A 中即第 i 個節點放在 A[i] 中 1 i n 則

(1) 節點 i 的父節點位在 A[i2]處 i ne 1( i = 1 時其節點正為樹根無父節點)

(2) 若 2i n 則節點 i 的左兒子節點位在 A[2i] 處若 2i gtn 節點 i 沒有左子節點

(3) 若 2i+1 n 節點 i 的右兒子節點位 A[2i+1]處若 2i+1gtn 節點 i 沒有左子

節點

證明先用數學歸納法證明 (2) bull 歸納法的基礎當 i = 1 時其左子節點確實在 A[2] 處(除非 nlt2 此時節點 1 沒有左子節點)

bull 歸納法的假設假設當 I = k 時其左子節點確在 A[2i] = A[2k] 處(除非 2i 已大於 n 此時節點 i 無左子節點)

bull 歸納法的推論當 i = k+1 時因第 k 節點的左子節點在 A[2k] 處其右子節點則位在 A[2k+1] 處那麼第 k+1 節點的左子節點應緊臨 A[2k+1] 亦即位於 A[2k+2] = A[2(k+1)] 處(除非 2i 已大於 n 此時節點 i 無左子節點)

bull 由數學歸納法的原理得證定理 5-5 (2) (3) 由於 (2) 成立而且節點 i 的右子節點會緊臨

A[2i] 亦即其位於 A[2i+1]處除非 i 根本沒有右子節點得證定理 5-5 (3)

由 (2) 和 (3) 我們可知定理 5-5 (1) 成立

542 以鍵結串列表示二元樹bull 以陣列來表示完備二元樹算是相當方便但ndash一般的二元樹將導致陣列空間的浪費ndash如果二元樹中節點的新增刪除動作頻繁則會造成陣列對應資料的搬動隨而頻繁

bull 以鍵結串列表示二元樹

為方便辦識樹根我們常用 root 指標指向樹根做為樹鍵結串列的起點

陣列表示和鍵結表示的空間需求bull 完備二元樹的陣列表示和鍵結表示皆沒浪費儲存資料的空間

bull 但歪斜樹的陣列表示比起鍵結表示則浪費許多

bull 以 n 個節點的歪斜樹而言陣列表示法可能需要 2n-1 個陣列元素其中 2n-1-n 個陣列元素空間是浪費的而鍵結表示法只需要 n 個節點空間

二元樹節點的宣告程式 5-2

1 struct BTreeNode

2 struct BtreeNode leftchiled

3 int data

4 struct BTreeNode rightchild

5

6 struct BTreeNode root

bull 這樣的二元樹節點要取得兒子節點的指標十分方便但若有取得父節點的需求則嫌不足我們可以加上 parent 欄位指向父節點

55  二元樹的走訪bull 假如一組資料已用二元樹的組織在一起總有需求對其全數資料做動作例如計算所有數目印出所有資料在所有資料中搜尋某項資料hellip等此時即須對此二元樹做走訪 (traversal) 的運算利用走訪二元樹的同時將計算列印或搜尋的動作完成

bull 事實上走訪即在決定二元樹上資料被處理(計算列印或搜尋)的順序我們也希望走訪的演算法對任何節點皆一致是容易撰寫程式實作的

bull 若對一個二元樹上的節點而言 V 表示處理節點上的資料 L 表示走訪其左子樹 R 表示走訪其右子樹

55  二元樹的走訪 ( 續 )bull 因為對稱的緣故我們只考慮先走訪左邊再走訪右邊的情形那麼右圖只剩下三種走訪方式

bull 以 V 所在的相對位置分別對此三種走訪方式取名如下

(1) LVR 中序走訪 (inorder traversal)或中序表示法 (infix notation)

(2) LRV 後序走訪 (postorder traversal) 或後序表示法 (postfix notation)

(3) VLR 前序走訪 (preorder traversal)或前序表示法 (prefix notation)

551  中序走訪bull 利用遞迴的方式撰寫中序走訪的程序希望把二元樹中序走訪的

順序印出來

程式 5-3二元樹的中序走訪1 struct BTreeNode2 struct BTreeNode leftchild3 char data4 struct BTreeNode rightchild5 6 struct BTreeNode root7 void inorder(struct BTreeNode node)8 if (node = NULL)9 inorder(node-gtleftchild)10 cout ltlt node-gtdata11 inorder(node-gtrightchild)12 13

範例 5-12此為運算式二元樹

其對應中序表示運算式為 (A+B)C+D(E-F)

6 struct BTreeNode root7 void inorder(struct BTreeNode node)8 if (node = NULL)9 inorder(node-gtleftchild)10 cout ltlt node-gtdata11 inorder(node-gtrightchild)12 13

6 struct BTreeNode root7 void inorder(struct BTreeNode node)8 if (node = NULL)9 inorder(node-gtleftchild)10 cout ltlt node-gtdata11 inorder(node-gtrightchild)12 13

(A+B)C+D(E-F)

如何加上 ( )

552  後序走訪程式 5-4二元樹的後序走訪14 void postorder(struct BTreeNode node)

15 if (node =NULL)16 postorder(node-gtleftchild)17 postorder(node-gtrightchild)18 cout ltlt node-gtdata19 20

以後序走訪可得到AB+CDEF-+

其正為對應的後序運算式

553  前序走訪程式 5-5二元樹的前序走訪21 void preorder(struct BTreeNode node)

22 if (node = NULL)

23 cout ltlt node-gtdata

24 preorder(node-gtleftchild)

25 preorder(node-gtrightchild)

26

27

以前序走訪可得到++ABCD-EF

其正為對應的前序運算式

554 利用堆疊和迴圈取代遞迴進行二元樹的走訪1 struct BTreeNode 2 struct BTreeNode leftchild3 char data4 struct BtreeNode rightchild5 6 struct BTreeNode root7 struct StackNode8 struct BTreeNode treenode9 struct StackNode next1011 struct StackNode top

12 void push_node(struct BtreeNode node )13 struct StackNode old_top14 old_top = top15 top = (struct StackNode ) malloc

(sizeof(struct StackNode))16 top-gttreenode = node17 top-gtnext = old_top18 19 struct BTreeNode pop_node()20 struct BTreeNode Tnode21 struct StackNode old_top22 if (top == NULL)23 StackEmpty()24 else25 Tnode = top-gttreenode26 old_top = top27 top = top-gtnext28 free(old_top)29 return Tnode30 31

32 void inorder_Stack(struct BtreeNode node)33 do34 while (node = NULL)35 push_node(node)36 node = node-gtleftchild37 push all left children38 if (top = NULL)39 node = pop_node()40 cout ltlt node-gtdata41 node = node-gtrightchild42 inorder printing and check right child43 while ((top=NULL)||(node=NULL))44 top==NULL 堆疊已空 node==NULL沒有右子

6 struct BTreeNode root7 void inorder(struct BTreeNode node)8 if (node = NULL)9 inorder(node-gtleftchild)10 cout ltlt node-gtdata11 inorder(node-gtrightchild)12 13

中序表示為DBGEHAFIC

32 void inorder_Stack()33 struct BTreeNode node34 node = root35 do36 while (node = NULL)37 push_node(node)38 node = node-gtleftchild39 40 if (top = NULL)41 node = pop_node()42 cout ltlt node-gtdata43 node = node-gtrightchild44 45 while(top=NULL)||(node=NULL))46

45 void Preorder_Stack(struct BSTreeNode node)46 do47 while (node = NULL)48 cout ltlt node-gtdata)49 push_node(node)50 node = node-gtleftchild51 push all left children52 if (top = NULL)53 node = pop_node()54 node = node-gtrightchild55 56 while ((top=NULL)||(node=NULL))57

程式 5-5二元樹的前序走訪21 void preorder(struct BTreeNode node)22 if (node = NULL)23 cout ltlt node-gtdata24 preorder(node-gtleftchild)25 preorder(node-gtrightchild)26 27

58 void Postorder_Stack(struct BTreeNode node)59 do60 while (node = NULL)61 cout ltlt node-gtdata62 push_node(node)63 node = node-gtrightchild64 push all left children65 if (top = NULL)66 node = pop_node()67 node = node-gtleftchild68 69 while ((top=NULL)||(node=NULL))70

程式 5-4二元樹的後序走訪14 void postorder(struct BTreeNode node)15 if (node =NULL)16 postorder(node-gtleftchild)17 postorder(node-gtrightchild)18 cout ltlt node-gtdata19 20

555  階層走訪bull 「階層走訪」 (level-order traversal) 是依階層

的順序進行二元樹的走訪先走訪階層小的節點後走訪階層大的節點同一階層者則依自左向右的順序走訪

bull 對下圖的二元樹進行階層走訪的結果為ABCDEFGHI

bull 對同一階層而言先走訪的節點其子節點亦在下一階層中先被走訪

bull 這種「先進先出」的特性恰可用佇列予以儲存與處理

程式 5-7 利用佇列進行二元樹的階層走訪

1 struct BTreeNode2 struct BTreeNode leftchild3 char data4 struct BTreeNode rightchild5 6 struct BTreeNode root7 struct QNode8 struct BTreeNode treenode9 struct QNode next10 11 struct QNode front rear

12 void AddQueue(struct BTreeNode Tnode)13 struct QNode node14 node=(struct QNode )malloc(sizeof(struct QNode))15 node-gttreenode = Tnode16 node-gtnext = NULL17 if (front == NULL) front = node19 else rear-gtnext = node20 rear = node21

22 struct BTreeNode DeleteQueue()23 struct BTreeNode Tnode24 struct QNode old_front25 if (rear == NULL)26 QueueEmpty()27 else28 Tnode = front-gttreenode29 old_front = front30 front = front-gtnext31 free(old_front)32 return Tnode33 34 35 void LevelOrder (struct BTreeNode node)36 AddQueue(node)37 while (front =NULL)38 node = DeleteQueue()39 cout ltlt node-gtdata40 if (node-gtleftchild = NULL)41 AddQueue(node-gtleftchild)42 if (node-gtrightchild = NULL)43 AddQueue(node-gtrightchild)44 45

35 void LevelOrder (struct BTreeNode node)36 AddQueue(node)37 while (front =NULL)38 node = DeleteQueue()39 cout ltlt node-gtdata40 if (node-gtleftchild = NULL)41 AddQueue(node-gtleftchild)42 if (node-gtrightchild = NULL)43 AddQueue(node-gtrightchild)44 45

bull Infix ABCbull Postfix ABC CBA ACB BCA

BAC CAB

B

A CA

B

C

B

A

C

C

B

AB

A

C

二元樹中序與前序式中序與後序式bull 每一棵二元樹皆有唯一的一對中序與前序表示法也有唯一的中序與後序次序

bull 換句話說給予一對中序與前序或中序與後序即可決定一棵二元樹

bull 然而給予前序和後序次序並不能決定唯一的二元樹可能會產生兩棵不同的二元樹

bull 想想階層走訪和中序走訪順序可否決定唯一的二元樹

A

B C

H J D E

F G

prefix ABHJCDFGEinfix HBJAFDGCEpostfix HJBFGDECA

VLRLVRLRV

A

prefix BHJinfix HBJ

prefix CDFGEinfix FDGCE

prefix ABHJCDFGEinfix HBJAFDGCE

A

B C

H J D E

F G

prefix ABHJCDFGE

infix HBJAFDGCEk-1

struct BTreeNode BTree_InfixPrefix (AnsiString infix AnsiString prefix)

int k struct BTreeNode node if (infixLength==0) return NULLnode = (struct BTreeNode ) malloc (sizeof(struct BTreeNode))node-gtdata = prefix[1]k = find(prefix[1] infix)node-gtleftchild = BTree_InfixPrefix

(infix[1k-1] prefix[2k])node-gtrightchild = BTree_InfixPrefix(infix[k+1infixLength]prefix[k+1prefixLength])

VLR

LVRk1

k2 k+1~

k+1~

1

struct BTreeNode BTree_InfixPostfix (AnsiString infix AnsiString prefix)

int k struct BTreeNode node if (infixLength==0) return NULLnode = (struct BTreeNode ) malloc (sizeof(struct BTreeNode))node-gtdata = postfix[postfixLength]k = find(postfix[postfixLength] infix)node-gtleftchild = BTree_InfixPrefix

(infix[1k-1] postfix[1k-1])node-gtrightchild = BTree_InfixPrefix(infix[k+1infixLength]postfix[k+1postfixLength-1])

56  二元樹的複製與相等測試程式 5-8 二元樹的複製1 struct BTreeNode2 struct BTreeNode leftchild3 char data4 struct BTreeNode rightchild5 6 struct BTreeNode root7 struct BTreeNode Copy(struct BTreeNode TreeRoot)8 struct BTreeNode CopyRoot9 if (TreeRoot == NULL)10 return NULL11 CopyRoot =(struct BTreeNode ) malloc (sizeof(BTreeNode))12 CopyRoot-gtdata = TreeRoot-gtdata13 CopyRoot-gtleftchild = Copy(TreeRoot-gtleftchild)14 CopyRoot-gtrightchild = Copy(TreeRoot-

gtrightchild)15 return CopyRoot16

程式 5-9 檢測兩棵二元樹是否相等1 struct BTreeNode2 struct BTreeNode leftchild3 char data4 struct BTreeNode rightchild5 6 struct BtreeNode rightchild7 int Equal(struct BTreeNode ustruct BTreeNode v)8 if ((u==NULL) ampamp (v==NULL)) return 19 if ((u=NULL) ampamp (v=NULL)10 ampamp (u-gtdata == v-gtdata )11 ampamp Equal(u-gtleftchildv-gtleftchild)12 ampamp Equal(u-gtrightchildv-gtrightchild))

13 return 114 return 015

Counting Nodes in a Binary Tree

int CountingNodes(struct BTreeNodes node)

if (node == NULL) return 0 int leftchildren = CountingNodes(node-gtleftchild)

int rightchildren= CountingNodes(node-gtrightchild)

return leftchildren+rightchildren

Counting Leaves in a Binary Tree

int CountingLeaves(struct BTreeNodes node)

if (node == NULL) return 0 int leftleaves = CountingLeaves(node-gtleftchild)

int rightchildren= CountingLeaves(node-gtrightchild)

if (leftleaves==0 ampamp leftleaves==0) return 1

return leftleaves + leftleaves

57  引線二元樹bull 在二元樹的鍵結表示中樹葉節點的兩個子樹指標皆指向 NULL 由定理 5-1 得知 n = E+1 其中 n 為節點個數 E 為分支數

bull 而 n 個鍵結節點有 2n 個指標空間每個分支恰佔用一個指標空間共有 E (=n-1) 個指標有用到(不是空的)而共有 n+1 個指標空間是 NULL 比非空的節點還多

bull 有學者提出對這些「放空指標的空間」加以利用的概念mdash與其放空指標不如放指向其它節點的指標稱之為引線 (thread) 使得某些運算(如走訪hellip等)可以加快

bull 這個概念發展出了引線二元樹 (threaded binary tree) 這種資料結構

範例 5-17下圖為加了引線的二元樹

加入的引線將使二元樹的中序走訪更加便利沿著上右圖箭頭所指示的順序走訪即可完成此二元樹的中序走訪GDHBAIECF 為此二元樹的中序表示

bull 範例 5-17 中所加的引線目的在方便二元樹的中序走訪

bull 對樹葉節點 p 而言令其資料為 P 則引線指標的規則如下

(1) p 的左子樹指標指向中序表示中 P 的前一個 元素(前接元素 (predecessor) )

(2) p 的右子樹指標指向中序表示中 P 的後一個 元素(後繼元素 (successor) )

為了區別節點指標放的是一般指標(內部節點)抑或是引線指標(樹葉節點)我們另以 2 個欄位分別區別左和右子樹指標空間存放的對象其節點的記憶體配置有如下圖所示

其中 leftthread (rightthread) 為 0 時表示 leftchild (rightchild) 存放的是一般節點指標而 leftthread (rightthread) 為 1 時表示 leftchild (rightchild) 存放的是引線指標1 struct TBTreeNode2 int leftthread3 struct TBTreeNode leftchild4 char data5 struct TBTreeNode rightchild6 int rightthread7

由右側的引線二元樹可知有最左和最右兩條引線尚無妥善的安排我們另設計一空的引線節點做為樹根如下圖所示

571  引線二元樹的中序走訪決定節點 p 在中序表示中的後繼節點 q (簡稱中序後繼點)

程式 5-10決定節點 node的中序後繼點1 struct TBTreeNode2 int leftthread3 struct TBTreeNode leftchild4 char data5 struct TBTreeNode rightchild6 int rightthread7 8 struct TBTreeNode root9 struct TBTreeNode Next(struct TBTreeNode node)

10 struct TBTreeNode temp11 temp = node-gtrightchild12 if (node-gtrightthead) return temp13 while (temp-gtleftthead) temp=temp-gtleftchild

14 return temp15

程式 5-11引線二元樹的中序表示(接程式 5-10)1 void InorderTBTree()2 struct TBTreeNode node3 for(node=Next(root)node=rootnode=Next(node))

4 cout ltlt node-gtdata5

bull只要 node 是引線二元樹上的任一節點透過呼叫 Next(node) 即可找到其在中序表示中的後繼節點bull(試想若程式 5-10 中的程序 Next 所傳入的參數指標正是 root Next(root) 是否可正常運作請思考)bull於是只須從中序表示中的第一節點起讓每一節點 node 都執行 Next(node) 程序即可得引線二元樹的中序表示

572  在引線二元樹中加入節點在引線二元樹中樹葉節點節點的右子樹指標所指向或內部節點右子樹的最左樹葉為其中序後繼節點而且任一節點若無左子樹則其左子樹指標乃指向其中序前接節點hellip這些性質必須在節點新增進入引線二元樹時也要保持接下來討論節點 new 插入成為引線二元樹的節點 p 的右子節點的情形至於插入成為左子節點的情形則讓各位模擬推敲

572572  在引線二元樹中加入節點 在引線二元樹中加入節點 (( 續續 ))

newnew 會成為會成為 pp 的右子節點的情況共有以下兩種的右子節點的情況共有以下兩種(1) (1) 若若 pp 沒有右子節點 沒有右子節點 ((p-gtrightthread p-gtrightthread 為為1) 1) 如圖如圖 ((a) a) 則 則 new new 插入後應形成如圖插入後應形成如圖 ((b) b) 所示所示

(2)(2) 若若 pp 有右子節點 有右子節點 ((p-gtrightthread p-gtrightthread 為為0) 0) 如圖如圖 ((c) c) 則 則 new new 插入後應形成如插入後應形成如圖圖 ((d) d) 所示所示

程式 5-12  插入節點 new 成為引線二元樹中節點 p的右子節點(上接程式 5-10 5-11 )

1 void InsertRight(struct TBTreeNode p 2 struct TBTreeNode new)3 struct TBTreeNode q4 new-gtrightchild = p-gtrightchild5 new-gtrightthead = p-gtrightthread6 new-gtleftchild = p7 new-gtleftthread = 18 p-gtrightchild = new9 p-gtrightthead = 010 if (new-gtrightthread)11 q = Next(new)12 q-gtleftchild = new13 14

58  堆積bull 「堆積」 (heap) 是一個特殊的完備二元樹節點的資料與其子節點的資料之間有事先定好的大小關係存在bull 定義ndash最大堆疊為一完備二元樹任一節點的資料內容不小於其子節點的資料內容ndash最小堆疊為一完備二元樹任一節點的資料內容不大於其子節點的資料內容

581  插入節點至最大堆積中最大堆積是一完備二元樹所以如圖 (a) 之最大堆積於加入新節點後樹的形狀必須如圖 (b) 所示灰色節點即為新節點若加入的節點資料分別為 1 9 12 則其最大堆積將分別變成如圖 (c) (d) (e) 所示

程式 5-13 新增節點至最大堆積中最大堆積本身是一棵完備二元樹可以用陣列表示之 1 define maxsize 1002 int heap[maxsize]3 int n i4 void InsertHeap(int x)5 if (n == maxsize) HeapFull()6 else7 n++8 i = n9 while ((i gt 1) ampamp (x gt heap[i2]))10 heap[i] = heap[i2]11 i = 212 13 heap[i] = x14 16

程序 InsertHeap 在插入第 n 個資料時需要的時間複雜度為 O(logn)

1 100

2 93

3 84

4 77

5 65

6 81

7 79

8 66

9 70

10 50

100

84

77

93

8165

66 70

79

50

1

2 3

4 5 6 7

8 9 10

1 define maxsize 1002 int heap[maxsize]3 int n i4 void InsertHeap(int x)5 if (n == maxsize) HeapFull()6 else7 n++8 i = n9 while ((i gt 1) ampamp (x gt heap[i2]))10 heap[i] = heap[i2]11 i = 212 13 heap[i] = x14 16

582 刪除最大堆積中的最大資料圖 (a) 中的最大堆積在刪除最大資料後其樹形應變成如圖 (b) 所示將最未一個樹葉節點的資料 6 暫挪至樹根節點內如圖 (c) 再將圖 (c) 中非最大堆積的結構調整成為最大堆積 10 和 8 中選大者與 6對調即成圖 (d)

程式 5-14 刪除最大堆積中的最大資料(上接程式 5-13 )1 int DeleteHeap()2 int x i j3 if (n == 0) HeapEmpty()4 else5 x = heap[1]6 heap[1] = heap[n]7 n--8 i = 19 while (i lt= n2)10 if (heap[2i] gt heap[2i+1])11 j = 2i12 else13 j = 2i+114 if (heap[i]gtheap[j]) break15 swap(heap[i] heap[j])16 i = j17 18 return x19 20

其時間複雜度與其堆積高度相同是為O(logn) 其中 n 為堆積中的元素個數

1 100

2 93

3 84

4 77

5 65

6 81

7 79

8 66

9 70

10 50

100

84

77

93

8165

66 70

79

50

1

2 3

4 5 6 7

8 9 10

1 int DeletHeap()2 int x i j3 if(n == 0) HeapEmpty()4 else5 x = heap[1]6 heap[1] = heap[n]7 n--8 i = 19 while (i lt= n2)10 if ([heap[2i]] gt heap[2i+1])11 j = 2i12 else13 j = 2i+114 if (heap[i]gtheap[j]) break15 swap(heap[i] heap[j])16 i = j17 18 return x19 20

583  優先佇列bull 由於最大堆積(或最小堆積)的結構可提供最大元素(或最小元素)而其維護堆積的成本為 O(logn) n 為總元素個數

bull 堆積常被當成「優先佇列」 (priority queue) 使用mdash此時佇列被組織成完備二元樹若新加入佇列的資料有大小關係優先順序的需求時此優先佇列即以 O(logn) 的代價將新資料放入優先佇列(可能是最大或最小堆積依實際大小關係優先順序的需求而定)中mdash提供依元素的優先順序做為處理順序的服務

bull 一般的佇列為先進先出先到的先行處理優先佇列則是依據其優先順序優先順序高者先行處理與來到的先後無關

範例 5-21bull 在「分時」 (time sharing) 的作業系統中我們不希望使用中央處理器 (Central Processing Unit CPU) 時間短的「處理程序」 (process) 等候過久於是有「最短時間工作優先」 (shortest-job-first) 的排程策略此時等候的處理程序即可組織成之一最小堆積而 CPU 每次即取出存於樹根的處理程序予以處理任何新來的處理程序則以 O(logn) 的時間加入此優先佇列(最小堆積)中

bull 注意之前提過的線性佇列新來的直接加入成為新的尾端元素花O(1) 的時間然線性佇列本身沒有大小的關係每次要取出最大(小)的資料得花O(n) 的代價(在所有元素中挑出最大(小)者) n 為佇列內的元素個數而優先佇列是在新元素加入時要花O(logn) 的時間至於取出最大(小)則只須 O(1) 的時間

59  二元搜尋樹bull 二元搜尋樹是一棵二元樹可能是空二元樹若不為空二元樹則滿足下列性質ndash所有節點內的資料內容是相異的 ndash左子節點的資內容 ( 如果有 ) 比父節點的資料內容小 ndash右子節點的資料內容 ( 如果有 ) 比父節點的資料內容大 ndash以左和右子節點為樹根的左子樹和右子樹也是二元搜尋樹

在二元搜尋樹中搜尋資料二元搜尋樹對資料大小的掌握十分明確若恰與節點 p 資料相等則搜尋成功若欲搜尋的資料 x 比樹節點 p 中的資料小 則只須搜尋節點 p 的左子樹若比節點 p 中的資料大 則只須搜尋 p 的右子樹這樣的判斷可以遞迴地應用在各子樹節點 中

程式 5-15 在二元搜尋樹中搜尋資料(遞迴呼叫)1 struct BSTreeNode2 struct BSTreeNode leftchild3 int data4 struct BSTreeNode rightchild5 6 struct BSTreeNode root7 struct BSTreeNode SearchBST(

struct BSTreeNode tree int x)8 if (tree == NULL) return NULL9 if (x == tree-gtdata) return tree10 if (x lt tree-gtdata)11 return SearchBST(tree-gtleftchild x)

12 return SearchBST(tree-gtrightchild x)13

程式 5-16 搜尋二元搜尋樹的資料(非遞迴呼叫)在二元搜尋樹十分龐大時遞迴呼叫的效率不會太好可利用迴圈來完成搜尋的動作以提高執行效率請看程式 5-16

14 struct BSTreeNode Search BST_iterative15 (struct BSTreeNode node int x)16 while (node = NULL)17 if (x == node-gtdata) return node18 if (x lt node-gtdata)19 node = node-gtleftchild20 else21 node = node-gtrightchild22 23 return NULL24

592  新增資料於二元搜尋樹中欲新增資料進入一二元搜尋樹中得先決定該資料在二元搜尋樹中應出現的位置這可透過上節提到的搜尋動作來解決

在二元搜尋樹定義的性質 (1) 中我們限定了元素必須相異所以搜尋新資料的結果必是 NULL

必須將搜尋的結果改為新增資料所應在位置的節點指標

程式 5-17  在二元搜尋樹中新增資料25 int InsertBST(int x)26 struct BSTreeNode p q27 p = root q = NULL28 while (p = NULL)29 q = p30 if (x == p-gtdata) return 0

若不考慮重複則可省略之31 if (x lt p-gtdata)32 p = p-gtleftchild33 else34 p = p-gtrightchild35

3636 p = (struct BSTreeNode ) p = (struct BSTreeNode ) malloc (sizeof(BSTreeNode))malloc (sizeof(BSTreeNode))

3737 p-gtdata = x p-gtdata = x3838 p-gtleftchild = p-gtrightchild = p-gtleftchild = p-gtrightchild = NULLNULL3939 if (root == NULL) root = p if (root == NULL) root = p4040 else if (x lt q-gtdata) else if (x lt q-gtdata)4141 q-gtleftchild = p q-gtleftchild = p4242 else else4343 q-gtrightchild = p q-gtrightchild = p4444 return 1 return 145 45

程式 程式 5-175-17  在二元搜尋樹中新增資料  在二元搜尋樹中新增資料 (( 續續 ))

範例 5-23 圖 (b) 為圖 (a) 新增整數 6 之後的結果圖 (c) (d) (e) (f) 則分別是逐步再加入 28 18

10 21 的結果

bull若目前的二元搜尋樹高度為 h 則新增資料得花O(h) 的時間找到其所應在的位置遂新增資料至二元搜尋樹中的時間複雜之度為 O(h)

593 自二元搜尋樹中刪除資料欲自二元搜尋樹中刪除資料可能會面臨兩種情況(1) 刪除節點 p 位於樹葉節點上如下圖所示 這是比較容易處理的情況

(2) 刪除的節點 p 不是樹葉刪除節點 p 後原 p 所指的節點可由 q ( q 中包含了 p 左子樹中的最大節點值)或 r ( r 中包含了 p 右子樹中的最小節點值)來取代以維持二元搜尋樹中資料的大小關係其結果分別如圖 (b) 和 (c) 所示而圖 (d) 則標明了指標會改變的節點位置即 p 會消失 father 的左或右子樹指標會異動 k(可能是 q 或 r )會移到原來 p 的位置其父節點 f 的左或右子樹指標會異動其中 father 為 p 父節點 f 為 k 的父節點

程式 5-18 自二元搜尋樹中刪除資料46 int DeleteBST (int x)47 Struct BSTreeNode p father k f48 p = root father = NULL49 while (p = NULL)50 if (x == p-gtdata) 找到 x 所在的節點 p 了51 if ((p-gtleftchild == NULL) ampamp 52 (p-gtrightchild == NULL))53 k = NULL p為樹葉節點54 else if (p-gtleftchild = NULL )x位於非子節

點55 f = p 找出 p左子樹的最右樹葉56 k = p-gtleftchild57 while (k-gtrightchild =NULL)58 f = k59 k = k-gtrightchild60 k將挪至原 p處61 if (p == f)62 f-gtleftchild = k-gtleftchild 63 else64 f-gtrightchild = k-gtleftchild 65

66 else p無左子樹67 f = p 找出 p右子樹的最左樹葉68 k = p-gtrightchild69 while (k-gtleftchild = NULL)70 f = k71 k = k -gtleftchild72 73 if (p == f)74 f-gtrightchild = k-gtrightchild 75 else76 f-gtleftchild = k-gtrightchild77 78 if (k = NULL) k挪至原 p處繼承 p的左右指標79 k-gtleftchild = p-gtleftchild80 k-gtrightchild = p-gtrightchild81 82 if (father == NULL) root = k83 else if (x lt father-gtdata) 決定 k是 father的左

或右兒子84 father-gtleftchild = k85 else86 father-gtrightchild = k87 free(p)88 return 1 成功地刪除 x於此傳回 1返回呼叫處89 51~89行為找到 x時應有的處理步驟

90 else 尚未找到 x繼續往下一階層找

91 father = p92 if (x lt p-gtdata)93 p = p-gtleftchild94 else95 p = p-gtrightchild96 97 98 return 0 未找到 x傳回 099

510  樹林 (forest)

bull 定義樹林(森林)是由 n 棵沒有交集的樹所構成的集合 n 0

下圖為一包含三棵樹的樹林

5101 轉換樹林為二元樹 之前我們以二元樹來表示樹因為二元樹有較多

好用的性質在此我們也將樹林轉換成二元樹看待

在 522 節中我們嘗試了用左子右兄弟的方式來表示樹這個方法事實上已將樹轉換成二元樹

即用左子右兄弟的結構重新組織樹林中的每一棵樹這些樹的樹根將視為兄弟而最左邊的樹則視為樹林二元樹的樹根如此一來樹林即可轉換成二元樹

範例 5-25右下圖為右上圖三棵樹所對應的三棵左子右兄弟二元樹

下圖則為上圖樹林所對應的一棵樹林二元樹

演算法 5-1  轉換樹林成二元樹輸入樹林 F = T1 T2 Tn

輸出 F 的二元樹表示 BT=(T1 T2 Tn)1 if (n == 0) 輸出空二元樹 2 else

3 root(T1 T2 Tn) = T1

4 leftsubtree(T1 T2 Tn) =

BT(T11 T12 T1m)5 BT(T11 T12 T1m)為 T1的子樹6 rightsubtree(T1 T2 Tn) =

BT(T2 T3 Tn)

樹林轉換成二元樹的過程樹林轉換成二元樹的過程

  • Slide 1
  • Slide 2
  • Slide 3
  • Slide 4
  • Slide 5
  • Slide 6
  • Slide 7
  • Slide 8
  • Slide 9
  • Slide 10
  • Slide 11
  • Slide 12
  • Slide 13
  • Slide 14
  • Slide 15
  • Slide 16
  • Slide 17
  • Slide 18
  • Slide 19
  • Slide 20
  • Slide 21
  • Slide 22
  • Slide 23
  • Slide 24
  • Slide 25
  • Slide 26
  • Slide 27
  • Slide 28
  • Slide 29
  • Slide 30
  • Slide 31
  • Slide 32
  • Slide 33
  • Slide 34
  • Slide 35
  • Slide 36
  • Slide 37
  • Slide 38
  • Slide 39
  • Slide 40
  • Slide 41
  • Slide 42
  • Slide 43
  • Slide 44
  • Slide 45
  • Slide 46
  • Slide 47
  • Slide 48
  • Slide 49
  • Slide 50
  • Slide 51
  • Slide 52
  • Slide 53
  • Slide 54
  • Slide 55
  • Slide 56
  • Slide 57
  • Slide 58
  • Slide 59
  • Slide 60
  • Slide 61
  • Slide 62
  • Slide 63
  • Slide 64
  • Slide 65
  • Slide 66
  • Slide 67
  • Slide 68
  • Slide 69
  • Slide 70
  • Slide 71
  • Slide 72
  • Slide 73
  • Slide 74
  • Slide 75
  • Slide 76
  • Slide 77
  • Slide 78
  • Slide 79
  • Slide 80
  • Slide 81
  • Slide 82
  • Slide 83
  • Slide 84
  • Slide 85
  • Slide 86
  • Slide 87
  • Slide 88
  • Slide 89
  • Slide 90
  • Slide 91
  • Slide 92
  • Slide 93
  • Slide 94
  • Slide 95
  • Slide 96
  • Slide 97
  • Slide 98
  • Slide 99
  • Slide 100
  • Slide 101
  • Slide 102
  • Slide 103
Page 19: 第五章 樹

證明利用數學歸納法歸納法的基礎當 V=1 時即 T 只有一個樹根節點 E 為 0 所以 V = E + 1 成立歸納法的假設假設當 1 V K 時該式成立歸納法的推論當 V = K + 1 時令此樹的樹根為 R 而 R 有 m 個子樹 T1 T2 hellip

Tm (如圖 5-12 所示)分別有個 V1 V2 hellip Vm 個節點其中 V1 +V2 + hellip+ Vm =K 若 E1 E2 hellip Em 分別為此 m 個子樹的邊數則

E = E1 +E2 + hellip+ Em + m (1)根據歸納法的假設可知

V1 = E1+1V2 = E2+1 Vm = Em+1

所以V1 +V2 + hellip+Vm = E1 + E2 + hellip+ Em +m (2)

然而V = V1 +V2 + hellip+Vm+1 (3)

將式 (2) 代入式 (3) 可得V = E1 + E2 + hellip+ Em + m + 1

引用式 (1) 則可得V = E + 1

由數學歸納法的原理可得證本定理

532 二元樹的性質(a) 稱為歪斜樹 (skew tree) (b) 稱為完備二元樹 (complete binary tree)

( 其樹葉節點皆在相鄰階層完整定義在後 ) 同樣數目的樹節點存放在歪斜樹上會花較多的階層放在完備二元樹上則只須最少的階層

532 二元樹的性質 ( 續 )定理 5-2 (1) 二元樹上階層 i 的節點數目最多為 2i-1 i 1 (2) 深度為 k 的二元樹其節點數目最多為 2k-

1 k1 bull 定理 5-2說明了 (1) 每一階層上可放的最多節點數

(2) 階層數已知時二元樹可放的最多節點數證明 (1) 利用數學歸納法歸納法的基礎當 i=1 時只有樹根一個節點 2i-1=21-1=1 原式成

立歸納法的假設假設 i = k 時該式成立即階層 k 的最大節點數為

2k-1 k 1 歸納法的推論當 i = k+1 時因在階層 k 的每一個節點最多會有

2 個子節點遂在 k+1 階層最多會有 22k-1 = 2k

= 2i-1 節點由數學歸納法的原理可知本定理得證(2) 由 (1) 可知二元樹在階層 i 的最多節點數為 2i-1那麼深度 k 的二元樹最多可有節點如下

k

i

ki

1

1-1- 24212 1-2

)12(1 k= 2k-1

=

bull 對二元樹而言任一節點的分支度可能為 0 1 或 2 分支度為 0 者是為樹葉

bull 樹葉的數目與分支度為 2 的節點數目存在著有趣的關係請見定理 5-3

定理 5-3 若 T 為一非空的二元樹 n0 為樹葉節點數目 n2為分支度為 2 的節點數目則 n0 = n2+1

證明令 n 為所有節點數目而 n1 為分支度為 1 的節點數目可得 n = n0 + n1 +n2 (1)令 B 為二元樹的分支數除了樹根外每一節點皆有一分支指向之所以

n = B+1 (亦可參考定理 5-1 )又 B = n1+2n2

且 n = n1 + 2n2 +1 (2)由式 (1) 和式 (2) 我們得到

n0 = n2 + 1

完滿二元樹 (full binary tree)

定義一個深度為 k 的完滿二元樹即為一深度為 k 且有 2k-1 個節點的二元樹k 0

bull 由定理 5-2(2) 可知深度為 k 的二元樹其最多節點數為 2k-1 這樣的樹會將樹上所有可能存放節點的位置都放滿完滿 (full) 之名因而生成

完備二元樹 (complete binary tree)我們可對二元樹上節點予以編號階層小者先編同一階

層則自左向右編號這樣的編號方式使我們定義出完備(complete) 二元樹

定義深度為 k 節點數為 n 的二元樹稱為完備二元樹若且唯若 (1) k = 1 時樹有一個節點

(2) 當 k 2 且 1iltk 時深度 i 有 2i-1 個節點且第 k 層的節點皆由第 k-1 層的分支由左至右逐一連接而成

完滿或完備二元樹之所以受人注意乃因它們可以將節點以較少的階層數存放

定理 5-4 n 個節點的完備或完滿二元樹其深度為 log2(n+1)

證明 完滿或完備二元樹的節點皆自階層

1 起排放任一階層 i 可排放 2i-1

個節點遂由定理 5-2 得知此 n 個節點所佔的深度為 log2(n+1)

正規二元樹 (formal binary tree)bull 在二元樹中非樹葉的節點又稱為內部節點

(internal node) 若所有內部節點恰有 2 個子節點即成為正規二元樹

定義若二元樹中所有內部節點恰有 2 個子節點則稱之為正規二元樹

正規二元樹常被引用在單淘汰賽正規二元樹常被引用在單淘汰賽制的各項賽程安排樹葉為參賽制的各項賽程安排樹葉為參賽隊伍內部節點為優勝隊伍樹隊伍內部節點為優勝隊伍樹根即為冠軍隊伍根即為冠軍隊伍

在單淘汰賽制中自在單淘汰賽制中自 n n 個隊伍中產個隊伍中產生冠軍須舉辦 生冠軍須舉辦 nn-1-1場比賽場比賽

54  二元樹的表示法541 以陣列表示二元樹定理 5-5 若一個具有 n 個節點的完備二元樹以循序的方式編號並依序存放在陣列 A 中即第 i 個節點放在 A[i] 中 1 i n 則

(1) 節點 i 的父節點位在 A[i2]處 i ne 1( i = 1 時其節點正為樹根無父節點)

(2) 若 2i n 則節點 i 的左兒子節點位在 A[2i] 處若 2i gtn 節點 i 沒有左子節點

(3) 若 2i+1 n 節點 i 的右兒子節點位 A[2i+1]處若 2i+1gtn 節點 i 沒有左子

節點

證明先用數學歸納法證明 (2) bull 歸納法的基礎當 i = 1 時其左子節點確實在 A[2] 處(除非 nlt2 此時節點 1 沒有左子節點)

bull 歸納法的假設假設當 I = k 時其左子節點確在 A[2i] = A[2k] 處(除非 2i 已大於 n 此時節點 i 無左子節點)

bull 歸納法的推論當 i = k+1 時因第 k 節點的左子節點在 A[2k] 處其右子節點則位在 A[2k+1] 處那麼第 k+1 節點的左子節點應緊臨 A[2k+1] 亦即位於 A[2k+2] = A[2(k+1)] 處(除非 2i 已大於 n 此時節點 i 無左子節點)

bull 由數學歸納法的原理得證定理 5-5 (2) (3) 由於 (2) 成立而且節點 i 的右子節點會緊臨

A[2i] 亦即其位於 A[2i+1]處除非 i 根本沒有右子節點得證定理 5-5 (3)

由 (2) 和 (3) 我們可知定理 5-5 (1) 成立

542 以鍵結串列表示二元樹bull 以陣列來表示完備二元樹算是相當方便但ndash一般的二元樹將導致陣列空間的浪費ndash如果二元樹中節點的新增刪除動作頻繁則會造成陣列對應資料的搬動隨而頻繁

bull 以鍵結串列表示二元樹

為方便辦識樹根我們常用 root 指標指向樹根做為樹鍵結串列的起點

陣列表示和鍵結表示的空間需求bull 完備二元樹的陣列表示和鍵結表示皆沒浪費儲存資料的空間

bull 但歪斜樹的陣列表示比起鍵結表示則浪費許多

bull 以 n 個節點的歪斜樹而言陣列表示法可能需要 2n-1 個陣列元素其中 2n-1-n 個陣列元素空間是浪費的而鍵結表示法只需要 n 個節點空間

二元樹節點的宣告程式 5-2

1 struct BTreeNode

2 struct BtreeNode leftchiled

3 int data

4 struct BTreeNode rightchild

5

6 struct BTreeNode root

bull 這樣的二元樹節點要取得兒子節點的指標十分方便但若有取得父節點的需求則嫌不足我們可以加上 parent 欄位指向父節點

55  二元樹的走訪bull 假如一組資料已用二元樹的組織在一起總有需求對其全數資料做動作例如計算所有數目印出所有資料在所有資料中搜尋某項資料hellip等此時即須對此二元樹做走訪 (traversal) 的運算利用走訪二元樹的同時將計算列印或搜尋的動作完成

bull 事實上走訪即在決定二元樹上資料被處理(計算列印或搜尋)的順序我們也希望走訪的演算法對任何節點皆一致是容易撰寫程式實作的

bull 若對一個二元樹上的節點而言 V 表示處理節點上的資料 L 表示走訪其左子樹 R 表示走訪其右子樹

55  二元樹的走訪 ( 續 )bull 因為對稱的緣故我們只考慮先走訪左邊再走訪右邊的情形那麼右圖只剩下三種走訪方式

bull 以 V 所在的相對位置分別對此三種走訪方式取名如下

(1) LVR 中序走訪 (inorder traversal)或中序表示法 (infix notation)

(2) LRV 後序走訪 (postorder traversal) 或後序表示法 (postfix notation)

(3) VLR 前序走訪 (preorder traversal)或前序表示法 (prefix notation)

551  中序走訪bull 利用遞迴的方式撰寫中序走訪的程序希望把二元樹中序走訪的

順序印出來

程式 5-3二元樹的中序走訪1 struct BTreeNode2 struct BTreeNode leftchild3 char data4 struct BTreeNode rightchild5 6 struct BTreeNode root7 void inorder(struct BTreeNode node)8 if (node = NULL)9 inorder(node-gtleftchild)10 cout ltlt node-gtdata11 inorder(node-gtrightchild)12 13

範例 5-12此為運算式二元樹

其對應中序表示運算式為 (A+B)C+D(E-F)

6 struct BTreeNode root7 void inorder(struct BTreeNode node)8 if (node = NULL)9 inorder(node-gtleftchild)10 cout ltlt node-gtdata11 inorder(node-gtrightchild)12 13

6 struct BTreeNode root7 void inorder(struct BTreeNode node)8 if (node = NULL)9 inorder(node-gtleftchild)10 cout ltlt node-gtdata11 inorder(node-gtrightchild)12 13

(A+B)C+D(E-F)

如何加上 ( )

552  後序走訪程式 5-4二元樹的後序走訪14 void postorder(struct BTreeNode node)

15 if (node =NULL)16 postorder(node-gtleftchild)17 postorder(node-gtrightchild)18 cout ltlt node-gtdata19 20

以後序走訪可得到AB+CDEF-+

其正為對應的後序運算式

553  前序走訪程式 5-5二元樹的前序走訪21 void preorder(struct BTreeNode node)

22 if (node = NULL)

23 cout ltlt node-gtdata

24 preorder(node-gtleftchild)

25 preorder(node-gtrightchild)

26

27

以前序走訪可得到++ABCD-EF

其正為對應的前序運算式

554 利用堆疊和迴圈取代遞迴進行二元樹的走訪1 struct BTreeNode 2 struct BTreeNode leftchild3 char data4 struct BtreeNode rightchild5 6 struct BTreeNode root7 struct StackNode8 struct BTreeNode treenode9 struct StackNode next1011 struct StackNode top

12 void push_node(struct BtreeNode node )13 struct StackNode old_top14 old_top = top15 top = (struct StackNode ) malloc

(sizeof(struct StackNode))16 top-gttreenode = node17 top-gtnext = old_top18 19 struct BTreeNode pop_node()20 struct BTreeNode Tnode21 struct StackNode old_top22 if (top == NULL)23 StackEmpty()24 else25 Tnode = top-gttreenode26 old_top = top27 top = top-gtnext28 free(old_top)29 return Tnode30 31

32 void inorder_Stack(struct BtreeNode node)33 do34 while (node = NULL)35 push_node(node)36 node = node-gtleftchild37 push all left children38 if (top = NULL)39 node = pop_node()40 cout ltlt node-gtdata41 node = node-gtrightchild42 inorder printing and check right child43 while ((top=NULL)||(node=NULL))44 top==NULL 堆疊已空 node==NULL沒有右子

6 struct BTreeNode root7 void inorder(struct BTreeNode node)8 if (node = NULL)9 inorder(node-gtleftchild)10 cout ltlt node-gtdata11 inorder(node-gtrightchild)12 13

中序表示為DBGEHAFIC

32 void inorder_Stack()33 struct BTreeNode node34 node = root35 do36 while (node = NULL)37 push_node(node)38 node = node-gtleftchild39 40 if (top = NULL)41 node = pop_node()42 cout ltlt node-gtdata43 node = node-gtrightchild44 45 while(top=NULL)||(node=NULL))46

45 void Preorder_Stack(struct BSTreeNode node)46 do47 while (node = NULL)48 cout ltlt node-gtdata)49 push_node(node)50 node = node-gtleftchild51 push all left children52 if (top = NULL)53 node = pop_node()54 node = node-gtrightchild55 56 while ((top=NULL)||(node=NULL))57

程式 5-5二元樹的前序走訪21 void preorder(struct BTreeNode node)22 if (node = NULL)23 cout ltlt node-gtdata24 preorder(node-gtleftchild)25 preorder(node-gtrightchild)26 27

58 void Postorder_Stack(struct BTreeNode node)59 do60 while (node = NULL)61 cout ltlt node-gtdata62 push_node(node)63 node = node-gtrightchild64 push all left children65 if (top = NULL)66 node = pop_node()67 node = node-gtleftchild68 69 while ((top=NULL)||(node=NULL))70

程式 5-4二元樹的後序走訪14 void postorder(struct BTreeNode node)15 if (node =NULL)16 postorder(node-gtleftchild)17 postorder(node-gtrightchild)18 cout ltlt node-gtdata19 20

555  階層走訪bull 「階層走訪」 (level-order traversal) 是依階層

的順序進行二元樹的走訪先走訪階層小的節點後走訪階層大的節點同一階層者則依自左向右的順序走訪

bull 對下圖的二元樹進行階層走訪的結果為ABCDEFGHI

bull 對同一階層而言先走訪的節點其子節點亦在下一階層中先被走訪

bull 這種「先進先出」的特性恰可用佇列予以儲存與處理

程式 5-7 利用佇列進行二元樹的階層走訪

1 struct BTreeNode2 struct BTreeNode leftchild3 char data4 struct BTreeNode rightchild5 6 struct BTreeNode root7 struct QNode8 struct BTreeNode treenode9 struct QNode next10 11 struct QNode front rear

12 void AddQueue(struct BTreeNode Tnode)13 struct QNode node14 node=(struct QNode )malloc(sizeof(struct QNode))15 node-gttreenode = Tnode16 node-gtnext = NULL17 if (front == NULL) front = node19 else rear-gtnext = node20 rear = node21

22 struct BTreeNode DeleteQueue()23 struct BTreeNode Tnode24 struct QNode old_front25 if (rear == NULL)26 QueueEmpty()27 else28 Tnode = front-gttreenode29 old_front = front30 front = front-gtnext31 free(old_front)32 return Tnode33 34 35 void LevelOrder (struct BTreeNode node)36 AddQueue(node)37 while (front =NULL)38 node = DeleteQueue()39 cout ltlt node-gtdata40 if (node-gtleftchild = NULL)41 AddQueue(node-gtleftchild)42 if (node-gtrightchild = NULL)43 AddQueue(node-gtrightchild)44 45

35 void LevelOrder (struct BTreeNode node)36 AddQueue(node)37 while (front =NULL)38 node = DeleteQueue()39 cout ltlt node-gtdata40 if (node-gtleftchild = NULL)41 AddQueue(node-gtleftchild)42 if (node-gtrightchild = NULL)43 AddQueue(node-gtrightchild)44 45

bull Infix ABCbull Postfix ABC CBA ACB BCA

BAC CAB

B

A CA

B

C

B

A

C

C

B

AB

A

C

二元樹中序與前序式中序與後序式bull 每一棵二元樹皆有唯一的一對中序與前序表示法也有唯一的中序與後序次序

bull 換句話說給予一對中序與前序或中序與後序即可決定一棵二元樹

bull 然而給予前序和後序次序並不能決定唯一的二元樹可能會產生兩棵不同的二元樹

bull 想想階層走訪和中序走訪順序可否決定唯一的二元樹

A

B C

H J D E

F G

prefix ABHJCDFGEinfix HBJAFDGCEpostfix HJBFGDECA

VLRLVRLRV

A

prefix BHJinfix HBJ

prefix CDFGEinfix FDGCE

prefix ABHJCDFGEinfix HBJAFDGCE

A

B C

H J D E

F G

prefix ABHJCDFGE

infix HBJAFDGCEk-1

struct BTreeNode BTree_InfixPrefix (AnsiString infix AnsiString prefix)

int k struct BTreeNode node if (infixLength==0) return NULLnode = (struct BTreeNode ) malloc (sizeof(struct BTreeNode))node-gtdata = prefix[1]k = find(prefix[1] infix)node-gtleftchild = BTree_InfixPrefix

(infix[1k-1] prefix[2k])node-gtrightchild = BTree_InfixPrefix(infix[k+1infixLength]prefix[k+1prefixLength])

VLR

LVRk1

k2 k+1~

k+1~

1

struct BTreeNode BTree_InfixPostfix (AnsiString infix AnsiString prefix)

int k struct BTreeNode node if (infixLength==0) return NULLnode = (struct BTreeNode ) malloc (sizeof(struct BTreeNode))node-gtdata = postfix[postfixLength]k = find(postfix[postfixLength] infix)node-gtleftchild = BTree_InfixPrefix

(infix[1k-1] postfix[1k-1])node-gtrightchild = BTree_InfixPrefix(infix[k+1infixLength]postfix[k+1postfixLength-1])

56  二元樹的複製與相等測試程式 5-8 二元樹的複製1 struct BTreeNode2 struct BTreeNode leftchild3 char data4 struct BTreeNode rightchild5 6 struct BTreeNode root7 struct BTreeNode Copy(struct BTreeNode TreeRoot)8 struct BTreeNode CopyRoot9 if (TreeRoot == NULL)10 return NULL11 CopyRoot =(struct BTreeNode ) malloc (sizeof(BTreeNode))12 CopyRoot-gtdata = TreeRoot-gtdata13 CopyRoot-gtleftchild = Copy(TreeRoot-gtleftchild)14 CopyRoot-gtrightchild = Copy(TreeRoot-

gtrightchild)15 return CopyRoot16

程式 5-9 檢測兩棵二元樹是否相等1 struct BTreeNode2 struct BTreeNode leftchild3 char data4 struct BTreeNode rightchild5 6 struct BtreeNode rightchild7 int Equal(struct BTreeNode ustruct BTreeNode v)8 if ((u==NULL) ampamp (v==NULL)) return 19 if ((u=NULL) ampamp (v=NULL)10 ampamp (u-gtdata == v-gtdata )11 ampamp Equal(u-gtleftchildv-gtleftchild)12 ampamp Equal(u-gtrightchildv-gtrightchild))

13 return 114 return 015

Counting Nodes in a Binary Tree

int CountingNodes(struct BTreeNodes node)

if (node == NULL) return 0 int leftchildren = CountingNodes(node-gtleftchild)

int rightchildren= CountingNodes(node-gtrightchild)

return leftchildren+rightchildren

Counting Leaves in a Binary Tree

int CountingLeaves(struct BTreeNodes node)

if (node == NULL) return 0 int leftleaves = CountingLeaves(node-gtleftchild)

int rightchildren= CountingLeaves(node-gtrightchild)

if (leftleaves==0 ampamp leftleaves==0) return 1

return leftleaves + leftleaves

57  引線二元樹bull 在二元樹的鍵結表示中樹葉節點的兩個子樹指標皆指向 NULL 由定理 5-1 得知 n = E+1 其中 n 為節點個數 E 為分支數

bull 而 n 個鍵結節點有 2n 個指標空間每個分支恰佔用一個指標空間共有 E (=n-1) 個指標有用到(不是空的)而共有 n+1 個指標空間是 NULL 比非空的節點還多

bull 有學者提出對這些「放空指標的空間」加以利用的概念mdash與其放空指標不如放指向其它節點的指標稱之為引線 (thread) 使得某些運算(如走訪hellip等)可以加快

bull 這個概念發展出了引線二元樹 (threaded binary tree) 這種資料結構

範例 5-17下圖為加了引線的二元樹

加入的引線將使二元樹的中序走訪更加便利沿著上右圖箭頭所指示的順序走訪即可完成此二元樹的中序走訪GDHBAIECF 為此二元樹的中序表示

bull 範例 5-17 中所加的引線目的在方便二元樹的中序走訪

bull 對樹葉節點 p 而言令其資料為 P 則引線指標的規則如下

(1) p 的左子樹指標指向中序表示中 P 的前一個 元素(前接元素 (predecessor) )

(2) p 的右子樹指標指向中序表示中 P 的後一個 元素(後繼元素 (successor) )

為了區別節點指標放的是一般指標(內部節點)抑或是引線指標(樹葉節點)我們另以 2 個欄位分別區別左和右子樹指標空間存放的對象其節點的記憶體配置有如下圖所示

其中 leftthread (rightthread) 為 0 時表示 leftchild (rightchild) 存放的是一般節點指標而 leftthread (rightthread) 為 1 時表示 leftchild (rightchild) 存放的是引線指標1 struct TBTreeNode2 int leftthread3 struct TBTreeNode leftchild4 char data5 struct TBTreeNode rightchild6 int rightthread7

由右側的引線二元樹可知有最左和最右兩條引線尚無妥善的安排我們另設計一空的引線節點做為樹根如下圖所示

571  引線二元樹的中序走訪決定節點 p 在中序表示中的後繼節點 q (簡稱中序後繼點)

程式 5-10決定節點 node的中序後繼點1 struct TBTreeNode2 int leftthread3 struct TBTreeNode leftchild4 char data5 struct TBTreeNode rightchild6 int rightthread7 8 struct TBTreeNode root9 struct TBTreeNode Next(struct TBTreeNode node)

10 struct TBTreeNode temp11 temp = node-gtrightchild12 if (node-gtrightthead) return temp13 while (temp-gtleftthead) temp=temp-gtleftchild

14 return temp15

程式 5-11引線二元樹的中序表示(接程式 5-10)1 void InorderTBTree()2 struct TBTreeNode node3 for(node=Next(root)node=rootnode=Next(node))

4 cout ltlt node-gtdata5

bull只要 node 是引線二元樹上的任一節點透過呼叫 Next(node) 即可找到其在中序表示中的後繼節點bull(試想若程式 5-10 中的程序 Next 所傳入的參數指標正是 root Next(root) 是否可正常運作請思考)bull於是只須從中序表示中的第一節點起讓每一節點 node 都執行 Next(node) 程序即可得引線二元樹的中序表示

572  在引線二元樹中加入節點在引線二元樹中樹葉節點節點的右子樹指標所指向或內部節點右子樹的最左樹葉為其中序後繼節點而且任一節點若無左子樹則其左子樹指標乃指向其中序前接節點hellip這些性質必須在節點新增進入引線二元樹時也要保持接下來討論節點 new 插入成為引線二元樹的節點 p 的右子節點的情形至於插入成為左子節點的情形則讓各位模擬推敲

572572  在引線二元樹中加入節點 在引線二元樹中加入節點 (( 續續 ))

newnew 會成為會成為 pp 的右子節點的情況共有以下兩種的右子節點的情況共有以下兩種(1) (1) 若若 pp 沒有右子節點 沒有右子節點 ((p-gtrightthread p-gtrightthread 為為1) 1) 如圖如圖 ((a) a) 則 則 new new 插入後應形成如圖插入後應形成如圖 ((b) b) 所示所示

(2)(2) 若若 pp 有右子節點 有右子節點 ((p-gtrightthread p-gtrightthread 為為0) 0) 如圖如圖 ((c) c) 則 則 new new 插入後應形成如插入後應形成如圖圖 ((d) d) 所示所示

程式 5-12  插入節點 new 成為引線二元樹中節點 p的右子節點(上接程式 5-10 5-11 )

1 void InsertRight(struct TBTreeNode p 2 struct TBTreeNode new)3 struct TBTreeNode q4 new-gtrightchild = p-gtrightchild5 new-gtrightthead = p-gtrightthread6 new-gtleftchild = p7 new-gtleftthread = 18 p-gtrightchild = new9 p-gtrightthead = 010 if (new-gtrightthread)11 q = Next(new)12 q-gtleftchild = new13 14

58  堆積bull 「堆積」 (heap) 是一個特殊的完備二元樹節點的資料與其子節點的資料之間有事先定好的大小關係存在bull 定義ndash最大堆疊為一完備二元樹任一節點的資料內容不小於其子節點的資料內容ndash最小堆疊為一完備二元樹任一節點的資料內容不大於其子節點的資料內容

581  插入節點至最大堆積中最大堆積是一完備二元樹所以如圖 (a) 之最大堆積於加入新節點後樹的形狀必須如圖 (b) 所示灰色節點即為新節點若加入的節點資料分別為 1 9 12 則其最大堆積將分別變成如圖 (c) (d) (e) 所示

程式 5-13 新增節點至最大堆積中最大堆積本身是一棵完備二元樹可以用陣列表示之 1 define maxsize 1002 int heap[maxsize]3 int n i4 void InsertHeap(int x)5 if (n == maxsize) HeapFull()6 else7 n++8 i = n9 while ((i gt 1) ampamp (x gt heap[i2]))10 heap[i] = heap[i2]11 i = 212 13 heap[i] = x14 16

程序 InsertHeap 在插入第 n 個資料時需要的時間複雜度為 O(logn)

1 100

2 93

3 84

4 77

5 65

6 81

7 79

8 66

9 70

10 50

100

84

77

93

8165

66 70

79

50

1

2 3

4 5 6 7

8 9 10

1 define maxsize 1002 int heap[maxsize]3 int n i4 void InsertHeap(int x)5 if (n == maxsize) HeapFull()6 else7 n++8 i = n9 while ((i gt 1) ampamp (x gt heap[i2]))10 heap[i] = heap[i2]11 i = 212 13 heap[i] = x14 16

582 刪除最大堆積中的最大資料圖 (a) 中的最大堆積在刪除最大資料後其樹形應變成如圖 (b) 所示將最未一個樹葉節點的資料 6 暫挪至樹根節點內如圖 (c) 再將圖 (c) 中非最大堆積的結構調整成為最大堆積 10 和 8 中選大者與 6對調即成圖 (d)

程式 5-14 刪除最大堆積中的最大資料(上接程式 5-13 )1 int DeleteHeap()2 int x i j3 if (n == 0) HeapEmpty()4 else5 x = heap[1]6 heap[1] = heap[n]7 n--8 i = 19 while (i lt= n2)10 if (heap[2i] gt heap[2i+1])11 j = 2i12 else13 j = 2i+114 if (heap[i]gtheap[j]) break15 swap(heap[i] heap[j])16 i = j17 18 return x19 20

其時間複雜度與其堆積高度相同是為O(logn) 其中 n 為堆積中的元素個數

1 100

2 93

3 84

4 77

5 65

6 81

7 79

8 66

9 70

10 50

100

84

77

93

8165

66 70

79

50

1

2 3

4 5 6 7

8 9 10

1 int DeletHeap()2 int x i j3 if(n == 0) HeapEmpty()4 else5 x = heap[1]6 heap[1] = heap[n]7 n--8 i = 19 while (i lt= n2)10 if ([heap[2i]] gt heap[2i+1])11 j = 2i12 else13 j = 2i+114 if (heap[i]gtheap[j]) break15 swap(heap[i] heap[j])16 i = j17 18 return x19 20

583  優先佇列bull 由於最大堆積(或最小堆積)的結構可提供最大元素(或最小元素)而其維護堆積的成本為 O(logn) n 為總元素個數

bull 堆積常被當成「優先佇列」 (priority queue) 使用mdash此時佇列被組織成完備二元樹若新加入佇列的資料有大小關係優先順序的需求時此優先佇列即以 O(logn) 的代價將新資料放入優先佇列(可能是最大或最小堆積依實際大小關係優先順序的需求而定)中mdash提供依元素的優先順序做為處理順序的服務

bull 一般的佇列為先進先出先到的先行處理優先佇列則是依據其優先順序優先順序高者先行處理與來到的先後無關

範例 5-21bull 在「分時」 (time sharing) 的作業系統中我們不希望使用中央處理器 (Central Processing Unit CPU) 時間短的「處理程序」 (process) 等候過久於是有「最短時間工作優先」 (shortest-job-first) 的排程策略此時等候的處理程序即可組織成之一最小堆積而 CPU 每次即取出存於樹根的處理程序予以處理任何新來的處理程序則以 O(logn) 的時間加入此優先佇列(最小堆積)中

bull 注意之前提過的線性佇列新來的直接加入成為新的尾端元素花O(1) 的時間然線性佇列本身沒有大小的關係每次要取出最大(小)的資料得花O(n) 的代價(在所有元素中挑出最大(小)者) n 為佇列內的元素個數而優先佇列是在新元素加入時要花O(logn) 的時間至於取出最大(小)則只須 O(1) 的時間

59  二元搜尋樹bull 二元搜尋樹是一棵二元樹可能是空二元樹若不為空二元樹則滿足下列性質ndash所有節點內的資料內容是相異的 ndash左子節點的資內容 ( 如果有 ) 比父節點的資料內容小 ndash右子節點的資料內容 ( 如果有 ) 比父節點的資料內容大 ndash以左和右子節點為樹根的左子樹和右子樹也是二元搜尋樹

在二元搜尋樹中搜尋資料二元搜尋樹對資料大小的掌握十分明確若恰與節點 p 資料相等則搜尋成功若欲搜尋的資料 x 比樹節點 p 中的資料小 則只須搜尋節點 p 的左子樹若比節點 p 中的資料大 則只須搜尋 p 的右子樹這樣的判斷可以遞迴地應用在各子樹節點 中

程式 5-15 在二元搜尋樹中搜尋資料(遞迴呼叫)1 struct BSTreeNode2 struct BSTreeNode leftchild3 int data4 struct BSTreeNode rightchild5 6 struct BSTreeNode root7 struct BSTreeNode SearchBST(

struct BSTreeNode tree int x)8 if (tree == NULL) return NULL9 if (x == tree-gtdata) return tree10 if (x lt tree-gtdata)11 return SearchBST(tree-gtleftchild x)

12 return SearchBST(tree-gtrightchild x)13

程式 5-16 搜尋二元搜尋樹的資料(非遞迴呼叫)在二元搜尋樹十分龐大時遞迴呼叫的效率不會太好可利用迴圈來完成搜尋的動作以提高執行效率請看程式 5-16

14 struct BSTreeNode Search BST_iterative15 (struct BSTreeNode node int x)16 while (node = NULL)17 if (x == node-gtdata) return node18 if (x lt node-gtdata)19 node = node-gtleftchild20 else21 node = node-gtrightchild22 23 return NULL24

592  新增資料於二元搜尋樹中欲新增資料進入一二元搜尋樹中得先決定該資料在二元搜尋樹中應出現的位置這可透過上節提到的搜尋動作來解決

在二元搜尋樹定義的性質 (1) 中我們限定了元素必須相異所以搜尋新資料的結果必是 NULL

必須將搜尋的結果改為新增資料所應在位置的節點指標

程式 5-17  在二元搜尋樹中新增資料25 int InsertBST(int x)26 struct BSTreeNode p q27 p = root q = NULL28 while (p = NULL)29 q = p30 if (x == p-gtdata) return 0

若不考慮重複則可省略之31 if (x lt p-gtdata)32 p = p-gtleftchild33 else34 p = p-gtrightchild35

3636 p = (struct BSTreeNode ) p = (struct BSTreeNode ) malloc (sizeof(BSTreeNode))malloc (sizeof(BSTreeNode))

3737 p-gtdata = x p-gtdata = x3838 p-gtleftchild = p-gtrightchild = p-gtleftchild = p-gtrightchild = NULLNULL3939 if (root == NULL) root = p if (root == NULL) root = p4040 else if (x lt q-gtdata) else if (x lt q-gtdata)4141 q-gtleftchild = p q-gtleftchild = p4242 else else4343 q-gtrightchild = p q-gtrightchild = p4444 return 1 return 145 45

程式 程式 5-175-17  在二元搜尋樹中新增資料  在二元搜尋樹中新增資料 (( 續續 ))

範例 5-23 圖 (b) 為圖 (a) 新增整數 6 之後的結果圖 (c) (d) (e) (f) 則分別是逐步再加入 28 18

10 21 的結果

bull若目前的二元搜尋樹高度為 h 則新增資料得花O(h) 的時間找到其所應在的位置遂新增資料至二元搜尋樹中的時間複雜之度為 O(h)

593 自二元搜尋樹中刪除資料欲自二元搜尋樹中刪除資料可能會面臨兩種情況(1) 刪除節點 p 位於樹葉節點上如下圖所示 這是比較容易處理的情況

(2) 刪除的節點 p 不是樹葉刪除節點 p 後原 p 所指的節點可由 q ( q 中包含了 p 左子樹中的最大節點值)或 r ( r 中包含了 p 右子樹中的最小節點值)來取代以維持二元搜尋樹中資料的大小關係其結果分別如圖 (b) 和 (c) 所示而圖 (d) 則標明了指標會改變的節點位置即 p 會消失 father 的左或右子樹指標會異動 k(可能是 q 或 r )會移到原來 p 的位置其父節點 f 的左或右子樹指標會異動其中 father 為 p 父節點 f 為 k 的父節點

程式 5-18 自二元搜尋樹中刪除資料46 int DeleteBST (int x)47 Struct BSTreeNode p father k f48 p = root father = NULL49 while (p = NULL)50 if (x == p-gtdata) 找到 x 所在的節點 p 了51 if ((p-gtleftchild == NULL) ampamp 52 (p-gtrightchild == NULL))53 k = NULL p為樹葉節點54 else if (p-gtleftchild = NULL )x位於非子節

點55 f = p 找出 p左子樹的最右樹葉56 k = p-gtleftchild57 while (k-gtrightchild =NULL)58 f = k59 k = k-gtrightchild60 k將挪至原 p處61 if (p == f)62 f-gtleftchild = k-gtleftchild 63 else64 f-gtrightchild = k-gtleftchild 65

66 else p無左子樹67 f = p 找出 p右子樹的最左樹葉68 k = p-gtrightchild69 while (k-gtleftchild = NULL)70 f = k71 k = k -gtleftchild72 73 if (p == f)74 f-gtrightchild = k-gtrightchild 75 else76 f-gtleftchild = k-gtrightchild77 78 if (k = NULL) k挪至原 p處繼承 p的左右指標79 k-gtleftchild = p-gtleftchild80 k-gtrightchild = p-gtrightchild81 82 if (father == NULL) root = k83 else if (x lt father-gtdata) 決定 k是 father的左

或右兒子84 father-gtleftchild = k85 else86 father-gtrightchild = k87 free(p)88 return 1 成功地刪除 x於此傳回 1返回呼叫處89 51~89行為找到 x時應有的處理步驟

90 else 尚未找到 x繼續往下一階層找

91 father = p92 if (x lt p-gtdata)93 p = p-gtleftchild94 else95 p = p-gtrightchild96 97 98 return 0 未找到 x傳回 099

510  樹林 (forest)

bull 定義樹林(森林)是由 n 棵沒有交集的樹所構成的集合 n 0

下圖為一包含三棵樹的樹林

5101 轉換樹林為二元樹 之前我們以二元樹來表示樹因為二元樹有較多

好用的性質在此我們也將樹林轉換成二元樹看待

在 522 節中我們嘗試了用左子右兄弟的方式來表示樹這個方法事實上已將樹轉換成二元樹

即用左子右兄弟的結構重新組織樹林中的每一棵樹這些樹的樹根將視為兄弟而最左邊的樹則視為樹林二元樹的樹根如此一來樹林即可轉換成二元樹

範例 5-25右下圖為右上圖三棵樹所對應的三棵左子右兄弟二元樹

下圖則為上圖樹林所對應的一棵樹林二元樹

演算法 5-1  轉換樹林成二元樹輸入樹林 F = T1 T2 Tn

輸出 F 的二元樹表示 BT=(T1 T2 Tn)1 if (n == 0) 輸出空二元樹 2 else

3 root(T1 T2 Tn) = T1

4 leftsubtree(T1 T2 Tn) =

BT(T11 T12 T1m)5 BT(T11 T12 T1m)為 T1的子樹6 rightsubtree(T1 T2 Tn) =

BT(T2 T3 Tn)

樹林轉換成二元樹的過程樹林轉換成二元樹的過程

  • Slide 1
  • Slide 2
  • Slide 3
  • Slide 4
  • Slide 5
  • Slide 6
  • Slide 7
  • Slide 8
  • Slide 9
  • Slide 10
  • Slide 11
  • Slide 12
  • Slide 13
  • Slide 14
  • Slide 15
  • Slide 16
  • Slide 17
  • Slide 18
  • Slide 19
  • Slide 20
  • Slide 21
  • Slide 22
  • Slide 23
  • Slide 24
  • Slide 25
  • Slide 26
  • Slide 27
  • Slide 28
  • Slide 29
  • Slide 30
  • Slide 31
  • Slide 32
  • Slide 33
  • Slide 34
  • Slide 35
  • Slide 36
  • Slide 37
  • Slide 38
  • Slide 39
  • Slide 40
  • Slide 41
  • Slide 42
  • Slide 43
  • Slide 44
  • Slide 45
  • Slide 46
  • Slide 47
  • Slide 48
  • Slide 49
  • Slide 50
  • Slide 51
  • Slide 52
  • Slide 53
  • Slide 54
  • Slide 55
  • Slide 56
  • Slide 57
  • Slide 58
  • Slide 59
  • Slide 60
  • Slide 61
  • Slide 62
  • Slide 63
  • Slide 64
  • Slide 65
  • Slide 66
  • Slide 67
  • Slide 68
  • Slide 69
  • Slide 70
  • Slide 71
  • Slide 72
  • Slide 73
  • Slide 74
  • Slide 75
  • Slide 76
  • Slide 77
  • Slide 78
  • Slide 79
  • Slide 80
  • Slide 81
  • Slide 82
  • Slide 83
  • Slide 84
  • Slide 85
  • Slide 86
  • Slide 87
  • Slide 88
  • Slide 89
  • Slide 90
  • Slide 91
  • Slide 92
  • Slide 93
  • Slide 94
  • Slide 95
  • Slide 96
  • Slide 97
  • Slide 98
  • Slide 99
  • Slide 100
  • Slide 101
  • Slide 102
  • Slide 103
Page 20: 第五章 樹

532 二元樹的性質(a) 稱為歪斜樹 (skew tree) (b) 稱為完備二元樹 (complete binary tree)

( 其樹葉節點皆在相鄰階層完整定義在後 ) 同樣數目的樹節點存放在歪斜樹上會花較多的階層放在完備二元樹上則只須最少的階層

532 二元樹的性質 ( 續 )定理 5-2 (1) 二元樹上階層 i 的節點數目最多為 2i-1 i 1 (2) 深度為 k 的二元樹其節點數目最多為 2k-

1 k1 bull 定理 5-2說明了 (1) 每一階層上可放的最多節點數

(2) 階層數已知時二元樹可放的最多節點數證明 (1) 利用數學歸納法歸納法的基礎當 i=1 時只有樹根一個節點 2i-1=21-1=1 原式成

立歸納法的假設假設 i = k 時該式成立即階層 k 的最大節點數為

2k-1 k 1 歸納法的推論當 i = k+1 時因在階層 k 的每一個節點最多會有

2 個子節點遂在 k+1 階層最多會有 22k-1 = 2k

= 2i-1 節點由數學歸納法的原理可知本定理得證(2) 由 (1) 可知二元樹在階層 i 的最多節點數為 2i-1那麼深度 k 的二元樹最多可有節點如下

k

i

ki

1

1-1- 24212 1-2

)12(1 k= 2k-1

=

bull 對二元樹而言任一節點的分支度可能為 0 1 或 2 分支度為 0 者是為樹葉

bull 樹葉的數目與分支度為 2 的節點數目存在著有趣的關係請見定理 5-3

定理 5-3 若 T 為一非空的二元樹 n0 為樹葉節點數目 n2為分支度為 2 的節點數目則 n0 = n2+1

證明令 n 為所有節點數目而 n1 為分支度為 1 的節點數目可得 n = n0 + n1 +n2 (1)令 B 為二元樹的分支數除了樹根外每一節點皆有一分支指向之所以

n = B+1 (亦可參考定理 5-1 )又 B = n1+2n2

且 n = n1 + 2n2 +1 (2)由式 (1) 和式 (2) 我們得到

n0 = n2 + 1

完滿二元樹 (full binary tree)

定義一個深度為 k 的完滿二元樹即為一深度為 k 且有 2k-1 個節點的二元樹k 0

bull 由定理 5-2(2) 可知深度為 k 的二元樹其最多節點數為 2k-1 這樣的樹會將樹上所有可能存放節點的位置都放滿完滿 (full) 之名因而生成

完備二元樹 (complete binary tree)我們可對二元樹上節點予以編號階層小者先編同一階

層則自左向右編號這樣的編號方式使我們定義出完備(complete) 二元樹

定義深度為 k 節點數為 n 的二元樹稱為完備二元樹若且唯若 (1) k = 1 時樹有一個節點

(2) 當 k 2 且 1iltk 時深度 i 有 2i-1 個節點且第 k 層的節點皆由第 k-1 層的分支由左至右逐一連接而成

完滿或完備二元樹之所以受人注意乃因它們可以將節點以較少的階層數存放

定理 5-4 n 個節點的完備或完滿二元樹其深度為 log2(n+1)

證明 完滿或完備二元樹的節點皆自階層

1 起排放任一階層 i 可排放 2i-1

個節點遂由定理 5-2 得知此 n 個節點所佔的深度為 log2(n+1)

正規二元樹 (formal binary tree)bull 在二元樹中非樹葉的節點又稱為內部節點

(internal node) 若所有內部節點恰有 2 個子節點即成為正規二元樹

定義若二元樹中所有內部節點恰有 2 個子節點則稱之為正規二元樹

正規二元樹常被引用在單淘汰賽正規二元樹常被引用在單淘汰賽制的各項賽程安排樹葉為參賽制的各項賽程安排樹葉為參賽隊伍內部節點為優勝隊伍樹隊伍內部節點為優勝隊伍樹根即為冠軍隊伍根即為冠軍隊伍

在單淘汰賽制中自在單淘汰賽制中自 n n 個隊伍中產個隊伍中產生冠軍須舉辦 生冠軍須舉辦 nn-1-1場比賽場比賽

54  二元樹的表示法541 以陣列表示二元樹定理 5-5 若一個具有 n 個節點的完備二元樹以循序的方式編號並依序存放在陣列 A 中即第 i 個節點放在 A[i] 中 1 i n 則

(1) 節點 i 的父節點位在 A[i2]處 i ne 1( i = 1 時其節點正為樹根無父節點)

(2) 若 2i n 則節點 i 的左兒子節點位在 A[2i] 處若 2i gtn 節點 i 沒有左子節點

(3) 若 2i+1 n 節點 i 的右兒子節點位 A[2i+1]處若 2i+1gtn 節點 i 沒有左子

節點

證明先用數學歸納法證明 (2) bull 歸納法的基礎當 i = 1 時其左子節點確實在 A[2] 處(除非 nlt2 此時節點 1 沒有左子節點)

bull 歸納法的假設假設當 I = k 時其左子節點確在 A[2i] = A[2k] 處(除非 2i 已大於 n 此時節點 i 無左子節點)

bull 歸納法的推論當 i = k+1 時因第 k 節點的左子節點在 A[2k] 處其右子節點則位在 A[2k+1] 處那麼第 k+1 節點的左子節點應緊臨 A[2k+1] 亦即位於 A[2k+2] = A[2(k+1)] 處(除非 2i 已大於 n 此時節點 i 無左子節點)

bull 由數學歸納法的原理得證定理 5-5 (2) (3) 由於 (2) 成立而且節點 i 的右子節點會緊臨

A[2i] 亦即其位於 A[2i+1]處除非 i 根本沒有右子節點得證定理 5-5 (3)

由 (2) 和 (3) 我們可知定理 5-5 (1) 成立

542 以鍵結串列表示二元樹bull 以陣列來表示完備二元樹算是相當方便但ndash一般的二元樹將導致陣列空間的浪費ndash如果二元樹中節點的新增刪除動作頻繁則會造成陣列對應資料的搬動隨而頻繁

bull 以鍵結串列表示二元樹

為方便辦識樹根我們常用 root 指標指向樹根做為樹鍵結串列的起點

陣列表示和鍵結表示的空間需求bull 完備二元樹的陣列表示和鍵結表示皆沒浪費儲存資料的空間

bull 但歪斜樹的陣列表示比起鍵結表示則浪費許多

bull 以 n 個節點的歪斜樹而言陣列表示法可能需要 2n-1 個陣列元素其中 2n-1-n 個陣列元素空間是浪費的而鍵結表示法只需要 n 個節點空間

二元樹節點的宣告程式 5-2

1 struct BTreeNode

2 struct BtreeNode leftchiled

3 int data

4 struct BTreeNode rightchild

5

6 struct BTreeNode root

bull 這樣的二元樹節點要取得兒子節點的指標十分方便但若有取得父節點的需求則嫌不足我們可以加上 parent 欄位指向父節點

55  二元樹的走訪bull 假如一組資料已用二元樹的組織在一起總有需求對其全數資料做動作例如計算所有數目印出所有資料在所有資料中搜尋某項資料hellip等此時即須對此二元樹做走訪 (traversal) 的運算利用走訪二元樹的同時將計算列印或搜尋的動作完成

bull 事實上走訪即在決定二元樹上資料被處理(計算列印或搜尋)的順序我們也希望走訪的演算法對任何節點皆一致是容易撰寫程式實作的

bull 若對一個二元樹上的節點而言 V 表示處理節點上的資料 L 表示走訪其左子樹 R 表示走訪其右子樹

55  二元樹的走訪 ( 續 )bull 因為對稱的緣故我們只考慮先走訪左邊再走訪右邊的情形那麼右圖只剩下三種走訪方式

bull 以 V 所在的相對位置分別對此三種走訪方式取名如下

(1) LVR 中序走訪 (inorder traversal)或中序表示法 (infix notation)

(2) LRV 後序走訪 (postorder traversal) 或後序表示法 (postfix notation)

(3) VLR 前序走訪 (preorder traversal)或前序表示法 (prefix notation)

551  中序走訪bull 利用遞迴的方式撰寫中序走訪的程序希望把二元樹中序走訪的

順序印出來

程式 5-3二元樹的中序走訪1 struct BTreeNode2 struct BTreeNode leftchild3 char data4 struct BTreeNode rightchild5 6 struct BTreeNode root7 void inorder(struct BTreeNode node)8 if (node = NULL)9 inorder(node-gtleftchild)10 cout ltlt node-gtdata11 inorder(node-gtrightchild)12 13

範例 5-12此為運算式二元樹

其對應中序表示運算式為 (A+B)C+D(E-F)

6 struct BTreeNode root7 void inorder(struct BTreeNode node)8 if (node = NULL)9 inorder(node-gtleftchild)10 cout ltlt node-gtdata11 inorder(node-gtrightchild)12 13

6 struct BTreeNode root7 void inorder(struct BTreeNode node)8 if (node = NULL)9 inorder(node-gtleftchild)10 cout ltlt node-gtdata11 inorder(node-gtrightchild)12 13

(A+B)C+D(E-F)

如何加上 ( )

552  後序走訪程式 5-4二元樹的後序走訪14 void postorder(struct BTreeNode node)

15 if (node =NULL)16 postorder(node-gtleftchild)17 postorder(node-gtrightchild)18 cout ltlt node-gtdata19 20

以後序走訪可得到AB+CDEF-+

其正為對應的後序運算式

553  前序走訪程式 5-5二元樹的前序走訪21 void preorder(struct BTreeNode node)

22 if (node = NULL)

23 cout ltlt node-gtdata

24 preorder(node-gtleftchild)

25 preorder(node-gtrightchild)

26

27

以前序走訪可得到++ABCD-EF

其正為對應的前序運算式

554 利用堆疊和迴圈取代遞迴進行二元樹的走訪1 struct BTreeNode 2 struct BTreeNode leftchild3 char data4 struct BtreeNode rightchild5 6 struct BTreeNode root7 struct StackNode8 struct BTreeNode treenode9 struct StackNode next1011 struct StackNode top

12 void push_node(struct BtreeNode node )13 struct StackNode old_top14 old_top = top15 top = (struct StackNode ) malloc

(sizeof(struct StackNode))16 top-gttreenode = node17 top-gtnext = old_top18 19 struct BTreeNode pop_node()20 struct BTreeNode Tnode21 struct StackNode old_top22 if (top == NULL)23 StackEmpty()24 else25 Tnode = top-gttreenode26 old_top = top27 top = top-gtnext28 free(old_top)29 return Tnode30 31

32 void inorder_Stack(struct BtreeNode node)33 do34 while (node = NULL)35 push_node(node)36 node = node-gtleftchild37 push all left children38 if (top = NULL)39 node = pop_node()40 cout ltlt node-gtdata41 node = node-gtrightchild42 inorder printing and check right child43 while ((top=NULL)||(node=NULL))44 top==NULL 堆疊已空 node==NULL沒有右子

6 struct BTreeNode root7 void inorder(struct BTreeNode node)8 if (node = NULL)9 inorder(node-gtleftchild)10 cout ltlt node-gtdata11 inorder(node-gtrightchild)12 13

中序表示為DBGEHAFIC

32 void inorder_Stack()33 struct BTreeNode node34 node = root35 do36 while (node = NULL)37 push_node(node)38 node = node-gtleftchild39 40 if (top = NULL)41 node = pop_node()42 cout ltlt node-gtdata43 node = node-gtrightchild44 45 while(top=NULL)||(node=NULL))46

45 void Preorder_Stack(struct BSTreeNode node)46 do47 while (node = NULL)48 cout ltlt node-gtdata)49 push_node(node)50 node = node-gtleftchild51 push all left children52 if (top = NULL)53 node = pop_node()54 node = node-gtrightchild55 56 while ((top=NULL)||(node=NULL))57

程式 5-5二元樹的前序走訪21 void preorder(struct BTreeNode node)22 if (node = NULL)23 cout ltlt node-gtdata24 preorder(node-gtleftchild)25 preorder(node-gtrightchild)26 27

58 void Postorder_Stack(struct BTreeNode node)59 do60 while (node = NULL)61 cout ltlt node-gtdata62 push_node(node)63 node = node-gtrightchild64 push all left children65 if (top = NULL)66 node = pop_node()67 node = node-gtleftchild68 69 while ((top=NULL)||(node=NULL))70

程式 5-4二元樹的後序走訪14 void postorder(struct BTreeNode node)15 if (node =NULL)16 postorder(node-gtleftchild)17 postorder(node-gtrightchild)18 cout ltlt node-gtdata19 20

555  階層走訪bull 「階層走訪」 (level-order traversal) 是依階層

的順序進行二元樹的走訪先走訪階層小的節點後走訪階層大的節點同一階層者則依自左向右的順序走訪

bull 對下圖的二元樹進行階層走訪的結果為ABCDEFGHI

bull 對同一階層而言先走訪的節點其子節點亦在下一階層中先被走訪

bull 這種「先進先出」的特性恰可用佇列予以儲存與處理

程式 5-7 利用佇列進行二元樹的階層走訪

1 struct BTreeNode2 struct BTreeNode leftchild3 char data4 struct BTreeNode rightchild5 6 struct BTreeNode root7 struct QNode8 struct BTreeNode treenode9 struct QNode next10 11 struct QNode front rear

12 void AddQueue(struct BTreeNode Tnode)13 struct QNode node14 node=(struct QNode )malloc(sizeof(struct QNode))15 node-gttreenode = Tnode16 node-gtnext = NULL17 if (front == NULL) front = node19 else rear-gtnext = node20 rear = node21

22 struct BTreeNode DeleteQueue()23 struct BTreeNode Tnode24 struct QNode old_front25 if (rear == NULL)26 QueueEmpty()27 else28 Tnode = front-gttreenode29 old_front = front30 front = front-gtnext31 free(old_front)32 return Tnode33 34 35 void LevelOrder (struct BTreeNode node)36 AddQueue(node)37 while (front =NULL)38 node = DeleteQueue()39 cout ltlt node-gtdata40 if (node-gtleftchild = NULL)41 AddQueue(node-gtleftchild)42 if (node-gtrightchild = NULL)43 AddQueue(node-gtrightchild)44 45

35 void LevelOrder (struct BTreeNode node)36 AddQueue(node)37 while (front =NULL)38 node = DeleteQueue()39 cout ltlt node-gtdata40 if (node-gtleftchild = NULL)41 AddQueue(node-gtleftchild)42 if (node-gtrightchild = NULL)43 AddQueue(node-gtrightchild)44 45

bull Infix ABCbull Postfix ABC CBA ACB BCA

BAC CAB

B

A CA

B

C

B

A

C

C

B

AB

A

C

二元樹中序與前序式中序與後序式bull 每一棵二元樹皆有唯一的一對中序與前序表示法也有唯一的中序與後序次序

bull 換句話說給予一對中序與前序或中序與後序即可決定一棵二元樹

bull 然而給予前序和後序次序並不能決定唯一的二元樹可能會產生兩棵不同的二元樹

bull 想想階層走訪和中序走訪順序可否決定唯一的二元樹

A

B C

H J D E

F G

prefix ABHJCDFGEinfix HBJAFDGCEpostfix HJBFGDECA

VLRLVRLRV

A

prefix BHJinfix HBJ

prefix CDFGEinfix FDGCE

prefix ABHJCDFGEinfix HBJAFDGCE

A

B C

H J D E

F G

prefix ABHJCDFGE

infix HBJAFDGCEk-1

struct BTreeNode BTree_InfixPrefix (AnsiString infix AnsiString prefix)

int k struct BTreeNode node if (infixLength==0) return NULLnode = (struct BTreeNode ) malloc (sizeof(struct BTreeNode))node-gtdata = prefix[1]k = find(prefix[1] infix)node-gtleftchild = BTree_InfixPrefix

(infix[1k-1] prefix[2k])node-gtrightchild = BTree_InfixPrefix(infix[k+1infixLength]prefix[k+1prefixLength])

VLR

LVRk1

k2 k+1~

k+1~

1

struct BTreeNode BTree_InfixPostfix (AnsiString infix AnsiString prefix)

int k struct BTreeNode node if (infixLength==0) return NULLnode = (struct BTreeNode ) malloc (sizeof(struct BTreeNode))node-gtdata = postfix[postfixLength]k = find(postfix[postfixLength] infix)node-gtleftchild = BTree_InfixPrefix

(infix[1k-1] postfix[1k-1])node-gtrightchild = BTree_InfixPrefix(infix[k+1infixLength]postfix[k+1postfixLength-1])

56  二元樹的複製與相等測試程式 5-8 二元樹的複製1 struct BTreeNode2 struct BTreeNode leftchild3 char data4 struct BTreeNode rightchild5 6 struct BTreeNode root7 struct BTreeNode Copy(struct BTreeNode TreeRoot)8 struct BTreeNode CopyRoot9 if (TreeRoot == NULL)10 return NULL11 CopyRoot =(struct BTreeNode ) malloc (sizeof(BTreeNode))12 CopyRoot-gtdata = TreeRoot-gtdata13 CopyRoot-gtleftchild = Copy(TreeRoot-gtleftchild)14 CopyRoot-gtrightchild = Copy(TreeRoot-

gtrightchild)15 return CopyRoot16

程式 5-9 檢測兩棵二元樹是否相等1 struct BTreeNode2 struct BTreeNode leftchild3 char data4 struct BTreeNode rightchild5 6 struct BtreeNode rightchild7 int Equal(struct BTreeNode ustruct BTreeNode v)8 if ((u==NULL) ampamp (v==NULL)) return 19 if ((u=NULL) ampamp (v=NULL)10 ampamp (u-gtdata == v-gtdata )11 ampamp Equal(u-gtleftchildv-gtleftchild)12 ampamp Equal(u-gtrightchildv-gtrightchild))

13 return 114 return 015

Counting Nodes in a Binary Tree

int CountingNodes(struct BTreeNodes node)

if (node == NULL) return 0 int leftchildren = CountingNodes(node-gtleftchild)

int rightchildren= CountingNodes(node-gtrightchild)

return leftchildren+rightchildren

Counting Leaves in a Binary Tree

int CountingLeaves(struct BTreeNodes node)

if (node == NULL) return 0 int leftleaves = CountingLeaves(node-gtleftchild)

int rightchildren= CountingLeaves(node-gtrightchild)

if (leftleaves==0 ampamp leftleaves==0) return 1

return leftleaves + leftleaves

57  引線二元樹bull 在二元樹的鍵結表示中樹葉節點的兩個子樹指標皆指向 NULL 由定理 5-1 得知 n = E+1 其中 n 為節點個數 E 為分支數

bull 而 n 個鍵結節點有 2n 個指標空間每個分支恰佔用一個指標空間共有 E (=n-1) 個指標有用到(不是空的)而共有 n+1 個指標空間是 NULL 比非空的節點還多

bull 有學者提出對這些「放空指標的空間」加以利用的概念mdash與其放空指標不如放指向其它節點的指標稱之為引線 (thread) 使得某些運算(如走訪hellip等)可以加快

bull 這個概念發展出了引線二元樹 (threaded binary tree) 這種資料結構

範例 5-17下圖為加了引線的二元樹

加入的引線將使二元樹的中序走訪更加便利沿著上右圖箭頭所指示的順序走訪即可完成此二元樹的中序走訪GDHBAIECF 為此二元樹的中序表示

bull 範例 5-17 中所加的引線目的在方便二元樹的中序走訪

bull 對樹葉節點 p 而言令其資料為 P 則引線指標的規則如下

(1) p 的左子樹指標指向中序表示中 P 的前一個 元素(前接元素 (predecessor) )

(2) p 的右子樹指標指向中序表示中 P 的後一個 元素(後繼元素 (successor) )

為了區別節點指標放的是一般指標(內部節點)抑或是引線指標(樹葉節點)我們另以 2 個欄位分別區別左和右子樹指標空間存放的對象其節點的記憶體配置有如下圖所示

其中 leftthread (rightthread) 為 0 時表示 leftchild (rightchild) 存放的是一般節點指標而 leftthread (rightthread) 為 1 時表示 leftchild (rightchild) 存放的是引線指標1 struct TBTreeNode2 int leftthread3 struct TBTreeNode leftchild4 char data5 struct TBTreeNode rightchild6 int rightthread7

由右側的引線二元樹可知有最左和最右兩條引線尚無妥善的安排我們另設計一空的引線節點做為樹根如下圖所示

571  引線二元樹的中序走訪決定節點 p 在中序表示中的後繼節點 q (簡稱中序後繼點)

程式 5-10決定節點 node的中序後繼點1 struct TBTreeNode2 int leftthread3 struct TBTreeNode leftchild4 char data5 struct TBTreeNode rightchild6 int rightthread7 8 struct TBTreeNode root9 struct TBTreeNode Next(struct TBTreeNode node)

10 struct TBTreeNode temp11 temp = node-gtrightchild12 if (node-gtrightthead) return temp13 while (temp-gtleftthead) temp=temp-gtleftchild

14 return temp15

程式 5-11引線二元樹的中序表示(接程式 5-10)1 void InorderTBTree()2 struct TBTreeNode node3 for(node=Next(root)node=rootnode=Next(node))

4 cout ltlt node-gtdata5

bull只要 node 是引線二元樹上的任一節點透過呼叫 Next(node) 即可找到其在中序表示中的後繼節點bull(試想若程式 5-10 中的程序 Next 所傳入的參數指標正是 root Next(root) 是否可正常運作請思考)bull於是只須從中序表示中的第一節點起讓每一節點 node 都執行 Next(node) 程序即可得引線二元樹的中序表示

572  在引線二元樹中加入節點在引線二元樹中樹葉節點節點的右子樹指標所指向或內部節點右子樹的最左樹葉為其中序後繼節點而且任一節點若無左子樹則其左子樹指標乃指向其中序前接節點hellip這些性質必須在節點新增進入引線二元樹時也要保持接下來討論節點 new 插入成為引線二元樹的節點 p 的右子節點的情形至於插入成為左子節點的情形則讓各位模擬推敲

572572  在引線二元樹中加入節點 在引線二元樹中加入節點 (( 續續 ))

newnew 會成為會成為 pp 的右子節點的情況共有以下兩種的右子節點的情況共有以下兩種(1) (1) 若若 pp 沒有右子節點 沒有右子節點 ((p-gtrightthread p-gtrightthread 為為1) 1) 如圖如圖 ((a) a) 則 則 new new 插入後應形成如圖插入後應形成如圖 ((b) b) 所示所示

(2)(2) 若若 pp 有右子節點 有右子節點 ((p-gtrightthread p-gtrightthread 為為0) 0) 如圖如圖 ((c) c) 則 則 new new 插入後應形成如插入後應形成如圖圖 ((d) d) 所示所示

程式 5-12  插入節點 new 成為引線二元樹中節點 p的右子節點(上接程式 5-10 5-11 )

1 void InsertRight(struct TBTreeNode p 2 struct TBTreeNode new)3 struct TBTreeNode q4 new-gtrightchild = p-gtrightchild5 new-gtrightthead = p-gtrightthread6 new-gtleftchild = p7 new-gtleftthread = 18 p-gtrightchild = new9 p-gtrightthead = 010 if (new-gtrightthread)11 q = Next(new)12 q-gtleftchild = new13 14

58  堆積bull 「堆積」 (heap) 是一個特殊的完備二元樹節點的資料與其子節點的資料之間有事先定好的大小關係存在bull 定義ndash最大堆疊為一完備二元樹任一節點的資料內容不小於其子節點的資料內容ndash最小堆疊為一完備二元樹任一節點的資料內容不大於其子節點的資料內容

581  插入節點至最大堆積中最大堆積是一完備二元樹所以如圖 (a) 之最大堆積於加入新節點後樹的形狀必須如圖 (b) 所示灰色節點即為新節點若加入的節點資料分別為 1 9 12 則其最大堆積將分別變成如圖 (c) (d) (e) 所示

程式 5-13 新增節點至最大堆積中最大堆積本身是一棵完備二元樹可以用陣列表示之 1 define maxsize 1002 int heap[maxsize]3 int n i4 void InsertHeap(int x)5 if (n == maxsize) HeapFull()6 else7 n++8 i = n9 while ((i gt 1) ampamp (x gt heap[i2]))10 heap[i] = heap[i2]11 i = 212 13 heap[i] = x14 16

程序 InsertHeap 在插入第 n 個資料時需要的時間複雜度為 O(logn)

1 100

2 93

3 84

4 77

5 65

6 81

7 79

8 66

9 70

10 50

100

84

77

93

8165

66 70

79

50

1

2 3

4 5 6 7

8 9 10

1 define maxsize 1002 int heap[maxsize]3 int n i4 void InsertHeap(int x)5 if (n == maxsize) HeapFull()6 else7 n++8 i = n9 while ((i gt 1) ampamp (x gt heap[i2]))10 heap[i] = heap[i2]11 i = 212 13 heap[i] = x14 16

582 刪除最大堆積中的最大資料圖 (a) 中的最大堆積在刪除最大資料後其樹形應變成如圖 (b) 所示將最未一個樹葉節點的資料 6 暫挪至樹根節點內如圖 (c) 再將圖 (c) 中非最大堆積的結構調整成為最大堆積 10 和 8 中選大者與 6對調即成圖 (d)

程式 5-14 刪除最大堆積中的最大資料(上接程式 5-13 )1 int DeleteHeap()2 int x i j3 if (n == 0) HeapEmpty()4 else5 x = heap[1]6 heap[1] = heap[n]7 n--8 i = 19 while (i lt= n2)10 if (heap[2i] gt heap[2i+1])11 j = 2i12 else13 j = 2i+114 if (heap[i]gtheap[j]) break15 swap(heap[i] heap[j])16 i = j17 18 return x19 20

其時間複雜度與其堆積高度相同是為O(logn) 其中 n 為堆積中的元素個數

1 100

2 93

3 84

4 77

5 65

6 81

7 79

8 66

9 70

10 50

100

84

77

93

8165

66 70

79

50

1

2 3

4 5 6 7

8 9 10

1 int DeletHeap()2 int x i j3 if(n == 0) HeapEmpty()4 else5 x = heap[1]6 heap[1] = heap[n]7 n--8 i = 19 while (i lt= n2)10 if ([heap[2i]] gt heap[2i+1])11 j = 2i12 else13 j = 2i+114 if (heap[i]gtheap[j]) break15 swap(heap[i] heap[j])16 i = j17 18 return x19 20

583  優先佇列bull 由於最大堆積(或最小堆積)的結構可提供最大元素(或最小元素)而其維護堆積的成本為 O(logn) n 為總元素個數

bull 堆積常被當成「優先佇列」 (priority queue) 使用mdash此時佇列被組織成完備二元樹若新加入佇列的資料有大小關係優先順序的需求時此優先佇列即以 O(logn) 的代價將新資料放入優先佇列(可能是最大或最小堆積依實際大小關係優先順序的需求而定)中mdash提供依元素的優先順序做為處理順序的服務

bull 一般的佇列為先進先出先到的先行處理優先佇列則是依據其優先順序優先順序高者先行處理與來到的先後無關

範例 5-21bull 在「分時」 (time sharing) 的作業系統中我們不希望使用中央處理器 (Central Processing Unit CPU) 時間短的「處理程序」 (process) 等候過久於是有「最短時間工作優先」 (shortest-job-first) 的排程策略此時等候的處理程序即可組織成之一最小堆積而 CPU 每次即取出存於樹根的處理程序予以處理任何新來的處理程序則以 O(logn) 的時間加入此優先佇列(最小堆積)中

bull 注意之前提過的線性佇列新來的直接加入成為新的尾端元素花O(1) 的時間然線性佇列本身沒有大小的關係每次要取出最大(小)的資料得花O(n) 的代價(在所有元素中挑出最大(小)者) n 為佇列內的元素個數而優先佇列是在新元素加入時要花O(logn) 的時間至於取出最大(小)則只須 O(1) 的時間

59  二元搜尋樹bull 二元搜尋樹是一棵二元樹可能是空二元樹若不為空二元樹則滿足下列性質ndash所有節點內的資料內容是相異的 ndash左子節點的資內容 ( 如果有 ) 比父節點的資料內容小 ndash右子節點的資料內容 ( 如果有 ) 比父節點的資料內容大 ndash以左和右子節點為樹根的左子樹和右子樹也是二元搜尋樹

在二元搜尋樹中搜尋資料二元搜尋樹對資料大小的掌握十分明確若恰與節點 p 資料相等則搜尋成功若欲搜尋的資料 x 比樹節點 p 中的資料小 則只須搜尋節點 p 的左子樹若比節點 p 中的資料大 則只須搜尋 p 的右子樹這樣的判斷可以遞迴地應用在各子樹節點 中

程式 5-15 在二元搜尋樹中搜尋資料(遞迴呼叫)1 struct BSTreeNode2 struct BSTreeNode leftchild3 int data4 struct BSTreeNode rightchild5 6 struct BSTreeNode root7 struct BSTreeNode SearchBST(

struct BSTreeNode tree int x)8 if (tree == NULL) return NULL9 if (x == tree-gtdata) return tree10 if (x lt tree-gtdata)11 return SearchBST(tree-gtleftchild x)

12 return SearchBST(tree-gtrightchild x)13

程式 5-16 搜尋二元搜尋樹的資料(非遞迴呼叫)在二元搜尋樹十分龐大時遞迴呼叫的效率不會太好可利用迴圈來完成搜尋的動作以提高執行效率請看程式 5-16

14 struct BSTreeNode Search BST_iterative15 (struct BSTreeNode node int x)16 while (node = NULL)17 if (x == node-gtdata) return node18 if (x lt node-gtdata)19 node = node-gtleftchild20 else21 node = node-gtrightchild22 23 return NULL24

592  新增資料於二元搜尋樹中欲新增資料進入一二元搜尋樹中得先決定該資料在二元搜尋樹中應出現的位置這可透過上節提到的搜尋動作來解決

在二元搜尋樹定義的性質 (1) 中我們限定了元素必須相異所以搜尋新資料的結果必是 NULL

必須將搜尋的結果改為新增資料所應在位置的節點指標

程式 5-17  在二元搜尋樹中新增資料25 int InsertBST(int x)26 struct BSTreeNode p q27 p = root q = NULL28 while (p = NULL)29 q = p30 if (x == p-gtdata) return 0

若不考慮重複則可省略之31 if (x lt p-gtdata)32 p = p-gtleftchild33 else34 p = p-gtrightchild35

3636 p = (struct BSTreeNode ) p = (struct BSTreeNode ) malloc (sizeof(BSTreeNode))malloc (sizeof(BSTreeNode))

3737 p-gtdata = x p-gtdata = x3838 p-gtleftchild = p-gtrightchild = p-gtleftchild = p-gtrightchild = NULLNULL3939 if (root == NULL) root = p if (root == NULL) root = p4040 else if (x lt q-gtdata) else if (x lt q-gtdata)4141 q-gtleftchild = p q-gtleftchild = p4242 else else4343 q-gtrightchild = p q-gtrightchild = p4444 return 1 return 145 45

程式 程式 5-175-17  在二元搜尋樹中新增資料  在二元搜尋樹中新增資料 (( 續續 ))

範例 5-23 圖 (b) 為圖 (a) 新增整數 6 之後的結果圖 (c) (d) (e) (f) 則分別是逐步再加入 28 18

10 21 的結果

bull若目前的二元搜尋樹高度為 h 則新增資料得花O(h) 的時間找到其所應在的位置遂新增資料至二元搜尋樹中的時間複雜之度為 O(h)

593 自二元搜尋樹中刪除資料欲自二元搜尋樹中刪除資料可能會面臨兩種情況(1) 刪除節點 p 位於樹葉節點上如下圖所示 這是比較容易處理的情況

(2) 刪除的節點 p 不是樹葉刪除節點 p 後原 p 所指的節點可由 q ( q 中包含了 p 左子樹中的最大節點值)或 r ( r 中包含了 p 右子樹中的最小節點值)來取代以維持二元搜尋樹中資料的大小關係其結果分別如圖 (b) 和 (c) 所示而圖 (d) 則標明了指標會改變的節點位置即 p 會消失 father 的左或右子樹指標會異動 k(可能是 q 或 r )會移到原來 p 的位置其父節點 f 的左或右子樹指標會異動其中 father 為 p 父節點 f 為 k 的父節點

程式 5-18 自二元搜尋樹中刪除資料46 int DeleteBST (int x)47 Struct BSTreeNode p father k f48 p = root father = NULL49 while (p = NULL)50 if (x == p-gtdata) 找到 x 所在的節點 p 了51 if ((p-gtleftchild == NULL) ampamp 52 (p-gtrightchild == NULL))53 k = NULL p為樹葉節點54 else if (p-gtleftchild = NULL )x位於非子節

點55 f = p 找出 p左子樹的最右樹葉56 k = p-gtleftchild57 while (k-gtrightchild =NULL)58 f = k59 k = k-gtrightchild60 k將挪至原 p處61 if (p == f)62 f-gtleftchild = k-gtleftchild 63 else64 f-gtrightchild = k-gtleftchild 65

66 else p無左子樹67 f = p 找出 p右子樹的最左樹葉68 k = p-gtrightchild69 while (k-gtleftchild = NULL)70 f = k71 k = k -gtleftchild72 73 if (p == f)74 f-gtrightchild = k-gtrightchild 75 else76 f-gtleftchild = k-gtrightchild77 78 if (k = NULL) k挪至原 p處繼承 p的左右指標79 k-gtleftchild = p-gtleftchild80 k-gtrightchild = p-gtrightchild81 82 if (father == NULL) root = k83 else if (x lt father-gtdata) 決定 k是 father的左

或右兒子84 father-gtleftchild = k85 else86 father-gtrightchild = k87 free(p)88 return 1 成功地刪除 x於此傳回 1返回呼叫處89 51~89行為找到 x時應有的處理步驟

90 else 尚未找到 x繼續往下一階層找

91 father = p92 if (x lt p-gtdata)93 p = p-gtleftchild94 else95 p = p-gtrightchild96 97 98 return 0 未找到 x傳回 099

510  樹林 (forest)

bull 定義樹林(森林)是由 n 棵沒有交集的樹所構成的集合 n 0

下圖為一包含三棵樹的樹林

5101 轉換樹林為二元樹 之前我們以二元樹來表示樹因為二元樹有較多

好用的性質在此我們也將樹林轉換成二元樹看待

在 522 節中我們嘗試了用左子右兄弟的方式來表示樹這個方法事實上已將樹轉換成二元樹

即用左子右兄弟的結構重新組織樹林中的每一棵樹這些樹的樹根將視為兄弟而最左邊的樹則視為樹林二元樹的樹根如此一來樹林即可轉換成二元樹

範例 5-25右下圖為右上圖三棵樹所對應的三棵左子右兄弟二元樹

下圖則為上圖樹林所對應的一棵樹林二元樹

演算法 5-1  轉換樹林成二元樹輸入樹林 F = T1 T2 Tn

輸出 F 的二元樹表示 BT=(T1 T2 Tn)1 if (n == 0) 輸出空二元樹 2 else

3 root(T1 T2 Tn) = T1

4 leftsubtree(T1 T2 Tn) =

BT(T11 T12 T1m)5 BT(T11 T12 T1m)為 T1的子樹6 rightsubtree(T1 T2 Tn) =

BT(T2 T3 Tn)

樹林轉換成二元樹的過程樹林轉換成二元樹的過程

  • Slide 1
  • Slide 2
  • Slide 3
  • Slide 4
  • Slide 5
  • Slide 6
  • Slide 7
  • Slide 8
  • Slide 9
  • Slide 10
  • Slide 11
  • Slide 12
  • Slide 13
  • Slide 14
  • Slide 15
  • Slide 16
  • Slide 17
  • Slide 18
  • Slide 19
  • Slide 20
  • Slide 21
  • Slide 22
  • Slide 23
  • Slide 24
  • Slide 25
  • Slide 26
  • Slide 27
  • Slide 28
  • Slide 29
  • Slide 30
  • Slide 31
  • Slide 32
  • Slide 33
  • Slide 34
  • Slide 35
  • Slide 36
  • Slide 37
  • Slide 38
  • Slide 39
  • Slide 40
  • Slide 41
  • Slide 42
  • Slide 43
  • Slide 44
  • Slide 45
  • Slide 46
  • Slide 47
  • Slide 48
  • Slide 49
  • Slide 50
  • Slide 51
  • Slide 52
  • Slide 53
  • Slide 54
  • Slide 55
  • Slide 56
  • Slide 57
  • Slide 58
  • Slide 59
  • Slide 60
  • Slide 61
  • Slide 62
  • Slide 63
  • Slide 64
  • Slide 65
  • Slide 66
  • Slide 67
  • Slide 68
  • Slide 69
  • Slide 70
  • Slide 71
  • Slide 72
  • Slide 73
  • Slide 74
  • Slide 75
  • Slide 76
  • Slide 77
  • Slide 78
  • Slide 79
  • Slide 80
  • Slide 81
  • Slide 82
  • Slide 83
  • Slide 84
  • Slide 85
  • Slide 86
  • Slide 87
  • Slide 88
  • Slide 89
  • Slide 90
  • Slide 91
  • Slide 92
  • Slide 93
  • Slide 94
  • Slide 95
  • Slide 96
  • Slide 97
  • Slide 98
  • Slide 99
  • Slide 100
  • Slide 101
  • Slide 102
  • Slide 103
Page 21: 第五章 樹

532 二元樹的性質 ( 續 )定理 5-2 (1) 二元樹上階層 i 的節點數目最多為 2i-1 i 1 (2) 深度為 k 的二元樹其節點數目最多為 2k-

1 k1 bull 定理 5-2說明了 (1) 每一階層上可放的最多節點數

(2) 階層數已知時二元樹可放的最多節點數證明 (1) 利用數學歸納法歸納法的基礎當 i=1 時只有樹根一個節點 2i-1=21-1=1 原式成

立歸納法的假設假設 i = k 時該式成立即階層 k 的最大節點數為

2k-1 k 1 歸納法的推論當 i = k+1 時因在階層 k 的每一個節點最多會有

2 個子節點遂在 k+1 階層最多會有 22k-1 = 2k

= 2i-1 節點由數學歸納法的原理可知本定理得證(2) 由 (1) 可知二元樹在階層 i 的最多節點數為 2i-1那麼深度 k 的二元樹最多可有節點如下

k

i

ki

1

1-1- 24212 1-2

)12(1 k= 2k-1

=

bull 對二元樹而言任一節點的分支度可能為 0 1 或 2 分支度為 0 者是為樹葉

bull 樹葉的數目與分支度為 2 的節點數目存在著有趣的關係請見定理 5-3

定理 5-3 若 T 為一非空的二元樹 n0 為樹葉節點數目 n2為分支度為 2 的節點數目則 n0 = n2+1

證明令 n 為所有節點數目而 n1 為分支度為 1 的節點數目可得 n = n0 + n1 +n2 (1)令 B 為二元樹的分支數除了樹根外每一節點皆有一分支指向之所以

n = B+1 (亦可參考定理 5-1 )又 B = n1+2n2

且 n = n1 + 2n2 +1 (2)由式 (1) 和式 (2) 我們得到

n0 = n2 + 1

完滿二元樹 (full binary tree)

定義一個深度為 k 的完滿二元樹即為一深度為 k 且有 2k-1 個節點的二元樹k 0

bull 由定理 5-2(2) 可知深度為 k 的二元樹其最多節點數為 2k-1 這樣的樹會將樹上所有可能存放節點的位置都放滿完滿 (full) 之名因而生成

完備二元樹 (complete binary tree)我們可對二元樹上節點予以編號階層小者先編同一階

層則自左向右編號這樣的編號方式使我們定義出完備(complete) 二元樹

定義深度為 k 節點數為 n 的二元樹稱為完備二元樹若且唯若 (1) k = 1 時樹有一個節點

(2) 當 k 2 且 1iltk 時深度 i 有 2i-1 個節點且第 k 層的節點皆由第 k-1 層的分支由左至右逐一連接而成

完滿或完備二元樹之所以受人注意乃因它們可以將節點以較少的階層數存放

定理 5-4 n 個節點的完備或完滿二元樹其深度為 log2(n+1)

證明 完滿或完備二元樹的節點皆自階層

1 起排放任一階層 i 可排放 2i-1

個節點遂由定理 5-2 得知此 n 個節點所佔的深度為 log2(n+1)

正規二元樹 (formal binary tree)bull 在二元樹中非樹葉的節點又稱為內部節點

(internal node) 若所有內部節點恰有 2 個子節點即成為正規二元樹

定義若二元樹中所有內部節點恰有 2 個子節點則稱之為正規二元樹

正規二元樹常被引用在單淘汰賽正規二元樹常被引用在單淘汰賽制的各項賽程安排樹葉為參賽制的各項賽程安排樹葉為參賽隊伍內部節點為優勝隊伍樹隊伍內部節點為優勝隊伍樹根即為冠軍隊伍根即為冠軍隊伍

在單淘汰賽制中自在單淘汰賽制中自 n n 個隊伍中產個隊伍中產生冠軍須舉辦 生冠軍須舉辦 nn-1-1場比賽場比賽

54  二元樹的表示法541 以陣列表示二元樹定理 5-5 若一個具有 n 個節點的完備二元樹以循序的方式編號並依序存放在陣列 A 中即第 i 個節點放在 A[i] 中 1 i n 則

(1) 節點 i 的父節點位在 A[i2]處 i ne 1( i = 1 時其節點正為樹根無父節點)

(2) 若 2i n 則節點 i 的左兒子節點位在 A[2i] 處若 2i gtn 節點 i 沒有左子節點

(3) 若 2i+1 n 節點 i 的右兒子節點位 A[2i+1]處若 2i+1gtn 節點 i 沒有左子

節點

證明先用數學歸納法證明 (2) bull 歸納法的基礎當 i = 1 時其左子節點確實在 A[2] 處(除非 nlt2 此時節點 1 沒有左子節點)

bull 歸納法的假設假設當 I = k 時其左子節點確在 A[2i] = A[2k] 處(除非 2i 已大於 n 此時節點 i 無左子節點)

bull 歸納法的推論當 i = k+1 時因第 k 節點的左子節點在 A[2k] 處其右子節點則位在 A[2k+1] 處那麼第 k+1 節點的左子節點應緊臨 A[2k+1] 亦即位於 A[2k+2] = A[2(k+1)] 處(除非 2i 已大於 n 此時節點 i 無左子節點)

bull 由數學歸納法的原理得證定理 5-5 (2) (3) 由於 (2) 成立而且節點 i 的右子節點會緊臨

A[2i] 亦即其位於 A[2i+1]處除非 i 根本沒有右子節點得證定理 5-5 (3)

由 (2) 和 (3) 我們可知定理 5-5 (1) 成立

542 以鍵結串列表示二元樹bull 以陣列來表示完備二元樹算是相當方便但ndash一般的二元樹將導致陣列空間的浪費ndash如果二元樹中節點的新增刪除動作頻繁則會造成陣列對應資料的搬動隨而頻繁

bull 以鍵結串列表示二元樹

為方便辦識樹根我們常用 root 指標指向樹根做為樹鍵結串列的起點

陣列表示和鍵結表示的空間需求bull 完備二元樹的陣列表示和鍵結表示皆沒浪費儲存資料的空間

bull 但歪斜樹的陣列表示比起鍵結表示則浪費許多

bull 以 n 個節點的歪斜樹而言陣列表示法可能需要 2n-1 個陣列元素其中 2n-1-n 個陣列元素空間是浪費的而鍵結表示法只需要 n 個節點空間

二元樹節點的宣告程式 5-2

1 struct BTreeNode

2 struct BtreeNode leftchiled

3 int data

4 struct BTreeNode rightchild

5

6 struct BTreeNode root

bull 這樣的二元樹節點要取得兒子節點的指標十分方便但若有取得父節點的需求則嫌不足我們可以加上 parent 欄位指向父節點

55  二元樹的走訪bull 假如一組資料已用二元樹的組織在一起總有需求對其全數資料做動作例如計算所有數目印出所有資料在所有資料中搜尋某項資料hellip等此時即須對此二元樹做走訪 (traversal) 的運算利用走訪二元樹的同時將計算列印或搜尋的動作完成

bull 事實上走訪即在決定二元樹上資料被處理(計算列印或搜尋)的順序我們也希望走訪的演算法對任何節點皆一致是容易撰寫程式實作的

bull 若對一個二元樹上的節點而言 V 表示處理節點上的資料 L 表示走訪其左子樹 R 表示走訪其右子樹

55  二元樹的走訪 ( 續 )bull 因為對稱的緣故我們只考慮先走訪左邊再走訪右邊的情形那麼右圖只剩下三種走訪方式

bull 以 V 所在的相對位置分別對此三種走訪方式取名如下

(1) LVR 中序走訪 (inorder traversal)或中序表示法 (infix notation)

(2) LRV 後序走訪 (postorder traversal) 或後序表示法 (postfix notation)

(3) VLR 前序走訪 (preorder traversal)或前序表示法 (prefix notation)

551  中序走訪bull 利用遞迴的方式撰寫中序走訪的程序希望把二元樹中序走訪的

順序印出來

程式 5-3二元樹的中序走訪1 struct BTreeNode2 struct BTreeNode leftchild3 char data4 struct BTreeNode rightchild5 6 struct BTreeNode root7 void inorder(struct BTreeNode node)8 if (node = NULL)9 inorder(node-gtleftchild)10 cout ltlt node-gtdata11 inorder(node-gtrightchild)12 13

範例 5-12此為運算式二元樹

其對應中序表示運算式為 (A+B)C+D(E-F)

6 struct BTreeNode root7 void inorder(struct BTreeNode node)8 if (node = NULL)9 inorder(node-gtleftchild)10 cout ltlt node-gtdata11 inorder(node-gtrightchild)12 13

6 struct BTreeNode root7 void inorder(struct BTreeNode node)8 if (node = NULL)9 inorder(node-gtleftchild)10 cout ltlt node-gtdata11 inorder(node-gtrightchild)12 13

(A+B)C+D(E-F)

如何加上 ( )

552  後序走訪程式 5-4二元樹的後序走訪14 void postorder(struct BTreeNode node)

15 if (node =NULL)16 postorder(node-gtleftchild)17 postorder(node-gtrightchild)18 cout ltlt node-gtdata19 20

以後序走訪可得到AB+CDEF-+

其正為對應的後序運算式

553  前序走訪程式 5-5二元樹的前序走訪21 void preorder(struct BTreeNode node)

22 if (node = NULL)

23 cout ltlt node-gtdata

24 preorder(node-gtleftchild)

25 preorder(node-gtrightchild)

26

27

以前序走訪可得到++ABCD-EF

其正為對應的前序運算式

554 利用堆疊和迴圈取代遞迴進行二元樹的走訪1 struct BTreeNode 2 struct BTreeNode leftchild3 char data4 struct BtreeNode rightchild5 6 struct BTreeNode root7 struct StackNode8 struct BTreeNode treenode9 struct StackNode next1011 struct StackNode top

12 void push_node(struct BtreeNode node )13 struct StackNode old_top14 old_top = top15 top = (struct StackNode ) malloc

(sizeof(struct StackNode))16 top-gttreenode = node17 top-gtnext = old_top18 19 struct BTreeNode pop_node()20 struct BTreeNode Tnode21 struct StackNode old_top22 if (top == NULL)23 StackEmpty()24 else25 Tnode = top-gttreenode26 old_top = top27 top = top-gtnext28 free(old_top)29 return Tnode30 31

32 void inorder_Stack(struct BtreeNode node)33 do34 while (node = NULL)35 push_node(node)36 node = node-gtleftchild37 push all left children38 if (top = NULL)39 node = pop_node()40 cout ltlt node-gtdata41 node = node-gtrightchild42 inorder printing and check right child43 while ((top=NULL)||(node=NULL))44 top==NULL 堆疊已空 node==NULL沒有右子

6 struct BTreeNode root7 void inorder(struct BTreeNode node)8 if (node = NULL)9 inorder(node-gtleftchild)10 cout ltlt node-gtdata11 inorder(node-gtrightchild)12 13

中序表示為DBGEHAFIC

32 void inorder_Stack()33 struct BTreeNode node34 node = root35 do36 while (node = NULL)37 push_node(node)38 node = node-gtleftchild39 40 if (top = NULL)41 node = pop_node()42 cout ltlt node-gtdata43 node = node-gtrightchild44 45 while(top=NULL)||(node=NULL))46

45 void Preorder_Stack(struct BSTreeNode node)46 do47 while (node = NULL)48 cout ltlt node-gtdata)49 push_node(node)50 node = node-gtleftchild51 push all left children52 if (top = NULL)53 node = pop_node()54 node = node-gtrightchild55 56 while ((top=NULL)||(node=NULL))57

程式 5-5二元樹的前序走訪21 void preorder(struct BTreeNode node)22 if (node = NULL)23 cout ltlt node-gtdata24 preorder(node-gtleftchild)25 preorder(node-gtrightchild)26 27

58 void Postorder_Stack(struct BTreeNode node)59 do60 while (node = NULL)61 cout ltlt node-gtdata62 push_node(node)63 node = node-gtrightchild64 push all left children65 if (top = NULL)66 node = pop_node()67 node = node-gtleftchild68 69 while ((top=NULL)||(node=NULL))70

程式 5-4二元樹的後序走訪14 void postorder(struct BTreeNode node)15 if (node =NULL)16 postorder(node-gtleftchild)17 postorder(node-gtrightchild)18 cout ltlt node-gtdata19 20

555  階層走訪bull 「階層走訪」 (level-order traversal) 是依階層

的順序進行二元樹的走訪先走訪階層小的節點後走訪階層大的節點同一階層者則依自左向右的順序走訪

bull 對下圖的二元樹進行階層走訪的結果為ABCDEFGHI

bull 對同一階層而言先走訪的節點其子節點亦在下一階層中先被走訪

bull 這種「先進先出」的特性恰可用佇列予以儲存與處理

程式 5-7 利用佇列進行二元樹的階層走訪

1 struct BTreeNode2 struct BTreeNode leftchild3 char data4 struct BTreeNode rightchild5 6 struct BTreeNode root7 struct QNode8 struct BTreeNode treenode9 struct QNode next10 11 struct QNode front rear

12 void AddQueue(struct BTreeNode Tnode)13 struct QNode node14 node=(struct QNode )malloc(sizeof(struct QNode))15 node-gttreenode = Tnode16 node-gtnext = NULL17 if (front == NULL) front = node19 else rear-gtnext = node20 rear = node21

22 struct BTreeNode DeleteQueue()23 struct BTreeNode Tnode24 struct QNode old_front25 if (rear == NULL)26 QueueEmpty()27 else28 Tnode = front-gttreenode29 old_front = front30 front = front-gtnext31 free(old_front)32 return Tnode33 34 35 void LevelOrder (struct BTreeNode node)36 AddQueue(node)37 while (front =NULL)38 node = DeleteQueue()39 cout ltlt node-gtdata40 if (node-gtleftchild = NULL)41 AddQueue(node-gtleftchild)42 if (node-gtrightchild = NULL)43 AddQueue(node-gtrightchild)44 45

35 void LevelOrder (struct BTreeNode node)36 AddQueue(node)37 while (front =NULL)38 node = DeleteQueue()39 cout ltlt node-gtdata40 if (node-gtleftchild = NULL)41 AddQueue(node-gtleftchild)42 if (node-gtrightchild = NULL)43 AddQueue(node-gtrightchild)44 45

bull Infix ABCbull Postfix ABC CBA ACB BCA

BAC CAB

B

A CA

B

C

B

A

C

C

B

AB

A

C

二元樹中序與前序式中序與後序式bull 每一棵二元樹皆有唯一的一對中序與前序表示法也有唯一的中序與後序次序

bull 換句話說給予一對中序與前序或中序與後序即可決定一棵二元樹

bull 然而給予前序和後序次序並不能決定唯一的二元樹可能會產生兩棵不同的二元樹

bull 想想階層走訪和中序走訪順序可否決定唯一的二元樹

A

B C

H J D E

F G

prefix ABHJCDFGEinfix HBJAFDGCEpostfix HJBFGDECA

VLRLVRLRV

A

prefix BHJinfix HBJ

prefix CDFGEinfix FDGCE

prefix ABHJCDFGEinfix HBJAFDGCE

A

B C

H J D E

F G

prefix ABHJCDFGE

infix HBJAFDGCEk-1

struct BTreeNode BTree_InfixPrefix (AnsiString infix AnsiString prefix)

int k struct BTreeNode node if (infixLength==0) return NULLnode = (struct BTreeNode ) malloc (sizeof(struct BTreeNode))node-gtdata = prefix[1]k = find(prefix[1] infix)node-gtleftchild = BTree_InfixPrefix

(infix[1k-1] prefix[2k])node-gtrightchild = BTree_InfixPrefix(infix[k+1infixLength]prefix[k+1prefixLength])

VLR

LVRk1

k2 k+1~

k+1~

1

struct BTreeNode BTree_InfixPostfix (AnsiString infix AnsiString prefix)

int k struct BTreeNode node if (infixLength==0) return NULLnode = (struct BTreeNode ) malloc (sizeof(struct BTreeNode))node-gtdata = postfix[postfixLength]k = find(postfix[postfixLength] infix)node-gtleftchild = BTree_InfixPrefix

(infix[1k-1] postfix[1k-1])node-gtrightchild = BTree_InfixPrefix(infix[k+1infixLength]postfix[k+1postfixLength-1])

56  二元樹的複製與相等測試程式 5-8 二元樹的複製1 struct BTreeNode2 struct BTreeNode leftchild3 char data4 struct BTreeNode rightchild5 6 struct BTreeNode root7 struct BTreeNode Copy(struct BTreeNode TreeRoot)8 struct BTreeNode CopyRoot9 if (TreeRoot == NULL)10 return NULL11 CopyRoot =(struct BTreeNode ) malloc (sizeof(BTreeNode))12 CopyRoot-gtdata = TreeRoot-gtdata13 CopyRoot-gtleftchild = Copy(TreeRoot-gtleftchild)14 CopyRoot-gtrightchild = Copy(TreeRoot-

gtrightchild)15 return CopyRoot16

程式 5-9 檢測兩棵二元樹是否相等1 struct BTreeNode2 struct BTreeNode leftchild3 char data4 struct BTreeNode rightchild5 6 struct BtreeNode rightchild7 int Equal(struct BTreeNode ustruct BTreeNode v)8 if ((u==NULL) ampamp (v==NULL)) return 19 if ((u=NULL) ampamp (v=NULL)10 ampamp (u-gtdata == v-gtdata )11 ampamp Equal(u-gtleftchildv-gtleftchild)12 ampamp Equal(u-gtrightchildv-gtrightchild))

13 return 114 return 015

Counting Nodes in a Binary Tree

int CountingNodes(struct BTreeNodes node)

if (node == NULL) return 0 int leftchildren = CountingNodes(node-gtleftchild)

int rightchildren= CountingNodes(node-gtrightchild)

return leftchildren+rightchildren

Counting Leaves in a Binary Tree

int CountingLeaves(struct BTreeNodes node)

if (node == NULL) return 0 int leftleaves = CountingLeaves(node-gtleftchild)

int rightchildren= CountingLeaves(node-gtrightchild)

if (leftleaves==0 ampamp leftleaves==0) return 1

return leftleaves + leftleaves

57  引線二元樹bull 在二元樹的鍵結表示中樹葉節點的兩個子樹指標皆指向 NULL 由定理 5-1 得知 n = E+1 其中 n 為節點個數 E 為分支數

bull 而 n 個鍵結節點有 2n 個指標空間每個分支恰佔用一個指標空間共有 E (=n-1) 個指標有用到(不是空的)而共有 n+1 個指標空間是 NULL 比非空的節點還多

bull 有學者提出對這些「放空指標的空間」加以利用的概念mdash與其放空指標不如放指向其它節點的指標稱之為引線 (thread) 使得某些運算(如走訪hellip等)可以加快

bull 這個概念發展出了引線二元樹 (threaded binary tree) 這種資料結構

範例 5-17下圖為加了引線的二元樹

加入的引線將使二元樹的中序走訪更加便利沿著上右圖箭頭所指示的順序走訪即可完成此二元樹的中序走訪GDHBAIECF 為此二元樹的中序表示

bull 範例 5-17 中所加的引線目的在方便二元樹的中序走訪

bull 對樹葉節點 p 而言令其資料為 P 則引線指標的規則如下

(1) p 的左子樹指標指向中序表示中 P 的前一個 元素(前接元素 (predecessor) )

(2) p 的右子樹指標指向中序表示中 P 的後一個 元素(後繼元素 (successor) )

為了區別節點指標放的是一般指標(內部節點)抑或是引線指標(樹葉節點)我們另以 2 個欄位分別區別左和右子樹指標空間存放的對象其節點的記憶體配置有如下圖所示

其中 leftthread (rightthread) 為 0 時表示 leftchild (rightchild) 存放的是一般節點指標而 leftthread (rightthread) 為 1 時表示 leftchild (rightchild) 存放的是引線指標1 struct TBTreeNode2 int leftthread3 struct TBTreeNode leftchild4 char data5 struct TBTreeNode rightchild6 int rightthread7

由右側的引線二元樹可知有最左和最右兩條引線尚無妥善的安排我們另設計一空的引線節點做為樹根如下圖所示

571  引線二元樹的中序走訪決定節點 p 在中序表示中的後繼節點 q (簡稱中序後繼點)

程式 5-10決定節點 node的中序後繼點1 struct TBTreeNode2 int leftthread3 struct TBTreeNode leftchild4 char data5 struct TBTreeNode rightchild6 int rightthread7 8 struct TBTreeNode root9 struct TBTreeNode Next(struct TBTreeNode node)

10 struct TBTreeNode temp11 temp = node-gtrightchild12 if (node-gtrightthead) return temp13 while (temp-gtleftthead) temp=temp-gtleftchild

14 return temp15

程式 5-11引線二元樹的中序表示(接程式 5-10)1 void InorderTBTree()2 struct TBTreeNode node3 for(node=Next(root)node=rootnode=Next(node))

4 cout ltlt node-gtdata5

bull只要 node 是引線二元樹上的任一節點透過呼叫 Next(node) 即可找到其在中序表示中的後繼節點bull(試想若程式 5-10 中的程序 Next 所傳入的參數指標正是 root Next(root) 是否可正常運作請思考)bull於是只須從中序表示中的第一節點起讓每一節點 node 都執行 Next(node) 程序即可得引線二元樹的中序表示

572  在引線二元樹中加入節點在引線二元樹中樹葉節點節點的右子樹指標所指向或內部節點右子樹的最左樹葉為其中序後繼節點而且任一節點若無左子樹則其左子樹指標乃指向其中序前接節點hellip這些性質必須在節點新增進入引線二元樹時也要保持接下來討論節點 new 插入成為引線二元樹的節點 p 的右子節點的情形至於插入成為左子節點的情形則讓各位模擬推敲

572572  在引線二元樹中加入節點 在引線二元樹中加入節點 (( 續續 ))

newnew 會成為會成為 pp 的右子節點的情況共有以下兩種的右子節點的情況共有以下兩種(1) (1) 若若 pp 沒有右子節點 沒有右子節點 ((p-gtrightthread p-gtrightthread 為為1) 1) 如圖如圖 ((a) a) 則 則 new new 插入後應形成如圖插入後應形成如圖 ((b) b) 所示所示

(2)(2) 若若 pp 有右子節點 有右子節點 ((p-gtrightthread p-gtrightthread 為為0) 0) 如圖如圖 ((c) c) 則 則 new new 插入後應形成如插入後應形成如圖圖 ((d) d) 所示所示

程式 5-12  插入節點 new 成為引線二元樹中節點 p的右子節點(上接程式 5-10 5-11 )

1 void InsertRight(struct TBTreeNode p 2 struct TBTreeNode new)3 struct TBTreeNode q4 new-gtrightchild = p-gtrightchild5 new-gtrightthead = p-gtrightthread6 new-gtleftchild = p7 new-gtleftthread = 18 p-gtrightchild = new9 p-gtrightthead = 010 if (new-gtrightthread)11 q = Next(new)12 q-gtleftchild = new13 14

58  堆積bull 「堆積」 (heap) 是一個特殊的完備二元樹節點的資料與其子節點的資料之間有事先定好的大小關係存在bull 定義ndash最大堆疊為一完備二元樹任一節點的資料內容不小於其子節點的資料內容ndash最小堆疊為一完備二元樹任一節點的資料內容不大於其子節點的資料內容

581  插入節點至最大堆積中最大堆積是一完備二元樹所以如圖 (a) 之最大堆積於加入新節點後樹的形狀必須如圖 (b) 所示灰色節點即為新節點若加入的節點資料分別為 1 9 12 則其最大堆積將分別變成如圖 (c) (d) (e) 所示

程式 5-13 新增節點至最大堆積中最大堆積本身是一棵完備二元樹可以用陣列表示之 1 define maxsize 1002 int heap[maxsize]3 int n i4 void InsertHeap(int x)5 if (n == maxsize) HeapFull()6 else7 n++8 i = n9 while ((i gt 1) ampamp (x gt heap[i2]))10 heap[i] = heap[i2]11 i = 212 13 heap[i] = x14 16

程序 InsertHeap 在插入第 n 個資料時需要的時間複雜度為 O(logn)

1 100

2 93

3 84

4 77

5 65

6 81

7 79

8 66

9 70

10 50

100

84

77

93

8165

66 70

79

50

1

2 3

4 5 6 7

8 9 10

1 define maxsize 1002 int heap[maxsize]3 int n i4 void InsertHeap(int x)5 if (n == maxsize) HeapFull()6 else7 n++8 i = n9 while ((i gt 1) ampamp (x gt heap[i2]))10 heap[i] = heap[i2]11 i = 212 13 heap[i] = x14 16

582 刪除最大堆積中的最大資料圖 (a) 中的最大堆積在刪除最大資料後其樹形應變成如圖 (b) 所示將最未一個樹葉節點的資料 6 暫挪至樹根節點內如圖 (c) 再將圖 (c) 中非最大堆積的結構調整成為最大堆積 10 和 8 中選大者與 6對調即成圖 (d)

程式 5-14 刪除最大堆積中的最大資料(上接程式 5-13 )1 int DeleteHeap()2 int x i j3 if (n == 0) HeapEmpty()4 else5 x = heap[1]6 heap[1] = heap[n]7 n--8 i = 19 while (i lt= n2)10 if (heap[2i] gt heap[2i+1])11 j = 2i12 else13 j = 2i+114 if (heap[i]gtheap[j]) break15 swap(heap[i] heap[j])16 i = j17 18 return x19 20

其時間複雜度與其堆積高度相同是為O(logn) 其中 n 為堆積中的元素個數

1 100

2 93

3 84

4 77

5 65

6 81

7 79

8 66

9 70

10 50

100

84

77

93

8165

66 70

79

50

1

2 3

4 5 6 7

8 9 10

1 int DeletHeap()2 int x i j3 if(n == 0) HeapEmpty()4 else5 x = heap[1]6 heap[1] = heap[n]7 n--8 i = 19 while (i lt= n2)10 if ([heap[2i]] gt heap[2i+1])11 j = 2i12 else13 j = 2i+114 if (heap[i]gtheap[j]) break15 swap(heap[i] heap[j])16 i = j17 18 return x19 20

583  優先佇列bull 由於最大堆積(或最小堆積)的結構可提供最大元素(或最小元素)而其維護堆積的成本為 O(logn) n 為總元素個數

bull 堆積常被當成「優先佇列」 (priority queue) 使用mdash此時佇列被組織成完備二元樹若新加入佇列的資料有大小關係優先順序的需求時此優先佇列即以 O(logn) 的代價將新資料放入優先佇列(可能是最大或最小堆積依實際大小關係優先順序的需求而定)中mdash提供依元素的優先順序做為處理順序的服務

bull 一般的佇列為先進先出先到的先行處理優先佇列則是依據其優先順序優先順序高者先行處理與來到的先後無關

範例 5-21bull 在「分時」 (time sharing) 的作業系統中我們不希望使用中央處理器 (Central Processing Unit CPU) 時間短的「處理程序」 (process) 等候過久於是有「最短時間工作優先」 (shortest-job-first) 的排程策略此時等候的處理程序即可組織成之一最小堆積而 CPU 每次即取出存於樹根的處理程序予以處理任何新來的處理程序則以 O(logn) 的時間加入此優先佇列(最小堆積)中

bull 注意之前提過的線性佇列新來的直接加入成為新的尾端元素花O(1) 的時間然線性佇列本身沒有大小的關係每次要取出最大(小)的資料得花O(n) 的代價(在所有元素中挑出最大(小)者) n 為佇列內的元素個數而優先佇列是在新元素加入時要花O(logn) 的時間至於取出最大(小)則只須 O(1) 的時間

59  二元搜尋樹bull 二元搜尋樹是一棵二元樹可能是空二元樹若不為空二元樹則滿足下列性質ndash所有節點內的資料內容是相異的 ndash左子節點的資內容 ( 如果有 ) 比父節點的資料內容小 ndash右子節點的資料內容 ( 如果有 ) 比父節點的資料內容大 ndash以左和右子節點為樹根的左子樹和右子樹也是二元搜尋樹

在二元搜尋樹中搜尋資料二元搜尋樹對資料大小的掌握十分明確若恰與節點 p 資料相等則搜尋成功若欲搜尋的資料 x 比樹節點 p 中的資料小 則只須搜尋節點 p 的左子樹若比節點 p 中的資料大 則只須搜尋 p 的右子樹這樣的判斷可以遞迴地應用在各子樹節點 中

程式 5-15 在二元搜尋樹中搜尋資料(遞迴呼叫)1 struct BSTreeNode2 struct BSTreeNode leftchild3 int data4 struct BSTreeNode rightchild5 6 struct BSTreeNode root7 struct BSTreeNode SearchBST(

struct BSTreeNode tree int x)8 if (tree == NULL) return NULL9 if (x == tree-gtdata) return tree10 if (x lt tree-gtdata)11 return SearchBST(tree-gtleftchild x)

12 return SearchBST(tree-gtrightchild x)13

程式 5-16 搜尋二元搜尋樹的資料(非遞迴呼叫)在二元搜尋樹十分龐大時遞迴呼叫的效率不會太好可利用迴圈來完成搜尋的動作以提高執行效率請看程式 5-16

14 struct BSTreeNode Search BST_iterative15 (struct BSTreeNode node int x)16 while (node = NULL)17 if (x == node-gtdata) return node18 if (x lt node-gtdata)19 node = node-gtleftchild20 else21 node = node-gtrightchild22 23 return NULL24

592  新增資料於二元搜尋樹中欲新增資料進入一二元搜尋樹中得先決定該資料在二元搜尋樹中應出現的位置這可透過上節提到的搜尋動作來解決

在二元搜尋樹定義的性質 (1) 中我們限定了元素必須相異所以搜尋新資料的結果必是 NULL

必須將搜尋的結果改為新增資料所應在位置的節點指標

程式 5-17  在二元搜尋樹中新增資料25 int InsertBST(int x)26 struct BSTreeNode p q27 p = root q = NULL28 while (p = NULL)29 q = p30 if (x == p-gtdata) return 0

若不考慮重複則可省略之31 if (x lt p-gtdata)32 p = p-gtleftchild33 else34 p = p-gtrightchild35

3636 p = (struct BSTreeNode ) p = (struct BSTreeNode ) malloc (sizeof(BSTreeNode))malloc (sizeof(BSTreeNode))

3737 p-gtdata = x p-gtdata = x3838 p-gtleftchild = p-gtrightchild = p-gtleftchild = p-gtrightchild = NULLNULL3939 if (root == NULL) root = p if (root == NULL) root = p4040 else if (x lt q-gtdata) else if (x lt q-gtdata)4141 q-gtleftchild = p q-gtleftchild = p4242 else else4343 q-gtrightchild = p q-gtrightchild = p4444 return 1 return 145 45

程式 程式 5-175-17  在二元搜尋樹中新增資料  在二元搜尋樹中新增資料 (( 續續 ))

範例 5-23 圖 (b) 為圖 (a) 新增整數 6 之後的結果圖 (c) (d) (e) (f) 則分別是逐步再加入 28 18

10 21 的結果

bull若目前的二元搜尋樹高度為 h 則新增資料得花O(h) 的時間找到其所應在的位置遂新增資料至二元搜尋樹中的時間複雜之度為 O(h)

593 自二元搜尋樹中刪除資料欲自二元搜尋樹中刪除資料可能會面臨兩種情況(1) 刪除節點 p 位於樹葉節點上如下圖所示 這是比較容易處理的情況

(2) 刪除的節點 p 不是樹葉刪除節點 p 後原 p 所指的節點可由 q ( q 中包含了 p 左子樹中的最大節點值)或 r ( r 中包含了 p 右子樹中的最小節點值)來取代以維持二元搜尋樹中資料的大小關係其結果分別如圖 (b) 和 (c) 所示而圖 (d) 則標明了指標會改變的節點位置即 p 會消失 father 的左或右子樹指標會異動 k(可能是 q 或 r )會移到原來 p 的位置其父節點 f 的左或右子樹指標會異動其中 father 為 p 父節點 f 為 k 的父節點

程式 5-18 自二元搜尋樹中刪除資料46 int DeleteBST (int x)47 Struct BSTreeNode p father k f48 p = root father = NULL49 while (p = NULL)50 if (x == p-gtdata) 找到 x 所在的節點 p 了51 if ((p-gtleftchild == NULL) ampamp 52 (p-gtrightchild == NULL))53 k = NULL p為樹葉節點54 else if (p-gtleftchild = NULL )x位於非子節

點55 f = p 找出 p左子樹的最右樹葉56 k = p-gtleftchild57 while (k-gtrightchild =NULL)58 f = k59 k = k-gtrightchild60 k將挪至原 p處61 if (p == f)62 f-gtleftchild = k-gtleftchild 63 else64 f-gtrightchild = k-gtleftchild 65

66 else p無左子樹67 f = p 找出 p右子樹的最左樹葉68 k = p-gtrightchild69 while (k-gtleftchild = NULL)70 f = k71 k = k -gtleftchild72 73 if (p == f)74 f-gtrightchild = k-gtrightchild 75 else76 f-gtleftchild = k-gtrightchild77 78 if (k = NULL) k挪至原 p處繼承 p的左右指標79 k-gtleftchild = p-gtleftchild80 k-gtrightchild = p-gtrightchild81 82 if (father == NULL) root = k83 else if (x lt father-gtdata) 決定 k是 father的左

或右兒子84 father-gtleftchild = k85 else86 father-gtrightchild = k87 free(p)88 return 1 成功地刪除 x於此傳回 1返回呼叫處89 51~89行為找到 x時應有的處理步驟

90 else 尚未找到 x繼續往下一階層找

91 father = p92 if (x lt p-gtdata)93 p = p-gtleftchild94 else95 p = p-gtrightchild96 97 98 return 0 未找到 x傳回 099

510  樹林 (forest)

bull 定義樹林(森林)是由 n 棵沒有交集的樹所構成的集合 n 0

下圖為一包含三棵樹的樹林

5101 轉換樹林為二元樹 之前我們以二元樹來表示樹因為二元樹有較多

好用的性質在此我們也將樹林轉換成二元樹看待

在 522 節中我們嘗試了用左子右兄弟的方式來表示樹這個方法事實上已將樹轉換成二元樹

即用左子右兄弟的結構重新組織樹林中的每一棵樹這些樹的樹根將視為兄弟而最左邊的樹則視為樹林二元樹的樹根如此一來樹林即可轉換成二元樹

範例 5-25右下圖為右上圖三棵樹所對應的三棵左子右兄弟二元樹

下圖則為上圖樹林所對應的一棵樹林二元樹

演算法 5-1  轉換樹林成二元樹輸入樹林 F = T1 T2 Tn

輸出 F 的二元樹表示 BT=(T1 T2 Tn)1 if (n == 0) 輸出空二元樹 2 else

3 root(T1 T2 Tn) = T1

4 leftsubtree(T1 T2 Tn) =

BT(T11 T12 T1m)5 BT(T11 T12 T1m)為 T1的子樹6 rightsubtree(T1 T2 Tn) =

BT(T2 T3 Tn)

樹林轉換成二元樹的過程樹林轉換成二元樹的過程

  • Slide 1
  • Slide 2
  • Slide 3
  • Slide 4
  • Slide 5
  • Slide 6
  • Slide 7
  • Slide 8
  • Slide 9
  • Slide 10
  • Slide 11
  • Slide 12
  • Slide 13
  • Slide 14
  • Slide 15
  • Slide 16
  • Slide 17
  • Slide 18
  • Slide 19
  • Slide 20
  • Slide 21
  • Slide 22
  • Slide 23
  • Slide 24
  • Slide 25
  • Slide 26
  • Slide 27
  • Slide 28
  • Slide 29
  • Slide 30
  • Slide 31
  • Slide 32
  • Slide 33
  • Slide 34
  • Slide 35
  • Slide 36
  • Slide 37
  • Slide 38
  • Slide 39
  • Slide 40
  • Slide 41
  • Slide 42
  • Slide 43
  • Slide 44
  • Slide 45
  • Slide 46
  • Slide 47
  • Slide 48
  • Slide 49
  • Slide 50
  • Slide 51
  • Slide 52
  • Slide 53
  • Slide 54
  • Slide 55
  • Slide 56
  • Slide 57
  • Slide 58
  • Slide 59
  • Slide 60
  • Slide 61
  • Slide 62
  • Slide 63
  • Slide 64
  • Slide 65
  • Slide 66
  • Slide 67
  • Slide 68
  • Slide 69
  • Slide 70
  • Slide 71
  • Slide 72
  • Slide 73
  • Slide 74
  • Slide 75
  • Slide 76
  • Slide 77
  • Slide 78
  • Slide 79
  • Slide 80
  • Slide 81
  • Slide 82
  • Slide 83
  • Slide 84
  • Slide 85
  • Slide 86
  • Slide 87
  • Slide 88
  • Slide 89
  • Slide 90
  • Slide 91
  • Slide 92
  • Slide 93
  • Slide 94
  • Slide 95
  • Slide 96
  • Slide 97
  • Slide 98
  • Slide 99
  • Slide 100
  • Slide 101
  • Slide 102
  • Slide 103
Page 22: 第五章 樹

bull 對二元樹而言任一節點的分支度可能為 0 1 或 2 分支度為 0 者是為樹葉

bull 樹葉的數目與分支度為 2 的節點數目存在著有趣的關係請見定理 5-3

定理 5-3 若 T 為一非空的二元樹 n0 為樹葉節點數目 n2為分支度為 2 的節點數目則 n0 = n2+1

證明令 n 為所有節點數目而 n1 為分支度為 1 的節點數目可得 n = n0 + n1 +n2 (1)令 B 為二元樹的分支數除了樹根外每一節點皆有一分支指向之所以

n = B+1 (亦可參考定理 5-1 )又 B = n1+2n2

且 n = n1 + 2n2 +1 (2)由式 (1) 和式 (2) 我們得到

n0 = n2 + 1

完滿二元樹 (full binary tree)

定義一個深度為 k 的完滿二元樹即為一深度為 k 且有 2k-1 個節點的二元樹k 0

bull 由定理 5-2(2) 可知深度為 k 的二元樹其最多節點數為 2k-1 這樣的樹會將樹上所有可能存放節點的位置都放滿完滿 (full) 之名因而生成

完備二元樹 (complete binary tree)我們可對二元樹上節點予以編號階層小者先編同一階

層則自左向右編號這樣的編號方式使我們定義出完備(complete) 二元樹

定義深度為 k 節點數為 n 的二元樹稱為完備二元樹若且唯若 (1) k = 1 時樹有一個節點

(2) 當 k 2 且 1iltk 時深度 i 有 2i-1 個節點且第 k 層的節點皆由第 k-1 層的分支由左至右逐一連接而成

完滿或完備二元樹之所以受人注意乃因它們可以將節點以較少的階層數存放

定理 5-4 n 個節點的完備或完滿二元樹其深度為 log2(n+1)

證明 完滿或完備二元樹的節點皆自階層

1 起排放任一階層 i 可排放 2i-1

個節點遂由定理 5-2 得知此 n 個節點所佔的深度為 log2(n+1)

正規二元樹 (formal binary tree)bull 在二元樹中非樹葉的節點又稱為內部節點

(internal node) 若所有內部節點恰有 2 個子節點即成為正規二元樹

定義若二元樹中所有內部節點恰有 2 個子節點則稱之為正規二元樹

正規二元樹常被引用在單淘汰賽正規二元樹常被引用在單淘汰賽制的各項賽程安排樹葉為參賽制的各項賽程安排樹葉為參賽隊伍內部節點為優勝隊伍樹隊伍內部節點為優勝隊伍樹根即為冠軍隊伍根即為冠軍隊伍

在單淘汰賽制中自在單淘汰賽制中自 n n 個隊伍中產個隊伍中產生冠軍須舉辦 生冠軍須舉辦 nn-1-1場比賽場比賽

54  二元樹的表示法541 以陣列表示二元樹定理 5-5 若一個具有 n 個節點的完備二元樹以循序的方式編號並依序存放在陣列 A 中即第 i 個節點放在 A[i] 中 1 i n 則

(1) 節點 i 的父節點位在 A[i2]處 i ne 1( i = 1 時其節點正為樹根無父節點)

(2) 若 2i n 則節點 i 的左兒子節點位在 A[2i] 處若 2i gtn 節點 i 沒有左子節點

(3) 若 2i+1 n 節點 i 的右兒子節點位 A[2i+1]處若 2i+1gtn 節點 i 沒有左子

節點

證明先用數學歸納法證明 (2) bull 歸納法的基礎當 i = 1 時其左子節點確實在 A[2] 處(除非 nlt2 此時節點 1 沒有左子節點)

bull 歸納法的假設假設當 I = k 時其左子節點確在 A[2i] = A[2k] 處(除非 2i 已大於 n 此時節點 i 無左子節點)

bull 歸納法的推論當 i = k+1 時因第 k 節點的左子節點在 A[2k] 處其右子節點則位在 A[2k+1] 處那麼第 k+1 節點的左子節點應緊臨 A[2k+1] 亦即位於 A[2k+2] = A[2(k+1)] 處(除非 2i 已大於 n 此時節點 i 無左子節點)

bull 由數學歸納法的原理得證定理 5-5 (2) (3) 由於 (2) 成立而且節點 i 的右子節點會緊臨

A[2i] 亦即其位於 A[2i+1]處除非 i 根本沒有右子節點得證定理 5-5 (3)

由 (2) 和 (3) 我們可知定理 5-5 (1) 成立

542 以鍵結串列表示二元樹bull 以陣列來表示完備二元樹算是相當方便但ndash一般的二元樹將導致陣列空間的浪費ndash如果二元樹中節點的新增刪除動作頻繁則會造成陣列對應資料的搬動隨而頻繁

bull 以鍵結串列表示二元樹

為方便辦識樹根我們常用 root 指標指向樹根做為樹鍵結串列的起點

陣列表示和鍵結表示的空間需求bull 完備二元樹的陣列表示和鍵結表示皆沒浪費儲存資料的空間

bull 但歪斜樹的陣列表示比起鍵結表示則浪費許多

bull 以 n 個節點的歪斜樹而言陣列表示法可能需要 2n-1 個陣列元素其中 2n-1-n 個陣列元素空間是浪費的而鍵結表示法只需要 n 個節點空間

二元樹節點的宣告程式 5-2

1 struct BTreeNode

2 struct BtreeNode leftchiled

3 int data

4 struct BTreeNode rightchild

5

6 struct BTreeNode root

bull 這樣的二元樹節點要取得兒子節點的指標十分方便但若有取得父節點的需求則嫌不足我們可以加上 parent 欄位指向父節點

55  二元樹的走訪bull 假如一組資料已用二元樹的組織在一起總有需求對其全數資料做動作例如計算所有數目印出所有資料在所有資料中搜尋某項資料hellip等此時即須對此二元樹做走訪 (traversal) 的運算利用走訪二元樹的同時將計算列印或搜尋的動作完成

bull 事實上走訪即在決定二元樹上資料被處理(計算列印或搜尋)的順序我們也希望走訪的演算法對任何節點皆一致是容易撰寫程式實作的

bull 若對一個二元樹上的節點而言 V 表示處理節點上的資料 L 表示走訪其左子樹 R 表示走訪其右子樹

55  二元樹的走訪 ( 續 )bull 因為對稱的緣故我們只考慮先走訪左邊再走訪右邊的情形那麼右圖只剩下三種走訪方式

bull 以 V 所在的相對位置分別對此三種走訪方式取名如下

(1) LVR 中序走訪 (inorder traversal)或中序表示法 (infix notation)

(2) LRV 後序走訪 (postorder traversal) 或後序表示法 (postfix notation)

(3) VLR 前序走訪 (preorder traversal)或前序表示法 (prefix notation)

551  中序走訪bull 利用遞迴的方式撰寫中序走訪的程序希望把二元樹中序走訪的

順序印出來

程式 5-3二元樹的中序走訪1 struct BTreeNode2 struct BTreeNode leftchild3 char data4 struct BTreeNode rightchild5 6 struct BTreeNode root7 void inorder(struct BTreeNode node)8 if (node = NULL)9 inorder(node-gtleftchild)10 cout ltlt node-gtdata11 inorder(node-gtrightchild)12 13

範例 5-12此為運算式二元樹

其對應中序表示運算式為 (A+B)C+D(E-F)

6 struct BTreeNode root7 void inorder(struct BTreeNode node)8 if (node = NULL)9 inorder(node-gtleftchild)10 cout ltlt node-gtdata11 inorder(node-gtrightchild)12 13

6 struct BTreeNode root7 void inorder(struct BTreeNode node)8 if (node = NULL)9 inorder(node-gtleftchild)10 cout ltlt node-gtdata11 inorder(node-gtrightchild)12 13

(A+B)C+D(E-F)

如何加上 ( )

552  後序走訪程式 5-4二元樹的後序走訪14 void postorder(struct BTreeNode node)

15 if (node =NULL)16 postorder(node-gtleftchild)17 postorder(node-gtrightchild)18 cout ltlt node-gtdata19 20

以後序走訪可得到AB+CDEF-+

其正為對應的後序運算式

553  前序走訪程式 5-5二元樹的前序走訪21 void preorder(struct BTreeNode node)

22 if (node = NULL)

23 cout ltlt node-gtdata

24 preorder(node-gtleftchild)

25 preorder(node-gtrightchild)

26

27

以前序走訪可得到++ABCD-EF

其正為對應的前序運算式

554 利用堆疊和迴圈取代遞迴進行二元樹的走訪1 struct BTreeNode 2 struct BTreeNode leftchild3 char data4 struct BtreeNode rightchild5 6 struct BTreeNode root7 struct StackNode8 struct BTreeNode treenode9 struct StackNode next1011 struct StackNode top

12 void push_node(struct BtreeNode node )13 struct StackNode old_top14 old_top = top15 top = (struct StackNode ) malloc

(sizeof(struct StackNode))16 top-gttreenode = node17 top-gtnext = old_top18 19 struct BTreeNode pop_node()20 struct BTreeNode Tnode21 struct StackNode old_top22 if (top == NULL)23 StackEmpty()24 else25 Tnode = top-gttreenode26 old_top = top27 top = top-gtnext28 free(old_top)29 return Tnode30 31

32 void inorder_Stack(struct BtreeNode node)33 do34 while (node = NULL)35 push_node(node)36 node = node-gtleftchild37 push all left children38 if (top = NULL)39 node = pop_node()40 cout ltlt node-gtdata41 node = node-gtrightchild42 inorder printing and check right child43 while ((top=NULL)||(node=NULL))44 top==NULL 堆疊已空 node==NULL沒有右子

6 struct BTreeNode root7 void inorder(struct BTreeNode node)8 if (node = NULL)9 inorder(node-gtleftchild)10 cout ltlt node-gtdata11 inorder(node-gtrightchild)12 13

中序表示為DBGEHAFIC

32 void inorder_Stack()33 struct BTreeNode node34 node = root35 do36 while (node = NULL)37 push_node(node)38 node = node-gtleftchild39 40 if (top = NULL)41 node = pop_node()42 cout ltlt node-gtdata43 node = node-gtrightchild44 45 while(top=NULL)||(node=NULL))46

45 void Preorder_Stack(struct BSTreeNode node)46 do47 while (node = NULL)48 cout ltlt node-gtdata)49 push_node(node)50 node = node-gtleftchild51 push all left children52 if (top = NULL)53 node = pop_node()54 node = node-gtrightchild55 56 while ((top=NULL)||(node=NULL))57

程式 5-5二元樹的前序走訪21 void preorder(struct BTreeNode node)22 if (node = NULL)23 cout ltlt node-gtdata24 preorder(node-gtleftchild)25 preorder(node-gtrightchild)26 27

58 void Postorder_Stack(struct BTreeNode node)59 do60 while (node = NULL)61 cout ltlt node-gtdata62 push_node(node)63 node = node-gtrightchild64 push all left children65 if (top = NULL)66 node = pop_node()67 node = node-gtleftchild68 69 while ((top=NULL)||(node=NULL))70

程式 5-4二元樹的後序走訪14 void postorder(struct BTreeNode node)15 if (node =NULL)16 postorder(node-gtleftchild)17 postorder(node-gtrightchild)18 cout ltlt node-gtdata19 20

555  階層走訪bull 「階層走訪」 (level-order traversal) 是依階層

的順序進行二元樹的走訪先走訪階層小的節點後走訪階層大的節點同一階層者則依自左向右的順序走訪

bull 對下圖的二元樹進行階層走訪的結果為ABCDEFGHI

bull 對同一階層而言先走訪的節點其子節點亦在下一階層中先被走訪

bull 這種「先進先出」的特性恰可用佇列予以儲存與處理

程式 5-7 利用佇列進行二元樹的階層走訪

1 struct BTreeNode2 struct BTreeNode leftchild3 char data4 struct BTreeNode rightchild5 6 struct BTreeNode root7 struct QNode8 struct BTreeNode treenode9 struct QNode next10 11 struct QNode front rear

12 void AddQueue(struct BTreeNode Tnode)13 struct QNode node14 node=(struct QNode )malloc(sizeof(struct QNode))15 node-gttreenode = Tnode16 node-gtnext = NULL17 if (front == NULL) front = node19 else rear-gtnext = node20 rear = node21

22 struct BTreeNode DeleteQueue()23 struct BTreeNode Tnode24 struct QNode old_front25 if (rear == NULL)26 QueueEmpty()27 else28 Tnode = front-gttreenode29 old_front = front30 front = front-gtnext31 free(old_front)32 return Tnode33 34 35 void LevelOrder (struct BTreeNode node)36 AddQueue(node)37 while (front =NULL)38 node = DeleteQueue()39 cout ltlt node-gtdata40 if (node-gtleftchild = NULL)41 AddQueue(node-gtleftchild)42 if (node-gtrightchild = NULL)43 AddQueue(node-gtrightchild)44 45

35 void LevelOrder (struct BTreeNode node)36 AddQueue(node)37 while (front =NULL)38 node = DeleteQueue()39 cout ltlt node-gtdata40 if (node-gtleftchild = NULL)41 AddQueue(node-gtleftchild)42 if (node-gtrightchild = NULL)43 AddQueue(node-gtrightchild)44 45

bull Infix ABCbull Postfix ABC CBA ACB BCA

BAC CAB

B

A CA

B

C

B

A

C

C

B

AB

A

C

二元樹中序與前序式中序與後序式bull 每一棵二元樹皆有唯一的一對中序與前序表示法也有唯一的中序與後序次序

bull 換句話說給予一對中序與前序或中序與後序即可決定一棵二元樹

bull 然而給予前序和後序次序並不能決定唯一的二元樹可能會產生兩棵不同的二元樹

bull 想想階層走訪和中序走訪順序可否決定唯一的二元樹

A

B C

H J D E

F G

prefix ABHJCDFGEinfix HBJAFDGCEpostfix HJBFGDECA

VLRLVRLRV

A

prefix BHJinfix HBJ

prefix CDFGEinfix FDGCE

prefix ABHJCDFGEinfix HBJAFDGCE

A

B C

H J D E

F G

prefix ABHJCDFGE

infix HBJAFDGCEk-1

struct BTreeNode BTree_InfixPrefix (AnsiString infix AnsiString prefix)

int k struct BTreeNode node if (infixLength==0) return NULLnode = (struct BTreeNode ) malloc (sizeof(struct BTreeNode))node-gtdata = prefix[1]k = find(prefix[1] infix)node-gtleftchild = BTree_InfixPrefix

(infix[1k-1] prefix[2k])node-gtrightchild = BTree_InfixPrefix(infix[k+1infixLength]prefix[k+1prefixLength])

VLR

LVRk1

k2 k+1~

k+1~

1

struct BTreeNode BTree_InfixPostfix (AnsiString infix AnsiString prefix)

int k struct BTreeNode node if (infixLength==0) return NULLnode = (struct BTreeNode ) malloc (sizeof(struct BTreeNode))node-gtdata = postfix[postfixLength]k = find(postfix[postfixLength] infix)node-gtleftchild = BTree_InfixPrefix

(infix[1k-1] postfix[1k-1])node-gtrightchild = BTree_InfixPrefix(infix[k+1infixLength]postfix[k+1postfixLength-1])

56  二元樹的複製與相等測試程式 5-8 二元樹的複製1 struct BTreeNode2 struct BTreeNode leftchild3 char data4 struct BTreeNode rightchild5 6 struct BTreeNode root7 struct BTreeNode Copy(struct BTreeNode TreeRoot)8 struct BTreeNode CopyRoot9 if (TreeRoot == NULL)10 return NULL11 CopyRoot =(struct BTreeNode ) malloc (sizeof(BTreeNode))12 CopyRoot-gtdata = TreeRoot-gtdata13 CopyRoot-gtleftchild = Copy(TreeRoot-gtleftchild)14 CopyRoot-gtrightchild = Copy(TreeRoot-

gtrightchild)15 return CopyRoot16

程式 5-9 檢測兩棵二元樹是否相等1 struct BTreeNode2 struct BTreeNode leftchild3 char data4 struct BTreeNode rightchild5 6 struct BtreeNode rightchild7 int Equal(struct BTreeNode ustruct BTreeNode v)8 if ((u==NULL) ampamp (v==NULL)) return 19 if ((u=NULL) ampamp (v=NULL)10 ampamp (u-gtdata == v-gtdata )11 ampamp Equal(u-gtleftchildv-gtleftchild)12 ampamp Equal(u-gtrightchildv-gtrightchild))

13 return 114 return 015

Counting Nodes in a Binary Tree

int CountingNodes(struct BTreeNodes node)

if (node == NULL) return 0 int leftchildren = CountingNodes(node-gtleftchild)

int rightchildren= CountingNodes(node-gtrightchild)

return leftchildren+rightchildren

Counting Leaves in a Binary Tree

int CountingLeaves(struct BTreeNodes node)

if (node == NULL) return 0 int leftleaves = CountingLeaves(node-gtleftchild)

int rightchildren= CountingLeaves(node-gtrightchild)

if (leftleaves==0 ampamp leftleaves==0) return 1

return leftleaves + leftleaves

57  引線二元樹bull 在二元樹的鍵結表示中樹葉節點的兩個子樹指標皆指向 NULL 由定理 5-1 得知 n = E+1 其中 n 為節點個數 E 為分支數

bull 而 n 個鍵結節點有 2n 個指標空間每個分支恰佔用一個指標空間共有 E (=n-1) 個指標有用到(不是空的)而共有 n+1 個指標空間是 NULL 比非空的節點還多

bull 有學者提出對這些「放空指標的空間」加以利用的概念mdash與其放空指標不如放指向其它節點的指標稱之為引線 (thread) 使得某些運算(如走訪hellip等)可以加快

bull 這個概念發展出了引線二元樹 (threaded binary tree) 這種資料結構

範例 5-17下圖為加了引線的二元樹

加入的引線將使二元樹的中序走訪更加便利沿著上右圖箭頭所指示的順序走訪即可完成此二元樹的中序走訪GDHBAIECF 為此二元樹的中序表示

bull 範例 5-17 中所加的引線目的在方便二元樹的中序走訪

bull 對樹葉節點 p 而言令其資料為 P 則引線指標的規則如下

(1) p 的左子樹指標指向中序表示中 P 的前一個 元素(前接元素 (predecessor) )

(2) p 的右子樹指標指向中序表示中 P 的後一個 元素(後繼元素 (successor) )

為了區別節點指標放的是一般指標(內部節點)抑或是引線指標(樹葉節點)我們另以 2 個欄位分別區別左和右子樹指標空間存放的對象其節點的記憶體配置有如下圖所示

其中 leftthread (rightthread) 為 0 時表示 leftchild (rightchild) 存放的是一般節點指標而 leftthread (rightthread) 為 1 時表示 leftchild (rightchild) 存放的是引線指標1 struct TBTreeNode2 int leftthread3 struct TBTreeNode leftchild4 char data5 struct TBTreeNode rightchild6 int rightthread7

由右側的引線二元樹可知有最左和最右兩條引線尚無妥善的安排我們另設計一空的引線節點做為樹根如下圖所示

571  引線二元樹的中序走訪決定節點 p 在中序表示中的後繼節點 q (簡稱中序後繼點)

程式 5-10決定節點 node的中序後繼點1 struct TBTreeNode2 int leftthread3 struct TBTreeNode leftchild4 char data5 struct TBTreeNode rightchild6 int rightthread7 8 struct TBTreeNode root9 struct TBTreeNode Next(struct TBTreeNode node)

10 struct TBTreeNode temp11 temp = node-gtrightchild12 if (node-gtrightthead) return temp13 while (temp-gtleftthead) temp=temp-gtleftchild

14 return temp15

程式 5-11引線二元樹的中序表示(接程式 5-10)1 void InorderTBTree()2 struct TBTreeNode node3 for(node=Next(root)node=rootnode=Next(node))

4 cout ltlt node-gtdata5

bull只要 node 是引線二元樹上的任一節點透過呼叫 Next(node) 即可找到其在中序表示中的後繼節點bull(試想若程式 5-10 中的程序 Next 所傳入的參數指標正是 root Next(root) 是否可正常運作請思考)bull於是只須從中序表示中的第一節點起讓每一節點 node 都執行 Next(node) 程序即可得引線二元樹的中序表示

572  在引線二元樹中加入節點在引線二元樹中樹葉節點節點的右子樹指標所指向或內部節點右子樹的最左樹葉為其中序後繼節點而且任一節點若無左子樹則其左子樹指標乃指向其中序前接節點hellip這些性質必須在節點新增進入引線二元樹時也要保持接下來討論節點 new 插入成為引線二元樹的節點 p 的右子節點的情形至於插入成為左子節點的情形則讓各位模擬推敲

572572  在引線二元樹中加入節點 在引線二元樹中加入節點 (( 續續 ))

newnew 會成為會成為 pp 的右子節點的情況共有以下兩種的右子節點的情況共有以下兩種(1) (1) 若若 pp 沒有右子節點 沒有右子節點 ((p-gtrightthread p-gtrightthread 為為1) 1) 如圖如圖 ((a) a) 則 則 new new 插入後應形成如圖插入後應形成如圖 ((b) b) 所示所示

(2)(2) 若若 pp 有右子節點 有右子節點 ((p-gtrightthread p-gtrightthread 為為0) 0) 如圖如圖 ((c) c) 則 則 new new 插入後應形成如插入後應形成如圖圖 ((d) d) 所示所示

程式 5-12  插入節點 new 成為引線二元樹中節點 p的右子節點(上接程式 5-10 5-11 )

1 void InsertRight(struct TBTreeNode p 2 struct TBTreeNode new)3 struct TBTreeNode q4 new-gtrightchild = p-gtrightchild5 new-gtrightthead = p-gtrightthread6 new-gtleftchild = p7 new-gtleftthread = 18 p-gtrightchild = new9 p-gtrightthead = 010 if (new-gtrightthread)11 q = Next(new)12 q-gtleftchild = new13 14

58  堆積bull 「堆積」 (heap) 是一個特殊的完備二元樹節點的資料與其子節點的資料之間有事先定好的大小關係存在bull 定義ndash最大堆疊為一完備二元樹任一節點的資料內容不小於其子節點的資料內容ndash最小堆疊為一完備二元樹任一節點的資料內容不大於其子節點的資料內容

581  插入節點至最大堆積中最大堆積是一完備二元樹所以如圖 (a) 之最大堆積於加入新節點後樹的形狀必須如圖 (b) 所示灰色節點即為新節點若加入的節點資料分別為 1 9 12 則其最大堆積將分別變成如圖 (c) (d) (e) 所示

程式 5-13 新增節點至最大堆積中最大堆積本身是一棵完備二元樹可以用陣列表示之 1 define maxsize 1002 int heap[maxsize]3 int n i4 void InsertHeap(int x)5 if (n == maxsize) HeapFull()6 else7 n++8 i = n9 while ((i gt 1) ampamp (x gt heap[i2]))10 heap[i] = heap[i2]11 i = 212 13 heap[i] = x14 16

程序 InsertHeap 在插入第 n 個資料時需要的時間複雜度為 O(logn)

1 100

2 93

3 84

4 77

5 65

6 81

7 79

8 66

9 70

10 50

100

84

77

93

8165

66 70

79

50

1

2 3

4 5 6 7

8 9 10

1 define maxsize 1002 int heap[maxsize]3 int n i4 void InsertHeap(int x)5 if (n == maxsize) HeapFull()6 else7 n++8 i = n9 while ((i gt 1) ampamp (x gt heap[i2]))10 heap[i] = heap[i2]11 i = 212 13 heap[i] = x14 16

582 刪除最大堆積中的最大資料圖 (a) 中的最大堆積在刪除最大資料後其樹形應變成如圖 (b) 所示將最未一個樹葉節點的資料 6 暫挪至樹根節點內如圖 (c) 再將圖 (c) 中非最大堆積的結構調整成為最大堆積 10 和 8 中選大者與 6對調即成圖 (d)

程式 5-14 刪除最大堆積中的最大資料(上接程式 5-13 )1 int DeleteHeap()2 int x i j3 if (n == 0) HeapEmpty()4 else5 x = heap[1]6 heap[1] = heap[n]7 n--8 i = 19 while (i lt= n2)10 if (heap[2i] gt heap[2i+1])11 j = 2i12 else13 j = 2i+114 if (heap[i]gtheap[j]) break15 swap(heap[i] heap[j])16 i = j17 18 return x19 20

其時間複雜度與其堆積高度相同是為O(logn) 其中 n 為堆積中的元素個數

1 100

2 93

3 84

4 77

5 65

6 81

7 79

8 66

9 70

10 50

100

84

77

93

8165

66 70

79

50

1

2 3

4 5 6 7

8 9 10

1 int DeletHeap()2 int x i j3 if(n == 0) HeapEmpty()4 else5 x = heap[1]6 heap[1] = heap[n]7 n--8 i = 19 while (i lt= n2)10 if ([heap[2i]] gt heap[2i+1])11 j = 2i12 else13 j = 2i+114 if (heap[i]gtheap[j]) break15 swap(heap[i] heap[j])16 i = j17 18 return x19 20

583  優先佇列bull 由於最大堆積(或最小堆積)的結構可提供最大元素(或最小元素)而其維護堆積的成本為 O(logn) n 為總元素個數

bull 堆積常被當成「優先佇列」 (priority queue) 使用mdash此時佇列被組織成完備二元樹若新加入佇列的資料有大小關係優先順序的需求時此優先佇列即以 O(logn) 的代價將新資料放入優先佇列(可能是最大或最小堆積依實際大小關係優先順序的需求而定)中mdash提供依元素的優先順序做為處理順序的服務

bull 一般的佇列為先進先出先到的先行處理優先佇列則是依據其優先順序優先順序高者先行處理與來到的先後無關

範例 5-21bull 在「分時」 (time sharing) 的作業系統中我們不希望使用中央處理器 (Central Processing Unit CPU) 時間短的「處理程序」 (process) 等候過久於是有「最短時間工作優先」 (shortest-job-first) 的排程策略此時等候的處理程序即可組織成之一最小堆積而 CPU 每次即取出存於樹根的處理程序予以處理任何新來的處理程序則以 O(logn) 的時間加入此優先佇列(最小堆積)中

bull 注意之前提過的線性佇列新來的直接加入成為新的尾端元素花O(1) 的時間然線性佇列本身沒有大小的關係每次要取出最大(小)的資料得花O(n) 的代價(在所有元素中挑出最大(小)者) n 為佇列內的元素個數而優先佇列是在新元素加入時要花O(logn) 的時間至於取出最大(小)則只須 O(1) 的時間

59  二元搜尋樹bull 二元搜尋樹是一棵二元樹可能是空二元樹若不為空二元樹則滿足下列性質ndash所有節點內的資料內容是相異的 ndash左子節點的資內容 ( 如果有 ) 比父節點的資料內容小 ndash右子節點的資料內容 ( 如果有 ) 比父節點的資料內容大 ndash以左和右子節點為樹根的左子樹和右子樹也是二元搜尋樹

在二元搜尋樹中搜尋資料二元搜尋樹對資料大小的掌握十分明確若恰與節點 p 資料相等則搜尋成功若欲搜尋的資料 x 比樹節點 p 中的資料小 則只須搜尋節點 p 的左子樹若比節點 p 中的資料大 則只須搜尋 p 的右子樹這樣的判斷可以遞迴地應用在各子樹節點 中

程式 5-15 在二元搜尋樹中搜尋資料(遞迴呼叫)1 struct BSTreeNode2 struct BSTreeNode leftchild3 int data4 struct BSTreeNode rightchild5 6 struct BSTreeNode root7 struct BSTreeNode SearchBST(

struct BSTreeNode tree int x)8 if (tree == NULL) return NULL9 if (x == tree-gtdata) return tree10 if (x lt tree-gtdata)11 return SearchBST(tree-gtleftchild x)

12 return SearchBST(tree-gtrightchild x)13

程式 5-16 搜尋二元搜尋樹的資料(非遞迴呼叫)在二元搜尋樹十分龐大時遞迴呼叫的效率不會太好可利用迴圈來完成搜尋的動作以提高執行效率請看程式 5-16

14 struct BSTreeNode Search BST_iterative15 (struct BSTreeNode node int x)16 while (node = NULL)17 if (x == node-gtdata) return node18 if (x lt node-gtdata)19 node = node-gtleftchild20 else21 node = node-gtrightchild22 23 return NULL24

592  新增資料於二元搜尋樹中欲新增資料進入一二元搜尋樹中得先決定該資料在二元搜尋樹中應出現的位置這可透過上節提到的搜尋動作來解決

在二元搜尋樹定義的性質 (1) 中我們限定了元素必須相異所以搜尋新資料的結果必是 NULL

必須將搜尋的結果改為新增資料所應在位置的節點指標

程式 5-17  在二元搜尋樹中新增資料25 int InsertBST(int x)26 struct BSTreeNode p q27 p = root q = NULL28 while (p = NULL)29 q = p30 if (x == p-gtdata) return 0

若不考慮重複則可省略之31 if (x lt p-gtdata)32 p = p-gtleftchild33 else34 p = p-gtrightchild35

3636 p = (struct BSTreeNode ) p = (struct BSTreeNode ) malloc (sizeof(BSTreeNode))malloc (sizeof(BSTreeNode))

3737 p-gtdata = x p-gtdata = x3838 p-gtleftchild = p-gtrightchild = p-gtleftchild = p-gtrightchild = NULLNULL3939 if (root == NULL) root = p if (root == NULL) root = p4040 else if (x lt q-gtdata) else if (x lt q-gtdata)4141 q-gtleftchild = p q-gtleftchild = p4242 else else4343 q-gtrightchild = p q-gtrightchild = p4444 return 1 return 145 45

程式 程式 5-175-17  在二元搜尋樹中新增資料  在二元搜尋樹中新增資料 (( 續續 ))

範例 5-23 圖 (b) 為圖 (a) 新增整數 6 之後的結果圖 (c) (d) (e) (f) 則分別是逐步再加入 28 18

10 21 的結果

bull若目前的二元搜尋樹高度為 h 則新增資料得花O(h) 的時間找到其所應在的位置遂新增資料至二元搜尋樹中的時間複雜之度為 O(h)

593 自二元搜尋樹中刪除資料欲自二元搜尋樹中刪除資料可能會面臨兩種情況(1) 刪除節點 p 位於樹葉節點上如下圖所示 這是比較容易處理的情況

(2) 刪除的節點 p 不是樹葉刪除節點 p 後原 p 所指的節點可由 q ( q 中包含了 p 左子樹中的最大節點值)或 r ( r 中包含了 p 右子樹中的最小節點值)來取代以維持二元搜尋樹中資料的大小關係其結果分別如圖 (b) 和 (c) 所示而圖 (d) 則標明了指標會改變的節點位置即 p 會消失 father 的左或右子樹指標會異動 k(可能是 q 或 r )會移到原來 p 的位置其父節點 f 的左或右子樹指標會異動其中 father 為 p 父節點 f 為 k 的父節點

程式 5-18 自二元搜尋樹中刪除資料46 int DeleteBST (int x)47 Struct BSTreeNode p father k f48 p = root father = NULL49 while (p = NULL)50 if (x == p-gtdata) 找到 x 所在的節點 p 了51 if ((p-gtleftchild == NULL) ampamp 52 (p-gtrightchild == NULL))53 k = NULL p為樹葉節點54 else if (p-gtleftchild = NULL )x位於非子節

點55 f = p 找出 p左子樹的最右樹葉56 k = p-gtleftchild57 while (k-gtrightchild =NULL)58 f = k59 k = k-gtrightchild60 k將挪至原 p處61 if (p == f)62 f-gtleftchild = k-gtleftchild 63 else64 f-gtrightchild = k-gtleftchild 65

66 else p無左子樹67 f = p 找出 p右子樹的最左樹葉68 k = p-gtrightchild69 while (k-gtleftchild = NULL)70 f = k71 k = k -gtleftchild72 73 if (p == f)74 f-gtrightchild = k-gtrightchild 75 else76 f-gtleftchild = k-gtrightchild77 78 if (k = NULL) k挪至原 p處繼承 p的左右指標79 k-gtleftchild = p-gtleftchild80 k-gtrightchild = p-gtrightchild81 82 if (father == NULL) root = k83 else if (x lt father-gtdata) 決定 k是 father的左

或右兒子84 father-gtleftchild = k85 else86 father-gtrightchild = k87 free(p)88 return 1 成功地刪除 x於此傳回 1返回呼叫處89 51~89行為找到 x時應有的處理步驟

90 else 尚未找到 x繼續往下一階層找

91 father = p92 if (x lt p-gtdata)93 p = p-gtleftchild94 else95 p = p-gtrightchild96 97 98 return 0 未找到 x傳回 099

510  樹林 (forest)

bull 定義樹林(森林)是由 n 棵沒有交集的樹所構成的集合 n 0

下圖為一包含三棵樹的樹林

5101 轉換樹林為二元樹 之前我們以二元樹來表示樹因為二元樹有較多

好用的性質在此我們也將樹林轉換成二元樹看待

在 522 節中我們嘗試了用左子右兄弟的方式來表示樹這個方法事實上已將樹轉換成二元樹

即用左子右兄弟的結構重新組織樹林中的每一棵樹這些樹的樹根將視為兄弟而最左邊的樹則視為樹林二元樹的樹根如此一來樹林即可轉換成二元樹

範例 5-25右下圖為右上圖三棵樹所對應的三棵左子右兄弟二元樹

下圖則為上圖樹林所對應的一棵樹林二元樹

演算法 5-1  轉換樹林成二元樹輸入樹林 F = T1 T2 Tn

輸出 F 的二元樹表示 BT=(T1 T2 Tn)1 if (n == 0) 輸出空二元樹 2 else

3 root(T1 T2 Tn) = T1

4 leftsubtree(T1 T2 Tn) =

BT(T11 T12 T1m)5 BT(T11 T12 T1m)為 T1的子樹6 rightsubtree(T1 T2 Tn) =

BT(T2 T3 Tn)

樹林轉換成二元樹的過程樹林轉換成二元樹的過程

  • Slide 1
  • Slide 2
  • Slide 3
  • Slide 4
  • Slide 5
  • Slide 6
  • Slide 7
  • Slide 8
  • Slide 9
  • Slide 10
  • Slide 11
  • Slide 12
  • Slide 13
  • Slide 14
  • Slide 15
  • Slide 16
  • Slide 17
  • Slide 18
  • Slide 19
  • Slide 20
  • Slide 21
  • Slide 22
  • Slide 23
  • Slide 24
  • Slide 25
  • Slide 26
  • Slide 27
  • Slide 28
  • Slide 29
  • Slide 30
  • Slide 31
  • Slide 32
  • Slide 33
  • Slide 34
  • Slide 35
  • Slide 36
  • Slide 37
  • Slide 38
  • Slide 39
  • Slide 40
  • Slide 41
  • Slide 42
  • Slide 43
  • Slide 44
  • Slide 45
  • Slide 46
  • Slide 47
  • Slide 48
  • Slide 49
  • Slide 50
  • Slide 51
  • Slide 52
  • Slide 53
  • Slide 54
  • Slide 55
  • Slide 56
  • Slide 57
  • Slide 58
  • Slide 59
  • Slide 60
  • Slide 61
  • Slide 62
  • Slide 63
  • Slide 64
  • Slide 65
  • Slide 66
  • Slide 67
  • Slide 68
  • Slide 69
  • Slide 70
  • Slide 71
  • Slide 72
  • Slide 73
  • Slide 74
  • Slide 75
  • Slide 76
  • Slide 77
  • Slide 78
  • Slide 79
  • Slide 80
  • Slide 81
  • Slide 82
  • Slide 83
  • Slide 84
  • Slide 85
  • Slide 86
  • Slide 87
  • Slide 88
  • Slide 89
  • Slide 90
  • Slide 91
  • Slide 92
  • Slide 93
  • Slide 94
  • Slide 95
  • Slide 96
  • Slide 97
  • Slide 98
  • Slide 99
  • Slide 100
  • Slide 101
  • Slide 102
  • Slide 103
Page 23: 第五章 樹

完滿二元樹 (full binary tree)

定義一個深度為 k 的完滿二元樹即為一深度為 k 且有 2k-1 個節點的二元樹k 0

bull 由定理 5-2(2) 可知深度為 k 的二元樹其最多節點數為 2k-1 這樣的樹會將樹上所有可能存放節點的位置都放滿完滿 (full) 之名因而生成

完備二元樹 (complete binary tree)我們可對二元樹上節點予以編號階層小者先編同一階

層則自左向右編號這樣的編號方式使我們定義出完備(complete) 二元樹

定義深度為 k 節點數為 n 的二元樹稱為完備二元樹若且唯若 (1) k = 1 時樹有一個節點

(2) 當 k 2 且 1iltk 時深度 i 有 2i-1 個節點且第 k 層的節點皆由第 k-1 層的分支由左至右逐一連接而成

完滿或完備二元樹之所以受人注意乃因它們可以將節點以較少的階層數存放

定理 5-4 n 個節點的完備或完滿二元樹其深度為 log2(n+1)

證明 完滿或完備二元樹的節點皆自階層

1 起排放任一階層 i 可排放 2i-1

個節點遂由定理 5-2 得知此 n 個節點所佔的深度為 log2(n+1)

正規二元樹 (formal binary tree)bull 在二元樹中非樹葉的節點又稱為內部節點

(internal node) 若所有內部節點恰有 2 個子節點即成為正規二元樹

定義若二元樹中所有內部節點恰有 2 個子節點則稱之為正規二元樹

正規二元樹常被引用在單淘汰賽正規二元樹常被引用在單淘汰賽制的各項賽程安排樹葉為參賽制的各項賽程安排樹葉為參賽隊伍內部節點為優勝隊伍樹隊伍內部節點為優勝隊伍樹根即為冠軍隊伍根即為冠軍隊伍

在單淘汰賽制中自在單淘汰賽制中自 n n 個隊伍中產個隊伍中產生冠軍須舉辦 生冠軍須舉辦 nn-1-1場比賽場比賽

54  二元樹的表示法541 以陣列表示二元樹定理 5-5 若一個具有 n 個節點的完備二元樹以循序的方式編號並依序存放在陣列 A 中即第 i 個節點放在 A[i] 中 1 i n 則

(1) 節點 i 的父節點位在 A[i2]處 i ne 1( i = 1 時其節點正為樹根無父節點)

(2) 若 2i n 則節點 i 的左兒子節點位在 A[2i] 處若 2i gtn 節點 i 沒有左子節點

(3) 若 2i+1 n 節點 i 的右兒子節點位 A[2i+1]處若 2i+1gtn 節點 i 沒有左子

節點

證明先用數學歸納法證明 (2) bull 歸納法的基礎當 i = 1 時其左子節點確實在 A[2] 處(除非 nlt2 此時節點 1 沒有左子節點)

bull 歸納法的假設假設當 I = k 時其左子節點確在 A[2i] = A[2k] 處(除非 2i 已大於 n 此時節點 i 無左子節點)

bull 歸納法的推論當 i = k+1 時因第 k 節點的左子節點在 A[2k] 處其右子節點則位在 A[2k+1] 處那麼第 k+1 節點的左子節點應緊臨 A[2k+1] 亦即位於 A[2k+2] = A[2(k+1)] 處(除非 2i 已大於 n 此時節點 i 無左子節點)

bull 由數學歸納法的原理得證定理 5-5 (2) (3) 由於 (2) 成立而且節點 i 的右子節點會緊臨

A[2i] 亦即其位於 A[2i+1]處除非 i 根本沒有右子節點得證定理 5-5 (3)

由 (2) 和 (3) 我們可知定理 5-5 (1) 成立

542 以鍵結串列表示二元樹bull 以陣列來表示完備二元樹算是相當方便但ndash一般的二元樹將導致陣列空間的浪費ndash如果二元樹中節點的新增刪除動作頻繁則會造成陣列對應資料的搬動隨而頻繁

bull 以鍵結串列表示二元樹

為方便辦識樹根我們常用 root 指標指向樹根做為樹鍵結串列的起點

陣列表示和鍵結表示的空間需求bull 完備二元樹的陣列表示和鍵結表示皆沒浪費儲存資料的空間

bull 但歪斜樹的陣列表示比起鍵結表示則浪費許多

bull 以 n 個節點的歪斜樹而言陣列表示法可能需要 2n-1 個陣列元素其中 2n-1-n 個陣列元素空間是浪費的而鍵結表示法只需要 n 個節點空間

二元樹節點的宣告程式 5-2

1 struct BTreeNode

2 struct BtreeNode leftchiled

3 int data

4 struct BTreeNode rightchild

5

6 struct BTreeNode root

bull 這樣的二元樹節點要取得兒子節點的指標十分方便但若有取得父節點的需求則嫌不足我們可以加上 parent 欄位指向父節點

55  二元樹的走訪bull 假如一組資料已用二元樹的組織在一起總有需求對其全數資料做動作例如計算所有數目印出所有資料在所有資料中搜尋某項資料hellip等此時即須對此二元樹做走訪 (traversal) 的運算利用走訪二元樹的同時將計算列印或搜尋的動作完成

bull 事實上走訪即在決定二元樹上資料被處理(計算列印或搜尋)的順序我們也希望走訪的演算法對任何節點皆一致是容易撰寫程式實作的

bull 若對一個二元樹上的節點而言 V 表示處理節點上的資料 L 表示走訪其左子樹 R 表示走訪其右子樹

55  二元樹的走訪 ( 續 )bull 因為對稱的緣故我們只考慮先走訪左邊再走訪右邊的情形那麼右圖只剩下三種走訪方式

bull 以 V 所在的相對位置分別對此三種走訪方式取名如下

(1) LVR 中序走訪 (inorder traversal)或中序表示法 (infix notation)

(2) LRV 後序走訪 (postorder traversal) 或後序表示法 (postfix notation)

(3) VLR 前序走訪 (preorder traversal)或前序表示法 (prefix notation)

551  中序走訪bull 利用遞迴的方式撰寫中序走訪的程序希望把二元樹中序走訪的

順序印出來

程式 5-3二元樹的中序走訪1 struct BTreeNode2 struct BTreeNode leftchild3 char data4 struct BTreeNode rightchild5 6 struct BTreeNode root7 void inorder(struct BTreeNode node)8 if (node = NULL)9 inorder(node-gtleftchild)10 cout ltlt node-gtdata11 inorder(node-gtrightchild)12 13

範例 5-12此為運算式二元樹

其對應中序表示運算式為 (A+B)C+D(E-F)

6 struct BTreeNode root7 void inorder(struct BTreeNode node)8 if (node = NULL)9 inorder(node-gtleftchild)10 cout ltlt node-gtdata11 inorder(node-gtrightchild)12 13

6 struct BTreeNode root7 void inorder(struct BTreeNode node)8 if (node = NULL)9 inorder(node-gtleftchild)10 cout ltlt node-gtdata11 inorder(node-gtrightchild)12 13

(A+B)C+D(E-F)

如何加上 ( )

552  後序走訪程式 5-4二元樹的後序走訪14 void postorder(struct BTreeNode node)

15 if (node =NULL)16 postorder(node-gtleftchild)17 postorder(node-gtrightchild)18 cout ltlt node-gtdata19 20

以後序走訪可得到AB+CDEF-+

其正為對應的後序運算式

553  前序走訪程式 5-5二元樹的前序走訪21 void preorder(struct BTreeNode node)

22 if (node = NULL)

23 cout ltlt node-gtdata

24 preorder(node-gtleftchild)

25 preorder(node-gtrightchild)

26

27

以前序走訪可得到++ABCD-EF

其正為對應的前序運算式

554 利用堆疊和迴圈取代遞迴進行二元樹的走訪1 struct BTreeNode 2 struct BTreeNode leftchild3 char data4 struct BtreeNode rightchild5 6 struct BTreeNode root7 struct StackNode8 struct BTreeNode treenode9 struct StackNode next1011 struct StackNode top

12 void push_node(struct BtreeNode node )13 struct StackNode old_top14 old_top = top15 top = (struct StackNode ) malloc

(sizeof(struct StackNode))16 top-gttreenode = node17 top-gtnext = old_top18 19 struct BTreeNode pop_node()20 struct BTreeNode Tnode21 struct StackNode old_top22 if (top == NULL)23 StackEmpty()24 else25 Tnode = top-gttreenode26 old_top = top27 top = top-gtnext28 free(old_top)29 return Tnode30 31

32 void inorder_Stack(struct BtreeNode node)33 do34 while (node = NULL)35 push_node(node)36 node = node-gtleftchild37 push all left children38 if (top = NULL)39 node = pop_node()40 cout ltlt node-gtdata41 node = node-gtrightchild42 inorder printing and check right child43 while ((top=NULL)||(node=NULL))44 top==NULL 堆疊已空 node==NULL沒有右子

6 struct BTreeNode root7 void inorder(struct BTreeNode node)8 if (node = NULL)9 inorder(node-gtleftchild)10 cout ltlt node-gtdata11 inorder(node-gtrightchild)12 13

中序表示為DBGEHAFIC

32 void inorder_Stack()33 struct BTreeNode node34 node = root35 do36 while (node = NULL)37 push_node(node)38 node = node-gtleftchild39 40 if (top = NULL)41 node = pop_node()42 cout ltlt node-gtdata43 node = node-gtrightchild44 45 while(top=NULL)||(node=NULL))46

45 void Preorder_Stack(struct BSTreeNode node)46 do47 while (node = NULL)48 cout ltlt node-gtdata)49 push_node(node)50 node = node-gtleftchild51 push all left children52 if (top = NULL)53 node = pop_node()54 node = node-gtrightchild55 56 while ((top=NULL)||(node=NULL))57

程式 5-5二元樹的前序走訪21 void preorder(struct BTreeNode node)22 if (node = NULL)23 cout ltlt node-gtdata24 preorder(node-gtleftchild)25 preorder(node-gtrightchild)26 27

58 void Postorder_Stack(struct BTreeNode node)59 do60 while (node = NULL)61 cout ltlt node-gtdata62 push_node(node)63 node = node-gtrightchild64 push all left children65 if (top = NULL)66 node = pop_node()67 node = node-gtleftchild68 69 while ((top=NULL)||(node=NULL))70

程式 5-4二元樹的後序走訪14 void postorder(struct BTreeNode node)15 if (node =NULL)16 postorder(node-gtleftchild)17 postorder(node-gtrightchild)18 cout ltlt node-gtdata19 20

555  階層走訪bull 「階層走訪」 (level-order traversal) 是依階層

的順序進行二元樹的走訪先走訪階層小的節點後走訪階層大的節點同一階層者則依自左向右的順序走訪

bull 對下圖的二元樹進行階層走訪的結果為ABCDEFGHI

bull 對同一階層而言先走訪的節點其子節點亦在下一階層中先被走訪

bull 這種「先進先出」的特性恰可用佇列予以儲存與處理

程式 5-7 利用佇列進行二元樹的階層走訪

1 struct BTreeNode2 struct BTreeNode leftchild3 char data4 struct BTreeNode rightchild5 6 struct BTreeNode root7 struct QNode8 struct BTreeNode treenode9 struct QNode next10 11 struct QNode front rear

12 void AddQueue(struct BTreeNode Tnode)13 struct QNode node14 node=(struct QNode )malloc(sizeof(struct QNode))15 node-gttreenode = Tnode16 node-gtnext = NULL17 if (front == NULL) front = node19 else rear-gtnext = node20 rear = node21

22 struct BTreeNode DeleteQueue()23 struct BTreeNode Tnode24 struct QNode old_front25 if (rear == NULL)26 QueueEmpty()27 else28 Tnode = front-gttreenode29 old_front = front30 front = front-gtnext31 free(old_front)32 return Tnode33 34 35 void LevelOrder (struct BTreeNode node)36 AddQueue(node)37 while (front =NULL)38 node = DeleteQueue()39 cout ltlt node-gtdata40 if (node-gtleftchild = NULL)41 AddQueue(node-gtleftchild)42 if (node-gtrightchild = NULL)43 AddQueue(node-gtrightchild)44 45

35 void LevelOrder (struct BTreeNode node)36 AddQueue(node)37 while (front =NULL)38 node = DeleteQueue()39 cout ltlt node-gtdata40 if (node-gtleftchild = NULL)41 AddQueue(node-gtleftchild)42 if (node-gtrightchild = NULL)43 AddQueue(node-gtrightchild)44 45

bull Infix ABCbull Postfix ABC CBA ACB BCA

BAC CAB

B

A CA

B

C

B

A

C

C

B

AB

A

C

二元樹中序與前序式中序與後序式bull 每一棵二元樹皆有唯一的一對中序與前序表示法也有唯一的中序與後序次序

bull 換句話說給予一對中序與前序或中序與後序即可決定一棵二元樹

bull 然而給予前序和後序次序並不能決定唯一的二元樹可能會產生兩棵不同的二元樹

bull 想想階層走訪和中序走訪順序可否決定唯一的二元樹

A

B C

H J D E

F G

prefix ABHJCDFGEinfix HBJAFDGCEpostfix HJBFGDECA

VLRLVRLRV

A

prefix BHJinfix HBJ

prefix CDFGEinfix FDGCE

prefix ABHJCDFGEinfix HBJAFDGCE

A

B C

H J D E

F G

prefix ABHJCDFGE

infix HBJAFDGCEk-1

struct BTreeNode BTree_InfixPrefix (AnsiString infix AnsiString prefix)

int k struct BTreeNode node if (infixLength==0) return NULLnode = (struct BTreeNode ) malloc (sizeof(struct BTreeNode))node-gtdata = prefix[1]k = find(prefix[1] infix)node-gtleftchild = BTree_InfixPrefix

(infix[1k-1] prefix[2k])node-gtrightchild = BTree_InfixPrefix(infix[k+1infixLength]prefix[k+1prefixLength])

VLR

LVRk1

k2 k+1~

k+1~

1

struct BTreeNode BTree_InfixPostfix (AnsiString infix AnsiString prefix)

int k struct BTreeNode node if (infixLength==0) return NULLnode = (struct BTreeNode ) malloc (sizeof(struct BTreeNode))node-gtdata = postfix[postfixLength]k = find(postfix[postfixLength] infix)node-gtleftchild = BTree_InfixPrefix

(infix[1k-1] postfix[1k-1])node-gtrightchild = BTree_InfixPrefix(infix[k+1infixLength]postfix[k+1postfixLength-1])

56  二元樹的複製與相等測試程式 5-8 二元樹的複製1 struct BTreeNode2 struct BTreeNode leftchild3 char data4 struct BTreeNode rightchild5 6 struct BTreeNode root7 struct BTreeNode Copy(struct BTreeNode TreeRoot)8 struct BTreeNode CopyRoot9 if (TreeRoot == NULL)10 return NULL11 CopyRoot =(struct BTreeNode ) malloc (sizeof(BTreeNode))12 CopyRoot-gtdata = TreeRoot-gtdata13 CopyRoot-gtleftchild = Copy(TreeRoot-gtleftchild)14 CopyRoot-gtrightchild = Copy(TreeRoot-

gtrightchild)15 return CopyRoot16

程式 5-9 檢測兩棵二元樹是否相等1 struct BTreeNode2 struct BTreeNode leftchild3 char data4 struct BTreeNode rightchild5 6 struct BtreeNode rightchild7 int Equal(struct BTreeNode ustruct BTreeNode v)8 if ((u==NULL) ampamp (v==NULL)) return 19 if ((u=NULL) ampamp (v=NULL)10 ampamp (u-gtdata == v-gtdata )11 ampamp Equal(u-gtleftchildv-gtleftchild)12 ampamp Equal(u-gtrightchildv-gtrightchild))

13 return 114 return 015

Counting Nodes in a Binary Tree

int CountingNodes(struct BTreeNodes node)

if (node == NULL) return 0 int leftchildren = CountingNodes(node-gtleftchild)

int rightchildren= CountingNodes(node-gtrightchild)

return leftchildren+rightchildren

Counting Leaves in a Binary Tree

int CountingLeaves(struct BTreeNodes node)

if (node == NULL) return 0 int leftleaves = CountingLeaves(node-gtleftchild)

int rightchildren= CountingLeaves(node-gtrightchild)

if (leftleaves==0 ampamp leftleaves==0) return 1

return leftleaves + leftleaves

57  引線二元樹bull 在二元樹的鍵結表示中樹葉節點的兩個子樹指標皆指向 NULL 由定理 5-1 得知 n = E+1 其中 n 為節點個數 E 為分支數

bull 而 n 個鍵結節點有 2n 個指標空間每個分支恰佔用一個指標空間共有 E (=n-1) 個指標有用到(不是空的)而共有 n+1 個指標空間是 NULL 比非空的節點還多

bull 有學者提出對這些「放空指標的空間」加以利用的概念mdash與其放空指標不如放指向其它節點的指標稱之為引線 (thread) 使得某些運算(如走訪hellip等)可以加快

bull 這個概念發展出了引線二元樹 (threaded binary tree) 這種資料結構

範例 5-17下圖為加了引線的二元樹

加入的引線將使二元樹的中序走訪更加便利沿著上右圖箭頭所指示的順序走訪即可完成此二元樹的中序走訪GDHBAIECF 為此二元樹的中序表示

bull 範例 5-17 中所加的引線目的在方便二元樹的中序走訪

bull 對樹葉節點 p 而言令其資料為 P 則引線指標的規則如下

(1) p 的左子樹指標指向中序表示中 P 的前一個 元素(前接元素 (predecessor) )

(2) p 的右子樹指標指向中序表示中 P 的後一個 元素(後繼元素 (successor) )

為了區別節點指標放的是一般指標(內部節點)抑或是引線指標(樹葉節點)我們另以 2 個欄位分別區別左和右子樹指標空間存放的對象其節點的記憶體配置有如下圖所示

其中 leftthread (rightthread) 為 0 時表示 leftchild (rightchild) 存放的是一般節點指標而 leftthread (rightthread) 為 1 時表示 leftchild (rightchild) 存放的是引線指標1 struct TBTreeNode2 int leftthread3 struct TBTreeNode leftchild4 char data5 struct TBTreeNode rightchild6 int rightthread7

由右側的引線二元樹可知有最左和最右兩條引線尚無妥善的安排我們另設計一空的引線節點做為樹根如下圖所示

571  引線二元樹的中序走訪決定節點 p 在中序表示中的後繼節點 q (簡稱中序後繼點)

程式 5-10決定節點 node的中序後繼點1 struct TBTreeNode2 int leftthread3 struct TBTreeNode leftchild4 char data5 struct TBTreeNode rightchild6 int rightthread7 8 struct TBTreeNode root9 struct TBTreeNode Next(struct TBTreeNode node)

10 struct TBTreeNode temp11 temp = node-gtrightchild12 if (node-gtrightthead) return temp13 while (temp-gtleftthead) temp=temp-gtleftchild

14 return temp15

程式 5-11引線二元樹的中序表示(接程式 5-10)1 void InorderTBTree()2 struct TBTreeNode node3 for(node=Next(root)node=rootnode=Next(node))

4 cout ltlt node-gtdata5

bull只要 node 是引線二元樹上的任一節點透過呼叫 Next(node) 即可找到其在中序表示中的後繼節點bull(試想若程式 5-10 中的程序 Next 所傳入的參數指標正是 root Next(root) 是否可正常運作請思考)bull於是只須從中序表示中的第一節點起讓每一節點 node 都執行 Next(node) 程序即可得引線二元樹的中序表示

572  在引線二元樹中加入節點在引線二元樹中樹葉節點節點的右子樹指標所指向或內部節點右子樹的最左樹葉為其中序後繼節點而且任一節點若無左子樹則其左子樹指標乃指向其中序前接節點hellip這些性質必須在節點新增進入引線二元樹時也要保持接下來討論節點 new 插入成為引線二元樹的節點 p 的右子節點的情形至於插入成為左子節點的情形則讓各位模擬推敲

572572  在引線二元樹中加入節點 在引線二元樹中加入節點 (( 續續 ))

newnew 會成為會成為 pp 的右子節點的情況共有以下兩種的右子節點的情況共有以下兩種(1) (1) 若若 pp 沒有右子節點 沒有右子節點 ((p-gtrightthread p-gtrightthread 為為1) 1) 如圖如圖 ((a) a) 則 則 new new 插入後應形成如圖插入後應形成如圖 ((b) b) 所示所示

(2)(2) 若若 pp 有右子節點 有右子節點 ((p-gtrightthread p-gtrightthread 為為0) 0) 如圖如圖 ((c) c) 則 則 new new 插入後應形成如插入後應形成如圖圖 ((d) d) 所示所示

程式 5-12  插入節點 new 成為引線二元樹中節點 p的右子節點(上接程式 5-10 5-11 )

1 void InsertRight(struct TBTreeNode p 2 struct TBTreeNode new)3 struct TBTreeNode q4 new-gtrightchild = p-gtrightchild5 new-gtrightthead = p-gtrightthread6 new-gtleftchild = p7 new-gtleftthread = 18 p-gtrightchild = new9 p-gtrightthead = 010 if (new-gtrightthread)11 q = Next(new)12 q-gtleftchild = new13 14

58  堆積bull 「堆積」 (heap) 是一個特殊的完備二元樹節點的資料與其子節點的資料之間有事先定好的大小關係存在bull 定義ndash最大堆疊為一完備二元樹任一節點的資料內容不小於其子節點的資料內容ndash最小堆疊為一完備二元樹任一節點的資料內容不大於其子節點的資料內容

581  插入節點至最大堆積中最大堆積是一完備二元樹所以如圖 (a) 之最大堆積於加入新節點後樹的形狀必須如圖 (b) 所示灰色節點即為新節點若加入的節點資料分別為 1 9 12 則其最大堆積將分別變成如圖 (c) (d) (e) 所示

程式 5-13 新增節點至最大堆積中最大堆積本身是一棵完備二元樹可以用陣列表示之 1 define maxsize 1002 int heap[maxsize]3 int n i4 void InsertHeap(int x)5 if (n == maxsize) HeapFull()6 else7 n++8 i = n9 while ((i gt 1) ampamp (x gt heap[i2]))10 heap[i] = heap[i2]11 i = 212 13 heap[i] = x14 16

程序 InsertHeap 在插入第 n 個資料時需要的時間複雜度為 O(logn)

1 100

2 93

3 84

4 77

5 65

6 81

7 79

8 66

9 70

10 50

100

84

77

93

8165

66 70

79

50

1

2 3

4 5 6 7

8 9 10

1 define maxsize 1002 int heap[maxsize]3 int n i4 void InsertHeap(int x)5 if (n == maxsize) HeapFull()6 else7 n++8 i = n9 while ((i gt 1) ampamp (x gt heap[i2]))10 heap[i] = heap[i2]11 i = 212 13 heap[i] = x14 16

582 刪除最大堆積中的最大資料圖 (a) 中的最大堆積在刪除最大資料後其樹形應變成如圖 (b) 所示將最未一個樹葉節點的資料 6 暫挪至樹根節點內如圖 (c) 再將圖 (c) 中非最大堆積的結構調整成為最大堆積 10 和 8 中選大者與 6對調即成圖 (d)

程式 5-14 刪除最大堆積中的最大資料(上接程式 5-13 )1 int DeleteHeap()2 int x i j3 if (n == 0) HeapEmpty()4 else5 x = heap[1]6 heap[1] = heap[n]7 n--8 i = 19 while (i lt= n2)10 if (heap[2i] gt heap[2i+1])11 j = 2i12 else13 j = 2i+114 if (heap[i]gtheap[j]) break15 swap(heap[i] heap[j])16 i = j17 18 return x19 20

其時間複雜度與其堆積高度相同是為O(logn) 其中 n 為堆積中的元素個數

1 100

2 93

3 84

4 77

5 65

6 81

7 79

8 66

9 70

10 50

100

84

77

93

8165

66 70

79

50

1

2 3

4 5 6 7

8 9 10

1 int DeletHeap()2 int x i j3 if(n == 0) HeapEmpty()4 else5 x = heap[1]6 heap[1] = heap[n]7 n--8 i = 19 while (i lt= n2)10 if ([heap[2i]] gt heap[2i+1])11 j = 2i12 else13 j = 2i+114 if (heap[i]gtheap[j]) break15 swap(heap[i] heap[j])16 i = j17 18 return x19 20

583  優先佇列bull 由於最大堆積(或最小堆積)的結構可提供最大元素(或最小元素)而其維護堆積的成本為 O(logn) n 為總元素個數

bull 堆積常被當成「優先佇列」 (priority queue) 使用mdash此時佇列被組織成完備二元樹若新加入佇列的資料有大小關係優先順序的需求時此優先佇列即以 O(logn) 的代價將新資料放入優先佇列(可能是最大或最小堆積依實際大小關係優先順序的需求而定)中mdash提供依元素的優先順序做為處理順序的服務

bull 一般的佇列為先進先出先到的先行處理優先佇列則是依據其優先順序優先順序高者先行處理與來到的先後無關

範例 5-21bull 在「分時」 (time sharing) 的作業系統中我們不希望使用中央處理器 (Central Processing Unit CPU) 時間短的「處理程序」 (process) 等候過久於是有「最短時間工作優先」 (shortest-job-first) 的排程策略此時等候的處理程序即可組織成之一最小堆積而 CPU 每次即取出存於樹根的處理程序予以處理任何新來的處理程序則以 O(logn) 的時間加入此優先佇列(最小堆積)中

bull 注意之前提過的線性佇列新來的直接加入成為新的尾端元素花O(1) 的時間然線性佇列本身沒有大小的關係每次要取出最大(小)的資料得花O(n) 的代價(在所有元素中挑出最大(小)者) n 為佇列內的元素個數而優先佇列是在新元素加入時要花O(logn) 的時間至於取出最大(小)則只須 O(1) 的時間

59  二元搜尋樹bull 二元搜尋樹是一棵二元樹可能是空二元樹若不為空二元樹則滿足下列性質ndash所有節點內的資料內容是相異的 ndash左子節點的資內容 ( 如果有 ) 比父節點的資料內容小 ndash右子節點的資料內容 ( 如果有 ) 比父節點的資料內容大 ndash以左和右子節點為樹根的左子樹和右子樹也是二元搜尋樹

在二元搜尋樹中搜尋資料二元搜尋樹對資料大小的掌握十分明確若恰與節點 p 資料相等則搜尋成功若欲搜尋的資料 x 比樹節點 p 中的資料小 則只須搜尋節點 p 的左子樹若比節點 p 中的資料大 則只須搜尋 p 的右子樹這樣的判斷可以遞迴地應用在各子樹節點 中

程式 5-15 在二元搜尋樹中搜尋資料(遞迴呼叫)1 struct BSTreeNode2 struct BSTreeNode leftchild3 int data4 struct BSTreeNode rightchild5 6 struct BSTreeNode root7 struct BSTreeNode SearchBST(

struct BSTreeNode tree int x)8 if (tree == NULL) return NULL9 if (x == tree-gtdata) return tree10 if (x lt tree-gtdata)11 return SearchBST(tree-gtleftchild x)

12 return SearchBST(tree-gtrightchild x)13

程式 5-16 搜尋二元搜尋樹的資料(非遞迴呼叫)在二元搜尋樹十分龐大時遞迴呼叫的效率不會太好可利用迴圈來完成搜尋的動作以提高執行效率請看程式 5-16

14 struct BSTreeNode Search BST_iterative15 (struct BSTreeNode node int x)16 while (node = NULL)17 if (x == node-gtdata) return node18 if (x lt node-gtdata)19 node = node-gtleftchild20 else21 node = node-gtrightchild22 23 return NULL24

592  新增資料於二元搜尋樹中欲新增資料進入一二元搜尋樹中得先決定該資料在二元搜尋樹中應出現的位置這可透過上節提到的搜尋動作來解決

在二元搜尋樹定義的性質 (1) 中我們限定了元素必須相異所以搜尋新資料的結果必是 NULL

必須將搜尋的結果改為新增資料所應在位置的節點指標

程式 5-17  在二元搜尋樹中新增資料25 int InsertBST(int x)26 struct BSTreeNode p q27 p = root q = NULL28 while (p = NULL)29 q = p30 if (x == p-gtdata) return 0

若不考慮重複則可省略之31 if (x lt p-gtdata)32 p = p-gtleftchild33 else34 p = p-gtrightchild35

3636 p = (struct BSTreeNode ) p = (struct BSTreeNode ) malloc (sizeof(BSTreeNode))malloc (sizeof(BSTreeNode))

3737 p-gtdata = x p-gtdata = x3838 p-gtleftchild = p-gtrightchild = p-gtleftchild = p-gtrightchild = NULLNULL3939 if (root == NULL) root = p if (root == NULL) root = p4040 else if (x lt q-gtdata) else if (x lt q-gtdata)4141 q-gtleftchild = p q-gtleftchild = p4242 else else4343 q-gtrightchild = p q-gtrightchild = p4444 return 1 return 145 45

程式 程式 5-175-17  在二元搜尋樹中新增資料  在二元搜尋樹中新增資料 (( 續續 ))

範例 5-23 圖 (b) 為圖 (a) 新增整數 6 之後的結果圖 (c) (d) (e) (f) 則分別是逐步再加入 28 18

10 21 的結果

bull若目前的二元搜尋樹高度為 h 則新增資料得花O(h) 的時間找到其所應在的位置遂新增資料至二元搜尋樹中的時間複雜之度為 O(h)

593 自二元搜尋樹中刪除資料欲自二元搜尋樹中刪除資料可能會面臨兩種情況(1) 刪除節點 p 位於樹葉節點上如下圖所示 這是比較容易處理的情況

(2) 刪除的節點 p 不是樹葉刪除節點 p 後原 p 所指的節點可由 q ( q 中包含了 p 左子樹中的最大節點值)或 r ( r 中包含了 p 右子樹中的最小節點值)來取代以維持二元搜尋樹中資料的大小關係其結果分別如圖 (b) 和 (c) 所示而圖 (d) 則標明了指標會改變的節點位置即 p 會消失 father 的左或右子樹指標會異動 k(可能是 q 或 r )會移到原來 p 的位置其父節點 f 的左或右子樹指標會異動其中 father 為 p 父節點 f 為 k 的父節點

程式 5-18 自二元搜尋樹中刪除資料46 int DeleteBST (int x)47 Struct BSTreeNode p father k f48 p = root father = NULL49 while (p = NULL)50 if (x == p-gtdata) 找到 x 所在的節點 p 了51 if ((p-gtleftchild == NULL) ampamp 52 (p-gtrightchild == NULL))53 k = NULL p為樹葉節點54 else if (p-gtleftchild = NULL )x位於非子節

點55 f = p 找出 p左子樹的最右樹葉56 k = p-gtleftchild57 while (k-gtrightchild =NULL)58 f = k59 k = k-gtrightchild60 k將挪至原 p處61 if (p == f)62 f-gtleftchild = k-gtleftchild 63 else64 f-gtrightchild = k-gtleftchild 65

66 else p無左子樹67 f = p 找出 p右子樹的最左樹葉68 k = p-gtrightchild69 while (k-gtleftchild = NULL)70 f = k71 k = k -gtleftchild72 73 if (p == f)74 f-gtrightchild = k-gtrightchild 75 else76 f-gtleftchild = k-gtrightchild77 78 if (k = NULL) k挪至原 p處繼承 p的左右指標79 k-gtleftchild = p-gtleftchild80 k-gtrightchild = p-gtrightchild81 82 if (father == NULL) root = k83 else if (x lt father-gtdata) 決定 k是 father的左

或右兒子84 father-gtleftchild = k85 else86 father-gtrightchild = k87 free(p)88 return 1 成功地刪除 x於此傳回 1返回呼叫處89 51~89行為找到 x時應有的處理步驟

90 else 尚未找到 x繼續往下一階層找

91 father = p92 if (x lt p-gtdata)93 p = p-gtleftchild94 else95 p = p-gtrightchild96 97 98 return 0 未找到 x傳回 099

510  樹林 (forest)

bull 定義樹林(森林)是由 n 棵沒有交集的樹所構成的集合 n 0

下圖為一包含三棵樹的樹林

5101 轉換樹林為二元樹 之前我們以二元樹來表示樹因為二元樹有較多

好用的性質在此我們也將樹林轉換成二元樹看待

在 522 節中我們嘗試了用左子右兄弟的方式來表示樹這個方法事實上已將樹轉換成二元樹

即用左子右兄弟的結構重新組織樹林中的每一棵樹這些樹的樹根將視為兄弟而最左邊的樹則視為樹林二元樹的樹根如此一來樹林即可轉換成二元樹

範例 5-25右下圖為右上圖三棵樹所對應的三棵左子右兄弟二元樹

下圖則為上圖樹林所對應的一棵樹林二元樹

演算法 5-1  轉換樹林成二元樹輸入樹林 F = T1 T2 Tn

輸出 F 的二元樹表示 BT=(T1 T2 Tn)1 if (n == 0) 輸出空二元樹 2 else

3 root(T1 T2 Tn) = T1

4 leftsubtree(T1 T2 Tn) =

BT(T11 T12 T1m)5 BT(T11 T12 T1m)為 T1的子樹6 rightsubtree(T1 T2 Tn) =

BT(T2 T3 Tn)

樹林轉換成二元樹的過程樹林轉換成二元樹的過程

  • Slide 1
  • Slide 2
  • Slide 3
  • Slide 4
  • Slide 5
  • Slide 6
  • Slide 7
  • Slide 8
  • Slide 9
  • Slide 10
  • Slide 11
  • Slide 12
  • Slide 13
  • Slide 14
  • Slide 15
  • Slide 16
  • Slide 17
  • Slide 18
  • Slide 19
  • Slide 20
  • Slide 21
  • Slide 22
  • Slide 23
  • Slide 24
  • Slide 25
  • Slide 26
  • Slide 27
  • Slide 28
  • Slide 29
  • Slide 30
  • Slide 31
  • Slide 32
  • Slide 33
  • Slide 34
  • Slide 35
  • Slide 36
  • Slide 37
  • Slide 38
  • Slide 39
  • Slide 40
  • Slide 41
  • Slide 42
  • Slide 43
  • Slide 44
  • Slide 45
  • Slide 46
  • Slide 47
  • Slide 48
  • Slide 49
  • Slide 50
  • Slide 51
  • Slide 52
  • Slide 53
  • Slide 54
  • Slide 55
  • Slide 56
  • Slide 57
  • Slide 58
  • Slide 59
  • Slide 60
  • Slide 61
  • Slide 62
  • Slide 63
  • Slide 64
  • Slide 65
  • Slide 66
  • Slide 67
  • Slide 68
  • Slide 69
  • Slide 70
  • Slide 71
  • Slide 72
  • Slide 73
  • Slide 74
  • Slide 75
  • Slide 76
  • Slide 77
  • Slide 78
  • Slide 79
  • Slide 80
  • Slide 81
  • Slide 82
  • Slide 83
  • Slide 84
  • Slide 85
  • Slide 86
  • Slide 87
  • Slide 88
  • Slide 89
  • Slide 90
  • Slide 91
  • Slide 92
  • Slide 93
  • Slide 94
  • Slide 95
  • Slide 96
  • Slide 97
  • Slide 98
  • Slide 99
  • Slide 100
  • Slide 101
  • Slide 102
  • Slide 103
Page 24: 第五章 樹

完備二元樹 (complete binary tree)我們可對二元樹上節點予以編號階層小者先編同一階

層則自左向右編號這樣的編號方式使我們定義出完備(complete) 二元樹

定義深度為 k 節點數為 n 的二元樹稱為完備二元樹若且唯若 (1) k = 1 時樹有一個節點

(2) 當 k 2 且 1iltk 時深度 i 有 2i-1 個節點且第 k 層的節點皆由第 k-1 層的分支由左至右逐一連接而成

完滿或完備二元樹之所以受人注意乃因它們可以將節點以較少的階層數存放

定理 5-4 n 個節點的完備或完滿二元樹其深度為 log2(n+1)

證明 完滿或完備二元樹的節點皆自階層

1 起排放任一階層 i 可排放 2i-1

個節點遂由定理 5-2 得知此 n 個節點所佔的深度為 log2(n+1)

正規二元樹 (formal binary tree)bull 在二元樹中非樹葉的節點又稱為內部節點

(internal node) 若所有內部節點恰有 2 個子節點即成為正規二元樹

定義若二元樹中所有內部節點恰有 2 個子節點則稱之為正規二元樹

正規二元樹常被引用在單淘汰賽正規二元樹常被引用在單淘汰賽制的各項賽程安排樹葉為參賽制的各項賽程安排樹葉為參賽隊伍內部節點為優勝隊伍樹隊伍內部節點為優勝隊伍樹根即為冠軍隊伍根即為冠軍隊伍

在單淘汰賽制中自在單淘汰賽制中自 n n 個隊伍中產個隊伍中產生冠軍須舉辦 生冠軍須舉辦 nn-1-1場比賽場比賽

54  二元樹的表示法541 以陣列表示二元樹定理 5-5 若一個具有 n 個節點的完備二元樹以循序的方式編號並依序存放在陣列 A 中即第 i 個節點放在 A[i] 中 1 i n 則

(1) 節點 i 的父節點位在 A[i2]處 i ne 1( i = 1 時其節點正為樹根無父節點)

(2) 若 2i n 則節點 i 的左兒子節點位在 A[2i] 處若 2i gtn 節點 i 沒有左子節點

(3) 若 2i+1 n 節點 i 的右兒子節點位 A[2i+1]處若 2i+1gtn 節點 i 沒有左子

節點

證明先用數學歸納法證明 (2) bull 歸納法的基礎當 i = 1 時其左子節點確實在 A[2] 處(除非 nlt2 此時節點 1 沒有左子節點)

bull 歸納法的假設假設當 I = k 時其左子節點確在 A[2i] = A[2k] 處(除非 2i 已大於 n 此時節點 i 無左子節點)

bull 歸納法的推論當 i = k+1 時因第 k 節點的左子節點在 A[2k] 處其右子節點則位在 A[2k+1] 處那麼第 k+1 節點的左子節點應緊臨 A[2k+1] 亦即位於 A[2k+2] = A[2(k+1)] 處(除非 2i 已大於 n 此時節點 i 無左子節點)

bull 由數學歸納法的原理得證定理 5-5 (2) (3) 由於 (2) 成立而且節點 i 的右子節點會緊臨

A[2i] 亦即其位於 A[2i+1]處除非 i 根本沒有右子節點得證定理 5-5 (3)

由 (2) 和 (3) 我們可知定理 5-5 (1) 成立

542 以鍵結串列表示二元樹bull 以陣列來表示完備二元樹算是相當方便但ndash一般的二元樹將導致陣列空間的浪費ndash如果二元樹中節點的新增刪除動作頻繁則會造成陣列對應資料的搬動隨而頻繁

bull 以鍵結串列表示二元樹

為方便辦識樹根我們常用 root 指標指向樹根做為樹鍵結串列的起點

陣列表示和鍵結表示的空間需求bull 完備二元樹的陣列表示和鍵結表示皆沒浪費儲存資料的空間

bull 但歪斜樹的陣列表示比起鍵結表示則浪費許多

bull 以 n 個節點的歪斜樹而言陣列表示法可能需要 2n-1 個陣列元素其中 2n-1-n 個陣列元素空間是浪費的而鍵結表示法只需要 n 個節點空間

二元樹節點的宣告程式 5-2

1 struct BTreeNode

2 struct BtreeNode leftchiled

3 int data

4 struct BTreeNode rightchild

5

6 struct BTreeNode root

bull 這樣的二元樹節點要取得兒子節點的指標十分方便但若有取得父節點的需求則嫌不足我們可以加上 parent 欄位指向父節點

55  二元樹的走訪bull 假如一組資料已用二元樹的組織在一起總有需求對其全數資料做動作例如計算所有數目印出所有資料在所有資料中搜尋某項資料hellip等此時即須對此二元樹做走訪 (traversal) 的運算利用走訪二元樹的同時將計算列印或搜尋的動作完成

bull 事實上走訪即在決定二元樹上資料被處理(計算列印或搜尋)的順序我們也希望走訪的演算法對任何節點皆一致是容易撰寫程式實作的

bull 若對一個二元樹上的節點而言 V 表示處理節點上的資料 L 表示走訪其左子樹 R 表示走訪其右子樹

55  二元樹的走訪 ( 續 )bull 因為對稱的緣故我們只考慮先走訪左邊再走訪右邊的情形那麼右圖只剩下三種走訪方式

bull 以 V 所在的相對位置分別對此三種走訪方式取名如下

(1) LVR 中序走訪 (inorder traversal)或中序表示法 (infix notation)

(2) LRV 後序走訪 (postorder traversal) 或後序表示法 (postfix notation)

(3) VLR 前序走訪 (preorder traversal)或前序表示法 (prefix notation)

551  中序走訪bull 利用遞迴的方式撰寫中序走訪的程序希望把二元樹中序走訪的

順序印出來

程式 5-3二元樹的中序走訪1 struct BTreeNode2 struct BTreeNode leftchild3 char data4 struct BTreeNode rightchild5 6 struct BTreeNode root7 void inorder(struct BTreeNode node)8 if (node = NULL)9 inorder(node-gtleftchild)10 cout ltlt node-gtdata11 inorder(node-gtrightchild)12 13

範例 5-12此為運算式二元樹

其對應中序表示運算式為 (A+B)C+D(E-F)

6 struct BTreeNode root7 void inorder(struct BTreeNode node)8 if (node = NULL)9 inorder(node-gtleftchild)10 cout ltlt node-gtdata11 inorder(node-gtrightchild)12 13

6 struct BTreeNode root7 void inorder(struct BTreeNode node)8 if (node = NULL)9 inorder(node-gtleftchild)10 cout ltlt node-gtdata11 inorder(node-gtrightchild)12 13

(A+B)C+D(E-F)

如何加上 ( )

552  後序走訪程式 5-4二元樹的後序走訪14 void postorder(struct BTreeNode node)

15 if (node =NULL)16 postorder(node-gtleftchild)17 postorder(node-gtrightchild)18 cout ltlt node-gtdata19 20

以後序走訪可得到AB+CDEF-+

其正為對應的後序運算式

553  前序走訪程式 5-5二元樹的前序走訪21 void preorder(struct BTreeNode node)

22 if (node = NULL)

23 cout ltlt node-gtdata

24 preorder(node-gtleftchild)

25 preorder(node-gtrightchild)

26

27

以前序走訪可得到++ABCD-EF

其正為對應的前序運算式

554 利用堆疊和迴圈取代遞迴進行二元樹的走訪1 struct BTreeNode 2 struct BTreeNode leftchild3 char data4 struct BtreeNode rightchild5 6 struct BTreeNode root7 struct StackNode8 struct BTreeNode treenode9 struct StackNode next1011 struct StackNode top

12 void push_node(struct BtreeNode node )13 struct StackNode old_top14 old_top = top15 top = (struct StackNode ) malloc

(sizeof(struct StackNode))16 top-gttreenode = node17 top-gtnext = old_top18 19 struct BTreeNode pop_node()20 struct BTreeNode Tnode21 struct StackNode old_top22 if (top == NULL)23 StackEmpty()24 else25 Tnode = top-gttreenode26 old_top = top27 top = top-gtnext28 free(old_top)29 return Tnode30 31

32 void inorder_Stack(struct BtreeNode node)33 do34 while (node = NULL)35 push_node(node)36 node = node-gtleftchild37 push all left children38 if (top = NULL)39 node = pop_node()40 cout ltlt node-gtdata41 node = node-gtrightchild42 inorder printing and check right child43 while ((top=NULL)||(node=NULL))44 top==NULL 堆疊已空 node==NULL沒有右子

6 struct BTreeNode root7 void inorder(struct BTreeNode node)8 if (node = NULL)9 inorder(node-gtleftchild)10 cout ltlt node-gtdata11 inorder(node-gtrightchild)12 13

中序表示為DBGEHAFIC

32 void inorder_Stack()33 struct BTreeNode node34 node = root35 do36 while (node = NULL)37 push_node(node)38 node = node-gtleftchild39 40 if (top = NULL)41 node = pop_node()42 cout ltlt node-gtdata43 node = node-gtrightchild44 45 while(top=NULL)||(node=NULL))46

45 void Preorder_Stack(struct BSTreeNode node)46 do47 while (node = NULL)48 cout ltlt node-gtdata)49 push_node(node)50 node = node-gtleftchild51 push all left children52 if (top = NULL)53 node = pop_node()54 node = node-gtrightchild55 56 while ((top=NULL)||(node=NULL))57

程式 5-5二元樹的前序走訪21 void preorder(struct BTreeNode node)22 if (node = NULL)23 cout ltlt node-gtdata24 preorder(node-gtleftchild)25 preorder(node-gtrightchild)26 27

58 void Postorder_Stack(struct BTreeNode node)59 do60 while (node = NULL)61 cout ltlt node-gtdata62 push_node(node)63 node = node-gtrightchild64 push all left children65 if (top = NULL)66 node = pop_node()67 node = node-gtleftchild68 69 while ((top=NULL)||(node=NULL))70

程式 5-4二元樹的後序走訪14 void postorder(struct BTreeNode node)15 if (node =NULL)16 postorder(node-gtleftchild)17 postorder(node-gtrightchild)18 cout ltlt node-gtdata19 20

555  階層走訪bull 「階層走訪」 (level-order traversal) 是依階層

的順序進行二元樹的走訪先走訪階層小的節點後走訪階層大的節點同一階層者則依自左向右的順序走訪

bull 對下圖的二元樹進行階層走訪的結果為ABCDEFGHI

bull 對同一階層而言先走訪的節點其子節點亦在下一階層中先被走訪

bull 這種「先進先出」的特性恰可用佇列予以儲存與處理

程式 5-7 利用佇列進行二元樹的階層走訪

1 struct BTreeNode2 struct BTreeNode leftchild3 char data4 struct BTreeNode rightchild5 6 struct BTreeNode root7 struct QNode8 struct BTreeNode treenode9 struct QNode next10 11 struct QNode front rear

12 void AddQueue(struct BTreeNode Tnode)13 struct QNode node14 node=(struct QNode )malloc(sizeof(struct QNode))15 node-gttreenode = Tnode16 node-gtnext = NULL17 if (front == NULL) front = node19 else rear-gtnext = node20 rear = node21

22 struct BTreeNode DeleteQueue()23 struct BTreeNode Tnode24 struct QNode old_front25 if (rear == NULL)26 QueueEmpty()27 else28 Tnode = front-gttreenode29 old_front = front30 front = front-gtnext31 free(old_front)32 return Tnode33 34 35 void LevelOrder (struct BTreeNode node)36 AddQueue(node)37 while (front =NULL)38 node = DeleteQueue()39 cout ltlt node-gtdata40 if (node-gtleftchild = NULL)41 AddQueue(node-gtleftchild)42 if (node-gtrightchild = NULL)43 AddQueue(node-gtrightchild)44 45

35 void LevelOrder (struct BTreeNode node)36 AddQueue(node)37 while (front =NULL)38 node = DeleteQueue()39 cout ltlt node-gtdata40 if (node-gtleftchild = NULL)41 AddQueue(node-gtleftchild)42 if (node-gtrightchild = NULL)43 AddQueue(node-gtrightchild)44 45

bull Infix ABCbull Postfix ABC CBA ACB BCA

BAC CAB

B

A CA

B

C

B

A

C

C

B

AB

A

C

二元樹中序與前序式中序與後序式bull 每一棵二元樹皆有唯一的一對中序與前序表示法也有唯一的中序與後序次序

bull 換句話說給予一對中序與前序或中序與後序即可決定一棵二元樹

bull 然而給予前序和後序次序並不能決定唯一的二元樹可能會產生兩棵不同的二元樹

bull 想想階層走訪和中序走訪順序可否決定唯一的二元樹

A

B C

H J D E

F G

prefix ABHJCDFGEinfix HBJAFDGCEpostfix HJBFGDECA

VLRLVRLRV

A

prefix BHJinfix HBJ

prefix CDFGEinfix FDGCE

prefix ABHJCDFGEinfix HBJAFDGCE

A

B C

H J D E

F G

prefix ABHJCDFGE

infix HBJAFDGCEk-1

struct BTreeNode BTree_InfixPrefix (AnsiString infix AnsiString prefix)

int k struct BTreeNode node if (infixLength==0) return NULLnode = (struct BTreeNode ) malloc (sizeof(struct BTreeNode))node-gtdata = prefix[1]k = find(prefix[1] infix)node-gtleftchild = BTree_InfixPrefix

(infix[1k-1] prefix[2k])node-gtrightchild = BTree_InfixPrefix(infix[k+1infixLength]prefix[k+1prefixLength])

VLR

LVRk1

k2 k+1~

k+1~

1

struct BTreeNode BTree_InfixPostfix (AnsiString infix AnsiString prefix)

int k struct BTreeNode node if (infixLength==0) return NULLnode = (struct BTreeNode ) malloc (sizeof(struct BTreeNode))node-gtdata = postfix[postfixLength]k = find(postfix[postfixLength] infix)node-gtleftchild = BTree_InfixPrefix

(infix[1k-1] postfix[1k-1])node-gtrightchild = BTree_InfixPrefix(infix[k+1infixLength]postfix[k+1postfixLength-1])

56  二元樹的複製與相等測試程式 5-8 二元樹的複製1 struct BTreeNode2 struct BTreeNode leftchild3 char data4 struct BTreeNode rightchild5 6 struct BTreeNode root7 struct BTreeNode Copy(struct BTreeNode TreeRoot)8 struct BTreeNode CopyRoot9 if (TreeRoot == NULL)10 return NULL11 CopyRoot =(struct BTreeNode ) malloc (sizeof(BTreeNode))12 CopyRoot-gtdata = TreeRoot-gtdata13 CopyRoot-gtleftchild = Copy(TreeRoot-gtleftchild)14 CopyRoot-gtrightchild = Copy(TreeRoot-

gtrightchild)15 return CopyRoot16

程式 5-9 檢測兩棵二元樹是否相等1 struct BTreeNode2 struct BTreeNode leftchild3 char data4 struct BTreeNode rightchild5 6 struct BtreeNode rightchild7 int Equal(struct BTreeNode ustruct BTreeNode v)8 if ((u==NULL) ampamp (v==NULL)) return 19 if ((u=NULL) ampamp (v=NULL)10 ampamp (u-gtdata == v-gtdata )11 ampamp Equal(u-gtleftchildv-gtleftchild)12 ampamp Equal(u-gtrightchildv-gtrightchild))

13 return 114 return 015

Counting Nodes in a Binary Tree

int CountingNodes(struct BTreeNodes node)

if (node == NULL) return 0 int leftchildren = CountingNodes(node-gtleftchild)

int rightchildren= CountingNodes(node-gtrightchild)

return leftchildren+rightchildren

Counting Leaves in a Binary Tree

int CountingLeaves(struct BTreeNodes node)

if (node == NULL) return 0 int leftleaves = CountingLeaves(node-gtleftchild)

int rightchildren= CountingLeaves(node-gtrightchild)

if (leftleaves==0 ampamp leftleaves==0) return 1

return leftleaves + leftleaves

57  引線二元樹bull 在二元樹的鍵結表示中樹葉節點的兩個子樹指標皆指向 NULL 由定理 5-1 得知 n = E+1 其中 n 為節點個數 E 為分支數

bull 而 n 個鍵結節點有 2n 個指標空間每個分支恰佔用一個指標空間共有 E (=n-1) 個指標有用到(不是空的)而共有 n+1 個指標空間是 NULL 比非空的節點還多

bull 有學者提出對這些「放空指標的空間」加以利用的概念mdash與其放空指標不如放指向其它節點的指標稱之為引線 (thread) 使得某些運算(如走訪hellip等)可以加快

bull 這個概念發展出了引線二元樹 (threaded binary tree) 這種資料結構

範例 5-17下圖為加了引線的二元樹

加入的引線將使二元樹的中序走訪更加便利沿著上右圖箭頭所指示的順序走訪即可完成此二元樹的中序走訪GDHBAIECF 為此二元樹的中序表示

bull 範例 5-17 中所加的引線目的在方便二元樹的中序走訪

bull 對樹葉節點 p 而言令其資料為 P 則引線指標的規則如下

(1) p 的左子樹指標指向中序表示中 P 的前一個 元素(前接元素 (predecessor) )

(2) p 的右子樹指標指向中序表示中 P 的後一個 元素(後繼元素 (successor) )

為了區別節點指標放的是一般指標(內部節點)抑或是引線指標(樹葉節點)我們另以 2 個欄位分別區別左和右子樹指標空間存放的對象其節點的記憶體配置有如下圖所示

其中 leftthread (rightthread) 為 0 時表示 leftchild (rightchild) 存放的是一般節點指標而 leftthread (rightthread) 為 1 時表示 leftchild (rightchild) 存放的是引線指標1 struct TBTreeNode2 int leftthread3 struct TBTreeNode leftchild4 char data5 struct TBTreeNode rightchild6 int rightthread7

由右側的引線二元樹可知有最左和最右兩條引線尚無妥善的安排我們另設計一空的引線節點做為樹根如下圖所示

571  引線二元樹的中序走訪決定節點 p 在中序表示中的後繼節點 q (簡稱中序後繼點)

程式 5-10決定節點 node的中序後繼點1 struct TBTreeNode2 int leftthread3 struct TBTreeNode leftchild4 char data5 struct TBTreeNode rightchild6 int rightthread7 8 struct TBTreeNode root9 struct TBTreeNode Next(struct TBTreeNode node)

10 struct TBTreeNode temp11 temp = node-gtrightchild12 if (node-gtrightthead) return temp13 while (temp-gtleftthead) temp=temp-gtleftchild

14 return temp15

程式 5-11引線二元樹的中序表示(接程式 5-10)1 void InorderTBTree()2 struct TBTreeNode node3 for(node=Next(root)node=rootnode=Next(node))

4 cout ltlt node-gtdata5

bull只要 node 是引線二元樹上的任一節點透過呼叫 Next(node) 即可找到其在中序表示中的後繼節點bull(試想若程式 5-10 中的程序 Next 所傳入的參數指標正是 root Next(root) 是否可正常運作請思考)bull於是只須從中序表示中的第一節點起讓每一節點 node 都執行 Next(node) 程序即可得引線二元樹的中序表示

572  在引線二元樹中加入節點在引線二元樹中樹葉節點節點的右子樹指標所指向或內部節點右子樹的最左樹葉為其中序後繼節點而且任一節點若無左子樹則其左子樹指標乃指向其中序前接節點hellip這些性質必須在節點新增進入引線二元樹時也要保持接下來討論節點 new 插入成為引線二元樹的節點 p 的右子節點的情形至於插入成為左子節點的情形則讓各位模擬推敲

572572  在引線二元樹中加入節點 在引線二元樹中加入節點 (( 續續 ))

newnew 會成為會成為 pp 的右子節點的情況共有以下兩種的右子節點的情況共有以下兩種(1) (1) 若若 pp 沒有右子節點 沒有右子節點 ((p-gtrightthread p-gtrightthread 為為1) 1) 如圖如圖 ((a) a) 則 則 new new 插入後應形成如圖插入後應形成如圖 ((b) b) 所示所示

(2)(2) 若若 pp 有右子節點 有右子節點 ((p-gtrightthread p-gtrightthread 為為0) 0) 如圖如圖 ((c) c) 則 則 new new 插入後應形成如插入後應形成如圖圖 ((d) d) 所示所示

程式 5-12  插入節點 new 成為引線二元樹中節點 p的右子節點(上接程式 5-10 5-11 )

1 void InsertRight(struct TBTreeNode p 2 struct TBTreeNode new)3 struct TBTreeNode q4 new-gtrightchild = p-gtrightchild5 new-gtrightthead = p-gtrightthread6 new-gtleftchild = p7 new-gtleftthread = 18 p-gtrightchild = new9 p-gtrightthead = 010 if (new-gtrightthread)11 q = Next(new)12 q-gtleftchild = new13 14

58  堆積bull 「堆積」 (heap) 是一個特殊的完備二元樹節點的資料與其子節點的資料之間有事先定好的大小關係存在bull 定義ndash最大堆疊為一完備二元樹任一節點的資料內容不小於其子節點的資料內容ndash最小堆疊為一完備二元樹任一節點的資料內容不大於其子節點的資料內容

581  插入節點至最大堆積中最大堆積是一完備二元樹所以如圖 (a) 之最大堆積於加入新節點後樹的形狀必須如圖 (b) 所示灰色節點即為新節點若加入的節點資料分別為 1 9 12 則其最大堆積將分別變成如圖 (c) (d) (e) 所示

程式 5-13 新增節點至最大堆積中最大堆積本身是一棵完備二元樹可以用陣列表示之 1 define maxsize 1002 int heap[maxsize]3 int n i4 void InsertHeap(int x)5 if (n == maxsize) HeapFull()6 else7 n++8 i = n9 while ((i gt 1) ampamp (x gt heap[i2]))10 heap[i] = heap[i2]11 i = 212 13 heap[i] = x14 16

程序 InsertHeap 在插入第 n 個資料時需要的時間複雜度為 O(logn)

1 100

2 93

3 84

4 77

5 65

6 81

7 79

8 66

9 70

10 50

100

84

77

93

8165

66 70

79

50

1

2 3

4 5 6 7

8 9 10

1 define maxsize 1002 int heap[maxsize]3 int n i4 void InsertHeap(int x)5 if (n == maxsize) HeapFull()6 else7 n++8 i = n9 while ((i gt 1) ampamp (x gt heap[i2]))10 heap[i] = heap[i2]11 i = 212 13 heap[i] = x14 16

582 刪除最大堆積中的最大資料圖 (a) 中的最大堆積在刪除最大資料後其樹形應變成如圖 (b) 所示將最未一個樹葉節點的資料 6 暫挪至樹根節點內如圖 (c) 再將圖 (c) 中非最大堆積的結構調整成為最大堆積 10 和 8 中選大者與 6對調即成圖 (d)

程式 5-14 刪除最大堆積中的最大資料(上接程式 5-13 )1 int DeleteHeap()2 int x i j3 if (n == 0) HeapEmpty()4 else5 x = heap[1]6 heap[1] = heap[n]7 n--8 i = 19 while (i lt= n2)10 if (heap[2i] gt heap[2i+1])11 j = 2i12 else13 j = 2i+114 if (heap[i]gtheap[j]) break15 swap(heap[i] heap[j])16 i = j17 18 return x19 20

其時間複雜度與其堆積高度相同是為O(logn) 其中 n 為堆積中的元素個數

1 100

2 93

3 84

4 77

5 65

6 81

7 79

8 66

9 70

10 50

100

84

77

93

8165

66 70

79

50

1

2 3

4 5 6 7

8 9 10

1 int DeletHeap()2 int x i j3 if(n == 0) HeapEmpty()4 else5 x = heap[1]6 heap[1] = heap[n]7 n--8 i = 19 while (i lt= n2)10 if ([heap[2i]] gt heap[2i+1])11 j = 2i12 else13 j = 2i+114 if (heap[i]gtheap[j]) break15 swap(heap[i] heap[j])16 i = j17 18 return x19 20

583  優先佇列bull 由於最大堆積(或最小堆積)的結構可提供最大元素(或最小元素)而其維護堆積的成本為 O(logn) n 為總元素個數

bull 堆積常被當成「優先佇列」 (priority queue) 使用mdash此時佇列被組織成完備二元樹若新加入佇列的資料有大小關係優先順序的需求時此優先佇列即以 O(logn) 的代價將新資料放入優先佇列(可能是最大或最小堆積依實際大小關係優先順序的需求而定)中mdash提供依元素的優先順序做為處理順序的服務

bull 一般的佇列為先進先出先到的先行處理優先佇列則是依據其優先順序優先順序高者先行處理與來到的先後無關

範例 5-21bull 在「分時」 (time sharing) 的作業系統中我們不希望使用中央處理器 (Central Processing Unit CPU) 時間短的「處理程序」 (process) 等候過久於是有「最短時間工作優先」 (shortest-job-first) 的排程策略此時等候的處理程序即可組織成之一最小堆積而 CPU 每次即取出存於樹根的處理程序予以處理任何新來的處理程序則以 O(logn) 的時間加入此優先佇列(最小堆積)中

bull 注意之前提過的線性佇列新來的直接加入成為新的尾端元素花O(1) 的時間然線性佇列本身沒有大小的關係每次要取出最大(小)的資料得花O(n) 的代價(在所有元素中挑出最大(小)者) n 為佇列內的元素個數而優先佇列是在新元素加入時要花O(logn) 的時間至於取出最大(小)則只須 O(1) 的時間

59  二元搜尋樹bull 二元搜尋樹是一棵二元樹可能是空二元樹若不為空二元樹則滿足下列性質ndash所有節點內的資料內容是相異的 ndash左子節點的資內容 ( 如果有 ) 比父節點的資料內容小 ndash右子節點的資料內容 ( 如果有 ) 比父節點的資料內容大 ndash以左和右子節點為樹根的左子樹和右子樹也是二元搜尋樹

在二元搜尋樹中搜尋資料二元搜尋樹對資料大小的掌握十分明確若恰與節點 p 資料相等則搜尋成功若欲搜尋的資料 x 比樹節點 p 中的資料小 則只須搜尋節點 p 的左子樹若比節點 p 中的資料大 則只須搜尋 p 的右子樹這樣的判斷可以遞迴地應用在各子樹節點 中

程式 5-15 在二元搜尋樹中搜尋資料(遞迴呼叫)1 struct BSTreeNode2 struct BSTreeNode leftchild3 int data4 struct BSTreeNode rightchild5 6 struct BSTreeNode root7 struct BSTreeNode SearchBST(

struct BSTreeNode tree int x)8 if (tree == NULL) return NULL9 if (x == tree-gtdata) return tree10 if (x lt tree-gtdata)11 return SearchBST(tree-gtleftchild x)

12 return SearchBST(tree-gtrightchild x)13

程式 5-16 搜尋二元搜尋樹的資料(非遞迴呼叫)在二元搜尋樹十分龐大時遞迴呼叫的效率不會太好可利用迴圈來完成搜尋的動作以提高執行效率請看程式 5-16

14 struct BSTreeNode Search BST_iterative15 (struct BSTreeNode node int x)16 while (node = NULL)17 if (x == node-gtdata) return node18 if (x lt node-gtdata)19 node = node-gtleftchild20 else21 node = node-gtrightchild22 23 return NULL24

592  新增資料於二元搜尋樹中欲新增資料進入一二元搜尋樹中得先決定該資料在二元搜尋樹中應出現的位置這可透過上節提到的搜尋動作來解決

在二元搜尋樹定義的性質 (1) 中我們限定了元素必須相異所以搜尋新資料的結果必是 NULL

必須將搜尋的結果改為新增資料所應在位置的節點指標

程式 5-17  在二元搜尋樹中新增資料25 int InsertBST(int x)26 struct BSTreeNode p q27 p = root q = NULL28 while (p = NULL)29 q = p30 if (x == p-gtdata) return 0

若不考慮重複則可省略之31 if (x lt p-gtdata)32 p = p-gtleftchild33 else34 p = p-gtrightchild35

3636 p = (struct BSTreeNode ) p = (struct BSTreeNode ) malloc (sizeof(BSTreeNode))malloc (sizeof(BSTreeNode))

3737 p-gtdata = x p-gtdata = x3838 p-gtleftchild = p-gtrightchild = p-gtleftchild = p-gtrightchild = NULLNULL3939 if (root == NULL) root = p if (root == NULL) root = p4040 else if (x lt q-gtdata) else if (x lt q-gtdata)4141 q-gtleftchild = p q-gtleftchild = p4242 else else4343 q-gtrightchild = p q-gtrightchild = p4444 return 1 return 145 45

程式 程式 5-175-17  在二元搜尋樹中新增資料  在二元搜尋樹中新增資料 (( 續續 ))

範例 5-23 圖 (b) 為圖 (a) 新增整數 6 之後的結果圖 (c) (d) (e) (f) 則分別是逐步再加入 28 18

10 21 的結果

bull若目前的二元搜尋樹高度為 h 則新增資料得花O(h) 的時間找到其所應在的位置遂新增資料至二元搜尋樹中的時間複雜之度為 O(h)

593 自二元搜尋樹中刪除資料欲自二元搜尋樹中刪除資料可能會面臨兩種情況(1) 刪除節點 p 位於樹葉節點上如下圖所示 這是比較容易處理的情況

(2) 刪除的節點 p 不是樹葉刪除節點 p 後原 p 所指的節點可由 q ( q 中包含了 p 左子樹中的最大節點值)或 r ( r 中包含了 p 右子樹中的最小節點值)來取代以維持二元搜尋樹中資料的大小關係其結果分別如圖 (b) 和 (c) 所示而圖 (d) 則標明了指標會改變的節點位置即 p 會消失 father 的左或右子樹指標會異動 k(可能是 q 或 r )會移到原來 p 的位置其父節點 f 的左或右子樹指標會異動其中 father 為 p 父節點 f 為 k 的父節點

程式 5-18 自二元搜尋樹中刪除資料46 int DeleteBST (int x)47 Struct BSTreeNode p father k f48 p = root father = NULL49 while (p = NULL)50 if (x == p-gtdata) 找到 x 所在的節點 p 了51 if ((p-gtleftchild == NULL) ampamp 52 (p-gtrightchild == NULL))53 k = NULL p為樹葉節點54 else if (p-gtleftchild = NULL )x位於非子節

點55 f = p 找出 p左子樹的最右樹葉56 k = p-gtleftchild57 while (k-gtrightchild =NULL)58 f = k59 k = k-gtrightchild60 k將挪至原 p處61 if (p == f)62 f-gtleftchild = k-gtleftchild 63 else64 f-gtrightchild = k-gtleftchild 65

66 else p無左子樹67 f = p 找出 p右子樹的最左樹葉68 k = p-gtrightchild69 while (k-gtleftchild = NULL)70 f = k71 k = k -gtleftchild72 73 if (p == f)74 f-gtrightchild = k-gtrightchild 75 else76 f-gtleftchild = k-gtrightchild77 78 if (k = NULL) k挪至原 p處繼承 p的左右指標79 k-gtleftchild = p-gtleftchild80 k-gtrightchild = p-gtrightchild81 82 if (father == NULL) root = k83 else if (x lt father-gtdata) 決定 k是 father的左

或右兒子84 father-gtleftchild = k85 else86 father-gtrightchild = k87 free(p)88 return 1 成功地刪除 x於此傳回 1返回呼叫處89 51~89行為找到 x時應有的處理步驟

90 else 尚未找到 x繼續往下一階層找

91 father = p92 if (x lt p-gtdata)93 p = p-gtleftchild94 else95 p = p-gtrightchild96 97 98 return 0 未找到 x傳回 099

510  樹林 (forest)

bull 定義樹林(森林)是由 n 棵沒有交集的樹所構成的集合 n 0

下圖為一包含三棵樹的樹林

5101 轉換樹林為二元樹 之前我們以二元樹來表示樹因為二元樹有較多

好用的性質在此我們也將樹林轉換成二元樹看待

在 522 節中我們嘗試了用左子右兄弟的方式來表示樹這個方法事實上已將樹轉換成二元樹

即用左子右兄弟的結構重新組織樹林中的每一棵樹這些樹的樹根將視為兄弟而最左邊的樹則視為樹林二元樹的樹根如此一來樹林即可轉換成二元樹

範例 5-25右下圖為右上圖三棵樹所對應的三棵左子右兄弟二元樹

下圖則為上圖樹林所對應的一棵樹林二元樹

演算法 5-1  轉換樹林成二元樹輸入樹林 F = T1 T2 Tn

輸出 F 的二元樹表示 BT=(T1 T2 Tn)1 if (n == 0) 輸出空二元樹 2 else

3 root(T1 T2 Tn) = T1

4 leftsubtree(T1 T2 Tn) =

BT(T11 T12 T1m)5 BT(T11 T12 T1m)為 T1的子樹6 rightsubtree(T1 T2 Tn) =

BT(T2 T3 Tn)

樹林轉換成二元樹的過程樹林轉換成二元樹的過程

  • Slide 1
  • Slide 2
  • Slide 3
  • Slide 4
  • Slide 5
  • Slide 6
  • Slide 7
  • Slide 8
  • Slide 9
  • Slide 10
  • Slide 11
  • Slide 12
  • Slide 13
  • Slide 14
  • Slide 15
  • Slide 16
  • Slide 17
  • Slide 18
  • Slide 19
  • Slide 20
  • Slide 21
  • Slide 22
  • Slide 23
  • Slide 24
  • Slide 25
  • Slide 26
  • Slide 27
  • Slide 28
  • Slide 29
  • Slide 30
  • Slide 31
  • Slide 32
  • Slide 33
  • Slide 34
  • Slide 35
  • Slide 36
  • Slide 37
  • Slide 38
  • Slide 39
  • Slide 40
  • Slide 41
  • Slide 42
  • Slide 43
  • Slide 44
  • Slide 45
  • Slide 46
  • Slide 47
  • Slide 48
  • Slide 49
  • Slide 50
  • Slide 51
  • Slide 52
  • Slide 53
  • Slide 54
  • Slide 55
  • Slide 56
  • Slide 57
  • Slide 58
  • Slide 59
  • Slide 60
  • Slide 61
  • Slide 62
  • Slide 63
  • Slide 64
  • Slide 65
  • Slide 66
  • Slide 67
  • Slide 68
  • Slide 69
  • Slide 70
  • Slide 71
  • Slide 72
  • Slide 73
  • Slide 74
  • Slide 75
  • Slide 76
  • Slide 77
  • Slide 78
  • Slide 79
  • Slide 80
  • Slide 81
  • Slide 82
  • Slide 83
  • Slide 84
  • Slide 85
  • Slide 86
  • Slide 87
  • Slide 88
  • Slide 89
  • Slide 90
  • Slide 91
  • Slide 92
  • Slide 93
  • Slide 94
  • Slide 95
  • Slide 96
  • Slide 97
  • Slide 98
  • Slide 99
  • Slide 100
  • Slide 101
  • Slide 102
  • Slide 103
Page 25: 第五章 樹

定理 5-4 n 個節點的完備或完滿二元樹其深度為 log2(n+1)

證明 完滿或完備二元樹的節點皆自階層

1 起排放任一階層 i 可排放 2i-1

個節點遂由定理 5-2 得知此 n 個節點所佔的深度為 log2(n+1)

正規二元樹 (formal binary tree)bull 在二元樹中非樹葉的節點又稱為內部節點

(internal node) 若所有內部節點恰有 2 個子節點即成為正規二元樹

定義若二元樹中所有內部節點恰有 2 個子節點則稱之為正規二元樹

正規二元樹常被引用在單淘汰賽正規二元樹常被引用在單淘汰賽制的各項賽程安排樹葉為參賽制的各項賽程安排樹葉為參賽隊伍內部節點為優勝隊伍樹隊伍內部節點為優勝隊伍樹根即為冠軍隊伍根即為冠軍隊伍

在單淘汰賽制中自在單淘汰賽制中自 n n 個隊伍中產個隊伍中產生冠軍須舉辦 生冠軍須舉辦 nn-1-1場比賽場比賽

54  二元樹的表示法541 以陣列表示二元樹定理 5-5 若一個具有 n 個節點的完備二元樹以循序的方式編號並依序存放在陣列 A 中即第 i 個節點放在 A[i] 中 1 i n 則

(1) 節點 i 的父節點位在 A[i2]處 i ne 1( i = 1 時其節點正為樹根無父節點)

(2) 若 2i n 則節點 i 的左兒子節點位在 A[2i] 處若 2i gtn 節點 i 沒有左子節點

(3) 若 2i+1 n 節點 i 的右兒子節點位 A[2i+1]處若 2i+1gtn 節點 i 沒有左子

節點

證明先用數學歸納法證明 (2) bull 歸納法的基礎當 i = 1 時其左子節點確實在 A[2] 處(除非 nlt2 此時節點 1 沒有左子節點)

bull 歸納法的假設假設當 I = k 時其左子節點確在 A[2i] = A[2k] 處(除非 2i 已大於 n 此時節點 i 無左子節點)

bull 歸納法的推論當 i = k+1 時因第 k 節點的左子節點在 A[2k] 處其右子節點則位在 A[2k+1] 處那麼第 k+1 節點的左子節點應緊臨 A[2k+1] 亦即位於 A[2k+2] = A[2(k+1)] 處(除非 2i 已大於 n 此時節點 i 無左子節點)

bull 由數學歸納法的原理得證定理 5-5 (2) (3) 由於 (2) 成立而且節點 i 的右子節點會緊臨

A[2i] 亦即其位於 A[2i+1]處除非 i 根本沒有右子節點得證定理 5-5 (3)

由 (2) 和 (3) 我們可知定理 5-5 (1) 成立

542 以鍵結串列表示二元樹bull 以陣列來表示完備二元樹算是相當方便但ndash一般的二元樹將導致陣列空間的浪費ndash如果二元樹中節點的新增刪除動作頻繁則會造成陣列對應資料的搬動隨而頻繁

bull 以鍵結串列表示二元樹

為方便辦識樹根我們常用 root 指標指向樹根做為樹鍵結串列的起點

陣列表示和鍵結表示的空間需求bull 完備二元樹的陣列表示和鍵結表示皆沒浪費儲存資料的空間

bull 但歪斜樹的陣列表示比起鍵結表示則浪費許多

bull 以 n 個節點的歪斜樹而言陣列表示法可能需要 2n-1 個陣列元素其中 2n-1-n 個陣列元素空間是浪費的而鍵結表示法只需要 n 個節點空間

二元樹節點的宣告程式 5-2

1 struct BTreeNode

2 struct BtreeNode leftchiled

3 int data

4 struct BTreeNode rightchild

5

6 struct BTreeNode root

bull 這樣的二元樹節點要取得兒子節點的指標十分方便但若有取得父節點的需求則嫌不足我們可以加上 parent 欄位指向父節點

55  二元樹的走訪bull 假如一組資料已用二元樹的組織在一起總有需求對其全數資料做動作例如計算所有數目印出所有資料在所有資料中搜尋某項資料hellip等此時即須對此二元樹做走訪 (traversal) 的運算利用走訪二元樹的同時將計算列印或搜尋的動作完成

bull 事實上走訪即在決定二元樹上資料被處理(計算列印或搜尋)的順序我們也希望走訪的演算法對任何節點皆一致是容易撰寫程式實作的

bull 若對一個二元樹上的節點而言 V 表示處理節點上的資料 L 表示走訪其左子樹 R 表示走訪其右子樹

55  二元樹的走訪 ( 續 )bull 因為對稱的緣故我們只考慮先走訪左邊再走訪右邊的情形那麼右圖只剩下三種走訪方式

bull 以 V 所在的相對位置分別對此三種走訪方式取名如下

(1) LVR 中序走訪 (inorder traversal)或中序表示法 (infix notation)

(2) LRV 後序走訪 (postorder traversal) 或後序表示法 (postfix notation)

(3) VLR 前序走訪 (preorder traversal)或前序表示法 (prefix notation)

551  中序走訪bull 利用遞迴的方式撰寫中序走訪的程序希望把二元樹中序走訪的

順序印出來

程式 5-3二元樹的中序走訪1 struct BTreeNode2 struct BTreeNode leftchild3 char data4 struct BTreeNode rightchild5 6 struct BTreeNode root7 void inorder(struct BTreeNode node)8 if (node = NULL)9 inorder(node-gtleftchild)10 cout ltlt node-gtdata11 inorder(node-gtrightchild)12 13

範例 5-12此為運算式二元樹

其對應中序表示運算式為 (A+B)C+D(E-F)

6 struct BTreeNode root7 void inorder(struct BTreeNode node)8 if (node = NULL)9 inorder(node-gtleftchild)10 cout ltlt node-gtdata11 inorder(node-gtrightchild)12 13

6 struct BTreeNode root7 void inorder(struct BTreeNode node)8 if (node = NULL)9 inorder(node-gtleftchild)10 cout ltlt node-gtdata11 inorder(node-gtrightchild)12 13

(A+B)C+D(E-F)

如何加上 ( )

552  後序走訪程式 5-4二元樹的後序走訪14 void postorder(struct BTreeNode node)

15 if (node =NULL)16 postorder(node-gtleftchild)17 postorder(node-gtrightchild)18 cout ltlt node-gtdata19 20

以後序走訪可得到AB+CDEF-+

其正為對應的後序運算式

553  前序走訪程式 5-5二元樹的前序走訪21 void preorder(struct BTreeNode node)

22 if (node = NULL)

23 cout ltlt node-gtdata

24 preorder(node-gtleftchild)

25 preorder(node-gtrightchild)

26

27

以前序走訪可得到++ABCD-EF

其正為對應的前序運算式

554 利用堆疊和迴圈取代遞迴進行二元樹的走訪1 struct BTreeNode 2 struct BTreeNode leftchild3 char data4 struct BtreeNode rightchild5 6 struct BTreeNode root7 struct StackNode8 struct BTreeNode treenode9 struct StackNode next1011 struct StackNode top

12 void push_node(struct BtreeNode node )13 struct StackNode old_top14 old_top = top15 top = (struct StackNode ) malloc

(sizeof(struct StackNode))16 top-gttreenode = node17 top-gtnext = old_top18 19 struct BTreeNode pop_node()20 struct BTreeNode Tnode21 struct StackNode old_top22 if (top == NULL)23 StackEmpty()24 else25 Tnode = top-gttreenode26 old_top = top27 top = top-gtnext28 free(old_top)29 return Tnode30 31

32 void inorder_Stack(struct BtreeNode node)33 do34 while (node = NULL)35 push_node(node)36 node = node-gtleftchild37 push all left children38 if (top = NULL)39 node = pop_node()40 cout ltlt node-gtdata41 node = node-gtrightchild42 inorder printing and check right child43 while ((top=NULL)||(node=NULL))44 top==NULL 堆疊已空 node==NULL沒有右子

6 struct BTreeNode root7 void inorder(struct BTreeNode node)8 if (node = NULL)9 inorder(node-gtleftchild)10 cout ltlt node-gtdata11 inorder(node-gtrightchild)12 13

中序表示為DBGEHAFIC

32 void inorder_Stack()33 struct BTreeNode node34 node = root35 do36 while (node = NULL)37 push_node(node)38 node = node-gtleftchild39 40 if (top = NULL)41 node = pop_node()42 cout ltlt node-gtdata43 node = node-gtrightchild44 45 while(top=NULL)||(node=NULL))46

45 void Preorder_Stack(struct BSTreeNode node)46 do47 while (node = NULL)48 cout ltlt node-gtdata)49 push_node(node)50 node = node-gtleftchild51 push all left children52 if (top = NULL)53 node = pop_node()54 node = node-gtrightchild55 56 while ((top=NULL)||(node=NULL))57

程式 5-5二元樹的前序走訪21 void preorder(struct BTreeNode node)22 if (node = NULL)23 cout ltlt node-gtdata24 preorder(node-gtleftchild)25 preorder(node-gtrightchild)26 27

58 void Postorder_Stack(struct BTreeNode node)59 do60 while (node = NULL)61 cout ltlt node-gtdata62 push_node(node)63 node = node-gtrightchild64 push all left children65 if (top = NULL)66 node = pop_node()67 node = node-gtleftchild68 69 while ((top=NULL)||(node=NULL))70

程式 5-4二元樹的後序走訪14 void postorder(struct BTreeNode node)15 if (node =NULL)16 postorder(node-gtleftchild)17 postorder(node-gtrightchild)18 cout ltlt node-gtdata19 20

555  階層走訪bull 「階層走訪」 (level-order traversal) 是依階層

的順序進行二元樹的走訪先走訪階層小的節點後走訪階層大的節點同一階層者則依自左向右的順序走訪

bull 對下圖的二元樹進行階層走訪的結果為ABCDEFGHI

bull 對同一階層而言先走訪的節點其子節點亦在下一階層中先被走訪

bull 這種「先進先出」的特性恰可用佇列予以儲存與處理

程式 5-7 利用佇列進行二元樹的階層走訪

1 struct BTreeNode2 struct BTreeNode leftchild3 char data4 struct BTreeNode rightchild5 6 struct BTreeNode root7 struct QNode8 struct BTreeNode treenode9 struct QNode next10 11 struct QNode front rear

12 void AddQueue(struct BTreeNode Tnode)13 struct QNode node14 node=(struct QNode )malloc(sizeof(struct QNode))15 node-gttreenode = Tnode16 node-gtnext = NULL17 if (front == NULL) front = node19 else rear-gtnext = node20 rear = node21

22 struct BTreeNode DeleteQueue()23 struct BTreeNode Tnode24 struct QNode old_front25 if (rear == NULL)26 QueueEmpty()27 else28 Tnode = front-gttreenode29 old_front = front30 front = front-gtnext31 free(old_front)32 return Tnode33 34 35 void LevelOrder (struct BTreeNode node)36 AddQueue(node)37 while (front =NULL)38 node = DeleteQueue()39 cout ltlt node-gtdata40 if (node-gtleftchild = NULL)41 AddQueue(node-gtleftchild)42 if (node-gtrightchild = NULL)43 AddQueue(node-gtrightchild)44 45

35 void LevelOrder (struct BTreeNode node)36 AddQueue(node)37 while (front =NULL)38 node = DeleteQueue()39 cout ltlt node-gtdata40 if (node-gtleftchild = NULL)41 AddQueue(node-gtleftchild)42 if (node-gtrightchild = NULL)43 AddQueue(node-gtrightchild)44 45

bull Infix ABCbull Postfix ABC CBA ACB BCA

BAC CAB

B

A CA

B

C

B

A

C

C

B

AB

A

C

二元樹中序與前序式中序與後序式bull 每一棵二元樹皆有唯一的一對中序與前序表示法也有唯一的中序與後序次序

bull 換句話說給予一對中序與前序或中序與後序即可決定一棵二元樹

bull 然而給予前序和後序次序並不能決定唯一的二元樹可能會產生兩棵不同的二元樹

bull 想想階層走訪和中序走訪順序可否決定唯一的二元樹

A

B C

H J D E

F G

prefix ABHJCDFGEinfix HBJAFDGCEpostfix HJBFGDECA

VLRLVRLRV

A

prefix BHJinfix HBJ

prefix CDFGEinfix FDGCE

prefix ABHJCDFGEinfix HBJAFDGCE

A

B C

H J D E

F G

prefix ABHJCDFGE

infix HBJAFDGCEk-1

struct BTreeNode BTree_InfixPrefix (AnsiString infix AnsiString prefix)

int k struct BTreeNode node if (infixLength==0) return NULLnode = (struct BTreeNode ) malloc (sizeof(struct BTreeNode))node-gtdata = prefix[1]k = find(prefix[1] infix)node-gtleftchild = BTree_InfixPrefix

(infix[1k-1] prefix[2k])node-gtrightchild = BTree_InfixPrefix(infix[k+1infixLength]prefix[k+1prefixLength])

VLR

LVRk1

k2 k+1~

k+1~

1

struct BTreeNode BTree_InfixPostfix (AnsiString infix AnsiString prefix)

int k struct BTreeNode node if (infixLength==0) return NULLnode = (struct BTreeNode ) malloc (sizeof(struct BTreeNode))node-gtdata = postfix[postfixLength]k = find(postfix[postfixLength] infix)node-gtleftchild = BTree_InfixPrefix

(infix[1k-1] postfix[1k-1])node-gtrightchild = BTree_InfixPrefix(infix[k+1infixLength]postfix[k+1postfixLength-1])

56  二元樹的複製與相等測試程式 5-8 二元樹的複製1 struct BTreeNode2 struct BTreeNode leftchild3 char data4 struct BTreeNode rightchild5 6 struct BTreeNode root7 struct BTreeNode Copy(struct BTreeNode TreeRoot)8 struct BTreeNode CopyRoot9 if (TreeRoot == NULL)10 return NULL11 CopyRoot =(struct BTreeNode ) malloc (sizeof(BTreeNode))12 CopyRoot-gtdata = TreeRoot-gtdata13 CopyRoot-gtleftchild = Copy(TreeRoot-gtleftchild)14 CopyRoot-gtrightchild = Copy(TreeRoot-

gtrightchild)15 return CopyRoot16

程式 5-9 檢測兩棵二元樹是否相等1 struct BTreeNode2 struct BTreeNode leftchild3 char data4 struct BTreeNode rightchild5 6 struct BtreeNode rightchild7 int Equal(struct BTreeNode ustruct BTreeNode v)8 if ((u==NULL) ampamp (v==NULL)) return 19 if ((u=NULL) ampamp (v=NULL)10 ampamp (u-gtdata == v-gtdata )11 ampamp Equal(u-gtleftchildv-gtleftchild)12 ampamp Equal(u-gtrightchildv-gtrightchild))

13 return 114 return 015

Counting Nodes in a Binary Tree

int CountingNodes(struct BTreeNodes node)

if (node == NULL) return 0 int leftchildren = CountingNodes(node-gtleftchild)

int rightchildren= CountingNodes(node-gtrightchild)

return leftchildren+rightchildren

Counting Leaves in a Binary Tree

int CountingLeaves(struct BTreeNodes node)

if (node == NULL) return 0 int leftleaves = CountingLeaves(node-gtleftchild)

int rightchildren= CountingLeaves(node-gtrightchild)

if (leftleaves==0 ampamp leftleaves==0) return 1

return leftleaves + leftleaves

57  引線二元樹bull 在二元樹的鍵結表示中樹葉節點的兩個子樹指標皆指向 NULL 由定理 5-1 得知 n = E+1 其中 n 為節點個數 E 為分支數

bull 而 n 個鍵結節點有 2n 個指標空間每個分支恰佔用一個指標空間共有 E (=n-1) 個指標有用到(不是空的)而共有 n+1 個指標空間是 NULL 比非空的節點還多

bull 有學者提出對這些「放空指標的空間」加以利用的概念mdash與其放空指標不如放指向其它節點的指標稱之為引線 (thread) 使得某些運算(如走訪hellip等)可以加快

bull 這個概念發展出了引線二元樹 (threaded binary tree) 這種資料結構

範例 5-17下圖為加了引線的二元樹

加入的引線將使二元樹的中序走訪更加便利沿著上右圖箭頭所指示的順序走訪即可完成此二元樹的中序走訪GDHBAIECF 為此二元樹的中序表示

bull 範例 5-17 中所加的引線目的在方便二元樹的中序走訪

bull 對樹葉節點 p 而言令其資料為 P 則引線指標的規則如下

(1) p 的左子樹指標指向中序表示中 P 的前一個 元素(前接元素 (predecessor) )

(2) p 的右子樹指標指向中序表示中 P 的後一個 元素(後繼元素 (successor) )

為了區別節點指標放的是一般指標(內部節點)抑或是引線指標(樹葉節點)我們另以 2 個欄位分別區別左和右子樹指標空間存放的對象其節點的記憶體配置有如下圖所示

其中 leftthread (rightthread) 為 0 時表示 leftchild (rightchild) 存放的是一般節點指標而 leftthread (rightthread) 為 1 時表示 leftchild (rightchild) 存放的是引線指標1 struct TBTreeNode2 int leftthread3 struct TBTreeNode leftchild4 char data5 struct TBTreeNode rightchild6 int rightthread7

由右側的引線二元樹可知有最左和最右兩條引線尚無妥善的安排我們另設計一空的引線節點做為樹根如下圖所示

571  引線二元樹的中序走訪決定節點 p 在中序表示中的後繼節點 q (簡稱中序後繼點)

程式 5-10決定節點 node的中序後繼點1 struct TBTreeNode2 int leftthread3 struct TBTreeNode leftchild4 char data5 struct TBTreeNode rightchild6 int rightthread7 8 struct TBTreeNode root9 struct TBTreeNode Next(struct TBTreeNode node)

10 struct TBTreeNode temp11 temp = node-gtrightchild12 if (node-gtrightthead) return temp13 while (temp-gtleftthead) temp=temp-gtleftchild

14 return temp15

程式 5-11引線二元樹的中序表示(接程式 5-10)1 void InorderTBTree()2 struct TBTreeNode node3 for(node=Next(root)node=rootnode=Next(node))

4 cout ltlt node-gtdata5

bull只要 node 是引線二元樹上的任一節點透過呼叫 Next(node) 即可找到其在中序表示中的後繼節點bull(試想若程式 5-10 中的程序 Next 所傳入的參數指標正是 root Next(root) 是否可正常運作請思考)bull於是只須從中序表示中的第一節點起讓每一節點 node 都執行 Next(node) 程序即可得引線二元樹的中序表示

572  在引線二元樹中加入節點在引線二元樹中樹葉節點節點的右子樹指標所指向或內部節點右子樹的最左樹葉為其中序後繼節點而且任一節點若無左子樹則其左子樹指標乃指向其中序前接節點hellip這些性質必須在節點新增進入引線二元樹時也要保持接下來討論節點 new 插入成為引線二元樹的節點 p 的右子節點的情形至於插入成為左子節點的情形則讓各位模擬推敲

572572  在引線二元樹中加入節點 在引線二元樹中加入節點 (( 續續 ))

newnew 會成為會成為 pp 的右子節點的情況共有以下兩種的右子節點的情況共有以下兩種(1) (1) 若若 pp 沒有右子節點 沒有右子節點 ((p-gtrightthread p-gtrightthread 為為1) 1) 如圖如圖 ((a) a) 則 則 new new 插入後應形成如圖插入後應形成如圖 ((b) b) 所示所示

(2)(2) 若若 pp 有右子節點 有右子節點 ((p-gtrightthread p-gtrightthread 為為0) 0) 如圖如圖 ((c) c) 則 則 new new 插入後應形成如插入後應形成如圖圖 ((d) d) 所示所示

程式 5-12  插入節點 new 成為引線二元樹中節點 p的右子節點(上接程式 5-10 5-11 )

1 void InsertRight(struct TBTreeNode p 2 struct TBTreeNode new)3 struct TBTreeNode q4 new-gtrightchild = p-gtrightchild5 new-gtrightthead = p-gtrightthread6 new-gtleftchild = p7 new-gtleftthread = 18 p-gtrightchild = new9 p-gtrightthead = 010 if (new-gtrightthread)11 q = Next(new)12 q-gtleftchild = new13 14

58  堆積bull 「堆積」 (heap) 是一個特殊的完備二元樹節點的資料與其子節點的資料之間有事先定好的大小關係存在bull 定義ndash最大堆疊為一完備二元樹任一節點的資料內容不小於其子節點的資料內容ndash最小堆疊為一完備二元樹任一節點的資料內容不大於其子節點的資料內容

581  插入節點至最大堆積中最大堆積是一完備二元樹所以如圖 (a) 之最大堆積於加入新節點後樹的形狀必須如圖 (b) 所示灰色節點即為新節點若加入的節點資料分別為 1 9 12 則其最大堆積將分別變成如圖 (c) (d) (e) 所示

程式 5-13 新增節點至最大堆積中最大堆積本身是一棵完備二元樹可以用陣列表示之 1 define maxsize 1002 int heap[maxsize]3 int n i4 void InsertHeap(int x)5 if (n == maxsize) HeapFull()6 else7 n++8 i = n9 while ((i gt 1) ampamp (x gt heap[i2]))10 heap[i] = heap[i2]11 i = 212 13 heap[i] = x14 16

程序 InsertHeap 在插入第 n 個資料時需要的時間複雜度為 O(logn)

1 100

2 93

3 84

4 77

5 65

6 81

7 79

8 66

9 70

10 50

100

84

77

93

8165

66 70

79

50

1

2 3

4 5 6 7

8 9 10

1 define maxsize 1002 int heap[maxsize]3 int n i4 void InsertHeap(int x)5 if (n == maxsize) HeapFull()6 else7 n++8 i = n9 while ((i gt 1) ampamp (x gt heap[i2]))10 heap[i] = heap[i2]11 i = 212 13 heap[i] = x14 16

582 刪除最大堆積中的最大資料圖 (a) 中的最大堆積在刪除最大資料後其樹形應變成如圖 (b) 所示將最未一個樹葉節點的資料 6 暫挪至樹根節點內如圖 (c) 再將圖 (c) 中非最大堆積的結構調整成為最大堆積 10 和 8 中選大者與 6對調即成圖 (d)

程式 5-14 刪除最大堆積中的最大資料(上接程式 5-13 )1 int DeleteHeap()2 int x i j3 if (n == 0) HeapEmpty()4 else5 x = heap[1]6 heap[1] = heap[n]7 n--8 i = 19 while (i lt= n2)10 if (heap[2i] gt heap[2i+1])11 j = 2i12 else13 j = 2i+114 if (heap[i]gtheap[j]) break15 swap(heap[i] heap[j])16 i = j17 18 return x19 20

其時間複雜度與其堆積高度相同是為O(logn) 其中 n 為堆積中的元素個數

1 100

2 93

3 84

4 77

5 65

6 81

7 79

8 66

9 70

10 50

100

84

77

93

8165

66 70

79

50

1

2 3

4 5 6 7

8 9 10

1 int DeletHeap()2 int x i j3 if(n == 0) HeapEmpty()4 else5 x = heap[1]6 heap[1] = heap[n]7 n--8 i = 19 while (i lt= n2)10 if ([heap[2i]] gt heap[2i+1])11 j = 2i12 else13 j = 2i+114 if (heap[i]gtheap[j]) break15 swap(heap[i] heap[j])16 i = j17 18 return x19 20

583  優先佇列bull 由於最大堆積(或最小堆積)的結構可提供最大元素(或最小元素)而其維護堆積的成本為 O(logn) n 為總元素個數

bull 堆積常被當成「優先佇列」 (priority queue) 使用mdash此時佇列被組織成完備二元樹若新加入佇列的資料有大小關係優先順序的需求時此優先佇列即以 O(logn) 的代價將新資料放入優先佇列(可能是最大或最小堆積依實際大小關係優先順序的需求而定)中mdash提供依元素的優先順序做為處理順序的服務

bull 一般的佇列為先進先出先到的先行處理優先佇列則是依據其優先順序優先順序高者先行處理與來到的先後無關

範例 5-21bull 在「分時」 (time sharing) 的作業系統中我們不希望使用中央處理器 (Central Processing Unit CPU) 時間短的「處理程序」 (process) 等候過久於是有「最短時間工作優先」 (shortest-job-first) 的排程策略此時等候的處理程序即可組織成之一最小堆積而 CPU 每次即取出存於樹根的處理程序予以處理任何新來的處理程序則以 O(logn) 的時間加入此優先佇列(最小堆積)中

bull 注意之前提過的線性佇列新來的直接加入成為新的尾端元素花O(1) 的時間然線性佇列本身沒有大小的關係每次要取出最大(小)的資料得花O(n) 的代價(在所有元素中挑出最大(小)者) n 為佇列內的元素個數而優先佇列是在新元素加入時要花O(logn) 的時間至於取出最大(小)則只須 O(1) 的時間

59  二元搜尋樹bull 二元搜尋樹是一棵二元樹可能是空二元樹若不為空二元樹則滿足下列性質ndash所有節點內的資料內容是相異的 ndash左子節點的資內容 ( 如果有 ) 比父節點的資料內容小 ndash右子節點的資料內容 ( 如果有 ) 比父節點的資料內容大 ndash以左和右子節點為樹根的左子樹和右子樹也是二元搜尋樹

在二元搜尋樹中搜尋資料二元搜尋樹對資料大小的掌握十分明確若恰與節點 p 資料相等則搜尋成功若欲搜尋的資料 x 比樹節點 p 中的資料小 則只須搜尋節點 p 的左子樹若比節點 p 中的資料大 則只須搜尋 p 的右子樹這樣的判斷可以遞迴地應用在各子樹節點 中

程式 5-15 在二元搜尋樹中搜尋資料(遞迴呼叫)1 struct BSTreeNode2 struct BSTreeNode leftchild3 int data4 struct BSTreeNode rightchild5 6 struct BSTreeNode root7 struct BSTreeNode SearchBST(

struct BSTreeNode tree int x)8 if (tree == NULL) return NULL9 if (x == tree-gtdata) return tree10 if (x lt tree-gtdata)11 return SearchBST(tree-gtleftchild x)

12 return SearchBST(tree-gtrightchild x)13

程式 5-16 搜尋二元搜尋樹的資料(非遞迴呼叫)在二元搜尋樹十分龐大時遞迴呼叫的效率不會太好可利用迴圈來完成搜尋的動作以提高執行效率請看程式 5-16

14 struct BSTreeNode Search BST_iterative15 (struct BSTreeNode node int x)16 while (node = NULL)17 if (x == node-gtdata) return node18 if (x lt node-gtdata)19 node = node-gtleftchild20 else21 node = node-gtrightchild22 23 return NULL24

592  新增資料於二元搜尋樹中欲新增資料進入一二元搜尋樹中得先決定該資料在二元搜尋樹中應出現的位置這可透過上節提到的搜尋動作來解決

在二元搜尋樹定義的性質 (1) 中我們限定了元素必須相異所以搜尋新資料的結果必是 NULL

必須將搜尋的結果改為新增資料所應在位置的節點指標

程式 5-17  在二元搜尋樹中新增資料25 int InsertBST(int x)26 struct BSTreeNode p q27 p = root q = NULL28 while (p = NULL)29 q = p30 if (x == p-gtdata) return 0

若不考慮重複則可省略之31 if (x lt p-gtdata)32 p = p-gtleftchild33 else34 p = p-gtrightchild35

3636 p = (struct BSTreeNode ) p = (struct BSTreeNode ) malloc (sizeof(BSTreeNode))malloc (sizeof(BSTreeNode))

3737 p-gtdata = x p-gtdata = x3838 p-gtleftchild = p-gtrightchild = p-gtleftchild = p-gtrightchild = NULLNULL3939 if (root == NULL) root = p if (root == NULL) root = p4040 else if (x lt q-gtdata) else if (x lt q-gtdata)4141 q-gtleftchild = p q-gtleftchild = p4242 else else4343 q-gtrightchild = p q-gtrightchild = p4444 return 1 return 145 45

程式 程式 5-175-17  在二元搜尋樹中新增資料  在二元搜尋樹中新增資料 (( 續續 ))

範例 5-23 圖 (b) 為圖 (a) 新增整數 6 之後的結果圖 (c) (d) (e) (f) 則分別是逐步再加入 28 18

10 21 的結果

bull若目前的二元搜尋樹高度為 h 則新增資料得花O(h) 的時間找到其所應在的位置遂新增資料至二元搜尋樹中的時間複雜之度為 O(h)

593 自二元搜尋樹中刪除資料欲自二元搜尋樹中刪除資料可能會面臨兩種情況(1) 刪除節點 p 位於樹葉節點上如下圖所示 這是比較容易處理的情況

(2) 刪除的節點 p 不是樹葉刪除節點 p 後原 p 所指的節點可由 q ( q 中包含了 p 左子樹中的最大節點值)或 r ( r 中包含了 p 右子樹中的最小節點值)來取代以維持二元搜尋樹中資料的大小關係其結果分別如圖 (b) 和 (c) 所示而圖 (d) 則標明了指標會改變的節點位置即 p 會消失 father 的左或右子樹指標會異動 k(可能是 q 或 r )會移到原來 p 的位置其父節點 f 的左或右子樹指標會異動其中 father 為 p 父節點 f 為 k 的父節點

程式 5-18 自二元搜尋樹中刪除資料46 int DeleteBST (int x)47 Struct BSTreeNode p father k f48 p = root father = NULL49 while (p = NULL)50 if (x == p-gtdata) 找到 x 所在的節點 p 了51 if ((p-gtleftchild == NULL) ampamp 52 (p-gtrightchild == NULL))53 k = NULL p為樹葉節點54 else if (p-gtleftchild = NULL )x位於非子節

點55 f = p 找出 p左子樹的最右樹葉56 k = p-gtleftchild57 while (k-gtrightchild =NULL)58 f = k59 k = k-gtrightchild60 k將挪至原 p處61 if (p == f)62 f-gtleftchild = k-gtleftchild 63 else64 f-gtrightchild = k-gtleftchild 65

66 else p無左子樹67 f = p 找出 p右子樹的最左樹葉68 k = p-gtrightchild69 while (k-gtleftchild = NULL)70 f = k71 k = k -gtleftchild72 73 if (p == f)74 f-gtrightchild = k-gtrightchild 75 else76 f-gtleftchild = k-gtrightchild77 78 if (k = NULL) k挪至原 p處繼承 p的左右指標79 k-gtleftchild = p-gtleftchild80 k-gtrightchild = p-gtrightchild81 82 if (father == NULL) root = k83 else if (x lt father-gtdata) 決定 k是 father的左

或右兒子84 father-gtleftchild = k85 else86 father-gtrightchild = k87 free(p)88 return 1 成功地刪除 x於此傳回 1返回呼叫處89 51~89行為找到 x時應有的處理步驟

90 else 尚未找到 x繼續往下一階層找

91 father = p92 if (x lt p-gtdata)93 p = p-gtleftchild94 else95 p = p-gtrightchild96 97 98 return 0 未找到 x傳回 099

510  樹林 (forest)

bull 定義樹林(森林)是由 n 棵沒有交集的樹所構成的集合 n 0

下圖為一包含三棵樹的樹林

5101 轉換樹林為二元樹 之前我們以二元樹來表示樹因為二元樹有較多

好用的性質在此我們也將樹林轉換成二元樹看待

在 522 節中我們嘗試了用左子右兄弟的方式來表示樹這個方法事實上已將樹轉換成二元樹

即用左子右兄弟的結構重新組織樹林中的每一棵樹這些樹的樹根將視為兄弟而最左邊的樹則視為樹林二元樹的樹根如此一來樹林即可轉換成二元樹

範例 5-25右下圖為右上圖三棵樹所對應的三棵左子右兄弟二元樹

下圖則為上圖樹林所對應的一棵樹林二元樹

演算法 5-1  轉換樹林成二元樹輸入樹林 F = T1 T2 Tn

輸出 F 的二元樹表示 BT=(T1 T2 Tn)1 if (n == 0) 輸出空二元樹 2 else

3 root(T1 T2 Tn) = T1

4 leftsubtree(T1 T2 Tn) =

BT(T11 T12 T1m)5 BT(T11 T12 T1m)為 T1的子樹6 rightsubtree(T1 T2 Tn) =

BT(T2 T3 Tn)

樹林轉換成二元樹的過程樹林轉換成二元樹的過程

  • Slide 1
  • Slide 2
  • Slide 3
  • Slide 4
  • Slide 5
  • Slide 6
  • Slide 7
  • Slide 8
  • Slide 9
  • Slide 10
  • Slide 11
  • Slide 12
  • Slide 13
  • Slide 14
  • Slide 15
  • Slide 16
  • Slide 17
  • Slide 18
  • Slide 19
  • Slide 20
  • Slide 21
  • Slide 22
  • Slide 23
  • Slide 24
  • Slide 25
  • Slide 26
  • Slide 27
  • Slide 28
  • Slide 29
  • Slide 30
  • Slide 31
  • Slide 32
  • Slide 33
  • Slide 34
  • Slide 35
  • Slide 36
  • Slide 37
  • Slide 38
  • Slide 39
  • Slide 40
  • Slide 41
  • Slide 42
  • Slide 43
  • Slide 44
  • Slide 45
  • Slide 46
  • Slide 47
  • Slide 48
  • Slide 49
  • Slide 50
  • Slide 51
  • Slide 52
  • Slide 53
  • Slide 54
  • Slide 55
  • Slide 56
  • Slide 57
  • Slide 58
  • Slide 59
  • Slide 60
  • Slide 61
  • Slide 62
  • Slide 63
  • Slide 64
  • Slide 65
  • Slide 66
  • Slide 67
  • Slide 68
  • Slide 69
  • Slide 70
  • Slide 71
  • Slide 72
  • Slide 73
  • Slide 74
  • Slide 75
  • Slide 76
  • Slide 77
  • Slide 78
  • Slide 79
  • Slide 80
  • Slide 81
  • Slide 82
  • Slide 83
  • Slide 84
  • Slide 85
  • Slide 86
  • Slide 87
  • Slide 88
  • Slide 89
  • Slide 90
  • Slide 91
  • Slide 92
  • Slide 93
  • Slide 94
  • Slide 95
  • Slide 96
  • Slide 97
  • Slide 98
  • Slide 99
  • Slide 100
  • Slide 101
  • Slide 102
  • Slide 103
Page 26: 第五章 樹

正規二元樹 (formal binary tree)bull 在二元樹中非樹葉的節點又稱為內部節點

(internal node) 若所有內部節點恰有 2 個子節點即成為正規二元樹

定義若二元樹中所有內部節點恰有 2 個子節點則稱之為正規二元樹

正規二元樹常被引用在單淘汰賽正規二元樹常被引用在單淘汰賽制的各項賽程安排樹葉為參賽制的各項賽程安排樹葉為參賽隊伍內部節點為優勝隊伍樹隊伍內部節點為優勝隊伍樹根即為冠軍隊伍根即為冠軍隊伍

在單淘汰賽制中自在單淘汰賽制中自 n n 個隊伍中產個隊伍中產生冠軍須舉辦 生冠軍須舉辦 nn-1-1場比賽場比賽

54  二元樹的表示法541 以陣列表示二元樹定理 5-5 若一個具有 n 個節點的完備二元樹以循序的方式編號並依序存放在陣列 A 中即第 i 個節點放在 A[i] 中 1 i n 則

(1) 節點 i 的父節點位在 A[i2]處 i ne 1( i = 1 時其節點正為樹根無父節點)

(2) 若 2i n 則節點 i 的左兒子節點位在 A[2i] 處若 2i gtn 節點 i 沒有左子節點

(3) 若 2i+1 n 節點 i 的右兒子節點位 A[2i+1]處若 2i+1gtn 節點 i 沒有左子

節點

證明先用數學歸納法證明 (2) bull 歸納法的基礎當 i = 1 時其左子節點確實在 A[2] 處(除非 nlt2 此時節點 1 沒有左子節點)

bull 歸納法的假設假設當 I = k 時其左子節點確在 A[2i] = A[2k] 處(除非 2i 已大於 n 此時節點 i 無左子節點)

bull 歸納法的推論當 i = k+1 時因第 k 節點的左子節點在 A[2k] 處其右子節點則位在 A[2k+1] 處那麼第 k+1 節點的左子節點應緊臨 A[2k+1] 亦即位於 A[2k+2] = A[2(k+1)] 處(除非 2i 已大於 n 此時節點 i 無左子節點)

bull 由數學歸納法的原理得證定理 5-5 (2) (3) 由於 (2) 成立而且節點 i 的右子節點會緊臨

A[2i] 亦即其位於 A[2i+1]處除非 i 根本沒有右子節點得證定理 5-5 (3)

由 (2) 和 (3) 我們可知定理 5-5 (1) 成立

542 以鍵結串列表示二元樹bull 以陣列來表示完備二元樹算是相當方便但ndash一般的二元樹將導致陣列空間的浪費ndash如果二元樹中節點的新增刪除動作頻繁則會造成陣列對應資料的搬動隨而頻繁

bull 以鍵結串列表示二元樹

為方便辦識樹根我們常用 root 指標指向樹根做為樹鍵結串列的起點

陣列表示和鍵結表示的空間需求bull 完備二元樹的陣列表示和鍵結表示皆沒浪費儲存資料的空間

bull 但歪斜樹的陣列表示比起鍵結表示則浪費許多

bull 以 n 個節點的歪斜樹而言陣列表示法可能需要 2n-1 個陣列元素其中 2n-1-n 個陣列元素空間是浪費的而鍵結表示法只需要 n 個節點空間

二元樹節點的宣告程式 5-2

1 struct BTreeNode

2 struct BtreeNode leftchiled

3 int data

4 struct BTreeNode rightchild

5

6 struct BTreeNode root

bull 這樣的二元樹節點要取得兒子節點的指標十分方便但若有取得父節點的需求則嫌不足我們可以加上 parent 欄位指向父節點

55  二元樹的走訪bull 假如一組資料已用二元樹的組織在一起總有需求對其全數資料做動作例如計算所有數目印出所有資料在所有資料中搜尋某項資料hellip等此時即須對此二元樹做走訪 (traversal) 的運算利用走訪二元樹的同時將計算列印或搜尋的動作完成

bull 事實上走訪即在決定二元樹上資料被處理(計算列印或搜尋)的順序我們也希望走訪的演算法對任何節點皆一致是容易撰寫程式實作的

bull 若對一個二元樹上的節點而言 V 表示處理節點上的資料 L 表示走訪其左子樹 R 表示走訪其右子樹

55  二元樹的走訪 ( 續 )bull 因為對稱的緣故我們只考慮先走訪左邊再走訪右邊的情形那麼右圖只剩下三種走訪方式

bull 以 V 所在的相對位置分別對此三種走訪方式取名如下

(1) LVR 中序走訪 (inorder traversal)或中序表示法 (infix notation)

(2) LRV 後序走訪 (postorder traversal) 或後序表示法 (postfix notation)

(3) VLR 前序走訪 (preorder traversal)或前序表示法 (prefix notation)

551  中序走訪bull 利用遞迴的方式撰寫中序走訪的程序希望把二元樹中序走訪的

順序印出來

程式 5-3二元樹的中序走訪1 struct BTreeNode2 struct BTreeNode leftchild3 char data4 struct BTreeNode rightchild5 6 struct BTreeNode root7 void inorder(struct BTreeNode node)8 if (node = NULL)9 inorder(node-gtleftchild)10 cout ltlt node-gtdata11 inorder(node-gtrightchild)12 13

範例 5-12此為運算式二元樹

其對應中序表示運算式為 (A+B)C+D(E-F)

6 struct BTreeNode root7 void inorder(struct BTreeNode node)8 if (node = NULL)9 inorder(node-gtleftchild)10 cout ltlt node-gtdata11 inorder(node-gtrightchild)12 13

6 struct BTreeNode root7 void inorder(struct BTreeNode node)8 if (node = NULL)9 inorder(node-gtleftchild)10 cout ltlt node-gtdata11 inorder(node-gtrightchild)12 13

(A+B)C+D(E-F)

如何加上 ( )

552  後序走訪程式 5-4二元樹的後序走訪14 void postorder(struct BTreeNode node)

15 if (node =NULL)16 postorder(node-gtleftchild)17 postorder(node-gtrightchild)18 cout ltlt node-gtdata19 20

以後序走訪可得到AB+CDEF-+

其正為對應的後序運算式

553  前序走訪程式 5-5二元樹的前序走訪21 void preorder(struct BTreeNode node)

22 if (node = NULL)

23 cout ltlt node-gtdata

24 preorder(node-gtleftchild)

25 preorder(node-gtrightchild)

26

27

以前序走訪可得到++ABCD-EF

其正為對應的前序運算式

554 利用堆疊和迴圈取代遞迴進行二元樹的走訪1 struct BTreeNode 2 struct BTreeNode leftchild3 char data4 struct BtreeNode rightchild5 6 struct BTreeNode root7 struct StackNode8 struct BTreeNode treenode9 struct StackNode next1011 struct StackNode top

12 void push_node(struct BtreeNode node )13 struct StackNode old_top14 old_top = top15 top = (struct StackNode ) malloc

(sizeof(struct StackNode))16 top-gttreenode = node17 top-gtnext = old_top18 19 struct BTreeNode pop_node()20 struct BTreeNode Tnode21 struct StackNode old_top22 if (top == NULL)23 StackEmpty()24 else25 Tnode = top-gttreenode26 old_top = top27 top = top-gtnext28 free(old_top)29 return Tnode30 31

32 void inorder_Stack(struct BtreeNode node)33 do34 while (node = NULL)35 push_node(node)36 node = node-gtleftchild37 push all left children38 if (top = NULL)39 node = pop_node()40 cout ltlt node-gtdata41 node = node-gtrightchild42 inorder printing and check right child43 while ((top=NULL)||(node=NULL))44 top==NULL 堆疊已空 node==NULL沒有右子

6 struct BTreeNode root7 void inorder(struct BTreeNode node)8 if (node = NULL)9 inorder(node-gtleftchild)10 cout ltlt node-gtdata11 inorder(node-gtrightchild)12 13

中序表示為DBGEHAFIC

32 void inorder_Stack()33 struct BTreeNode node34 node = root35 do36 while (node = NULL)37 push_node(node)38 node = node-gtleftchild39 40 if (top = NULL)41 node = pop_node()42 cout ltlt node-gtdata43 node = node-gtrightchild44 45 while(top=NULL)||(node=NULL))46

45 void Preorder_Stack(struct BSTreeNode node)46 do47 while (node = NULL)48 cout ltlt node-gtdata)49 push_node(node)50 node = node-gtleftchild51 push all left children52 if (top = NULL)53 node = pop_node()54 node = node-gtrightchild55 56 while ((top=NULL)||(node=NULL))57

程式 5-5二元樹的前序走訪21 void preorder(struct BTreeNode node)22 if (node = NULL)23 cout ltlt node-gtdata24 preorder(node-gtleftchild)25 preorder(node-gtrightchild)26 27

58 void Postorder_Stack(struct BTreeNode node)59 do60 while (node = NULL)61 cout ltlt node-gtdata62 push_node(node)63 node = node-gtrightchild64 push all left children65 if (top = NULL)66 node = pop_node()67 node = node-gtleftchild68 69 while ((top=NULL)||(node=NULL))70

程式 5-4二元樹的後序走訪14 void postorder(struct BTreeNode node)15 if (node =NULL)16 postorder(node-gtleftchild)17 postorder(node-gtrightchild)18 cout ltlt node-gtdata19 20

555  階層走訪bull 「階層走訪」 (level-order traversal) 是依階層

的順序進行二元樹的走訪先走訪階層小的節點後走訪階層大的節點同一階層者則依自左向右的順序走訪

bull 對下圖的二元樹進行階層走訪的結果為ABCDEFGHI

bull 對同一階層而言先走訪的節點其子節點亦在下一階層中先被走訪

bull 這種「先進先出」的特性恰可用佇列予以儲存與處理

程式 5-7 利用佇列進行二元樹的階層走訪

1 struct BTreeNode2 struct BTreeNode leftchild3 char data4 struct BTreeNode rightchild5 6 struct BTreeNode root7 struct QNode8 struct BTreeNode treenode9 struct QNode next10 11 struct QNode front rear

12 void AddQueue(struct BTreeNode Tnode)13 struct QNode node14 node=(struct QNode )malloc(sizeof(struct QNode))15 node-gttreenode = Tnode16 node-gtnext = NULL17 if (front == NULL) front = node19 else rear-gtnext = node20 rear = node21

22 struct BTreeNode DeleteQueue()23 struct BTreeNode Tnode24 struct QNode old_front25 if (rear == NULL)26 QueueEmpty()27 else28 Tnode = front-gttreenode29 old_front = front30 front = front-gtnext31 free(old_front)32 return Tnode33 34 35 void LevelOrder (struct BTreeNode node)36 AddQueue(node)37 while (front =NULL)38 node = DeleteQueue()39 cout ltlt node-gtdata40 if (node-gtleftchild = NULL)41 AddQueue(node-gtleftchild)42 if (node-gtrightchild = NULL)43 AddQueue(node-gtrightchild)44 45

35 void LevelOrder (struct BTreeNode node)36 AddQueue(node)37 while (front =NULL)38 node = DeleteQueue()39 cout ltlt node-gtdata40 if (node-gtleftchild = NULL)41 AddQueue(node-gtleftchild)42 if (node-gtrightchild = NULL)43 AddQueue(node-gtrightchild)44 45

bull Infix ABCbull Postfix ABC CBA ACB BCA

BAC CAB

B

A CA

B

C

B

A

C

C

B

AB

A

C

二元樹中序與前序式中序與後序式bull 每一棵二元樹皆有唯一的一對中序與前序表示法也有唯一的中序與後序次序

bull 換句話說給予一對中序與前序或中序與後序即可決定一棵二元樹

bull 然而給予前序和後序次序並不能決定唯一的二元樹可能會產生兩棵不同的二元樹

bull 想想階層走訪和中序走訪順序可否決定唯一的二元樹

A

B C

H J D E

F G

prefix ABHJCDFGEinfix HBJAFDGCEpostfix HJBFGDECA

VLRLVRLRV

A

prefix BHJinfix HBJ

prefix CDFGEinfix FDGCE

prefix ABHJCDFGEinfix HBJAFDGCE

A

B C

H J D E

F G

prefix ABHJCDFGE

infix HBJAFDGCEk-1

struct BTreeNode BTree_InfixPrefix (AnsiString infix AnsiString prefix)

int k struct BTreeNode node if (infixLength==0) return NULLnode = (struct BTreeNode ) malloc (sizeof(struct BTreeNode))node-gtdata = prefix[1]k = find(prefix[1] infix)node-gtleftchild = BTree_InfixPrefix

(infix[1k-1] prefix[2k])node-gtrightchild = BTree_InfixPrefix(infix[k+1infixLength]prefix[k+1prefixLength])

VLR

LVRk1

k2 k+1~

k+1~

1

struct BTreeNode BTree_InfixPostfix (AnsiString infix AnsiString prefix)

int k struct BTreeNode node if (infixLength==0) return NULLnode = (struct BTreeNode ) malloc (sizeof(struct BTreeNode))node-gtdata = postfix[postfixLength]k = find(postfix[postfixLength] infix)node-gtleftchild = BTree_InfixPrefix

(infix[1k-1] postfix[1k-1])node-gtrightchild = BTree_InfixPrefix(infix[k+1infixLength]postfix[k+1postfixLength-1])

56  二元樹的複製與相等測試程式 5-8 二元樹的複製1 struct BTreeNode2 struct BTreeNode leftchild3 char data4 struct BTreeNode rightchild5 6 struct BTreeNode root7 struct BTreeNode Copy(struct BTreeNode TreeRoot)8 struct BTreeNode CopyRoot9 if (TreeRoot == NULL)10 return NULL11 CopyRoot =(struct BTreeNode ) malloc (sizeof(BTreeNode))12 CopyRoot-gtdata = TreeRoot-gtdata13 CopyRoot-gtleftchild = Copy(TreeRoot-gtleftchild)14 CopyRoot-gtrightchild = Copy(TreeRoot-

gtrightchild)15 return CopyRoot16

程式 5-9 檢測兩棵二元樹是否相等1 struct BTreeNode2 struct BTreeNode leftchild3 char data4 struct BTreeNode rightchild5 6 struct BtreeNode rightchild7 int Equal(struct BTreeNode ustruct BTreeNode v)8 if ((u==NULL) ampamp (v==NULL)) return 19 if ((u=NULL) ampamp (v=NULL)10 ampamp (u-gtdata == v-gtdata )11 ampamp Equal(u-gtleftchildv-gtleftchild)12 ampamp Equal(u-gtrightchildv-gtrightchild))

13 return 114 return 015

Counting Nodes in a Binary Tree

int CountingNodes(struct BTreeNodes node)

if (node == NULL) return 0 int leftchildren = CountingNodes(node-gtleftchild)

int rightchildren= CountingNodes(node-gtrightchild)

return leftchildren+rightchildren

Counting Leaves in a Binary Tree

int CountingLeaves(struct BTreeNodes node)

if (node == NULL) return 0 int leftleaves = CountingLeaves(node-gtleftchild)

int rightchildren= CountingLeaves(node-gtrightchild)

if (leftleaves==0 ampamp leftleaves==0) return 1

return leftleaves + leftleaves

57  引線二元樹bull 在二元樹的鍵結表示中樹葉節點的兩個子樹指標皆指向 NULL 由定理 5-1 得知 n = E+1 其中 n 為節點個數 E 為分支數

bull 而 n 個鍵結節點有 2n 個指標空間每個分支恰佔用一個指標空間共有 E (=n-1) 個指標有用到(不是空的)而共有 n+1 個指標空間是 NULL 比非空的節點還多

bull 有學者提出對這些「放空指標的空間」加以利用的概念mdash與其放空指標不如放指向其它節點的指標稱之為引線 (thread) 使得某些運算(如走訪hellip等)可以加快

bull 這個概念發展出了引線二元樹 (threaded binary tree) 這種資料結構

範例 5-17下圖為加了引線的二元樹

加入的引線將使二元樹的中序走訪更加便利沿著上右圖箭頭所指示的順序走訪即可完成此二元樹的中序走訪GDHBAIECF 為此二元樹的中序表示

bull 範例 5-17 中所加的引線目的在方便二元樹的中序走訪

bull 對樹葉節點 p 而言令其資料為 P 則引線指標的規則如下

(1) p 的左子樹指標指向中序表示中 P 的前一個 元素(前接元素 (predecessor) )

(2) p 的右子樹指標指向中序表示中 P 的後一個 元素(後繼元素 (successor) )

為了區別節點指標放的是一般指標(內部節點)抑或是引線指標(樹葉節點)我們另以 2 個欄位分別區別左和右子樹指標空間存放的對象其節點的記憶體配置有如下圖所示

其中 leftthread (rightthread) 為 0 時表示 leftchild (rightchild) 存放的是一般節點指標而 leftthread (rightthread) 為 1 時表示 leftchild (rightchild) 存放的是引線指標1 struct TBTreeNode2 int leftthread3 struct TBTreeNode leftchild4 char data5 struct TBTreeNode rightchild6 int rightthread7

由右側的引線二元樹可知有最左和最右兩條引線尚無妥善的安排我們另設計一空的引線節點做為樹根如下圖所示

571  引線二元樹的中序走訪決定節點 p 在中序表示中的後繼節點 q (簡稱中序後繼點)

程式 5-10決定節點 node的中序後繼點1 struct TBTreeNode2 int leftthread3 struct TBTreeNode leftchild4 char data5 struct TBTreeNode rightchild6 int rightthread7 8 struct TBTreeNode root9 struct TBTreeNode Next(struct TBTreeNode node)

10 struct TBTreeNode temp11 temp = node-gtrightchild12 if (node-gtrightthead) return temp13 while (temp-gtleftthead) temp=temp-gtleftchild

14 return temp15

程式 5-11引線二元樹的中序表示(接程式 5-10)1 void InorderTBTree()2 struct TBTreeNode node3 for(node=Next(root)node=rootnode=Next(node))

4 cout ltlt node-gtdata5

bull只要 node 是引線二元樹上的任一節點透過呼叫 Next(node) 即可找到其在中序表示中的後繼節點bull(試想若程式 5-10 中的程序 Next 所傳入的參數指標正是 root Next(root) 是否可正常運作請思考)bull於是只須從中序表示中的第一節點起讓每一節點 node 都執行 Next(node) 程序即可得引線二元樹的中序表示

572  在引線二元樹中加入節點在引線二元樹中樹葉節點節點的右子樹指標所指向或內部節點右子樹的最左樹葉為其中序後繼節點而且任一節點若無左子樹則其左子樹指標乃指向其中序前接節點hellip這些性質必須在節點新增進入引線二元樹時也要保持接下來討論節點 new 插入成為引線二元樹的節點 p 的右子節點的情形至於插入成為左子節點的情形則讓各位模擬推敲

572572  在引線二元樹中加入節點 在引線二元樹中加入節點 (( 續續 ))

newnew 會成為會成為 pp 的右子節點的情況共有以下兩種的右子節點的情況共有以下兩種(1) (1) 若若 pp 沒有右子節點 沒有右子節點 ((p-gtrightthread p-gtrightthread 為為1) 1) 如圖如圖 ((a) a) 則 則 new new 插入後應形成如圖插入後應形成如圖 ((b) b) 所示所示

(2)(2) 若若 pp 有右子節點 有右子節點 ((p-gtrightthread p-gtrightthread 為為0) 0) 如圖如圖 ((c) c) 則 則 new new 插入後應形成如插入後應形成如圖圖 ((d) d) 所示所示

程式 5-12  插入節點 new 成為引線二元樹中節點 p的右子節點(上接程式 5-10 5-11 )

1 void InsertRight(struct TBTreeNode p 2 struct TBTreeNode new)3 struct TBTreeNode q4 new-gtrightchild = p-gtrightchild5 new-gtrightthead = p-gtrightthread6 new-gtleftchild = p7 new-gtleftthread = 18 p-gtrightchild = new9 p-gtrightthead = 010 if (new-gtrightthread)11 q = Next(new)12 q-gtleftchild = new13 14

58  堆積bull 「堆積」 (heap) 是一個特殊的完備二元樹節點的資料與其子節點的資料之間有事先定好的大小關係存在bull 定義ndash最大堆疊為一完備二元樹任一節點的資料內容不小於其子節點的資料內容ndash最小堆疊為一完備二元樹任一節點的資料內容不大於其子節點的資料內容

581  插入節點至最大堆積中最大堆積是一完備二元樹所以如圖 (a) 之最大堆積於加入新節點後樹的形狀必須如圖 (b) 所示灰色節點即為新節點若加入的節點資料分別為 1 9 12 則其最大堆積將分別變成如圖 (c) (d) (e) 所示

程式 5-13 新增節點至最大堆積中最大堆積本身是一棵完備二元樹可以用陣列表示之 1 define maxsize 1002 int heap[maxsize]3 int n i4 void InsertHeap(int x)5 if (n == maxsize) HeapFull()6 else7 n++8 i = n9 while ((i gt 1) ampamp (x gt heap[i2]))10 heap[i] = heap[i2]11 i = 212 13 heap[i] = x14 16

程序 InsertHeap 在插入第 n 個資料時需要的時間複雜度為 O(logn)

1 100

2 93

3 84

4 77

5 65

6 81

7 79

8 66

9 70

10 50

100

84

77

93

8165

66 70

79

50

1

2 3

4 5 6 7

8 9 10

1 define maxsize 1002 int heap[maxsize]3 int n i4 void InsertHeap(int x)5 if (n == maxsize) HeapFull()6 else7 n++8 i = n9 while ((i gt 1) ampamp (x gt heap[i2]))10 heap[i] = heap[i2]11 i = 212 13 heap[i] = x14 16

582 刪除最大堆積中的最大資料圖 (a) 中的最大堆積在刪除最大資料後其樹形應變成如圖 (b) 所示將最未一個樹葉節點的資料 6 暫挪至樹根節點內如圖 (c) 再將圖 (c) 中非最大堆積的結構調整成為最大堆積 10 和 8 中選大者與 6對調即成圖 (d)

程式 5-14 刪除最大堆積中的最大資料(上接程式 5-13 )1 int DeleteHeap()2 int x i j3 if (n == 0) HeapEmpty()4 else5 x = heap[1]6 heap[1] = heap[n]7 n--8 i = 19 while (i lt= n2)10 if (heap[2i] gt heap[2i+1])11 j = 2i12 else13 j = 2i+114 if (heap[i]gtheap[j]) break15 swap(heap[i] heap[j])16 i = j17 18 return x19 20

其時間複雜度與其堆積高度相同是為O(logn) 其中 n 為堆積中的元素個數

1 100

2 93

3 84

4 77

5 65

6 81

7 79

8 66

9 70

10 50

100

84

77

93

8165

66 70

79

50

1

2 3

4 5 6 7

8 9 10

1 int DeletHeap()2 int x i j3 if(n == 0) HeapEmpty()4 else5 x = heap[1]6 heap[1] = heap[n]7 n--8 i = 19 while (i lt= n2)10 if ([heap[2i]] gt heap[2i+1])11 j = 2i12 else13 j = 2i+114 if (heap[i]gtheap[j]) break15 swap(heap[i] heap[j])16 i = j17 18 return x19 20

583  優先佇列bull 由於最大堆積(或最小堆積)的結構可提供最大元素(或最小元素)而其維護堆積的成本為 O(logn) n 為總元素個數

bull 堆積常被當成「優先佇列」 (priority queue) 使用mdash此時佇列被組織成完備二元樹若新加入佇列的資料有大小關係優先順序的需求時此優先佇列即以 O(logn) 的代價將新資料放入優先佇列(可能是最大或最小堆積依實際大小關係優先順序的需求而定)中mdash提供依元素的優先順序做為處理順序的服務

bull 一般的佇列為先進先出先到的先行處理優先佇列則是依據其優先順序優先順序高者先行處理與來到的先後無關

範例 5-21bull 在「分時」 (time sharing) 的作業系統中我們不希望使用中央處理器 (Central Processing Unit CPU) 時間短的「處理程序」 (process) 等候過久於是有「最短時間工作優先」 (shortest-job-first) 的排程策略此時等候的處理程序即可組織成之一最小堆積而 CPU 每次即取出存於樹根的處理程序予以處理任何新來的處理程序則以 O(logn) 的時間加入此優先佇列(最小堆積)中

bull 注意之前提過的線性佇列新來的直接加入成為新的尾端元素花O(1) 的時間然線性佇列本身沒有大小的關係每次要取出最大(小)的資料得花O(n) 的代價(在所有元素中挑出最大(小)者) n 為佇列內的元素個數而優先佇列是在新元素加入時要花O(logn) 的時間至於取出最大(小)則只須 O(1) 的時間

59  二元搜尋樹bull 二元搜尋樹是一棵二元樹可能是空二元樹若不為空二元樹則滿足下列性質ndash所有節點內的資料內容是相異的 ndash左子節點的資內容 ( 如果有 ) 比父節點的資料內容小 ndash右子節點的資料內容 ( 如果有 ) 比父節點的資料內容大 ndash以左和右子節點為樹根的左子樹和右子樹也是二元搜尋樹

在二元搜尋樹中搜尋資料二元搜尋樹對資料大小的掌握十分明確若恰與節點 p 資料相等則搜尋成功若欲搜尋的資料 x 比樹節點 p 中的資料小 則只須搜尋節點 p 的左子樹若比節點 p 中的資料大 則只須搜尋 p 的右子樹這樣的判斷可以遞迴地應用在各子樹節點 中

程式 5-15 在二元搜尋樹中搜尋資料(遞迴呼叫)1 struct BSTreeNode2 struct BSTreeNode leftchild3 int data4 struct BSTreeNode rightchild5 6 struct BSTreeNode root7 struct BSTreeNode SearchBST(

struct BSTreeNode tree int x)8 if (tree == NULL) return NULL9 if (x == tree-gtdata) return tree10 if (x lt tree-gtdata)11 return SearchBST(tree-gtleftchild x)

12 return SearchBST(tree-gtrightchild x)13

程式 5-16 搜尋二元搜尋樹的資料(非遞迴呼叫)在二元搜尋樹十分龐大時遞迴呼叫的效率不會太好可利用迴圈來完成搜尋的動作以提高執行效率請看程式 5-16

14 struct BSTreeNode Search BST_iterative15 (struct BSTreeNode node int x)16 while (node = NULL)17 if (x == node-gtdata) return node18 if (x lt node-gtdata)19 node = node-gtleftchild20 else21 node = node-gtrightchild22 23 return NULL24

592  新增資料於二元搜尋樹中欲新增資料進入一二元搜尋樹中得先決定該資料在二元搜尋樹中應出現的位置這可透過上節提到的搜尋動作來解決

在二元搜尋樹定義的性質 (1) 中我們限定了元素必須相異所以搜尋新資料的結果必是 NULL

必須將搜尋的結果改為新增資料所應在位置的節點指標

程式 5-17  在二元搜尋樹中新增資料25 int InsertBST(int x)26 struct BSTreeNode p q27 p = root q = NULL28 while (p = NULL)29 q = p30 if (x == p-gtdata) return 0

若不考慮重複則可省略之31 if (x lt p-gtdata)32 p = p-gtleftchild33 else34 p = p-gtrightchild35

3636 p = (struct BSTreeNode ) p = (struct BSTreeNode ) malloc (sizeof(BSTreeNode))malloc (sizeof(BSTreeNode))

3737 p-gtdata = x p-gtdata = x3838 p-gtleftchild = p-gtrightchild = p-gtleftchild = p-gtrightchild = NULLNULL3939 if (root == NULL) root = p if (root == NULL) root = p4040 else if (x lt q-gtdata) else if (x lt q-gtdata)4141 q-gtleftchild = p q-gtleftchild = p4242 else else4343 q-gtrightchild = p q-gtrightchild = p4444 return 1 return 145 45

程式 程式 5-175-17  在二元搜尋樹中新增資料  在二元搜尋樹中新增資料 (( 續續 ))

範例 5-23 圖 (b) 為圖 (a) 新增整數 6 之後的結果圖 (c) (d) (e) (f) 則分別是逐步再加入 28 18

10 21 的結果

bull若目前的二元搜尋樹高度為 h 則新增資料得花O(h) 的時間找到其所應在的位置遂新增資料至二元搜尋樹中的時間複雜之度為 O(h)

593 自二元搜尋樹中刪除資料欲自二元搜尋樹中刪除資料可能會面臨兩種情況(1) 刪除節點 p 位於樹葉節點上如下圖所示 這是比較容易處理的情況

(2) 刪除的節點 p 不是樹葉刪除節點 p 後原 p 所指的節點可由 q ( q 中包含了 p 左子樹中的最大節點值)或 r ( r 中包含了 p 右子樹中的最小節點值)來取代以維持二元搜尋樹中資料的大小關係其結果分別如圖 (b) 和 (c) 所示而圖 (d) 則標明了指標會改變的節點位置即 p 會消失 father 的左或右子樹指標會異動 k(可能是 q 或 r )會移到原來 p 的位置其父節點 f 的左或右子樹指標會異動其中 father 為 p 父節點 f 為 k 的父節點

程式 5-18 自二元搜尋樹中刪除資料46 int DeleteBST (int x)47 Struct BSTreeNode p father k f48 p = root father = NULL49 while (p = NULL)50 if (x == p-gtdata) 找到 x 所在的節點 p 了51 if ((p-gtleftchild == NULL) ampamp 52 (p-gtrightchild == NULL))53 k = NULL p為樹葉節點54 else if (p-gtleftchild = NULL )x位於非子節

點55 f = p 找出 p左子樹的最右樹葉56 k = p-gtleftchild57 while (k-gtrightchild =NULL)58 f = k59 k = k-gtrightchild60 k將挪至原 p處61 if (p == f)62 f-gtleftchild = k-gtleftchild 63 else64 f-gtrightchild = k-gtleftchild 65

66 else p無左子樹67 f = p 找出 p右子樹的最左樹葉68 k = p-gtrightchild69 while (k-gtleftchild = NULL)70 f = k71 k = k -gtleftchild72 73 if (p == f)74 f-gtrightchild = k-gtrightchild 75 else76 f-gtleftchild = k-gtrightchild77 78 if (k = NULL) k挪至原 p處繼承 p的左右指標79 k-gtleftchild = p-gtleftchild80 k-gtrightchild = p-gtrightchild81 82 if (father == NULL) root = k83 else if (x lt father-gtdata) 決定 k是 father的左

或右兒子84 father-gtleftchild = k85 else86 father-gtrightchild = k87 free(p)88 return 1 成功地刪除 x於此傳回 1返回呼叫處89 51~89行為找到 x時應有的處理步驟

90 else 尚未找到 x繼續往下一階層找

91 father = p92 if (x lt p-gtdata)93 p = p-gtleftchild94 else95 p = p-gtrightchild96 97 98 return 0 未找到 x傳回 099

510  樹林 (forest)

bull 定義樹林(森林)是由 n 棵沒有交集的樹所構成的集合 n 0

下圖為一包含三棵樹的樹林

5101 轉換樹林為二元樹 之前我們以二元樹來表示樹因為二元樹有較多

好用的性質在此我們也將樹林轉換成二元樹看待

在 522 節中我們嘗試了用左子右兄弟的方式來表示樹這個方法事實上已將樹轉換成二元樹

即用左子右兄弟的結構重新組織樹林中的每一棵樹這些樹的樹根將視為兄弟而最左邊的樹則視為樹林二元樹的樹根如此一來樹林即可轉換成二元樹

範例 5-25右下圖為右上圖三棵樹所對應的三棵左子右兄弟二元樹

下圖則為上圖樹林所對應的一棵樹林二元樹

演算法 5-1  轉換樹林成二元樹輸入樹林 F = T1 T2 Tn

輸出 F 的二元樹表示 BT=(T1 T2 Tn)1 if (n == 0) 輸出空二元樹 2 else

3 root(T1 T2 Tn) = T1

4 leftsubtree(T1 T2 Tn) =

BT(T11 T12 T1m)5 BT(T11 T12 T1m)為 T1的子樹6 rightsubtree(T1 T2 Tn) =

BT(T2 T3 Tn)

樹林轉換成二元樹的過程樹林轉換成二元樹的過程

  • Slide 1
  • Slide 2
  • Slide 3
  • Slide 4
  • Slide 5
  • Slide 6
  • Slide 7
  • Slide 8
  • Slide 9
  • Slide 10
  • Slide 11
  • Slide 12
  • Slide 13
  • Slide 14
  • Slide 15
  • Slide 16
  • Slide 17
  • Slide 18
  • Slide 19
  • Slide 20
  • Slide 21
  • Slide 22
  • Slide 23
  • Slide 24
  • Slide 25
  • Slide 26
  • Slide 27
  • Slide 28
  • Slide 29
  • Slide 30
  • Slide 31
  • Slide 32
  • Slide 33
  • Slide 34
  • Slide 35
  • Slide 36
  • Slide 37
  • Slide 38
  • Slide 39
  • Slide 40
  • Slide 41
  • Slide 42
  • Slide 43
  • Slide 44
  • Slide 45
  • Slide 46
  • Slide 47
  • Slide 48
  • Slide 49
  • Slide 50
  • Slide 51
  • Slide 52
  • Slide 53
  • Slide 54
  • Slide 55
  • Slide 56
  • Slide 57
  • Slide 58
  • Slide 59
  • Slide 60
  • Slide 61
  • Slide 62
  • Slide 63
  • Slide 64
  • Slide 65
  • Slide 66
  • Slide 67
  • Slide 68
  • Slide 69
  • Slide 70
  • Slide 71
  • Slide 72
  • Slide 73
  • Slide 74
  • Slide 75
  • Slide 76
  • Slide 77
  • Slide 78
  • Slide 79
  • Slide 80
  • Slide 81
  • Slide 82
  • Slide 83
  • Slide 84
  • Slide 85
  • Slide 86
  • Slide 87
  • Slide 88
  • Slide 89
  • Slide 90
  • Slide 91
  • Slide 92
  • Slide 93
  • Slide 94
  • Slide 95
  • Slide 96
  • Slide 97
  • Slide 98
  • Slide 99
  • Slide 100
  • Slide 101
  • Slide 102
  • Slide 103
Page 27: 第五章 樹

54  二元樹的表示法541 以陣列表示二元樹定理 5-5 若一個具有 n 個節點的完備二元樹以循序的方式編號並依序存放在陣列 A 中即第 i 個節點放在 A[i] 中 1 i n 則

(1) 節點 i 的父節點位在 A[i2]處 i ne 1( i = 1 時其節點正為樹根無父節點)

(2) 若 2i n 則節點 i 的左兒子節點位在 A[2i] 處若 2i gtn 節點 i 沒有左子節點

(3) 若 2i+1 n 節點 i 的右兒子節點位 A[2i+1]處若 2i+1gtn 節點 i 沒有左子

節點

證明先用數學歸納法證明 (2) bull 歸納法的基礎當 i = 1 時其左子節點確實在 A[2] 處(除非 nlt2 此時節點 1 沒有左子節點)

bull 歸納法的假設假設當 I = k 時其左子節點確在 A[2i] = A[2k] 處(除非 2i 已大於 n 此時節點 i 無左子節點)

bull 歸納法的推論當 i = k+1 時因第 k 節點的左子節點在 A[2k] 處其右子節點則位在 A[2k+1] 處那麼第 k+1 節點的左子節點應緊臨 A[2k+1] 亦即位於 A[2k+2] = A[2(k+1)] 處(除非 2i 已大於 n 此時節點 i 無左子節點)

bull 由數學歸納法的原理得證定理 5-5 (2) (3) 由於 (2) 成立而且節點 i 的右子節點會緊臨

A[2i] 亦即其位於 A[2i+1]處除非 i 根本沒有右子節點得證定理 5-5 (3)

由 (2) 和 (3) 我們可知定理 5-5 (1) 成立

542 以鍵結串列表示二元樹bull 以陣列來表示完備二元樹算是相當方便但ndash一般的二元樹將導致陣列空間的浪費ndash如果二元樹中節點的新增刪除動作頻繁則會造成陣列對應資料的搬動隨而頻繁

bull 以鍵結串列表示二元樹

為方便辦識樹根我們常用 root 指標指向樹根做為樹鍵結串列的起點

陣列表示和鍵結表示的空間需求bull 完備二元樹的陣列表示和鍵結表示皆沒浪費儲存資料的空間

bull 但歪斜樹的陣列表示比起鍵結表示則浪費許多

bull 以 n 個節點的歪斜樹而言陣列表示法可能需要 2n-1 個陣列元素其中 2n-1-n 個陣列元素空間是浪費的而鍵結表示法只需要 n 個節點空間

二元樹節點的宣告程式 5-2

1 struct BTreeNode

2 struct BtreeNode leftchiled

3 int data

4 struct BTreeNode rightchild

5

6 struct BTreeNode root

bull 這樣的二元樹節點要取得兒子節點的指標十分方便但若有取得父節點的需求則嫌不足我們可以加上 parent 欄位指向父節點

55  二元樹的走訪bull 假如一組資料已用二元樹的組織在一起總有需求對其全數資料做動作例如計算所有數目印出所有資料在所有資料中搜尋某項資料hellip等此時即須對此二元樹做走訪 (traversal) 的運算利用走訪二元樹的同時將計算列印或搜尋的動作完成

bull 事實上走訪即在決定二元樹上資料被處理(計算列印或搜尋)的順序我們也希望走訪的演算法對任何節點皆一致是容易撰寫程式實作的

bull 若對一個二元樹上的節點而言 V 表示處理節點上的資料 L 表示走訪其左子樹 R 表示走訪其右子樹

55  二元樹的走訪 ( 續 )bull 因為對稱的緣故我們只考慮先走訪左邊再走訪右邊的情形那麼右圖只剩下三種走訪方式

bull 以 V 所在的相對位置分別對此三種走訪方式取名如下

(1) LVR 中序走訪 (inorder traversal)或中序表示法 (infix notation)

(2) LRV 後序走訪 (postorder traversal) 或後序表示法 (postfix notation)

(3) VLR 前序走訪 (preorder traversal)或前序表示法 (prefix notation)

551  中序走訪bull 利用遞迴的方式撰寫中序走訪的程序希望把二元樹中序走訪的

順序印出來

程式 5-3二元樹的中序走訪1 struct BTreeNode2 struct BTreeNode leftchild3 char data4 struct BTreeNode rightchild5 6 struct BTreeNode root7 void inorder(struct BTreeNode node)8 if (node = NULL)9 inorder(node-gtleftchild)10 cout ltlt node-gtdata11 inorder(node-gtrightchild)12 13

範例 5-12此為運算式二元樹

其對應中序表示運算式為 (A+B)C+D(E-F)

6 struct BTreeNode root7 void inorder(struct BTreeNode node)8 if (node = NULL)9 inorder(node-gtleftchild)10 cout ltlt node-gtdata11 inorder(node-gtrightchild)12 13

6 struct BTreeNode root7 void inorder(struct BTreeNode node)8 if (node = NULL)9 inorder(node-gtleftchild)10 cout ltlt node-gtdata11 inorder(node-gtrightchild)12 13

(A+B)C+D(E-F)

如何加上 ( )

552  後序走訪程式 5-4二元樹的後序走訪14 void postorder(struct BTreeNode node)

15 if (node =NULL)16 postorder(node-gtleftchild)17 postorder(node-gtrightchild)18 cout ltlt node-gtdata19 20

以後序走訪可得到AB+CDEF-+

其正為對應的後序運算式

553  前序走訪程式 5-5二元樹的前序走訪21 void preorder(struct BTreeNode node)

22 if (node = NULL)

23 cout ltlt node-gtdata

24 preorder(node-gtleftchild)

25 preorder(node-gtrightchild)

26

27

以前序走訪可得到++ABCD-EF

其正為對應的前序運算式

554 利用堆疊和迴圈取代遞迴進行二元樹的走訪1 struct BTreeNode 2 struct BTreeNode leftchild3 char data4 struct BtreeNode rightchild5 6 struct BTreeNode root7 struct StackNode8 struct BTreeNode treenode9 struct StackNode next1011 struct StackNode top

12 void push_node(struct BtreeNode node )13 struct StackNode old_top14 old_top = top15 top = (struct StackNode ) malloc

(sizeof(struct StackNode))16 top-gttreenode = node17 top-gtnext = old_top18 19 struct BTreeNode pop_node()20 struct BTreeNode Tnode21 struct StackNode old_top22 if (top == NULL)23 StackEmpty()24 else25 Tnode = top-gttreenode26 old_top = top27 top = top-gtnext28 free(old_top)29 return Tnode30 31

32 void inorder_Stack(struct BtreeNode node)33 do34 while (node = NULL)35 push_node(node)36 node = node-gtleftchild37 push all left children38 if (top = NULL)39 node = pop_node()40 cout ltlt node-gtdata41 node = node-gtrightchild42 inorder printing and check right child43 while ((top=NULL)||(node=NULL))44 top==NULL 堆疊已空 node==NULL沒有右子

6 struct BTreeNode root7 void inorder(struct BTreeNode node)8 if (node = NULL)9 inorder(node-gtleftchild)10 cout ltlt node-gtdata11 inorder(node-gtrightchild)12 13

中序表示為DBGEHAFIC

32 void inorder_Stack()33 struct BTreeNode node34 node = root35 do36 while (node = NULL)37 push_node(node)38 node = node-gtleftchild39 40 if (top = NULL)41 node = pop_node()42 cout ltlt node-gtdata43 node = node-gtrightchild44 45 while(top=NULL)||(node=NULL))46

45 void Preorder_Stack(struct BSTreeNode node)46 do47 while (node = NULL)48 cout ltlt node-gtdata)49 push_node(node)50 node = node-gtleftchild51 push all left children52 if (top = NULL)53 node = pop_node()54 node = node-gtrightchild55 56 while ((top=NULL)||(node=NULL))57

程式 5-5二元樹的前序走訪21 void preorder(struct BTreeNode node)22 if (node = NULL)23 cout ltlt node-gtdata24 preorder(node-gtleftchild)25 preorder(node-gtrightchild)26 27

58 void Postorder_Stack(struct BTreeNode node)59 do60 while (node = NULL)61 cout ltlt node-gtdata62 push_node(node)63 node = node-gtrightchild64 push all left children65 if (top = NULL)66 node = pop_node()67 node = node-gtleftchild68 69 while ((top=NULL)||(node=NULL))70

程式 5-4二元樹的後序走訪14 void postorder(struct BTreeNode node)15 if (node =NULL)16 postorder(node-gtleftchild)17 postorder(node-gtrightchild)18 cout ltlt node-gtdata19 20

555  階層走訪bull 「階層走訪」 (level-order traversal) 是依階層

的順序進行二元樹的走訪先走訪階層小的節點後走訪階層大的節點同一階層者則依自左向右的順序走訪

bull 對下圖的二元樹進行階層走訪的結果為ABCDEFGHI

bull 對同一階層而言先走訪的節點其子節點亦在下一階層中先被走訪

bull 這種「先進先出」的特性恰可用佇列予以儲存與處理

程式 5-7 利用佇列進行二元樹的階層走訪

1 struct BTreeNode2 struct BTreeNode leftchild3 char data4 struct BTreeNode rightchild5 6 struct BTreeNode root7 struct QNode8 struct BTreeNode treenode9 struct QNode next10 11 struct QNode front rear

12 void AddQueue(struct BTreeNode Tnode)13 struct QNode node14 node=(struct QNode )malloc(sizeof(struct QNode))15 node-gttreenode = Tnode16 node-gtnext = NULL17 if (front == NULL) front = node19 else rear-gtnext = node20 rear = node21

22 struct BTreeNode DeleteQueue()23 struct BTreeNode Tnode24 struct QNode old_front25 if (rear == NULL)26 QueueEmpty()27 else28 Tnode = front-gttreenode29 old_front = front30 front = front-gtnext31 free(old_front)32 return Tnode33 34 35 void LevelOrder (struct BTreeNode node)36 AddQueue(node)37 while (front =NULL)38 node = DeleteQueue()39 cout ltlt node-gtdata40 if (node-gtleftchild = NULL)41 AddQueue(node-gtleftchild)42 if (node-gtrightchild = NULL)43 AddQueue(node-gtrightchild)44 45

35 void LevelOrder (struct BTreeNode node)36 AddQueue(node)37 while (front =NULL)38 node = DeleteQueue()39 cout ltlt node-gtdata40 if (node-gtleftchild = NULL)41 AddQueue(node-gtleftchild)42 if (node-gtrightchild = NULL)43 AddQueue(node-gtrightchild)44 45

bull Infix ABCbull Postfix ABC CBA ACB BCA

BAC CAB

B

A CA

B

C

B

A

C

C

B

AB

A

C

二元樹中序與前序式中序與後序式bull 每一棵二元樹皆有唯一的一對中序與前序表示法也有唯一的中序與後序次序

bull 換句話說給予一對中序與前序或中序與後序即可決定一棵二元樹

bull 然而給予前序和後序次序並不能決定唯一的二元樹可能會產生兩棵不同的二元樹

bull 想想階層走訪和中序走訪順序可否決定唯一的二元樹

A

B C

H J D E

F G

prefix ABHJCDFGEinfix HBJAFDGCEpostfix HJBFGDECA

VLRLVRLRV

A

prefix BHJinfix HBJ

prefix CDFGEinfix FDGCE

prefix ABHJCDFGEinfix HBJAFDGCE

A

B C

H J D E

F G

prefix ABHJCDFGE

infix HBJAFDGCEk-1

struct BTreeNode BTree_InfixPrefix (AnsiString infix AnsiString prefix)

int k struct BTreeNode node if (infixLength==0) return NULLnode = (struct BTreeNode ) malloc (sizeof(struct BTreeNode))node-gtdata = prefix[1]k = find(prefix[1] infix)node-gtleftchild = BTree_InfixPrefix

(infix[1k-1] prefix[2k])node-gtrightchild = BTree_InfixPrefix(infix[k+1infixLength]prefix[k+1prefixLength])

VLR

LVRk1

k2 k+1~

k+1~

1

struct BTreeNode BTree_InfixPostfix (AnsiString infix AnsiString prefix)

int k struct BTreeNode node if (infixLength==0) return NULLnode = (struct BTreeNode ) malloc (sizeof(struct BTreeNode))node-gtdata = postfix[postfixLength]k = find(postfix[postfixLength] infix)node-gtleftchild = BTree_InfixPrefix

(infix[1k-1] postfix[1k-1])node-gtrightchild = BTree_InfixPrefix(infix[k+1infixLength]postfix[k+1postfixLength-1])

56  二元樹的複製與相等測試程式 5-8 二元樹的複製1 struct BTreeNode2 struct BTreeNode leftchild3 char data4 struct BTreeNode rightchild5 6 struct BTreeNode root7 struct BTreeNode Copy(struct BTreeNode TreeRoot)8 struct BTreeNode CopyRoot9 if (TreeRoot == NULL)10 return NULL11 CopyRoot =(struct BTreeNode ) malloc (sizeof(BTreeNode))12 CopyRoot-gtdata = TreeRoot-gtdata13 CopyRoot-gtleftchild = Copy(TreeRoot-gtleftchild)14 CopyRoot-gtrightchild = Copy(TreeRoot-

gtrightchild)15 return CopyRoot16

程式 5-9 檢測兩棵二元樹是否相等1 struct BTreeNode2 struct BTreeNode leftchild3 char data4 struct BTreeNode rightchild5 6 struct BtreeNode rightchild7 int Equal(struct BTreeNode ustruct BTreeNode v)8 if ((u==NULL) ampamp (v==NULL)) return 19 if ((u=NULL) ampamp (v=NULL)10 ampamp (u-gtdata == v-gtdata )11 ampamp Equal(u-gtleftchildv-gtleftchild)12 ampamp Equal(u-gtrightchildv-gtrightchild))

13 return 114 return 015

Counting Nodes in a Binary Tree

int CountingNodes(struct BTreeNodes node)

if (node == NULL) return 0 int leftchildren = CountingNodes(node-gtleftchild)

int rightchildren= CountingNodes(node-gtrightchild)

return leftchildren+rightchildren

Counting Leaves in a Binary Tree

int CountingLeaves(struct BTreeNodes node)

if (node == NULL) return 0 int leftleaves = CountingLeaves(node-gtleftchild)

int rightchildren= CountingLeaves(node-gtrightchild)

if (leftleaves==0 ampamp leftleaves==0) return 1

return leftleaves + leftleaves

57  引線二元樹bull 在二元樹的鍵結表示中樹葉節點的兩個子樹指標皆指向 NULL 由定理 5-1 得知 n = E+1 其中 n 為節點個數 E 為分支數

bull 而 n 個鍵結節點有 2n 個指標空間每個分支恰佔用一個指標空間共有 E (=n-1) 個指標有用到(不是空的)而共有 n+1 個指標空間是 NULL 比非空的節點還多

bull 有學者提出對這些「放空指標的空間」加以利用的概念mdash與其放空指標不如放指向其它節點的指標稱之為引線 (thread) 使得某些運算(如走訪hellip等)可以加快

bull 這個概念發展出了引線二元樹 (threaded binary tree) 這種資料結構

範例 5-17下圖為加了引線的二元樹

加入的引線將使二元樹的中序走訪更加便利沿著上右圖箭頭所指示的順序走訪即可完成此二元樹的中序走訪GDHBAIECF 為此二元樹的中序表示

bull 範例 5-17 中所加的引線目的在方便二元樹的中序走訪

bull 對樹葉節點 p 而言令其資料為 P 則引線指標的規則如下

(1) p 的左子樹指標指向中序表示中 P 的前一個 元素(前接元素 (predecessor) )

(2) p 的右子樹指標指向中序表示中 P 的後一個 元素(後繼元素 (successor) )

為了區別節點指標放的是一般指標(內部節點)抑或是引線指標(樹葉節點)我們另以 2 個欄位分別區別左和右子樹指標空間存放的對象其節點的記憶體配置有如下圖所示

其中 leftthread (rightthread) 為 0 時表示 leftchild (rightchild) 存放的是一般節點指標而 leftthread (rightthread) 為 1 時表示 leftchild (rightchild) 存放的是引線指標1 struct TBTreeNode2 int leftthread3 struct TBTreeNode leftchild4 char data5 struct TBTreeNode rightchild6 int rightthread7

由右側的引線二元樹可知有最左和最右兩條引線尚無妥善的安排我們另設計一空的引線節點做為樹根如下圖所示

571  引線二元樹的中序走訪決定節點 p 在中序表示中的後繼節點 q (簡稱中序後繼點)

程式 5-10決定節點 node的中序後繼點1 struct TBTreeNode2 int leftthread3 struct TBTreeNode leftchild4 char data5 struct TBTreeNode rightchild6 int rightthread7 8 struct TBTreeNode root9 struct TBTreeNode Next(struct TBTreeNode node)

10 struct TBTreeNode temp11 temp = node-gtrightchild12 if (node-gtrightthead) return temp13 while (temp-gtleftthead) temp=temp-gtleftchild

14 return temp15

程式 5-11引線二元樹的中序表示(接程式 5-10)1 void InorderTBTree()2 struct TBTreeNode node3 for(node=Next(root)node=rootnode=Next(node))

4 cout ltlt node-gtdata5

bull只要 node 是引線二元樹上的任一節點透過呼叫 Next(node) 即可找到其在中序表示中的後繼節點bull(試想若程式 5-10 中的程序 Next 所傳入的參數指標正是 root Next(root) 是否可正常運作請思考)bull於是只須從中序表示中的第一節點起讓每一節點 node 都執行 Next(node) 程序即可得引線二元樹的中序表示

572  在引線二元樹中加入節點在引線二元樹中樹葉節點節點的右子樹指標所指向或內部節點右子樹的最左樹葉為其中序後繼節點而且任一節點若無左子樹則其左子樹指標乃指向其中序前接節點hellip這些性質必須在節點新增進入引線二元樹時也要保持接下來討論節點 new 插入成為引線二元樹的節點 p 的右子節點的情形至於插入成為左子節點的情形則讓各位模擬推敲

572572  在引線二元樹中加入節點 在引線二元樹中加入節點 (( 續續 ))

newnew 會成為會成為 pp 的右子節點的情況共有以下兩種的右子節點的情況共有以下兩種(1) (1) 若若 pp 沒有右子節點 沒有右子節點 ((p-gtrightthread p-gtrightthread 為為1) 1) 如圖如圖 ((a) a) 則 則 new new 插入後應形成如圖插入後應形成如圖 ((b) b) 所示所示

(2)(2) 若若 pp 有右子節點 有右子節點 ((p-gtrightthread p-gtrightthread 為為0) 0) 如圖如圖 ((c) c) 則 則 new new 插入後應形成如插入後應形成如圖圖 ((d) d) 所示所示

程式 5-12  插入節點 new 成為引線二元樹中節點 p的右子節點(上接程式 5-10 5-11 )

1 void InsertRight(struct TBTreeNode p 2 struct TBTreeNode new)3 struct TBTreeNode q4 new-gtrightchild = p-gtrightchild5 new-gtrightthead = p-gtrightthread6 new-gtleftchild = p7 new-gtleftthread = 18 p-gtrightchild = new9 p-gtrightthead = 010 if (new-gtrightthread)11 q = Next(new)12 q-gtleftchild = new13 14

58  堆積bull 「堆積」 (heap) 是一個特殊的完備二元樹節點的資料與其子節點的資料之間有事先定好的大小關係存在bull 定義ndash最大堆疊為一完備二元樹任一節點的資料內容不小於其子節點的資料內容ndash最小堆疊為一完備二元樹任一節點的資料內容不大於其子節點的資料內容

581  插入節點至最大堆積中最大堆積是一完備二元樹所以如圖 (a) 之最大堆積於加入新節點後樹的形狀必須如圖 (b) 所示灰色節點即為新節點若加入的節點資料分別為 1 9 12 則其最大堆積將分別變成如圖 (c) (d) (e) 所示

程式 5-13 新增節點至最大堆積中最大堆積本身是一棵完備二元樹可以用陣列表示之 1 define maxsize 1002 int heap[maxsize]3 int n i4 void InsertHeap(int x)5 if (n == maxsize) HeapFull()6 else7 n++8 i = n9 while ((i gt 1) ampamp (x gt heap[i2]))10 heap[i] = heap[i2]11 i = 212 13 heap[i] = x14 16

程序 InsertHeap 在插入第 n 個資料時需要的時間複雜度為 O(logn)

1 100

2 93

3 84

4 77

5 65

6 81

7 79

8 66

9 70

10 50

100

84

77

93

8165

66 70

79

50

1

2 3

4 5 6 7

8 9 10

1 define maxsize 1002 int heap[maxsize]3 int n i4 void InsertHeap(int x)5 if (n == maxsize) HeapFull()6 else7 n++8 i = n9 while ((i gt 1) ampamp (x gt heap[i2]))10 heap[i] = heap[i2]11 i = 212 13 heap[i] = x14 16

582 刪除最大堆積中的最大資料圖 (a) 中的最大堆積在刪除最大資料後其樹形應變成如圖 (b) 所示將最未一個樹葉節點的資料 6 暫挪至樹根節點內如圖 (c) 再將圖 (c) 中非最大堆積的結構調整成為最大堆積 10 和 8 中選大者與 6對調即成圖 (d)

程式 5-14 刪除最大堆積中的最大資料(上接程式 5-13 )1 int DeleteHeap()2 int x i j3 if (n == 0) HeapEmpty()4 else5 x = heap[1]6 heap[1] = heap[n]7 n--8 i = 19 while (i lt= n2)10 if (heap[2i] gt heap[2i+1])11 j = 2i12 else13 j = 2i+114 if (heap[i]gtheap[j]) break15 swap(heap[i] heap[j])16 i = j17 18 return x19 20

其時間複雜度與其堆積高度相同是為O(logn) 其中 n 為堆積中的元素個數

1 100

2 93

3 84

4 77

5 65

6 81

7 79

8 66

9 70

10 50

100

84

77

93

8165

66 70

79

50

1

2 3

4 5 6 7

8 9 10

1 int DeletHeap()2 int x i j3 if(n == 0) HeapEmpty()4 else5 x = heap[1]6 heap[1] = heap[n]7 n--8 i = 19 while (i lt= n2)10 if ([heap[2i]] gt heap[2i+1])11 j = 2i12 else13 j = 2i+114 if (heap[i]gtheap[j]) break15 swap(heap[i] heap[j])16 i = j17 18 return x19 20

583  優先佇列bull 由於最大堆積(或最小堆積)的結構可提供最大元素(或最小元素)而其維護堆積的成本為 O(logn) n 為總元素個數

bull 堆積常被當成「優先佇列」 (priority queue) 使用mdash此時佇列被組織成完備二元樹若新加入佇列的資料有大小關係優先順序的需求時此優先佇列即以 O(logn) 的代價將新資料放入優先佇列(可能是最大或最小堆積依實際大小關係優先順序的需求而定)中mdash提供依元素的優先順序做為處理順序的服務

bull 一般的佇列為先進先出先到的先行處理優先佇列則是依據其優先順序優先順序高者先行處理與來到的先後無關

範例 5-21bull 在「分時」 (time sharing) 的作業系統中我們不希望使用中央處理器 (Central Processing Unit CPU) 時間短的「處理程序」 (process) 等候過久於是有「最短時間工作優先」 (shortest-job-first) 的排程策略此時等候的處理程序即可組織成之一最小堆積而 CPU 每次即取出存於樹根的處理程序予以處理任何新來的處理程序則以 O(logn) 的時間加入此優先佇列(最小堆積)中

bull 注意之前提過的線性佇列新來的直接加入成為新的尾端元素花O(1) 的時間然線性佇列本身沒有大小的關係每次要取出最大(小)的資料得花O(n) 的代價(在所有元素中挑出最大(小)者) n 為佇列內的元素個數而優先佇列是在新元素加入時要花O(logn) 的時間至於取出最大(小)則只須 O(1) 的時間

59  二元搜尋樹bull 二元搜尋樹是一棵二元樹可能是空二元樹若不為空二元樹則滿足下列性質ndash所有節點內的資料內容是相異的 ndash左子節點的資內容 ( 如果有 ) 比父節點的資料內容小 ndash右子節點的資料內容 ( 如果有 ) 比父節點的資料內容大 ndash以左和右子節點為樹根的左子樹和右子樹也是二元搜尋樹

在二元搜尋樹中搜尋資料二元搜尋樹對資料大小的掌握十分明確若恰與節點 p 資料相等則搜尋成功若欲搜尋的資料 x 比樹節點 p 中的資料小 則只須搜尋節點 p 的左子樹若比節點 p 中的資料大 則只須搜尋 p 的右子樹這樣的判斷可以遞迴地應用在各子樹節點 中

程式 5-15 在二元搜尋樹中搜尋資料(遞迴呼叫)1 struct BSTreeNode2 struct BSTreeNode leftchild3 int data4 struct BSTreeNode rightchild5 6 struct BSTreeNode root7 struct BSTreeNode SearchBST(

struct BSTreeNode tree int x)8 if (tree == NULL) return NULL9 if (x == tree-gtdata) return tree10 if (x lt tree-gtdata)11 return SearchBST(tree-gtleftchild x)

12 return SearchBST(tree-gtrightchild x)13

程式 5-16 搜尋二元搜尋樹的資料(非遞迴呼叫)在二元搜尋樹十分龐大時遞迴呼叫的效率不會太好可利用迴圈來完成搜尋的動作以提高執行效率請看程式 5-16

14 struct BSTreeNode Search BST_iterative15 (struct BSTreeNode node int x)16 while (node = NULL)17 if (x == node-gtdata) return node18 if (x lt node-gtdata)19 node = node-gtleftchild20 else21 node = node-gtrightchild22 23 return NULL24

592  新增資料於二元搜尋樹中欲新增資料進入一二元搜尋樹中得先決定該資料在二元搜尋樹中應出現的位置這可透過上節提到的搜尋動作來解決

在二元搜尋樹定義的性質 (1) 中我們限定了元素必須相異所以搜尋新資料的結果必是 NULL

必須將搜尋的結果改為新增資料所應在位置的節點指標

程式 5-17  在二元搜尋樹中新增資料25 int InsertBST(int x)26 struct BSTreeNode p q27 p = root q = NULL28 while (p = NULL)29 q = p30 if (x == p-gtdata) return 0

若不考慮重複則可省略之31 if (x lt p-gtdata)32 p = p-gtleftchild33 else34 p = p-gtrightchild35

3636 p = (struct BSTreeNode ) p = (struct BSTreeNode ) malloc (sizeof(BSTreeNode))malloc (sizeof(BSTreeNode))

3737 p-gtdata = x p-gtdata = x3838 p-gtleftchild = p-gtrightchild = p-gtleftchild = p-gtrightchild = NULLNULL3939 if (root == NULL) root = p if (root == NULL) root = p4040 else if (x lt q-gtdata) else if (x lt q-gtdata)4141 q-gtleftchild = p q-gtleftchild = p4242 else else4343 q-gtrightchild = p q-gtrightchild = p4444 return 1 return 145 45

程式 程式 5-175-17  在二元搜尋樹中新增資料  在二元搜尋樹中新增資料 (( 續續 ))

範例 5-23 圖 (b) 為圖 (a) 新增整數 6 之後的結果圖 (c) (d) (e) (f) 則分別是逐步再加入 28 18

10 21 的結果

bull若目前的二元搜尋樹高度為 h 則新增資料得花O(h) 的時間找到其所應在的位置遂新增資料至二元搜尋樹中的時間複雜之度為 O(h)

593 自二元搜尋樹中刪除資料欲自二元搜尋樹中刪除資料可能會面臨兩種情況(1) 刪除節點 p 位於樹葉節點上如下圖所示 這是比較容易處理的情況

(2) 刪除的節點 p 不是樹葉刪除節點 p 後原 p 所指的節點可由 q ( q 中包含了 p 左子樹中的最大節點值)或 r ( r 中包含了 p 右子樹中的最小節點值)來取代以維持二元搜尋樹中資料的大小關係其結果分別如圖 (b) 和 (c) 所示而圖 (d) 則標明了指標會改變的節點位置即 p 會消失 father 的左或右子樹指標會異動 k(可能是 q 或 r )會移到原來 p 的位置其父節點 f 的左或右子樹指標會異動其中 father 為 p 父節點 f 為 k 的父節點

程式 5-18 自二元搜尋樹中刪除資料46 int DeleteBST (int x)47 Struct BSTreeNode p father k f48 p = root father = NULL49 while (p = NULL)50 if (x == p-gtdata) 找到 x 所在的節點 p 了51 if ((p-gtleftchild == NULL) ampamp 52 (p-gtrightchild == NULL))53 k = NULL p為樹葉節點54 else if (p-gtleftchild = NULL )x位於非子節

點55 f = p 找出 p左子樹的最右樹葉56 k = p-gtleftchild57 while (k-gtrightchild =NULL)58 f = k59 k = k-gtrightchild60 k將挪至原 p處61 if (p == f)62 f-gtleftchild = k-gtleftchild 63 else64 f-gtrightchild = k-gtleftchild 65

66 else p無左子樹67 f = p 找出 p右子樹的最左樹葉68 k = p-gtrightchild69 while (k-gtleftchild = NULL)70 f = k71 k = k -gtleftchild72 73 if (p == f)74 f-gtrightchild = k-gtrightchild 75 else76 f-gtleftchild = k-gtrightchild77 78 if (k = NULL) k挪至原 p處繼承 p的左右指標79 k-gtleftchild = p-gtleftchild80 k-gtrightchild = p-gtrightchild81 82 if (father == NULL) root = k83 else if (x lt father-gtdata) 決定 k是 father的左

或右兒子84 father-gtleftchild = k85 else86 father-gtrightchild = k87 free(p)88 return 1 成功地刪除 x於此傳回 1返回呼叫處89 51~89行為找到 x時應有的處理步驟

90 else 尚未找到 x繼續往下一階層找

91 father = p92 if (x lt p-gtdata)93 p = p-gtleftchild94 else95 p = p-gtrightchild96 97 98 return 0 未找到 x傳回 099

510  樹林 (forest)

bull 定義樹林(森林)是由 n 棵沒有交集的樹所構成的集合 n 0

下圖為一包含三棵樹的樹林

5101 轉換樹林為二元樹 之前我們以二元樹來表示樹因為二元樹有較多

好用的性質在此我們也將樹林轉換成二元樹看待

在 522 節中我們嘗試了用左子右兄弟的方式來表示樹這個方法事實上已將樹轉換成二元樹

即用左子右兄弟的結構重新組織樹林中的每一棵樹這些樹的樹根將視為兄弟而最左邊的樹則視為樹林二元樹的樹根如此一來樹林即可轉換成二元樹

範例 5-25右下圖為右上圖三棵樹所對應的三棵左子右兄弟二元樹

下圖則為上圖樹林所對應的一棵樹林二元樹

演算法 5-1  轉換樹林成二元樹輸入樹林 F = T1 T2 Tn

輸出 F 的二元樹表示 BT=(T1 T2 Tn)1 if (n == 0) 輸出空二元樹 2 else

3 root(T1 T2 Tn) = T1

4 leftsubtree(T1 T2 Tn) =

BT(T11 T12 T1m)5 BT(T11 T12 T1m)為 T1的子樹6 rightsubtree(T1 T2 Tn) =

BT(T2 T3 Tn)

樹林轉換成二元樹的過程樹林轉換成二元樹的過程

  • Slide 1
  • Slide 2
  • Slide 3
  • Slide 4
  • Slide 5
  • Slide 6
  • Slide 7
  • Slide 8
  • Slide 9
  • Slide 10
  • Slide 11
  • Slide 12
  • Slide 13
  • Slide 14
  • Slide 15
  • Slide 16
  • Slide 17
  • Slide 18
  • Slide 19
  • Slide 20
  • Slide 21
  • Slide 22
  • Slide 23
  • Slide 24
  • Slide 25
  • Slide 26
  • Slide 27
  • Slide 28
  • Slide 29
  • Slide 30
  • Slide 31
  • Slide 32
  • Slide 33
  • Slide 34
  • Slide 35
  • Slide 36
  • Slide 37
  • Slide 38
  • Slide 39
  • Slide 40
  • Slide 41
  • Slide 42
  • Slide 43
  • Slide 44
  • Slide 45
  • Slide 46
  • Slide 47
  • Slide 48
  • Slide 49
  • Slide 50
  • Slide 51
  • Slide 52
  • Slide 53
  • Slide 54
  • Slide 55
  • Slide 56
  • Slide 57
  • Slide 58
  • Slide 59
  • Slide 60
  • Slide 61
  • Slide 62
  • Slide 63
  • Slide 64
  • Slide 65
  • Slide 66
  • Slide 67
  • Slide 68
  • Slide 69
  • Slide 70
  • Slide 71
  • Slide 72
  • Slide 73
  • Slide 74
  • Slide 75
  • Slide 76
  • Slide 77
  • Slide 78
  • Slide 79
  • Slide 80
  • Slide 81
  • Slide 82
  • Slide 83
  • Slide 84
  • Slide 85
  • Slide 86
  • Slide 87
  • Slide 88
  • Slide 89
  • Slide 90
  • Slide 91
  • Slide 92
  • Slide 93
  • Slide 94
  • Slide 95
  • Slide 96
  • Slide 97
  • Slide 98
  • Slide 99
  • Slide 100
  • Slide 101
  • Slide 102
  • Slide 103
Page 28: 第五章 樹

證明先用數學歸納法證明 (2) bull 歸納法的基礎當 i = 1 時其左子節點確實在 A[2] 處(除非 nlt2 此時節點 1 沒有左子節點)

bull 歸納法的假設假設當 I = k 時其左子節點確在 A[2i] = A[2k] 處(除非 2i 已大於 n 此時節點 i 無左子節點)

bull 歸納法的推論當 i = k+1 時因第 k 節點的左子節點在 A[2k] 處其右子節點則位在 A[2k+1] 處那麼第 k+1 節點的左子節點應緊臨 A[2k+1] 亦即位於 A[2k+2] = A[2(k+1)] 處(除非 2i 已大於 n 此時節點 i 無左子節點)

bull 由數學歸納法的原理得證定理 5-5 (2) (3) 由於 (2) 成立而且節點 i 的右子節點會緊臨

A[2i] 亦即其位於 A[2i+1]處除非 i 根本沒有右子節點得證定理 5-5 (3)

由 (2) 和 (3) 我們可知定理 5-5 (1) 成立

542 以鍵結串列表示二元樹bull 以陣列來表示完備二元樹算是相當方便但ndash一般的二元樹將導致陣列空間的浪費ndash如果二元樹中節點的新增刪除動作頻繁則會造成陣列對應資料的搬動隨而頻繁

bull 以鍵結串列表示二元樹

為方便辦識樹根我們常用 root 指標指向樹根做為樹鍵結串列的起點

陣列表示和鍵結表示的空間需求bull 完備二元樹的陣列表示和鍵結表示皆沒浪費儲存資料的空間

bull 但歪斜樹的陣列表示比起鍵結表示則浪費許多

bull 以 n 個節點的歪斜樹而言陣列表示法可能需要 2n-1 個陣列元素其中 2n-1-n 個陣列元素空間是浪費的而鍵結表示法只需要 n 個節點空間

二元樹節點的宣告程式 5-2

1 struct BTreeNode

2 struct BtreeNode leftchiled

3 int data

4 struct BTreeNode rightchild

5

6 struct BTreeNode root

bull 這樣的二元樹節點要取得兒子節點的指標十分方便但若有取得父節點的需求則嫌不足我們可以加上 parent 欄位指向父節點

55  二元樹的走訪bull 假如一組資料已用二元樹的組織在一起總有需求對其全數資料做動作例如計算所有數目印出所有資料在所有資料中搜尋某項資料hellip等此時即須對此二元樹做走訪 (traversal) 的運算利用走訪二元樹的同時將計算列印或搜尋的動作完成

bull 事實上走訪即在決定二元樹上資料被處理(計算列印或搜尋)的順序我們也希望走訪的演算法對任何節點皆一致是容易撰寫程式實作的

bull 若對一個二元樹上的節點而言 V 表示處理節點上的資料 L 表示走訪其左子樹 R 表示走訪其右子樹

55  二元樹的走訪 ( 續 )bull 因為對稱的緣故我們只考慮先走訪左邊再走訪右邊的情形那麼右圖只剩下三種走訪方式

bull 以 V 所在的相對位置分別對此三種走訪方式取名如下

(1) LVR 中序走訪 (inorder traversal)或中序表示法 (infix notation)

(2) LRV 後序走訪 (postorder traversal) 或後序表示法 (postfix notation)

(3) VLR 前序走訪 (preorder traversal)或前序表示法 (prefix notation)

551  中序走訪bull 利用遞迴的方式撰寫中序走訪的程序希望把二元樹中序走訪的

順序印出來

程式 5-3二元樹的中序走訪1 struct BTreeNode2 struct BTreeNode leftchild3 char data4 struct BTreeNode rightchild5 6 struct BTreeNode root7 void inorder(struct BTreeNode node)8 if (node = NULL)9 inorder(node-gtleftchild)10 cout ltlt node-gtdata11 inorder(node-gtrightchild)12 13

範例 5-12此為運算式二元樹

其對應中序表示運算式為 (A+B)C+D(E-F)

6 struct BTreeNode root7 void inorder(struct BTreeNode node)8 if (node = NULL)9 inorder(node-gtleftchild)10 cout ltlt node-gtdata11 inorder(node-gtrightchild)12 13

6 struct BTreeNode root7 void inorder(struct BTreeNode node)8 if (node = NULL)9 inorder(node-gtleftchild)10 cout ltlt node-gtdata11 inorder(node-gtrightchild)12 13

(A+B)C+D(E-F)

如何加上 ( )

552  後序走訪程式 5-4二元樹的後序走訪14 void postorder(struct BTreeNode node)

15 if (node =NULL)16 postorder(node-gtleftchild)17 postorder(node-gtrightchild)18 cout ltlt node-gtdata19 20

以後序走訪可得到AB+CDEF-+

其正為對應的後序運算式

553  前序走訪程式 5-5二元樹的前序走訪21 void preorder(struct BTreeNode node)

22 if (node = NULL)

23 cout ltlt node-gtdata

24 preorder(node-gtleftchild)

25 preorder(node-gtrightchild)

26

27

以前序走訪可得到++ABCD-EF

其正為對應的前序運算式

554 利用堆疊和迴圈取代遞迴進行二元樹的走訪1 struct BTreeNode 2 struct BTreeNode leftchild3 char data4 struct BtreeNode rightchild5 6 struct BTreeNode root7 struct StackNode8 struct BTreeNode treenode9 struct StackNode next1011 struct StackNode top

12 void push_node(struct BtreeNode node )13 struct StackNode old_top14 old_top = top15 top = (struct StackNode ) malloc

(sizeof(struct StackNode))16 top-gttreenode = node17 top-gtnext = old_top18 19 struct BTreeNode pop_node()20 struct BTreeNode Tnode21 struct StackNode old_top22 if (top == NULL)23 StackEmpty()24 else25 Tnode = top-gttreenode26 old_top = top27 top = top-gtnext28 free(old_top)29 return Tnode30 31

32 void inorder_Stack(struct BtreeNode node)33 do34 while (node = NULL)35 push_node(node)36 node = node-gtleftchild37 push all left children38 if (top = NULL)39 node = pop_node()40 cout ltlt node-gtdata41 node = node-gtrightchild42 inorder printing and check right child43 while ((top=NULL)||(node=NULL))44 top==NULL 堆疊已空 node==NULL沒有右子

6 struct BTreeNode root7 void inorder(struct BTreeNode node)8 if (node = NULL)9 inorder(node-gtleftchild)10 cout ltlt node-gtdata11 inorder(node-gtrightchild)12 13

中序表示為DBGEHAFIC

32 void inorder_Stack()33 struct BTreeNode node34 node = root35 do36 while (node = NULL)37 push_node(node)38 node = node-gtleftchild39 40 if (top = NULL)41 node = pop_node()42 cout ltlt node-gtdata43 node = node-gtrightchild44 45 while(top=NULL)||(node=NULL))46

45 void Preorder_Stack(struct BSTreeNode node)46 do47 while (node = NULL)48 cout ltlt node-gtdata)49 push_node(node)50 node = node-gtleftchild51 push all left children52 if (top = NULL)53 node = pop_node()54 node = node-gtrightchild55 56 while ((top=NULL)||(node=NULL))57

程式 5-5二元樹的前序走訪21 void preorder(struct BTreeNode node)22 if (node = NULL)23 cout ltlt node-gtdata24 preorder(node-gtleftchild)25 preorder(node-gtrightchild)26 27

58 void Postorder_Stack(struct BTreeNode node)59 do60 while (node = NULL)61 cout ltlt node-gtdata62 push_node(node)63 node = node-gtrightchild64 push all left children65 if (top = NULL)66 node = pop_node()67 node = node-gtleftchild68 69 while ((top=NULL)||(node=NULL))70

程式 5-4二元樹的後序走訪14 void postorder(struct BTreeNode node)15 if (node =NULL)16 postorder(node-gtleftchild)17 postorder(node-gtrightchild)18 cout ltlt node-gtdata19 20

555  階層走訪bull 「階層走訪」 (level-order traversal) 是依階層

的順序進行二元樹的走訪先走訪階層小的節點後走訪階層大的節點同一階層者則依自左向右的順序走訪

bull 對下圖的二元樹進行階層走訪的結果為ABCDEFGHI

bull 對同一階層而言先走訪的節點其子節點亦在下一階層中先被走訪

bull 這種「先進先出」的特性恰可用佇列予以儲存與處理

程式 5-7 利用佇列進行二元樹的階層走訪

1 struct BTreeNode2 struct BTreeNode leftchild3 char data4 struct BTreeNode rightchild5 6 struct BTreeNode root7 struct QNode8 struct BTreeNode treenode9 struct QNode next10 11 struct QNode front rear

12 void AddQueue(struct BTreeNode Tnode)13 struct QNode node14 node=(struct QNode )malloc(sizeof(struct QNode))15 node-gttreenode = Tnode16 node-gtnext = NULL17 if (front == NULL) front = node19 else rear-gtnext = node20 rear = node21

22 struct BTreeNode DeleteQueue()23 struct BTreeNode Tnode24 struct QNode old_front25 if (rear == NULL)26 QueueEmpty()27 else28 Tnode = front-gttreenode29 old_front = front30 front = front-gtnext31 free(old_front)32 return Tnode33 34 35 void LevelOrder (struct BTreeNode node)36 AddQueue(node)37 while (front =NULL)38 node = DeleteQueue()39 cout ltlt node-gtdata40 if (node-gtleftchild = NULL)41 AddQueue(node-gtleftchild)42 if (node-gtrightchild = NULL)43 AddQueue(node-gtrightchild)44 45

35 void LevelOrder (struct BTreeNode node)36 AddQueue(node)37 while (front =NULL)38 node = DeleteQueue()39 cout ltlt node-gtdata40 if (node-gtleftchild = NULL)41 AddQueue(node-gtleftchild)42 if (node-gtrightchild = NULL)43 AddQueue(node-gtrightchild)44 45

bull Infix ABCbull Postfix ABC CBA ACB BCA

BAC CAB

B

A CA

B

C

B

A

C

C

B

AB

A

C

二元樹中序與前序式中序與後序式bull 每一棵二元樹皆有唯一的一對中序與前序表示法也有唯一的中序與後序次序

bull 換句話說給予一對中序與前序或中序與後序即可決定一棵二元樹

bull 然而給予前序和後序次序並不能決定唯一的二元樹可能會產生兩棵不同的二元樹

bull 想想階層走訪和中序走訪順序可否決定唯一的二元樹

A

B C

H J D E

F G

prefix ABHJCDFGEinfix HBJAFDGCEpostfix HJBFGDECA

VLRLVRLRV

A

prefix BHJinfix HBJ

prefix CDFGEinfix FDGCE

prefix ABHJCDFGEinfix HBJAFDGCE

A

B C

H J D E

F G

prefix ABHJCDFGE

infix HBJAFDGCEk-1

struct BTreeNode BTree_InfixPrefix (AnsiString infix AnsiString prefix)

int k struct BTreeNode node if (infixLength==0) return NULLnode = (struct BTreeNode ) malloc (sizeof(struct BTreeNode))node-gtdata = prefix[1]k = find(prefix[1] infix)node-gtleftchild = BTree_InfixPrefix

(infix[1k-1] prefix[2k])node-gtrightchild = BTree_InfixPrefix(infix[k+1infixLength]prefix[k+1prefixLength])

VLR

LVRk1

k2 k+1~

k+1~

1

struct BTreeNode BTree_InfixPostfix (AnsiString infix AnsiString prefix)

int k struct BTreeNode node if (infixLength==0) return NULLnode = (struct BTreeNode ) malloc (sizeof(struct BTreeNode))node-gtdata = postfix[postfixLength]k = find(postfix[postfixLength] infix)node-gtleftchild = BTree_InfixPrefix

(infix[1k-1] postfix[1k-1])node-gtrightchild = BTree_InfixPrefix(infix[k+1infixLength]postfix[k+1postfixLength-1])

56  二元樹的複製與相等測試程式 5-8 二元樹的複製1 struct BTreeNode2 struct BTreeNode leftchild3 char data4 struct BTreeNode rightchild5 6 struct BTreeNode root7 struct BTreeNode Copy(struct BTreeNode TreeRoot)8 struct BTreeNode CopyRoot9 if (TreeRoot == NULL)10 return NULL11 CopyRoot =(struct BTreeNode ) malloc (sizeof(BTreeNode))12 CopyRoot-gtdata = TreeRoot-gtdata13 CopyRoot-gtleftchild = Copy(TreeRoot-gtleftchild)14 CopyRoot-gtrightchild = Copy(TreeRoot-

gtrightchild)15 return CopyRoot16

程式 5-9 檢測兩棵二元樹是否相等1 struct BTreeNode2 struct BTreeNode leftchild3 char data4 struct BTreeNode rightchild5 6 struct BtreeNode rightchild7 int Equal(struct BTreeNode ustruct BTreeNode v)8 if ((u==NULL) ampamp (v==NULL)) return 19 if ((u=NULL) ampamp (v=NULL)10 ampamp (u-gtdata == v-gtdata )11 ampamp Equal(u-gtleftchildv-gtleftchild)12 ampamp Equal(u-gtrightchildv-gtrightchild))

13 return 114 return 015

Counting Nodes in a Binary Tree

int CountingNodes(struct BTreeNodes node)

if (node == NULL) return 0 int leftchildren = CountingNodes(node-gtleftchild)

int rightchildren= CountingNodes(node-gtrightchild)

return leftchildren+rightchildren

Counting Leaves in a Binary Tree

int CountingLeaves(struct BTreeNodes node)

if (node == NULL) return 0 int leftleaves = CountingLeaves(node-gtleftchild)

int rightchildren= CountingLeaves(node-gtrightchild)

if (leftleaves==0 ampamp leftleaves==0) return 1

return leftleaves + leftleaves

57  引線二元樹bull 在二元樹的鍵結表示中樹葉節點的兩個子樹指標皆指向 NULL 由定理 5-1 得知 n = E+1 其中 n 為節點個數 E 為分支數

bull 而 n 個鍵結節點有 2n 個指標空間每個分支恰佔用一個指標空間共有 E (=n-1) 個指標有用到(不是空的)而共有 n+1 個指標空間是 NULL 比非空的節點還多

bull 有學者提出對這些「放空指標的空間」加以利用的概念mdash與其放空指標不如放指向其它節點的指標稱之為引線 (thread) 使得某些運算(如走訪hellip等)可以加快

bull 這個概念發展出了引線二元樹 (threaded binary tree) 這種資料結構

範例 5-17下圖為加了引線的二元樹

加入的引線將使二元樹的中序走訪更加便利沿著上右圖箭頭所指示的順序走訪即可完成此二元樹的中序走訪GDHBAIECF 為此二元樹的中序表示

bull 範例 5-17 中所加的引線目的在方便二元樹的中序走訪

bull 對樹葉節點 p 而言令其資料為 P 則引線指標的規則如下

(1) p 的左子樹指標指向中序表示中 P 的前一個 元素(前接元素 (predecessor) )

(2) p 的右子樹指標指向中序表示中 P 的後一個 元素(後繼元素 (successor) )

為了區別節點指標放的是一般指標(內部節點)抑或是引線指標(樹葉節點)我們另以 2 個欄位分別區別左和右子樹指標空間存放的對象其節點的記憶體配置有如下圖所示

其中 leftthread (rightthread) 為 0 時表示 leftchild (rightchild) 存放的是一般節點指標而 leftthread (rightthread) 為 1 時表示 leftchild (rightchild) 存放的是引線指標1 struct TBTreeNode2 int leftthread3 struct TBTreeNode leftchild4 char data5 struct TBTreeNode rightchild6 int rightthread7

由右側的引線二元樹可知有最左和最右兩條引線尚無妥善的安排我們另設計一空的引線節點做為樹根如下圖所示

571  引線二元樹的中序走訪決定節點 p 在中序表示中的後繼節點 q (簡稱中序後繼點)

程式 5-10決定節點 node的中序後繼點1 struct TBTreeNode2 int leftthread3 struct TBTreeNode leftchild4 char data5 struct TBTreeNode rightchild6 int rightthread7 8 struct TBTreeNode root9 struct TBTreeNode Next(struct TBTreeNode node)

10 struct TBTreeNode temp11 temp = node-gtrightchild12 if (node-gtrightthead) return temp13 while (temp-gtleftthead) temp=temp-gtleftchild

14 return temp15

程式 5-11引線二元樹的中序表示(接程式 5-10)1 void InorderTBTree()2 struct TBTreeNode node3 for(node=Next(root)node=rootnode=Next(node))

4 cout ltlt node-gtdata5

bull只要 node 是引線二元樹上的任一節點透過呼叫 Next(node) 即可找到其在中序表示中的後繼節點bull(試想若程式 5-10 中的程序 Next 所傳入的參數指標正是 root Next(root) 是否可正常運作請思考)bull於是只須從中序表示中的第一節點起讓每一節點 node 都執行 Next(node) 程序即可得引線二元樹的中序表示

572  在引線二元樹中加入節點在引線二元樹中樹葉節點節點的右子樹指標所指向或內部節點右子樹的最左樹葉為其中序後繼節點而且任一節點若無左子樹則其左子樹指標乃指向其中序前接節點hellip這些性質必須在節點新增進入引線二元樹時也要保持接下來討論節點 new 插入成為引線二元樹的節點 p 的右子節點的情形至於插入成為左子節點的情形則讓各位模擬推敲

572572  在引線二元樹中加入節點 在引線二元樹中加入節點 (( 續續 ))

newnew 會成為會成為 pp 的右子節點的情況共有以下兩種的右子節點的情況共有以下兩種(1) (1) 若若 pp 沒有右子節點 沒有右子節點 ((p-gtrightthread p-gtrightthread 為為1) 1) 如圖如圖 ((a) a) 則 則 new new 插入後應形成如圖插入後應形成如圖 ((b) b) 所示所示

(2)(2) 若若 pp 有右子節點 有右子節點 ((p-gtrightthread p-gtrightthread 為為0) 0) 如圖如圖 ((c) c) 則 則 new new 插入後應形成如插入後應形成如圖圖 ((d) d) 所示所示

程式 5-12  插入節點 new 成為引線二元樹中節點 p的右子節點(上接程式 5-10 5-11 )

1 void InsertRight(struct TBTreeNode p 2 struct TBTreeNode new)3 struct TBTreeNode q4 new-gtrightchild = p-gtrightchild5 new-gtrightthead = p-gtrightthread6 new-gtleftchild = p7 new-gtleftthread = 18 p-gtrightchild = new9 p-gtrightthead = 010 if (new-gtrightthread)11 q = Next(new)12 q-gtleftchild = new13 14

58  堆積bull 「堆積」 (heap) 是一個特殊的完備二元樹節點的資料與其子節點的資料之間有事先定好的大小關係存在bull 定義ndash最大堆疊為一完備二元樹任一節點的資料內容不小於其子節點的資料內容ndash最小堆疊為一完備二元樹任一節點的資料內容不大於其子節點的資料內容

581  插入節點至最大堆積中最大堆積是一完備二元樹所以如圖 (a) 之最大堆積於加入新節點後樹的形狀必須如圖 (b) 所示灰色節點即為新節點若加入的節點資料分別為 1 9 12 則其最大堆積將分別變成如圖 (c) (d) (e) 所示

程式 5-13 新增節點至最大堆積中最大堆積本身是一棵完備二元樹可以用陣列表示之 1 define maxsize 1002 int heap[maxsize]3 int n i4 void InsertHeap(int x)5 if (n == maxsize) HeapFull()6 else7 n++8 i = n9 while ((i gt 1) ampamp (x gt heap[i2]))10 heap[i] = heap[i2]11 i = 212 13 heap[i] = x14 16

程序 InsertHeap 在插入第 n 個資料時需要的時間複雜度為 O(logn)

1 100

2 93

3 84

4 77

5 65

6 81

7 79

8 66

9 70

10 50

100

84

77

93

8165

66 70

79

50

1

2 3

4 5 6 7

8 9 10

1 define maxsize 1002 int heap[maxsize]3 int n i4 void InsertHeap(int x)5 if (n == maxsize) HeapFull()6 else7 n++8 i = n9 while ((i gt 1) ampamp (x gt heap[i2]))10 heap[i] = heap[i2]11 i = 212 13 heap[i] = x14 16

582 刪除最大堆積中的最大資料圖 (a) 中的最大堆積在刪除最大資料後其樹形應變成如圖 (b) 所示將最未一個樹葉節點的資料 6 暫挪至樹根節點內如圖 (c) 再將圖 (c) 中非最大堆積的結構調整成為最大堆積 10 和 8 中選大者與 6對調即成圖 (d)

程式 5-14 刪除最大堆積中的最大資料(上接程式 5-13 )1 int DeleteHeap()2 int x i j3 if (n == 0) HeapEmpty()4 else5 x = heap[1]6 heap[1] = heap[n]7 n--8 i = 19 while (i lt= n2)10 if (heap[2i] gt heap[2i+1])11 j = 2i12 else13 j = 2i+114 if (heap[i]gtheap[j]) break15 swap(heap[i] heap[j])16 i = j17 18 return x19 20

其時間複雜度與其堆積高度相同是為O(logn) 其中 n 為堆積中的元素個數

1 100

2 93

3 84

4 77

5 65

6 81

7 79

8 66

9 70

10 50

100

84

77

93

8165

66 70

79

50

1

2 3

4 5 6 7

8 9 10

1 int DeletHeap()2 int x i j3 if(n == 0) HeapEmpty()4 else5 x = heap[1]6 heap[1] = heap[n]7 n--8 i = 19 while (i lt= n2)10 if ([heap[2i]] gt heap[2i+1])11 j = 2i12 else13 j = 2i+114 if (heap[i]gtheap[j]) break15 swap(heap[i] heap[j])16 i = j17 18 return x19 20

583  優先佇列bull 由於最大堆積(或最小堆積)的結構可提供最大元素(或最小元素)而其維護堆積的成本為 O(logn) n 為總元素個數

bull 堆積常被當成「優先佇列」 (priority queue) 使用mdash此時佇列被組織成完備二元樹若新加入佇列的資料有大小關係優先順序的需求時此優先佇列即以 O(logn) 的代價將新資料放入優先佇列(可能是最大或最小堆積依實際大小關係優先順序的需求而定)中mdash提供依元素的優先順序做為處理順序的服務

bull 一般的佇列為先進先出先到的先行處理優先佇列則是依據其優先順序優先順序高者先行處理與來到的先後無關

範例 5-21bull 在「分時」 (time sharing) 的作業系統中我們不希望使用中央處理器 (Central Processing Unit CPU) 時間短的「處理程序」 (process) 等候過久於是有「最短時間工作優先」 (shortest-job-first) 的排程策略此時等候的處理程序即可組織成之一最小堆積而 CPU 每次即取出存於樹根的處理程序予以處理任何新來的處理程序則以 O(logn) 的時間加入此優先佇列(最小堆積)中

bull 注意之前提過的線性佇列新來的直接加入成為新的尾端元素花O(1) 的時間然線性佇列本身沒有大小的關係每次要取出最大(小)的資料得花O(n) 的代價(在所有元素中挑出最大(小)者) n 為佇列內的元素個數而優先佇列是在新元素加入時要花O(logn) 的時間至於取出最大(小)則只須 O(1) 的時間

59  二元搜尋樹bull 二元搜尋樹是一棵二元樹可能是空二元樹若不為空二元樹則滿足下列性質ndash所有節點內的資料內容是相異的 ndash左子節點的資內容 ( 如果有 ) 比父節點的資料內容小 ndash右子節點的資料內容 ( 如果有 ) 比父節點的資料內容大 ndash以左和右子節點為樹根的左子樹和右子樹也是二元搜尋樹

在二元搜尋樹中搜尋資料二元搜尋樹對資料大小的掌握十分明確若恰與節點 p 資料相等則搜尋成功若欲搜尋的資料 x 比樹節點 p 中的資料小 則只須搜尋節點 p 的左子樹若比節點 p 中的資料大 則只須搜尋 p 的右子樹這樣的判斷可以遞迴地應用在各子樹節點 中

程式 5-15 在二元搜尋樹中搜尋資料(遞迴呼叫)1 struct BSTreeNode2 struct BSTreeNode leftchild3 int data4 struct BSTreeNode rightchild5 6 struct BSTreeNode root7 struct BSTreeNode SearchBST(

struct BSTreeNode tree int x)8 if (tree == NULL) return NULL9 if (x == tree-gtdata) return tree10 if (x lt tree-gtdata)11 return SearchBST(tree-gtleftchild x)

12 return SearchBST(tree-gtrightchild x)13

程式 5-16 搜尋二元搜尋樹的資料(非遞迴呼叫)在二元搜尋樹十分龐大時遞迴呼叫的效率不會太好可利用迴圈來完成搜尋的動作以提高執行效率請看程式 5-16

14 struct BSTreeNode Search BST_iterative15 (struct BSTreeNode node int x)16 while (node = NULL)17 if (x == node-gtdata) return node18 if (x lt node-gtdata)19 node = node-gtleftchild20 else21 node = node-gtrightchild22 23 return NULL24

592  新增資料於二元搜尋樹中欲新增資料進入一二元搜尋樹中得先決定該資料在二元搜尋樹中應出現的位置這可透過上節提到的搜尋動作來解決

在二元搜尋樹定義的性質 (1) 中我們限定了元素必須相異所以搜尋新資料的結果必是 NULL

必須將搜尋的結果改為新增資料所應在位置的節點指標

程式 5-17  在二元搜尋樹中新增資料25 int InsertBST(int x)26 struct BSTreeNode p q27 p = root q = NULL28 while (p = NULL)29 q = p30 if (x == p-gtdata) return 0

若不考慮重複則可省略之31 if (x lt p-gtdata)32 p = p-gtleftchild33 else34 p = p-gtrightchild35

3636 p = (struct BSTreeNode ) p = (struct BSTreeNode ) malloc (sizeof(BSTreeNode))malloc (sizeof(BSTreeNode))

3737 p-gtdata = x p-gtdata = x3838 p-gtleftchild = p-gtrightchild = p-gtleftchild = p-gtrightchild = NULLNULL3939 if (root == NULL) root = p if (root == NULL) root = p4040 else if (x lt q-gtdata) else if (x lt q-gtdata)4141 q-gtleftchild = p q-gtleftchild = p4242 else else4343 q-gtrightchild = p q-gtrightchild = p4444 return 1 return 145 45

程式 程式 5-175-17  在二元搜尋樹中新增資料  在二元搜尋樹中新增資料 (( 續續 ))

範例 5-23 圖 (b) 為圖 (a) 新增整數 6 之後的結果圖 (c) (d) (e) (f) 則分別是逐步再加入 28 18

10 21 的結果

bull若目前的二元搜尋樹高度為 h 則新增資料得花O(h) 的時間找到其所應在的位置遂新增資料至二元搜尋樹中的時間複雜之度為 O(h)

593 自二元搜尋樹中刪除資料欲自二元搜尋樹中刪除資料可能會面臨兩種情況(1) 刪除節點 p 位於樹葉節點上如下圖所示 這是比較容易處理的情況

(2) 刪除的節點 p 不是樹葉刪除節點 p 後原 p 所指的節點可由 q ( q 中包含了 p 左子樹中的最大節點值)或 r ( r 中包含了 p 右子樹中的最小節點值)來取代以維持二元搜尋樹中資料的大小關係其結果分別如圖 (b) 和 (c) 所示而圖 (d) 則標明了指標會改變的節點位置即 p 會消失 father 的左或右子樹指標會異動 k(可能是 q 或 r )會移到原來 p 的位置其父節點 f 的左或右子樹指標會異動其中 father 為 p 父節點 f 為 k 的父節點

程式 5-18 自二元搜尋樹中刪除資料46 int DeleteBST (int x)47 Struct BSTreeNode p father k f48 p = root father = NULL49 while (p = NULL)50 if (x == p-gtdata) 找到 x 所在的節點 p 了51 if ((p-gtleftchild == NULL) ampamp 52 (p-gtrightchild == NULL))53 k = NULL p為樹葉節點54 else if (p-gtleftchild = NULL )x位於非子節

點55 f = p 找出 p左子樹的最右樹葉56 k = p-gtleftchild57 while (k-gtrightchild =NULL)58 f = k59 k = k-gtrightchild60 k將挪至原 p處61 if (p == f)62 f-gtleftchild = k-gtleftchild 63 else64 f-gtrightchild = k-gtleftchild 65

66 else p無左子樹67 f = p 找出 p右子樹的最左樹葉68 k = p-gtrightchild69 while (k-gtleftchild = NULL)70 f = k71 k = k -gtleftchild72 73 if (p == f)74 f-gtrightchild = k-gtrightchild 75 else76 f-gtleftchild = k-gtrightchild77 78 if (k = NULL) k挪至原 p處繼承 p的左右指標79 k-gtleftchild = p-gtleftchild80 k-gtrightchild = p-gtrightchild81 82 if (father == NULL) root = k83 else if (x lt father-gtdata) 決定 k是 father的左

或右兒子84 father-gtleftchild = k85 else86 father-gtrightchild = k87 free(p)88 return 1 成功地刪除 x於此傳回 1返回呼叫處89 51~89行為找到 x時應有的處理步驟

90 else 尚未找到 x繼續往下一階層找

91 father = p92 if (x lt p-gtdata)93 p = p-gtleftchild94 else95 p = p-gtrightchild96 97 98 return 0 未找到 x傳回 099

510  樹林 (forest)

bull 定義樹林(森林)是由 n 棵沒有交集的樹所構成的集合 n 0

下圖為一包含三棵樹的樹林

5101 轉換樹林為二元樹 之前我們以二元樹來表示樹因為二元樹有較多

好用的性質在此我們也將樹林轉換成二元樹看待

在 522 節中我們嘗試了用左子右兄弟的方式來表示樹這個方法事實上已將樹轉換成二元樹

即用左子右兄弟的結構重新組織樹林中的每一棵樹這些樹的樹根將視為兄弟而最左邊的樹則視為樹林二元樹的樹根如此一來樹林即可轉換成二元樹

範例 5-25右下圖為右上圖三棵樹所對應的三棵左子右兄弟二元樹

下圖則為上圖樹林所對應的一棵樹林二元樹

演算法 5-1  轉換樹林成二元樹輸入樹林 F = T1 T2 Tn

輸出 F 的二元樹表示 BT=(T1 T2 Tn)1 if (n == 0) 輸出空二元樹 2 else

3 root(T1 T2 Tn) = T1

4 leftsubtree(T1 T2 Tn) =

BT(T11 T12 T1m)5 BT(T11 T12 T1m)為 T1的子樹6 rightsubtree(T1 T2 Tn) =

BT(T2 T3 Tn)

樹林轉換成二元樹的過程樹林轉換成二元樹的過程

  • Slide 1
  • Slide 2
  • Slide 3
  • Slide 4
  • Slide 5
  • Slide 6
  • Slide 7
  • Slide 8
  • Slide 9
  • Slide 10
  • Slide 11
  • Slide 12
  • Slide 13
  • Slide 14
  • Slide 15
  • Slide 16
  • Slide 17
  • Slide 18
  • Slide 19
  • Slide 20
  • Slide 21
  • Slide 22
  • Slide 23
  • Slide 24
  • Slide 25
  • Slide 26
  • Slide 27
  • Slide 28
  • Slide 29
  • Slide 30
  • Slide 31
  • Slide 32
  • Slide 33
  • Slide 34
  • Slide 35
  • Slide 36
  • Slide 37
  • Slide 38
  • Slide 39
  • Slide 40
  • Slide 41
  • Slide 42
  • Slide 43
  • Slide 44
  • Slide 45
  • Slide 46
  • Slide 47
  • Slide 48
  • Slide 49
  • Slide 50
  • Slide 51
  • Slide 52
  • Slide 53
  • Slide 54
  • Slide 55
  • Slide 56
  • Slide 57
  • Slide 58
  • Slide 59
  • Slide 60
  • Slide 61
  • Slide 62
  • Slide 63
  • Slide 64
  • Slide 65
  • Slide 66
  • Slide 67
  • Slide 68
  • Slide 69
  • Slide 70
  • Slide 71
  • Slide 72
  • Slide 73
  • Slide 74
  • Slide 75
  • Slide 76
  • Slide 77
  • Slide 78
  • Slide 79
  • Slide 80
  • Slide 81
  • Slide 82
  • Slide 83
  • Slide 84
  • Slide 85
  • Slide 86
  • Slide 87
  • Slide 88
  • Slide 89
  • Slide 90
  • Slide 91
  • Slide 92
  • Slide 93
  • Slide 94
  • Slide 95
  • Slide 96
  • Slide 97
  • Slide 98
  • Slide 99
  • Slide 100
  • Slide 101
  • Slide 102
  • Slide 103
Page 29: 第五章 樹

542 以鍵結串列表示二元樹bull 以陣列來表示完備二元樹算是相當方便但ndash一般的二元樹將導致陣列空間的浪費ndash如果二元樹中節點的新增刪除動作頻繁則會造成陣列對應資料的搬動隨而頻繁

bull 以鍵結串列表示二元樹

為方便辦識樹根我們常用 root 指標指向樹根做為樹鍵結串列的起點

陣列表示和鍵結表示的空間需求bull 完備二元樹的陣列表示和鍵結表示皆沒浪費儲存資料的空間

bull 但歪斜樹的陣列表示比起鍵結表示則浪費許多

bull 以 n 個節點的歪斜樹而言陣列表示法可能需要 2n-1 個陣列元素其中 2n-1-n 個陣列元素空間是浪費的而鍵結表示法只需要 n 個節點空間

二元樹節點的宣告程式 5-2

1 struct BTreeNode

2 struct BtreeNode leftchiled

3 int data

4 struct BTreeNode rightchild

5

6 struct BTreeNode root

bull 這樣的二元樹節點要取得兒子節點的指標十分方便但若有取得父節點的需求則嫌不足我們可以加上 parent 欄位指向父節點

55  二元樹的走訪bull 假如一組資料已用二元樹的組織在一起總有需求對其全數資料做動作例如計算所有數目印出所有資料在所有資料中搜尋某項資料hellip等此時即須對此二元樹做走訪 (traversal) 的運算利用走訪二元樹的同時將計算列印或搜尋的動作完成

bull 事實上走訪即在決定二元樹上資料被處理(計算列印或搜尋)的順序我們也希望走訪的演算法對任何節點皆一致是容易撰寫程式實作的

bull 若對一個二元樹上的節點而言 V 表示處理節點上的資料 L 表示走訪其左子樹 R 表示走訪其右子樹

55  二元樹的走訪 ( 續 )bull 因為對稱的緣故我們只考慮先走訪左邊再走訪右邊的情形那麼右圖只剩下三種走訪方式

bull 以 V 所在的相對位置分別對此三種走訪方式取名如下

(1) LVR 中序走訪 (inorder traversal)或中序表示法 (infix notation)

(2) LRV 後序走訪 (postorder traversal) 或後序表示法 (postfix notation)

(3) VLR 前序走訪 (preorder traversal)或前序表示法 (prefix notation)

551  中序走訪bull 利用遞迴的方式撰寫中序走訪的程序希望把二元樹中序走訪的

順序印出來

程式 5-3二元樹的中序走訪1 struct BTreeNode2 struct BTreeNode leftchild3 char data4 struct BTreeNode rightchild5 6 struct BTreeNode root7 void inorder(struct BTreeNode node)8 if (node = NULL)9 inorder(node-gtleftchild)10 cout ltlt node-gtdata11 inorder(node-gtrightchild)12 13

範例 5-12此為運算式二元樹

其對應中序表示運算式為 (A+B)C+D(E-F)

6 struct BTreeNode root7 void inorder(struct BTreeNode node)8 if (node = NULL)9 inorder(node-gtleftchild)10 cout ltlt node-gtdata11 inorder(node-gtrightchild)12 13

6 struct BTreeNode root7 void inorder(struct BTreeNode node)8 if (node = NULL)9 inorder(node-gtleftchild)10 cout ltlt node-gtdata11 inorder(node-gtrightchild)12 13

(A+B)C+D(E-F)

如何加上 ( )

552  後序走訪程式 5-4二元樹的後序走訪14 void postorder(struct BTreeNode node)

15 if (node =NULL)16 postorder(node-gtleftchild)17 postorder(node-gtrightchild)18 cout ltlt node-gtdata19 20

以後序走訪可得到AB+CDEF-+

其正為對應的後序運算式

553  前序走訪程式 5-5二元樹的前序走訪21 void preorder(struct BTreeNode node)

22 if (node = NULL)

23 cout ltlt node-gtdata

24 preorder(node-gtleftchild)

25 preorder(node-gtrightchild)

26

27

以前序走訪可得到++ABCD-EF

其正為對應的前序運算式

554 利用堆疊和迴圈取代遞迴進行二元樹的走訪1 struct BTreeNode 2 struct BTreeNode leftchild3 char data4 struct BtreeNode rightchild5 6 struct BTreeNode root7 struct StackNode8 struct BTreeNode treenode9 struct StackNode next1011 struct StackNode top

12 void push_node(struct BtreeNode node )13 struct StackNode old_top14 old_top = top15 top = (struct StackNode ) malloc

(sizeof(struct StackNode))16 top-gttreenode = node17 top-gtnext = old_top18 19 struct BTreeNode pop_node()20 struct BTreeNode Tnode21 struct StackNode old_top22 if (top == NULL)23 StackEmpty()24 else25 Tnode = top-gttreenode26 old_top = top27 top = top-gtnext28 free(old_top)29 return Tnode30 31

32 void inorder_Stack(struct BtreeNode node)33 do34 while (node = NULL)35 push_node(node)36 node = node-gtleftchild37 push all left children38 if (top = NULL)39 node = pop_node()40 cout ltlt node-gtdata41 node = node-gtrightchild42 inorder printing and check right child43 while ((top=NULL)||(node=NULL))44 top==NULL 堆疊已空 node==NULL沒有右子

6 struct BTreeNode root7 void inorder(struct BTreeNode node)8 if (node = NULL)9 inorder(node-gtleftchild)10 cout ltlt node-gtdata11 inorder(node-gtrightchild)12 13

中序表示為DBGEHAFIC

32 void inorder_Stack()33 struct BTreeNode node34 node = root35 do36 while (node = NULL)37 push_node(node)38 node = node-gtleftchild39 40 if (top = NULL)41 node = pop_node()42 cout ltlt node-gtdata43 node = node-gtrightchild44 45 while(top=NULL)||(node=NULL))46

45 void Preorder_Stack(struct BSTreeNode node)46 do47 while (node = NULL)48 cout ltlt node-gtdata)49 push_node(node)50 node = node-gtleftchild51 push all left children52 if (top = NULL)53 node = pop_node()54 node = node-gtrightchild55 56 while ((top=NULL)||(node=NULL))57

程式 5-5二元樹的前序走訪21 void preorder(struct BTreeNode node)22 if (node = NULL)23 cout ltlt node-gtdata24 preorder(node-gtleftchild)25 preorder(node-gtrightchild)26 27

58 void Postorder_Stack(struct BTreeNode node)59 do60 while (node = NULL)61 cout ltlt node-gtdata62 push_node(node)63 node = node-gtrightchild64 push all left children65 if (top = NULL)66 node = pop_node()67 node = node-gtleftchild68 69 while ((top=NULL)||(node=NULL))70

程式 5-4二元樹的後序走訪14 void postorder(struct BTreeNode node)15 if (node =NULL)16 postorder(node-gtleftchild)17 postorder(node-gtrightchild)18 cout ltlt node-gtdata19 20

555  階層走訪bull 「階層走訪」 (level-order traversal) 是依階層

的順序進行二元樹的走訪先走訪階層小的節點後走訪階層大的節點同一階層者則依自左向右的順序走訪

bull 對下圖的二元樹進行階層走訪的結果為ABCDEFGHI

bull 對同一階層而言先走訪的節點其子節點亦在下一階層中先被走訪

bull 這種「先進先出」的特性恰可用佇列予以儲存與處理

程式 5-7 利用佇列進行二元樹的階層走訪

1 struct BTreeNode2 struct BTreeNode leftchild3 char data4 struct BTreeNode rightchild5 6 struct BTreeNode root7 struct QNode8 struct BTreeNode treenode9 struct QNode next10 11 struct QNode front rear

12 void AddQueue(struct BTreeNode Tnode)13 struct QNode node14 node=(struct QNode )malloc(sizeof(struct QNode))15 node-gttreenode = Tnode16 node-gtnext = NULL17 if (front == NULL) front = node19 else rear-gtnext = node20 rear = node21

22 struct BTreeNode DeleteQueue()23 struct BTreeNode Tnode24 struct QNode old_front25 if (rear == NULL)26 QueueEmpty()27 else28 Tnode = front-gttreenode29 old_front = front30 front = front-gtnext31 free(old_front)32 return Tnode33 34 35 void LevelOrder (struct BTreeNode node)36 AddQueue(node)37 while (front =NULL)38 node = DeleteQueue()39 cout ltlt node-gtdata40 if (node-gtleftchild = NULL)41 AddQueue(node-gtleftchild)42 if (node-gtrightchild = NULL)43 AddQueue(node-gtrightchild)44 45

35 void LevelOrder (struct BTreeNode node)36 AddQueue(node)37 while (front =NULL)38 node = DeleteQueue()39 cout ltlt node-gtdata40 if (node-gtleftchild = NULL)41 AddQueue(node-gtleftchild)42 if (node-gtrightchild = NULL)43 AddQueue(node-gtrightchild)44 45

bull Infix ABCbull Postfix ABC CBA ACB BCA

BAC CAB

B

A CA

B

C

B

A

C

C

B

AB

A

C

二元樹中序與前序式中序與後序式bull 每一棵二元樹皆有唯一的一對中序與前序表示法也有唯一的中序與後序次序

bull 換句話說給予一對中序與前序或中序與後序即可決定一棵二元樹

bull 然而給予前序和後序次序並不能決定唯一的二元樹可能會產生兩棵不同的二元樹

bull 想想階層走訪和中序走訪順序可否決定唯一的二元樹

A

B C

H J D E

F G

prefix ABHJCDFGEinfix HBJAFDGCEpostfix HJBFGDECA

VLRLVRLRV

A

prefix BHJinfix HBJ

prefix CDFGEinfix FDGCE

prefix ABHJCDFGEinfix HBJAFDGCE

A

B C

H J D E

F G

prefix ABHJCDFGE

infix HBJAFDGCEk-1

struct BTreeNode BTree_InfixPrefix (AnsiString infix AnsiString prefix)

int k struct BTreeNode node if (infixLength==0) return NULLnode = (struct BTreeNode ) malloc (sizeof(struct BTreeNode))node-gtdata = prefix[1]k = find(prefix[1] infix)node-gtleftchild = BTree_InfixPrefix

(infix[1k-1] prefix[2k])node-gtrightchild = BTree_InfixPrefix(infix[k+1infixLength]prefix[k+1prefixLength])

VLR

LVRk1

k2 k+1~

k+1~

1

struct BTreeNode BTree_InfixPostfix (AnsiString infix AnsiString prefix)

int k struct BTreeNode node if (infixLength==0) return NULLnode = (struct BTreeNode ) malloc (sizeof(struct BTreeNode))node-gtdata = postfix[postfixLength]k = find(postfix[postfixLength] infix)node-gtleftchild = BTree_InfixPrefix

(infix[1k-1] postfix[1k-1])node-gtrightchild = BTree_InfixPrefix(infix[k+1infixLength]postfix[k+1postfixLength-1])

56  二元樹的複製與相等測試程式 5-8 二元樹的複製1 struct BTreeNode2 struct BTreeNode leftchild3 char data4 struct BTreeNode rightchild5 6 struct BTreeNode root7 struct BTreeNode Copy(struct BTreeNode TreeRoot)8 struct BTreeNode CopyRoot9 if (TreeRoot == NULL)10 return NULL11 CopyRoot =(struct BTreeNode ) malloc (sizeof(BTreeNode))12 CopyRoot-gtdata = TreeRoot-gtdata13 CopyRoot-gtleftchild = Copy(TreeRoot-gtleftchild)14 CopyRoot-gtrightchild = Copy(TreeRoot-

gtrightchild)15 return CopyRoot16

程式 5-9 檢測兩棵二元樹是否相等1 struct BTreeNode2 struct BTreeNode leftchild3 char data4 struct BTreeNode rightchild5 6 struct BtreeNode rightchild7 int Equal(struct BTreeNode ustruct BTreeNode v)8 if ((u==NULL) ampamp (v==NULL)) return 19 if ((u=NULL) ampamp (v=NULL)10 ampamp (u-gtdata == v-gtdata )11 ampamp Equal(u-gtleftchildv-gtleftchild)12 ampamp Equal(u-gtrightchildv-gtrightchild))

13 return 114 return 015

Counting Nodes in a Binary Tree

int CountingNodes(struct BTreeNodes node)

if (node == NULL) return 0 int leftchildren = CountingNodes(node-gtleftchild)

int rightchildren= CountingNodes(node-gtrightchild)

return leftchildren+rightchildren

Counting Leaves in a Binary Tree

int CountingLeaves(struct BTreeNodes node)

if (node == NULL) return 0 int leftleaves = CountingLeaves(node-gtleftchild)

int rightchildren= CountingLeaves(node-gtrightchild)

if (leftleaves==0 ampamp leftleaves==0) return 1

return leftleaves + leftleaves

57  引線二元樹bull 在二元樹的鍵結表示中樹葉節點的兩個子樹指標皆指向 NULL 由定理 5-1 得知 n = E+1 其中 n 為節點個數 E 為分支數

bull 而 n 個鍵結節點有 2n 個指標空間每個分支恰佔用一個指標空間共有 E (=n-1) 個指標有用到(不是空的)而共有 n+1 個指標空間是 NULL 比非空的節點還多

bull 有學者提出對這些「放空指標的空間」加以利用的概念mdash與其放空指標不如放指向其它節點的指標稱之為引線 (thread) 使得某些運算(如走訪hellip等)可以加快

bull 這個概念發展出了引線二元樹 (threaded binary tree) 這種資料結構

範例 5-17下圖為加了引線的二元樹

加入的引線將使二元樹的中序走訪更加便利沿著上右圖箭頭所指示的順序走訪即可完成此二元樹的中序走訪GDHBAIECF 為此二元樹的中序表示

bull 範例 5-17 中所加的引線目的在方便二元樹的中序走訪

bull 對樹葉節點 p 而言令其資料為 P 則引線指標的規則如下

(1) p 的左子樹指標指向中序表示中 P 的前一個 元素(前接元素 (predecessor) )

(2) p 的右子樹指標指向中序表示中 P 的後一個 元素(後繼元素 (successor) )

為了區別節點指標放的是一般指標(內部節點)抑或是引線指標(樹葉節點)我們另以 2 個欄位分別區別左和右子樹指標空間存放的對象其節點的記憶體配置有如下圖所示

其中 leftthread (rightthread) 為 0 時表示 leftchild (rightchild) 存放的是一般節點指標而 leftthread (rightthread) 為 1 時表示 leftchild (rightchild) 存放的是引線指標1 struct TBTreeNode2 int leftthread3 struct TBTreeNode leftchild4 char data5 struct TBTreeNode rightchild6 int rightthread7

由右側的引線二元樹可知有最左和最右兩條引線尚無妥善的安排我們另設計一空的引線節點做為樹根如下圖所示

571  引線二元樹的中序走訪決定節點 p 在中序表示中的後繼節點 q (簡稱中序後繼點)

程式 5-10決定節點 node的中序後繼點1 struct TBTreeNode2 int leftthread3 struct TBTreeNode leftchild4 char data5 struct TBTreeNode rightchild6 int rightthread7 8 struct TBTreeNode root9 struct TBTreeNode Next(struct TBTreeNode node)

10 struct TBTreeNode temp11 temp = node-gtrightchild12 if (node-gtrightthead) return temp13 while (temp-gtleftthead) temp=temp-gtleftchild

14 return temp15

程式 5-11引線二元樹的中序表示(接程式 5-10)1 void InorderTBTree()2 struct TBTreeNode node3 for(node=Next(root)node=rootnode=Next(node))

4 cout ltlt node-gtdata5

bull只要 node 是引線二元樹上的任一節點透過呼叫 Next(node) 即可找到其在中序表示中的後繼節點bull(試想若程式 5-10 中的程序 Next 所傳入的參數指標正是 root Next(root) 是否可正常運作請思考)bull於是只須從中序表示中的第一節點起讓每一節點 node 都執行 Next(node) 程序即可得引線二元樹的中序表示

572  在引線二元樹中加入節點在引線二元樹中樹葉節點節點的右子樹指標所指向或內部節點右子樹的最左樹葉為其中序後繼節點而且任一節點若無左子樹則其左子樹指標乃指向其中序前接節點hellip這些性質必須在節點新增進入引線二元樹時也要保持接下來討論節點 new 插入成為引線二元樹的節點 p 的右子節點的情形至於插入成為左子節點的情形則讓各位模擬推敲

572572  在引線二元樹中加入節點 在引線二元樹中加入節點 (( 續續 ))

newnew 會成為會成為 pp 的右子節點的情況共有以下兩種的右子節點的情況共有以下兩種(1) (1) 若若 pp 沒有右子節點 沒有右子節點 ((p-gtrightthread p-gtrightthread 為為1) 1) 如圖如圖 ((a) a) 則 則 new new 插入後應形成如圖插入後應形成如圖 ((b) b) 所示所示

(2)(2) 若若 pp 有右子節點 有右子節點 ((p-gtrightthread p-gtrightthread 為為0) 0) 如圖如圖 ((c) c) 則 則 new new 插入後應形成如插入後應形成如圖圖 ((d) d) 所示所示

程式 5-12  插入節點 new 成為引線二元樹中節點 p的右子節點(上接程式 5-10 5-11 )

1 void InsertRight(struct TBTreeNode p 2 struct TBTreeNode new)3 struct TBTreeNode q4 new-gtrightchild = p-gtrightchild5 new-gtrightthead = p-gtrightthread6 new-gtleftchild = p7 new-gtleftthread = 18 p-gtrightchild = new9 p-gtrightthead = 010 if (new-gtrightthread)11 q = Next(new)12 q-gtleftchild = new13 14

58  堆積bull 「堆積」 (heap) 是一個特殊的完備二元樹節點的資料與其子節點的資料之間有事先定好的大小關係存在bull 定義ndash最大堆疊為一完備二元樹任一節點的資料內容不小於其子節點的資料內容ndash最小堆疊為一完備二元樹任一節點的資料內容不大於其子節點的資料內容

581  插入節點至最大堆積中最大堆積是一完備二元樹所以如圖 (a) 之最大堆積於加入新節點後樹的形狀必須如圖 (b) 所示灰色節點即為新節點若加入的節點資料分別為 1 9 12 則其最大堆積將分別變成如圖 (c) (d) (e) 所示

程式 5-13 新增節點至最大堆積中最大堆積本身是一棵完備二元樹可以用陣列表示之 1 define maxsize 1002 int heap[maxsize]3 int n i4 void InsertHeap(int x)5 if (n == maxsize) HeapFull()6 else7 n++8 i = n9 while ((i gt 1) ampamp (x gt heap[i2]))10 heap[i] = heap[i2]11 i = 212 13 heap[i] = x14 16

程序 InsertHeap 在插入第 n 個資料時需要的時間複雜度為 O(logn)

1 100

2 93

3 84

4 77

5 65

6 81

7 79

8 66

9 70

10 50

100

84

77

93

8165

66 70

79

50

1

2 3

4 5 6 7

8 9 10

1 define maxsize 1002 int heap[maxsize]3 int n i4 void InsertHeap(int x)5 if (n == maxsize) HeapFull()6 else7 n++8 i = n9 while ((i gt 1) ampamp (x gt heap[i2]))10 heap[i] = heap[i2]11 i = 212 13 heap[i] = x14 16

582 刪除最大堆積中的最大資料圖 (a) 中的最大堆積在刪除最大資料後其樹形應變成如圖 (b) 所示將最未一個樹葉節點的資料 6 暫挪至樹根節點內如圖 (c) 再將圖 (c) 中非最大堆積的結構調整成為最大堆積 10 和 8 中選大者與 6對調即成圖 (d)

程式 5-14 刪除最大堆積中的最大資料(上接程式 5-13 )1 int DeleteHeap()2 int x i j3 if (n == 0) HeapEmpty()4 else5 x = heap[1]6 heap[1] = heap[n]7 n--8 i = 19 while (i lt= n2)10 if (heap[2i] gt heap[2i+1])11 j = 2i12 else13 j = 2i+114 if (heap[i]gtheap[j]) break15 swap(heap[i] heap[j])16 i = j17 18 return x19 20

其時間複雜度與其堆積高度相同是為O(logn) 其中 n 為堆積中的元素個數

1 100

2 93

3 84

4 77

5 65

6 81

7 79

8 66

9 70

10 50

100

84

77

93

8165

66 70

79

50

1

2 3

4 5 6 7

8 9 10

1 int DeletHeap()2 int x i j3 if(n == 0) HeapEmpty()4 else5 x = heap[1]6 heap[1] = heap[n]7 n--8 i = 19 while (i lt= n2)10 if ([heap[2i]] gt heap[2i+1])11 j = 2i12 else13 j = 2i+114 if (heap[i]gtheap[j]) break15 swap(heap[i] heap[j])16 i = j17 18 return x19 20

583  優先佇列bull 由於最大堆積(或最小堆積)的結構可提供最大元素(或最小元素)而其維護堆積的成本為 O(logn) n 為總元素個數

bull 堆積常被當成「優先佇列」 (priority queue) 使用mdash此時佇列被組織成完備二元樹若新加入佇列的資料有大小關係優先順序的需求時此優先佇列即以 O(logn) 的代價將新資料放入優先佇列(可能是最大或最小堆積依實際大小關係優先順序的需求而定)中mdash提供依元素的優先順序做為處理順序的服務

bull 一般的佇列為先進先出先到的先行處理優先佇列則是依據其優先順序優先順序高者先行處理與來到的先後無關

範例 5-21bull 在「分時」 (time sharing) 的作業系統中我們不希望使用中央處理器 (Central Processing Unit CPU) 時間短的「處理程序」 (process) 等候過久於是有「最短時間工作優先」 (shortest-job-first) 的排程策略此時等候的處理程序即可組織成之一最小堆積而 CPU 每次即取出存於樹根的處理程序予以處理任何新來的處理程序則以 O(logn) 的時間加入此優先佇列(最小堆積)中

bull 注意之前提過的線性佇列新來的直接加入成為新的尾端元素花O(1) 的時間然線性佇列本身沒有大小的關係每次要取出最大(小)的資料得花O(n) 的代價(在所有元素中挑出最大(小)者) n 為佇列內的元素個數而優先佇列是在新元素加入時要花O(logn) 的時間至於取出最大(小)則只須 O(1) 的時間

59  二元搜尋樹bull 二元搜尋樹是一棵二元樹可能是空二元樹若不為空二元樹則滿足下列性質ndash所有節點內的資料內容是相異的 ndash左子節點的資內容 ( 如果有 ) 比父節點的資料內容小 ndash右子節點的資料內容 ( 如果有 ) 比父節點的資料內容大 ndash以左和右子節點為樹根的左子樹和右子樹也是二元搜尋樹

在二元搜尋樹中搜尋資料二元搜尋樹對資料大小的掌握十分明確若恰與節點 p 資料相等則搜尋成功若欲搜尋的資料 x 比樹節點 p 中的資料小 則只須搜尋節點 p 的左子樹若比節點 p 中的資料大 則只須搜尋 p 的右子樹這樣的判斷可以遞迴地應用在各子樹節點 中

程式 5-15 在二元搜尋樹中搜尋資料(遞迴呼叫)1 struct BSTreeNode2 struct BSTreeNode leftchild3 int data4 struct BSTreeNode rightchild5 6 struct BSTreeNode root7 struct BSTreeNode SearchBST(

struct BSTreeNode tree int x)8 if (tree == NULL) return NULL9 if (x == tree-gtdata) return tree10 if (x lt tree-gtdata)11 return SearchBST(tree-gtleftchild x)

12 return SearchBST(tree-gtrightchild x)13

程式 5-16 搜尋二元搜尋樹的資料(非遞迴呼叫)在二元搜尋樹十分龐大時遞迴呼叫的效率不會太好可利用迴圈來完成搜尋的動作以提高執行效率請看程式 5-16

14 struct BSTreeNode Search BST_iterative15 (struct BSTreeNode node int x)16 while (node = NULL)17 if (x == node-gtdata) return node18 if (x lt node-gtdata)19 node = node-gtleftchild20 else21 node = node-gtrightchild22 23 return NULL24

592  新增資料於二元搜尋樹中欲新增資料進入一二元搜尋樹中得先決定該資料在二元搜尋樹中應出現的位置這可透過上節提到的搜尋動作來解決

在二元搜尋樹定義的性質 (1) 中我們限定了元素必須相異所以搜尋新資料的結果必是 NULL

必須將搜尋的結果改為新增資料所應在位置的節點指標

程式 5-17  在二元搜尋樹中新增資料25 int InsertBST(int x)26 struct BSTreeNode p q27 p = root q = NULL28 while (p = NULL)29 q = p30 if (x == p-gtdata) return 0

若不考慮重複則可省略之31 if (x lt p-gtdata)32 p = p-gtleftchild33 else34 p = p-gtrightchild35

3636 p = (struct BSTreeNode ) p = (struct BSTreeNode ) malloc (sizeof(BSTreeNode))malloc (sizeof(BSTreeNode))

3737 p-gtdata = x p-gtdata = x3838 p-gtleftchild = p-gtrightchild = p-gtleftchild = p-gtrightchild = NULLNULL3939 if (root == NULL) root = p if (root == NULL) root = p4040 else if (x lt q-gtdata) else if (x lt q-gtdata)4141 q-gtleftchild = p q-gtleftchild = p4242 else else4343 q-gtrightchild = p q-gtrightchild = p4444 return 1 return 145 45

程式 程式 5-175-17  在二元搜尋樹中新增資料  在二元搜尋樹中新增資料 (( 續續 ))

範例 5-23 圖 (b) 為圖 (a) 新增整數 6 之後的結果圖 (c) (d) (e) (f) 則分別是逐步再加入 28 18

10 21 的結果

bull若目前的二元搜尋樹高度為 h 則新增資料得花O(h) 的時間找到其所應在的位置遂新增資料至二元搜尋樹中的時間複雜之度為 O(h)

593 自二元搜尋樹中刪除資料欲自二元搜尋樹中刪除資料可能會面臨兩種情況(1) 刪除節點 p 位於樹葉節點上如下圖所示 這是比較容易處理的情況

(2) 刪除的節點 p 不是樹葉刪除節點 p 後原 p 所指的節點可由 q ( q 中包含了 p 左子樹中的最大節點值)或 r ( r 中包含了 p 右子樹中的最小節點值)來取代以維持二元搜尋樹中資料的大小關係其結果分別如圖 (b) 和 (c) 所示而圖 (d) 則標明了指標會改變的節點位置即 p 會消失 father 的左或右子樹指標會異動 k(可能是 q 或 r )會移到原來 p 的位置其父節點 f 的左或右子樹指標會異動其中 father 為 p 父節點 f 為 k 的父節點

程式 5-18 自二元搜尋樹中刪除資料46 int DeleteBST (int x)47 Struct BSTreeNode p father k f48 p = root father = NULL49 while (p = NULL)50 if (x == p-gtdata) 找到 x 所在的節點 p 了51 if ((p-gtleftchild == NULL) ampamp 52 (p-gtrightchild == NULL))53 k = NULL p為樹葉節點54 else if (p-gtleftchild = NULL )x位於非子節

點55 f = p 找出 p左子樹的最右樹葉56 k = p-gtleftchild57 while (k-gtrightchild =NULL)58 f = k59 k = k-gtrightchild60 k將挪至原 p處61 if (p == f)62 f-gtleftchild = k-gtleftchild 63 else64 f-gtrightchild = k-gtleftchild 65

66 else p無左子樹67 f = p 找出 p右子樹的最左樹葉68 k = p-gtrightchild69 while (k-gtleftchild = NULL)70 f = k71 k = k -gtleftchild72 73 if (p == f)74 f-gtrightchild = k-gtrightchild 75 else76 f-gtleftchild = k-gtrightchild77 78 if (k = NULL) k挪至原 p處繼承 p的左右指標79 k-gtleftchild = p-gtleftchild80 k-gtrightchild = p-gtrightchild81 82 if (father == NULL) root = k83 else if (x lt father-gtdata) 決定 k是 father的左

或右兒子84 father-gtleftchild = k85 else86 father-gtrightchild = k87 free(p)88 return 1 成功地刪除 x於此傳回 1返回呼叫處89 51~89行為找到 x時應有的處理步驟

90 else 尚未找到 x繼續往下一階層找

91 father = p92 if (x lt p-gtdata)93 p = p-gtleftchild94 else95 p = p-gtrightchild96 97 98 return 0 未找到 x傳回 099

510  樹林 (forest)

bull 定義樹林(森林)是由 n 棵沒有交集的樹所構成的集合 n 0

下圖為一包含三棵樹的樹林

5101 轉換樹林為二元樹 之前我們以二元樹來表示樹因為二元樹有較多

好用的性質在此我們也將樹林轉換成二元樹看待

在 522 節中我們嘗試了用左子右兄弟的方式來表示樹這個方法事實上已將樹轉換成二元樹

即用左子右兄弟的結構重新組織樹林中的每一棵樹這些樹的樹根將視為兄弟而最左邊的樹則視為樹林二元樹的樹根如此一來樹林即可轉換成二元樹

範例 5-25右下圖為右上圖三棵樹所對應的三棵左子右兄弟二元樹

下圖則為上圖樹林所對應的一棵樹林二元樹

演算法 5-1  轉換樹林成二元樹輸入樹林 F = T1 T2 Tn

輸出 F 的二元樹表示 BT=(T1 T2 Tn)1 if (n == 0) 輸出空二元樹 2 else

3 root(T1 T2 Tn) = T1

4 leftsubtree(T1 T2 Tn) =

BT(T11 T12 T1m)5 BT(T11 T12 T1m)為 T1的子樹6 rightsubtree(T1 T2 Tn) =

BT(T2 T3 Tn)

樹林轉換成二元樹的過程樹林轉換成二元樹的過程

  • Slide 1
  • Slide 2
  • Slide 3
  • Slide 4
  • Slide 5
  • Slide 6
  • Slide 7
  • Slide 8
  • Slide 9
  • Slide 10
  • Slide 11
  • Slide 12
  • Slide 13
  • Slide 14
  • Slide 15
  • Slide 16
  • Slide 17
  • Slide 18
  • Slide 19
  • Slide 20
  • Slide 21
  • Slide 22
  • Slide 23
  • Slide 24
  • Slide 25
  • Slide 26
  • Slide 27
  • Slide 28
  • Slide 29
  • Slide 30
  • Slide 31
  • Slide 32
  • Slide 33
  • Slide 34
  • Slide 35
  • Slide 36
  • Slide 37
  • Slide 38
  • Slide 39
  • Slide 40
  • Slide 41
  • Slide 42
  • Slide 43
  • Slide 44
  • Slide 45
  • Slide 46
  • Slide 47
  • Slide 48
  • Slide 49
  • Slide 50
  • Slide 51
  • Slide 52
  • Slide 53
  • Slide 54
  • Slide 55
  • Slide 56
  • Slide 57
  • Slide 58
  • Slide 59
  • Slide 60
  • Slide 61
  • Slide 62
  • Slide 63
  • Slide 64
  • Slide 65
  • Slide 66
  • Slide 67
  • Slide 68
  • Slide 69
  • Slide 70
  • Slide 71
  • Slide 72
  • Slide 73
  • Slide 74
  • Slide 75
  • Slide 76
  • Slide 77
  • Slide 78
  • Slide 79
  • Slide 80
  • Slide 81
  • Slide 82
  • Slide 83
  • Slide 84
  • Slide 85
  • Slide 86
  • Slide 87
  • Slide 88
  • Slide 89
  • Slide 90
  • Slide 91
  • Slide 92
  • Slide 93
  • Slide 94
  • Slide 95
  • Slide 96
  • Slide 97
  • Slide 98
  • Slide 99
  • Slide 100
  • Slide 101
  • Slide 102
  • Slide 103
Page 30: 第五章 樹

陣列表示和鍵結表示的空間需求bull 完備二元樹的陣列表示和鍵結表示皆沒浪費儲存資料的空間

bull 但歪斜樹的陣列表示比起鍵結表示則浪費許多

bull 以 n 個節點的歪斜樹而言陣列表示法可能需要 2n-1 個陣列元素其中 2n-1-n 個陣列元素空間是浪費的而鍵結表示法只需要 n 個節點空間

二元樹節點的宣告程式 5-2

1 struct BTreeNode

2 struct BtreeNode leftchiled

3 int data

4 struct BTreeNode rightchild

5

6 struct BTreeNode root

bull 這樣的二元樹節點要取得兒子節點的指標十分方便但若有取得父節點的需求則嫌不足我們可以加上 parent 欄位指向父節點

55  二元樹的走訪bull 假如一組資料已用二元樹的組織在一起總有需求對其全數資料做動作例如計算所有數目印出所有資料在所有資料中搜尋某項資料hellip等此時即須對此二元樹做走訪 (traversal) 的運算利用走訪二元樹的同時將計算列印或搜尋的動作完成

bull 事實上走訪即在決定二元樹上資料被處理(計算列印或搜尋)的順序我們也希望走訪的演算法對任何節點皆一致是容易撰寫程式實作的

bull 若對一個二元樹上的節點而言 V 表示處理節點上的資料 L 表示走訪其左子樹 R 表示走訪其右子樹

55  二元樹的走訪 ( 續 )bull 因為對稱的緣故我們只考慮先走訪左邊再走訪右邊的情形那麼右圖只剩下三種走訪方式

bull 以 V 所在的相對位置分別對此三種走訪方式取名如下

(1) LVR 中序走訪 (inorder traversal)或中序表示法 (infix notation)

(2) LRV 後序走訪 (postorder traversal) 或後序表示法 (postfix notation)

(3) VLR 前序走訪 (preorder traversal)或前序表示法 (prefix notation)

551  中序走訪bull 利用遞迴的方式撰寫中序走訪的程序希望把二元樹中序走訪的

順序印出來

程式 5-3二元樹的中序走訪1 struct BTreeNode2 struct BTreeNode leftchild3 char data4 struct BTreeNode rightchild5 6 struct BTreeNode root7 void inorder(struct BTreeNode node)8 if (node = NULL)9 inorder(node-gtleftchild)10 cout ltlt node-gtdata11 inorder(node-gtrightchild)12 13

範例 5-12此為運算式二元樹

其對應中序表示運算式為 (A+B)C+D(E-F)

6 struct BTreeNode root7 void inorder(struct BTreeNode node)8 if (node = NULL)9 inorder(node-gtleftchild)10 cout ltlt node-gtdata11 inorder(node-gtrightchild)12 13

6 struct BTreeNode root7 void inorder(struct BTreeNode node)8 if (node = NULL)9 inorder(node-gtleftchild)10 cout ltlt node-gtdata11 inorder(node-gtrightchild)12 13

(A+B)C+D(E-F)

如何加上 ( )

552  後序走訪程式 5-4二元樹的後序走訪14 void postorder(struct BTreeNode node)

15 if (node =NULL)16 postorder(node-gtleftchild)17 postorder(node-gtrightchild)18 cout ltlt node-gtdata19 20

以後序走訪可得到AB+CDEF-+

其正為對應的後序運算式

553  前序走訪程式 5-5二元樹的前序走訪21 void preorder(struct BTreeNode node)

22 if (node = NULL)

23 cout ltlt node-gtdata

24 preorder(node-gtleftchild)

25 preorder(node-gtrightchild)

26

27

以前序走訪可得到++ABCD-EF

其正為對應的前序運算式

554 利用堆疊和迴圈取代遞迴進行二元樹的走訪1 struct BTreeNode 2 struct BTreeNode leftchild3 char data4 struct BtreeNode rightchild5 6 struct BTreeNode root7 struct StackNode8 struct BTreeNode treenode9 struct StackNode next1011 struct StackNode top

12 void push_node(struct BtreeNode node )13 struct StackNode old_top14 old_top = top15 top = (struct StackNode ) malloc

(sizeof(struct StackNode))16 top-gttreenode = node17 top-gtnext = old_top18 19 struct BTreeNode pop_node()20 struct BTreeNode Tnode21 struct StackNode old_top22 if (top == NULL)23 StackEmpty()24 else25 Tnode = top-gttreenode26 old_top = top27 top = top-gtnext28 free(old_top)29 return Tnode30 31

32 void inorder_Stack(struct BtreeNode node)33 do34 while (node = NULL)35 push_node(node)36 node = node-gtleftchild37 push all left children38 if (top = NULL)39 node = pop_node()40 cout ltlt node-gtdata41 node = node-gtrightchild42 inorder printing and check right child43 while ((top=NULL)||(node=NULL))44 top==NULL 堆疊已空 node==NULL沒有右子

6 struct BTreeNode root7 void inorder(struct BTreeNode node)8 if (node = NULL)9 inorder(node-gtleftchild)10 cout ltlt node-gtdata11 inorder(node-gtrightchild)12 13

中序表示為DBGEHAFIC

32 void inorder_Stack()33 struct BTreeNode node34 node = root35 do36 while (node = NULL)37 push_node(node)38 node = node-gtleftchild39 40 if (top = NULL)41 node = pop_node()42 cout ltlt node-gtdata43 node = node-gtrightchild44 45 while(top=NULL)||(node=NULL))46

45 void Preorder_Stack(struct BSTreeNode node)46 do47 while (node = NULL)48 cout ltlt node-gtdata)49 push_node(node)50 node = node-gtleftchild51 push all left children52 if (top = NULL)53 node = pop_node()54 node = node-gtrightchild55 56 while ((top=NULL)||(node=NULL))57

程式 5-5二元樹的前序走訪21 void preorder(struct BTreeNode node)22 if (node = NULL)23 cout ltlt node-gtdata24 preorder(node-gtleftchild)25 preorder(node-gtrightchild)26 27

58 void Postorder_Stack(struct BTreeNode node)59 do60 while (node = NULL)61 cout ltlt node-gtdata62 push_node(node)63 node = node-gtrightchild64 push all left children65 if (top = NULL)66 node = pop_node()67 node = node-gtleftchild68 69 while ((top=NULL)||(node=NULL))70

程式 5-4二元樹的後序走訪14 void postorder(struct BTreeNode node)15 if (node =NULL)16 postorder(node-gtleftchild)17 postorder(node-gtrightchild)18 cout ltlt node-gtdata19 20

555  階層走訪bull 「階層走訪」 (level-order traversal) 是依階層

的順序進行二元樹的走訪先走訪階層小的節點後走訪階層大的節點同一階層者則依自左向右的順序走訪

bull 對下圖的二元樹進行階層走訪的結果為ABCDEFGHI

bull 對同一階層而言先走訪的節點其子節點亦在下一階層中先被走訪

bull 這種「先進先出」的特性恰可用佇列予以儲存與處理

程式 5-7 利用佇列進行二元樹的階層走訪

1 struct BTreeNode2 struct BTreeNode leftchild3 char data4 struct BTreeNode rightchild5 6 struct BTreeNode root7 struct QNode8 struct BTreeNode treenode9 struct QNode next10 11 struct QNode front rear

12 void AddQueue(struct BTreeNode Tnode)13 struct QNode node14 node=(struct QNode )malloc(sizeof(struct QNode))15 node-gttreenode = Tnode16 node-gtnext = NULL17 if (front == NULL) front = node19 else rear-gtnext = node20 rear = node21

22 struct BTreeNode DeleteQueue()23 struct BTreeNode Tnode24 struct QNode old_front25 if (rear == NULL)26 QueueEmpty()27 else28 Tnode = front-gttreenode29 old_front = front30 front = front-gtnext31 free(old_front)32 return Tnode33 34 35 void LevelOrder (struct BTreeNode node)36 AddQueue(node)37 while (front =NULL)38 node = DeleteQueue()39 cout ltlt node-gtdata40 if (node-gtleftchild = NULL)41 AddQueue(node-gtleftchild)42 if (node-gtrightchild = NULL)43 AddQueue(node-gtrightchild)44 45

35 void LevelOrder (struct BTreeNode node)36 AddQueue(node)37 while (front =NULL)38 node = DeleteQueue()39 cout ltlt node-gtdata40 if (node-gtleftchild = NULL)41 AddQueue(node-gtleftchild)42 if (node-gtrightchild = NULL)43 AddQueue(node-gtrightchild)44 45

bull Infix ABCbull Postfix ABC CBA ACB BCA

BAC CAB

B

A CA

B

C

B

A

C

C

B

AB

A

C

二元樹中序與前序式中序與後序式bull 每一棵二元樹皆有唯一的一對中序與前序表示法也有唯一的中序與後序次序

bull 換句話說給予一對中序與前序或中序與後序即可決定一棵二元樹

bull 然而給予前序和後序次序並不能決定唯一的二元樹可能會產生兩棵不同的二元樹

bull 想想階層走訪和中序走訪順序可否決定唯一的二元樹

A

B C

H J D E

F G

prefix ABHJCDFGEinfix HBJAFDGCEpostfix HJBFGDECA

VLRLVRLRV

A

prefix BHJinfix HBJ

prefix CDFGEinfix FDGCE

prefix ABHJCDFGEinfix HBJAFDGCE

A

B C

H J D E

F G

prefix ABHJCDFGE

infix HBJAFDGCEk-1

struct BTreeNode BTree_InfixPrefix (AnsiString infix AnsiString prefix)

int k struct BTreeNode node if (infixLength==0) return NULLnode = (struct BTreeNode ) malloc (sizeof(struct BTreeNode))node-gtdata = prefix[1]k = find(prefix[1] infix)node-gtleftchild = BTree_InfixPrefix

(infix[1k-1] prefix[2k])node-gtrightchild = BTree_InfixPrefix(infix[k+1infixLength]prefix[k+1prefixLength])

VLR

LVRk1

k2 k+1~

k+1~

1

struct BTreeNode BTree_InfixPostfix (AnsiString infix AnsiString prefix)

int k struct BTreeNode node if (infixLength==0) return NULLnode = (struct BTreeNode ) malloc (sizeof(struct BTreeNode))node-gtdata = postfix[postfixLength]k = find(postfix[postfixLength] infix)node-gtleftchild = BTree_InfixPrefix

(infix[1k-1] postfix[1k-1])node-gtrightchild = BTree_InfixPrefix(infix[k+1infixLength]postfix[k+1postfixLength-1])

56  二元樹的複製與相等測試程式 5-8 二元樹的複製1 struct BTreeNode2 struct BTreeNode leftchild3 char data4 struct BTreeNode rightchild5 6 struct BTreeNode root7 struct BTreeNode Copy(struct BTreeNode TreeRoot)8 struct BTreeNode CopyRoot9 if (TreeRoot == NULL)10 return NULL11 CopyRoot =(struct BTreeNode ) malloc (sizeof(BTreeNode))12 CopyRoot-gtdata = TreeRoot-gtdata13 CopyRoot-gtleftchild = Copy(TreeRoot-gtleftchild)14 CopyRoot-gtrightchild = Copy(TreeRoot-

gtrightchild)15 return CopyRoot16

程式 5-9 檢測兩棵二元樹是否相等1 struct BTreeNode2 struct BTreeNode leftchild3 char data4 struct BTreeNode rightchild5 6 struct BtreeNode rightchild7 int Equal(struct BTreeNode ustruct BTreeNode v)8 if ((u==NULL) ampamp (v==NULL)) return 19 if ((u=NULL) ampamp (v=NULL)10 ampamp (u-gtdata == v-gtdata )11 ampamp Equal(u-gtleftchildv-gtleftchild)12 ampamp Equal(u-gtrightchildv-gtrightchild))

13 return 114 return 015

Counting Nodes in a Binary Tree

int CountingNodes(struct BTreeNodes node)

if (node == NULL) return 0 int leftchildren = CountingNodes(node-gtleftchild)

int rightchildren= CountingNodes(node-gtrightchild)

return leftchildren+rightchildren

Counting Leaves in a Binary Tree

int CountingLeaves(struct BTreeNodes node)

if (node == NULL) return 0 int leftleaves = CountingLeaves(node-gtleftchild)

int rightchildren= CountingLeaves(node-gtrightchild)

if (leftleaves==0 ampamp leftleaves==0) return 1

return leftleaves + leftleaves

57  引線二元樹bull 在二元樹的鍵結表示中樹葉節點的兩個子樹指標皆指向 NULL 由定理 5-1 得知 n = E+1 其中 n 為節點個數 E 為分支數

bull 而 n 個鍵結節點有 2n 個指標空間每個分支恰佔用一個指標空間共有 E (=n-1) 個指標有用到(不是空的)而共有 n+1 個指標空間是 NULL 比非空的節點還多

bull 有學者提出對這些「放空指標的空間」加以利用的概念mdash與其放空指標不如放指向其它節點的指標稱之為引線 (thread) 使得某些運算(如走訪hellip等)可以加快

bull 這個概念發展出了引線二元樹 (threaded binary tree) 這種資料結構

範例 5-17下圖為加了引線的二元樹

加入的引線將使二元樹的中序走訪更加便利沿著上右圖箭頭所指示的順序走訪即可完成此二元樹的中序走訪GDHBAIECF 為此二元樹的中序表示

bull 範例 5-17 中所加的引線目的在方便二元樹的中序走訪

bull 對樹葉節點 p 而言令其資料為 P 則引線指標的規則如下

(1) p 的左子樹指標指向中序表示中 P 的前一個 元素(前接元素 (predecessor) )

(2) p 的右子樹指標指向中序表示中 P 的後一個 元素(後繼元素 (successor) )

為了區別節點指標放的是一般指標(內部節點)抑或是引線指標(樹葉節點)我們另以 2 個欄位分別區別左和右子樹指標空間存放的對象其節點的記憶體配置有如下圖所示

其中 leftthread (rightthread) 為 0 時表示 leftchild (rightchild) 存放的是一般節點指標而 leftthread (rightthread) 為 1 時表示 leftchild (rightchild) 存放的是引線指標1 struct TBTreeNode2 int leftthread3 struct TBTreeNode leftchild4 char data5 struct TBTreeNode rightchild6 int rightthread7

由右側的引線二元樹可知有最左和最右兩條引線尚無妥善的安排我們另設計一空的引線節點做為樹根如下圖所示

571  引線二元樹的中序走訪決定節點 p 在中序表示中的後繼節點 q (簡稱中序後繼點)

程式 5-10決定節點 node的中序後繼點1 struct TBTreeNode2 int leftthread3 struct TBTreeNode leftchild4 char data5 struct TBTreeNode rightchild6 int rightthread7 8 struct TBTreeNode root9 struct TBTreeNode Next(struct TBTreeNode node)

10 struct TBTreeNode temp11 temp = node-gtrightchild12 if (node-gtrightthead) return temp13 while (temp-gtleftthead) temp=temp-gtleftchild

14 return temp15

程式 5-11引線二元樹的中序表示(接程式 5-10)1 void InorderTBTree()2 struct TBTreeNode node3 for(node=Next(root)node=rootnode=Next(node))

4 cout ltlt node-gtdata5

bull只要 node 是引線二元樹上的任一節點透過呼叫 Next(node) 即可找到其在中序表示中的後繼節點bull(試想若程式 5-10 中的程序 Next 所傳入的參數指標正是 root Next(root) 是否可正常運作請思考)bull於是只須從中序表示中的第一節點起讓每一節點 node 都執行 Next(node) 程序即可得引線二元樹的中序表示

572  在引線二元樹中加入節點在引線二元樹中樹葉節點節點的右子樹指標所指向或內部節點右子樹的最左樹葉為其中序後繼節點而且任一節點若無左子樹則其左子樹指標乃指向其中序前接節點hellip這些性質必須在節點新增進入引線二元樹時也要保持接下來討論節點 new 插入成為引線二元樹的節點 p 的右子節點的情形至於插入成為左子節點的情形則讓各位模擬推敲

572572  在引線二元樹中加入節點 在引線二元樹中加入節點 (( 續續 ))

newnew 會成為會成為 pp 的右子節點的情況共有以下兩種的右子節點的情況共有以下兩種(1) (1) 若若 pp 沒有右子節點 沒有右子節點 ((p-gtrightthread p-gtrightthread 為為1) 1) 如圖如圖 ((a) a) 則 則 new new 插入後應形成如圖插入後應形成如圖 ((b) b) 所示所示

(2)(2) 若若 pp 有右子節點 有右子節點 ((p-gtrightthread p-gtrightthread 為為0) 0) 如圖如圖 ((c) c) 則 則 new new 插入後應形成如插入後應形成如圖圖 ((d) d) 所示所示

程式 5-12  插入節點 new 成為引線二元樹中節點 p的右子節點(上接程式 5-10 5-11 )

1 void InsertRight(struct TBTreeNode p 2 struct TBTreeNode new)3 struct TBTreeNode q4 new-gtrightchild = p-gtrightchild5 new-gtrightthead = p-gtrightthread6 new-gtleftchild = p7 new-gtleftthread = 18 p-gtrightchild = new9 p-gtrightthead = 010 if (new-gtrightthread)11 q = Next(new)12 q-gtleftchild = new13 14

58  堆積bull 「堆積」 (heap) 是一個特殊的完備二元樹節點的資料與其子節點的資料之間有事先定好的大小關係存在bull 定義ndash最大堆疊為一完備二元樹任一節點的資料內容不小於其子節點的資料內容ndash最小堆疊為一完備二元樹任一節點的資料內容不大於其子節點的資料內容

581  插入節點至最大堆積中最大堆積是一完備二元樹所以如圖 (a) 之最大堆積於加入新節點後樹的形狀必須如圖 (b) 所示灰色節點即為新節點若加入的節點資料分別為 1 9 12 則其最大堆積將分別變成如圖 (c) (d) (e) 所示

程式 5-13 新增節點至最大堆積中最大堆積本身是一棵完備二元樹可以用陣列表示之 1 define maxsize 1002 int heap[maxsize]3 int n i4 void InsertHeap(int x)5 if (n == maxsize) HeapFull()6 else7 n++8 i = n9 while ((i gt 1) ampamp (x gt heap[i2]))10 heap[i] = heap[i2]11 i = 212 13 heap[i] = x14 16

程序 InsertHeap 在插入第 n 個資料時需要的時間複雜度為 O(logn)

1 100

2 93

3 84

4 77

5 65

6 81

7 79

8 66

9 70

10 50

100

84

77

93

8165

66 70

79

50

1

2 3

4 5 6 7

8 9 10

1 define maxsize 1002 int heap[maxsize]3 int n i4 void InsertHeap(int x)5 if (n == maxsize) HeapFull()6 else7 n++8 i = n9 while ((i gt 1) ampamp (x gt heap[i2]))10 heap[i] = heap[i2]11 i = 212 13 heap[i] = x14 16

582 刪除最大堆積中的最大資料圖 (a) 中的最大堆積在刪除最大資料後其樹形應變成如圖 (b) 所示將最未一個樹葉節點的資料 6 暫挪至樹根節點內如圖 (c) 再將圖 (c) 中非最大堆積的結構調整成為最大堆積 10 和 8 中選大者與 6對調即成圖 (d)

程式 5-14 刪除最大堆積中的最大資料(上接程式 5-13 )1 int DeleteHeap()2 int x i j3 if (n == 0) HeapEmpty()4 else5 x = heap[1]6 heap[1] = heap[n]7 n--8 i = 19 while (i lt= n2)10 if (heap[2i] gt heap[2i+1])11 j = 2i12 else13 j = 2i+114 if (heap[i]gtheap[j]) break15 swap(heap[i] heap[j])16 i = j17 18 return x19 20

其時間複雜度與其堆積高度相同是為O(logn) 其中 n 為堆積中的元素個數

1 100

2 93

3 84

4 77

5 65

6 81

7 79

8 66

9 70

10 50

100

84

77

93

8165

66 70

79

50

1

2 3

4 5 6 7

8 9 10

1 int DeletHeap()2 int x i j3 if(n == 0) HeapEmpty()4 else5 x = heap[1]6 heap[1] = heap[n]7 n--8 i = 19 while (i lt= n2)10 if ([heap[2i]] gt heap[2i+1])11 j = 2i12 else13 j = 2i+114 if (heap[i]gtheap[j]) break15 swap(heap[i] heap[j])16 i = j17 18 return x19 20

583  優先佇列bull 由於最大堆積(或最小堆積)的結構可提供最大元素(或最小元素)而其維護堆積的成本為 O(logn) n 為總元素個數

bull 堆積常被當成「優先佇列」 (priority queue) 使用mdash此時佇列被組織成完備二元樹若新加入佇列的資料有大小關係優先順序的需求時此優先佇列即以 O(logn) 的代價將新資料放入優先佇列(可能是最大或最小堆積依實際大小關係優先順序的需求而定)中mdash提供依元素的優先順序做為處理順序的服務

bull 一般的佇列為先進先出先到的先行處理優先佇列則是依據其優先順序優先順序高者先行處理與來到的先後無關

範例 5-21bull 在「分時」 (time sharing) 的作業系統中我們不希望使用中央處理器 (Central Processing Unit CPU) 時間短的「處理程序」 (process) 等候過久於是有「最短時間工作優先」 (shortest-job-first) 的排程策略此時等候的處理程序即可組織成之一最小堆積而 CPU 每次即取出存於樹根的處理程序予以處理任何新來的處理程序則以 O(logn) 的時間加入此優先佇列(最小堆積)中

bull 注意之前提過的線性佇列新來的直接加入成為新的尾端元素花O(1) 的時間然線性佇列本身沒有大小的關係每次要取出最大(小)的資料得花O(n) 的代價(在所有元素中挑出最大(小)者) n 為佇列內的元素個數而優先佇列是在新元素加入時要花O(logn) 的時間至於取出最大(小)則只須 O(1) 的時間

59  二元搜尋樹bull 二元搜尋樹是一棵二元樹可能是空二元樹若不為空二元樹則滿足下列性質ndash所有節點內的資料內容是相異的 ndash左子節點的資內容 ( 如果有 ) 比父節點的資料內容小 ndash右子節點的資料內容 ( 如果有 ) 比父節點的資料內容大 ndash以左和右子節點為樹根的左子樹和右子樹也是二元搜尋樹

在二元搜尋樹中搜尋資料二元搜尋樹對資料大小的掌握十分明確若恰與節點 p 資料相等則搜尋成功若欲搜尋的資料 x 比樹節點 p 中的資料小 則只須搜尋節點 p 的左子樹若比節點 p 中的資料大 則只須搜尋 p 的右子樹這樣的判斷可以遞迴地應用在各子樹節點 中

程式 5-15 在二元搜尋樹中搜尋資料(遞迴呼叫)1 struct BSTreeNode2 struct BSTreeNode leftchild3 int data4 struct BSTreeNode rightchild5 6 struct BSTreeNode root7 struct BSTreeNode SearchBST(

struct BSTreeNode tree int x)8 if (tree == NULL) return NULL9 if (x == tree-gtdata) return tree10 if (x lt tree-gtdata)11 return SearchBST(tree-gtleftchild x)

12 return SearchBST(tree-gtrightchild x)13

程式 5-16 搜尋二元搜尋樹的資料(非遞迴呼叫)在二元搜尋樹十分龐大時遞迴呼叫的效率不會太好可利用迴圈來完成搜尋的動作以提高執行效率請看程式 5-16

14 struct BSTreeNode Search BST_iterative15 (struct BSTreeNode node int x)16 while (node = NULL)17 if (x == node-gtdata) return node18 if (x lt node-gtdata)19 node = node-gtleftchild20 else21 node = node-gtrightchild22 23 return NULL24

592  新增資料於二元搜尋樹中欲新增資料進入一二元搜尋樹中得先決定該資料在二元搜尋樹中應出現的位置這可透過上節提到的搜尋動作來解決

在二元搜尋樹定義的性質 (1) 中我們限定了元素必須相異所以搜尋新資料的結果必是 NULL

必須將搜尋的結果改為新增資料所應在位置的節點指標

程式 5-17  在二元搜尋樹中新增資料25 int InsertBST(int x)26 struct BSTreeNode p q27 p = root q = NULL28 while (p = NULL)29 q = p30 if (x == p-gtdata) return 0

若不考慮重複則可省略之31 if (x lt p-gtdata)32 p = p-gtleftchild33 else34 p = p-gtrightchild35

3636 p = (struct BSTreeNode ) p = (struct BSTreeNode ) malloc (sizeof(BSTreeNode))malloc (sizeof(BSTreeNode))

3737 p-gtdata = x p-gtdata = x3838 p-gtleftchild = p-gtrightchild = p-gtleftchild = p-gtrightchild = NULLNULL3939 if (root == NULL) root = p if (root == NULL) root = p4040 else if (x lt q-gtdata) else if (x lt q-gtdata)4141 q-gtleftchild = p q-gtleftchild = p4242 else else4343 q-gtrightchild = p q-gtrightchild = p4444 return 1 return 145 45

程式 程式 5-175-17  在二元搜尋樹中新增資料  在二元搜尋樹中新增資料 (( 續續 ))

範例 5-23 圖 (b) 為圖 (a) 新增整數 6 之後的結果圖 (c) (d) (e) (f) 則分別是逐步再加入 28 18

10 21 的結果

bull若目前的二元搜尋樹高度為 h 則新增資料得花O(h) 的時間找到其所應在的位置遂新增資料至二元搜尋樹中的時間複雜之度為 O(h)

593 自二元搜尋樹中刪除資料欲自二元搜尋樹中刪除資料可能會面臨兩種情況(1) 刪除節點 p 位於樹葉節點上如下圖所示 這是比較容易處理的情況

(2) 刪除的節點 p 不是樹葉刪除節點 p 後原 p 所指的節點可由 q ( q 中包含了 p 左子樹中的最大節點值)或 r ( r 中包含了 p 右子樹中的最小節點值)來取代以維持二元搜尋樹中資料的大小關係其結果分別如圖 (b) 和 (c) 所示而圖 (d) 則標明了指標會改變的節點位置即 p 會消失 father 的左或右子樹指標會異動 k(可能是 q 或 r )會移到原來 p 的位置其父節點 f 的左或右子樹指標會異動其中 father 為 p 父節點 f 為 k 的父節點

程式 5-18 自二元搜尋樹中刪除資料46 int DeleteBST (int x)47 Struct BSTreeNode p father k f48 p = root father = NULL49 while (p = NULL)50 if (x == p-gtdata) 找到 x 所在的節點 p 了51 if ((p-gtleftchild == NULL) ampamp 52 (p-gtrightchild == NULL))53 k = NULL p為樹葉節點54 else if (p-gtleftchild = NULL )x位於非子節

點55 f = p 找出 p左子樹的最右樹葉56 k = p-gtleftchild57 while (k-gtrightchild =NULL)58 f = k59 k = k-gtrightchild60 k將挪至原 p處61 if (p == f)62 f-gtleftchild = k-gtleftchild 63 else64 f-gtrightchild = k-gtleftchild 65

66 else p無左子樹67 f = p 找出 p右子樹的最左樹葉68 k = p-gtrightchild69 while (k-gtleftchild = NULL)70 f = k71 k = k -gtleftchild72 73 if (p == f)74 f-gtrightchild = k-gtrightchild 75 else76 f-gtleftchild = k-gtrightchild77 78 if (k = NULL) k挪至原 p處繼承 p的左右指標79 k-gtleftchild = p-gtleftchild80 k-gtrightchild = p-gtrightchild81 82 if (father == NULL) root = k83 else if (x lt father-gtdata) 決定 k是 father的左

或右兒子84 father-gtleftchild = k85 else86 father-gtrightchild = k87 free(p)88 return 1 成功地刪除 x於此傳回 1返回呼叫處89 51~89行為找到 x時應有的處理步驟

90 else 尚未找到 x繼續往下一階層找

91 father = p92 if (x lt p-gtdata)93 p = p-gtleftchild94 else95 p = p-gtrightchild96 97 98 return 0 未找到 x傳回 099

510  樹林 (forest)

bull 定義樹林(森林)是由 n 棵沒有交集的樹所構成的集合 n 0

下圖為一包含三棵樹的樹林

5101 轉換樹林為二元樹 之前我們以二元樹來表示樹因為二元樹有較多

好用的性質在此我們也將樹林轉換成二元樹看待

在 522 節中我們嘗試了用左子右兄弟的方式來表示樹這個方法事實上已將樹轉換成二元樹

即用左子右兄弟的結構重新組織樹林中的每一棵樹這些樹的樹根將視為兄弟而最左邊的樹則視為樹林二元樹的樹根如此一來樹林即可轉換成二元樹

範例 5-25右下圖為右上圖三棵樹所對應的三棵左子右兄弟二元樹

下圖則為上圖樹林所對應的一棵樹林二元樹

演算法 5-1  轉換樹林成二元樹輸入樹林 F = T1 T2 Tn

輸出 F 的二元樹表示 BT=(T1 T2 Tn)1 if (n == 0) 輸出空二元樹 2 else

3 root(T1 T2 Tn) = T1

4 leftsubtree(T1 T2 Tn) =

BT(T11 T12 T1m)5 BT(T11 T12 T1m)為 T1的子樹6 rightsubtree(T1 T2 Tn) =

BT(T2 T3 Tn)

樹林轉換成二元樹的過程樹林轉換成二元樹的過程

  • Slide 1
  • Slide 2
  • Slide 3
  • Slide 4
  • Slide 5
  • Slide 6
  • Slide 7
  • Slide 8
  • Slide 9
  • Slide 10
  • Slide 11
  • Slide 12
  • Slide 13
  • Slide 14
  • Slide 15
  • Slide 16
  • Slide 17
  • Slide 18
  • Slide 19
  • Slide 20
  • Slide 21
  • Slide 22
  • Slide 23
  • Slide 24
  • Slide 25
  • Slide 26
  • Slide 27
  • Slide 28
  • Slide 29
  • Slide 30
  • Slide 31
  • Slide 32
  • Slide 33
  • Slide 34
  • Slide 35
  • Slide 36
  • Slide 37
  • Slide 38
  • Slide 39
  • Slide 40
  • Slide 41
  • Slide 42
  • Slide 43
  • Slide 44
  • Slide 45
  • Slide 46
  • Slide 47
  • Slide 48
  • Slide 49
  • Slide 50
  • Slide 51
  • Slide 52
  • Slide 53
  • Slide 54
  • Slide 55
  • Slide 56
  • Slide 57
  • Slide 58
  • Slide 59
  • Slide 60
  • Slide 61
  • Slide 62
  • Slide 63
  • Slide 64
  • Slide 65
  • Slide 66
  • Slide 67
  • Slide 68
  • Slide 69
  • Slide 70
  • Slide 71
  • Slide 72
  • Slide 73
  • Slide 74
  • Slide 75
  • Slide 76
  • Slide 77
  • Slide 78
  • Slide 79
  • Slide 80
  • Slide 81
  • Slide 82
  • Slide 83
  • Slide 84
  • Slide 85
  • Slide 86
  • Slide 87
  • Slide 88
  • Slide 89
  • Slide 90
  • Slide 91
  • Slide 92
  • Slide 93
  • Slide 94
  • Slide 95
  • Slide 96
  • Slide 97
  • Slide 98
  • Slide 99
  • Slide 100
  • Slide 101
  • Slide 102
  • Slide 103
Page 31: 第五章 樹

二元樹節點的宣告程式 5-2

1 struct BTreeNode

2 struct BtreeNode leftchiled

3 int data

4 struct BTreeNode rightchild

5

6 struct BTreeNode root

bull 這樣的二元樹節點要取得兒子節點的指標十分方便但若有取得父節點的需求則嫌不足我們可以加上 parent 欄位指向父節點

55  二元樹的走訪bull 假如一組資料已用二元樹的組織在一起總有需求對其全數資料做動作例如計算所有數目印出所有資料在所有資料中搜尋某項資料hellip等此時即須對此二元樹做走訪 (traversal) 的運算利用走訪二元樹的同時將計算列印或搜尋的動作完成

bull 事實上走訪即在決定二元樹上資料被處理(計算列印或搜尋)的順序我們也希望走訪的演算法對任何節點皆一致是容易撰寫程式實作的

bull 若對一個二元樹上的節點而言 V 表示處理節點上的資料 L 表示走訪其左子樹 R 表示走訪其右子樹

55  二元樹的走訪 ( 續 )bull 因為對稱的緣故我們只考慮先走訪左邊再走訪右邊的情形那麼右圖只剩下三種走訪方式

bull 以 V 所在的相對位置分別對此三種走訪方式取名如下

(1) LVR 中序走訪 (inorder traversal)或中序表示法 (infix notation)

(2) LRV 後序走訪 (postorder traversal) 或後序表示法 (postfix notation)

(3) VLR 前序走訪 (preorder traversal)或前序表示法 (prefix notation)

551  中序走訪bull 利用遞迴的方式撰寫中序走訪的程序希望把二元樹中序走訪的

順序印出來

程式 5-3二元樹的中序走訪1 struct BTreeNode2 struct BTreeNode leftchild3 char data4 struct BTreeNode rightchild5 6 struct BTreeNode root7 void inorder(struct BTreeNode node)8 if (node = NULL)9 inorder(node-gtleftchild)10 cout ltlt node-gtdata11 inorder(node-gtrightchild)12 13

範例 5-12此為運算式二元樹

其對應中序表示運算式為 (A+B)C+D(E-F)

6 struct BTreeNode root7 void inorder(struct BTreeNode node)8 if (node = NULL)9 inorder(node-gtleftchild)10 cout ltlt node-gtdata11 inorder(node-gtrightchild)12 13

6 struct BTreeNode root7 void inorder(struct BTreeNode node)8 if (node = NULL)9 inorder(node-gtleftchild)10 cout ltlt node-gtdata11 inorder(node-gtrightchild)12 13

(A+B)C+D(E-F)

如何加上 ( )

552  後序走訪程式 5-4二元樹的後序走訪14 void postorder(struct BTreeNode node)

15 if (node =NULL)16 postorder(node-gtleftchild)17 postorder(node-gtrightchild)18 cout ltlt node-gtdata19 20

以後序走訪可得到AB+CDEF-+

其正為對應的後序運算式

553  前序走訪程式 5-5二元樹的前序走訪21 void preorder(struct BTreeNode node)

22 if (node = NULL)

23 cout ltlt node-gtdata

24 preorder(node-gtleftchild)

25 preorder(node-gtrightchild)

26

27

以前序走訪可得到++ABCD-EF

其正為對應的前序運算式

554 利用堆疊和迴圈取代遞迴進行二元樹的走訪1 struct BTreeNode 2 struct BTreeNode leftchild3 char data4 struct BtreeNode rightchild5 6 struct BTreeNode root7 struct StackNode8 struct BTreeNode treenode9 struct StackNode next1011 struct StackNode top

12 void push_node(struct BtreeNode node )13 struct StackNode old_top14 old_top = top15 top = (struct StackNode ) malloc

(sizeof(struct StackNode))16 top-gttreenode = node17 top-gtnext = old_top18 19 struct BTreeNode pop_node()20 struct BTreeNode Tnode21 struct StackNode old_top22 if (top == NULL)23 StackEmpty()24 else25 Tnode = top-gttreenode26 old_top = top27 top = top-gtnext28 free(old_top)29 return Tnode30 31

32 void inorder_Stack(struct BtreeNode node)33 do34 while (node = NULL)35 push_node(node)36 node = node-gtleftchild37 push all left children38 if (top = NULL)39 node = pop_node()40 cout ltlt node-gtdata41 node = node-gtrightchild42 inorder printing and check right child43 while ((top=NULL)||(node=NULL))44 top==NULL 堆疊已空 node==NULL沒有右子

6 struct BTreeNode root7 void inorder(struct BTreeNode node)8 if (node = NULL)9 inorder(node-gtleftchild)10 cout ltlt node-gtdata11 inorder(node-gtrightchild)12 13

中序表示為DBGEHAFIC

32 void inorder_Stack()33 struct BTreeNode node34 node = root35 do36 while (node = NULL)37 push_node(node)38 node = node-gtleftchild39 40 if (top = NULL)41 node = pop_node()42 cout ltlt node-gtdata43 node = node-gtrightchild44 45 while(top=NULL)||(node=NULL))46

45 void Preorder_Stack(struct BSTreeNode node)46 do47 while (node = NULL)48 cout ltlt node-gtdata)49 push_node(node)50 node = node-gtleftchild51 push all left children52 if (top = NULL)53 node = pop_node()54 node = node-gtrightchild55 56 while ((top=NULL)||(node=NULL))57

程式 5-5二元樹的前序走訪21 void preorder(struct BTreeNode node)22 if (node = NULL)23 cout ltlt node-gtdata24 preorder(node-gtleftchild)25 preorder(node-gtrightchild)26 27

58 void Postorder_Stack(struct BTreeNode node)59 do60 while (node = NULL)61 cout ltlt node-gtdata62 push_node(node)63 node = node-gtrightchild64 push all left children65 if (top = NULL)66 node = pop_node()67 node = node-gtleftchild68 69 while ((top=NULL)||(node=NULL))70

程式 5-4二元樹的後序走訪14 void postorder(struct BTreeNode node)15 if (node =NULL)16 postorder(node-gtleftchild)17 postorder(node-gtrightchild)18 cout ltlt node-gtdata19 20

555  階層走訪bull 「階層走訪」 (level-order traversal) 是依階層

的順序進行二元樹的走訪先走訪階層小的節點後走訪階層大的節點同一階層者則依自左向右的順序走訪

bull 對下圖的二元樹進行階層走訪的結果為ABCDEFGHI

bull 對同一階層而言先走訪的節點其子節點亦在下一階層中先被走訪

bull 這種「先進先出」的特性恰可用佇列予以儲存與處理

程式 5-7 利用佇列進行二元樹的階層走訪

1 struct BTreeNode2 struct BTreeNode leftchild3 char data4 struct BTreeNode rightchild5 6 struct BTreeNode root7 struct QNode8 struct BTreeNode treenode9 struct QNode next10 11 struct QNode front rear

12 void AddQueue(struct BTreeNode Tnode)13 struct QNode node14 node=(struct QNode )malloc(sizeof(struct QNode))15 node-gttreenode = Tnode16 node-gtnext = NULL17 if (front == NULL) front = node19 else rear-gtnext = node20 rear = node21

22 struct BTreeNode DeleteQueue()23 struct BTreeNode Tnode24 struct QNode old_front25 if (rear == NULL)26 QueueEmpty()27 else28 Tnode = front-gttreenode29 old_front = front30 front = front-gtnext31 free(old_front)32 return Tnode33 34 35 void LevelOrder (struct BTreeNode node)36 AddQueue(node)37 while (front =NULL)38 node = DeleteQueue()39 cout ltlt node-gtdata40 if (node-gtleftchild = NULL)41 AddQueue(node-gtleftchild)42 if (node-gtrightchild = NULL)43 AddQueue(node-gtrightchild)44 45

35 void LevelOrder (struct BTreeNode node)36 AddQueue(node)37 while (front =NULL)38 node = DeleteQueue()39 cout ltlt node-gtdata40 if (node-gtleftchild = NULL)41 AddQueue(node-gtleftchild)42 if (node-gtrightchild = NULL)43 AddQueue(node-gtrightchild)44 45

bull Infix ABCbull Postfix ABC CBA ACB BCA

BAC CAB

B

A CA

B

C

B

A

C

C

B

AB

A

C

二元樹中序與前序式中序與後序式bull 每一棵二元樹皆有唯一的一對中序與前序表示法也有唯一的中序與後序次序

bull 換句話說給予一對中序與前序或中序與後序即可決定一棵二元樹

bull 然而給予前序和後序次序並不能決定唯一的二元樹可能會產生兩棵不同的二元樹

bull 想想階層走訪和中序走訪順序可否決定唯一的二元樹

A

B C

H J D E

F G

prefix ABHJCDFGEinfix HBJAFDGCEpostfix HJBFGDECA

VLRLVRLRV

A

prefix BHJinfix HBJ

prefix CDFGEinfix FDGCE

prefix ABHJCDFGEinfix HBJAFDGCE

A

B C

H J D E

F G

prefix ABHJCDFGE

infix HBJAFDGCEk-1

struct BTreeNode BTree_InfixPrefix (AnsiString infix AnsiString prefix)

int k struct BTreeNode node if (infixLength==0) return NULLnode = (struct BTreeNode ) malloc (sizeof(struct BTreeNode))node-gtdata = prefix[1]k = find(prefix[1] infix)node-gtleftchild = BTree_InfixPrefix

(infix[1k-1] prefix[2k])node-gtrightchild = BTree_InfixPrefix(infix[k+1infixLength]prefix[k+1prefixLength])

VLR

LVRk1

k2 k+1~

k+1~

1

struct BTreeNode BTree_InfixPostfix (AnsiString infix AnsiString prefix)

int k struct BTreeNode node if (infixLength==0) return NULLnode = (struct BTreeNode ) malloc (sizeof(struct BTreeNode))node-gtdata = postfix[postfixLength]k = find(postfix[postfixLength] infix)node-gtleftchild = BTree_InfixPrefix

(infix[1k-1] postfix[1k-1])node-gtrightchild = BTree_InfixPrefix(infix[k+1infixLength]postfix[k+1postfixLength-1])

56  二元樹的複製與相等測試程式 5-8 二元樹的複製1 struct BTreeNode2 struct BTreeNode leftchild3 char data4 struct BTreeNode rightchild5 6 struct BTreeNode root7 struct BTreeNode Copy(struct BTreeNode TreeRoot)8 struct BTreeNode CopyRoot9 if (TreeRoot == NULL)10 return NULL11 CopyRoot =(struct BTreeNode ) malloc (sizeof(BTreeNode))12 CopyRoot-gtdata = TreeRoot-gtdata13 CopyRoot-gtleftchild = Copy(TreeRoot-gtleftchild)14 CopyRoot-gtrightchild = Copy(TreeRoot-

gtrightchild)15 return CopyRoot16

程式 5-9 檢測兩棵二元樹是否相等1 struct BTreeNode2 struct BTreeNode leftchild3 char data4 struct BTreeNode rightchild5 6 struct BtreeNode rightchild7 int Equal(struct BTreeNode ustruct BTreeNode v)8 if ((u==NULL) ampamp (v==NULL)) return 19 if ((u=NULL) ampamp (v=NULL)10 ampamp (u-gtdata == v-gtdata )11 ampamp Equal(u-gtleftchildv-gtleftchild)12 ampamp Equal(u-gtrightchildv-gtrightchild))

13 return 114 return 015

Counting Nodes in a Binary Tree

int CountingNodes(struct BTreeNodes node)

if (node == NULL) return 0 int leftchildren = CountingNodes(node-gtleftchild)

int rightchildren= CountingNodes(node-gtrightchild)

return leftchildren+rightchildren

Counting Leaves in a Binary Tree

int CountingLeaves(struct BTreeNodes node)

if (node == NULL) return 0 int leftleaves = CountingLeaves(node-gtleftchild)

int rightchildren= CountingLeaves(node-gtrightchild)

if (leftleaves==0 ampamp leftleaves==0) return 1

return leftleaves + leftleaves

57  引線二元樹bull 在二元樹的鍵結表示中樹葉節點的兩個子樹指標皆指向 NULL 由定理 5-1 得知 n = E+1 其中 n 為節點個數 E 為分支數

bull 而 n 個鍵結節點有 2n 個指標空間每個分支恰佔用一個指標空間共有 E (=n-1) 個指標有用到(不是空的)而共有 n+1 個指標空間是 NULL 比非空的節點還多

bull 有學者提出對這些「放空指標的空間」加以利用的概念mdash與其放空指標不如放指向其它節點的指標稱之為引線 (thread) 使得某些運算(如走訪hellip等)可以加快

bull 這個概念發展出了引線二元樹 (threaded binary tree) 這種資料結構

範例 5-17下圖為加了引線的二元樹

加入的引線將使二元樹的中序走訪更加便利沿著上右圖箭頭所指示的順序走訪即可完成此二元樹的中序走訪GDHBAIECF 為此二元樹的中序表示

bull 範例 5-17 中所加的引線目的在方便二元樹的中序走訪

bull 對樹葉節點 p 而言令其資料為 P 則引線指標的規則如下

(1) p 的左子樹指標指向中序表示中 P 的前一個 元素(前接元素 (predecessor) )

(2) p 的右子樹指標指向中序表示中 P 的後一個 元素(後繼元素 (successor) )

為了區別節點指標放的是一般指標(內部節點)抑或是引線指標(樹葉節點)我們另以 2 個欄位分別區別左和右子樹指標空間存放的對象其節點的記憶體配置有如下圖所示

其中 leftthread (rightthread) 為 0 時表示 leftchild (rightchild) 存放的是一般節點指標而 leftthread (rightthread) 為 1 時表示 leftchild (rightchild) 存放的是引線指標1 struct TBTreeNode2 int leftthread3 struct TBTreeNode leftchild4 char data5 struct TBTreeNode rightchild6 int rightthread7

由右側的引線二元樹可知有最左和最右兩條引線尚無妥善的安排我們另設計一空的引線節點做為樹根如下圖所示

571  引線二元樹的中序走訪決定節點 p 在中序表示中的後繼節點 q (簡稱中序後繼點)

程式 5-10決定節點 node的中序後繼點1 struct TBTreeNode2 int leftthread3 struct TBTreeNode leftchild4 char data5 struct TBTreeNode rightchild6 int rightthread7 8 struct TBTreeNode root9 struct TBTreeNode Next(struct TBTreeNode node)

10 struct TBTreeNode temp11 temp = node-gtrightchild12 if (node-gtrightthead) return temp13 while (temp-gtleftthead) temp=temp-gtleftchild

14 return temp15

程式 5-11引線二元樹的中序表示(接程式 5-10)1 void InorderTBTree()2 struct TBTreeNode node3 for(node=Next(root)node=rootnode=Next(node))

4 cout ltlt node-gtdata5

bull只要 node 是引線二元樹上的任一節點透過呼叫 Next(node) 即可找到其在中序表示中的後繼節點bull(試想若程式 5-10 中的程序 Next 所傳入的參數指標正是 root Next(root) 是否可正常運作請思考)bull於是只須從中序表示中的第一節點起讓每一節點 node 都執行 Next(node) 程序即可得引線二元樹的中序表示

572  在引線二元樹中加入節點在引線二元樹中樹葉節點節點的右子樹指標所指向或內部節點右子樹的最左樹葉為其中序後繼節點而且任一節點若無左子樹則其左子樹指標乃指向其中序前接節點hellip這些性質必須在節點新增進入引線二元樹時也要保持接下來討論節點 new 插入成為引線二元樹的節點 p 的右子節點的情形至於插入成為左子節點的情形則讓各位模擬推敲

572572  在引線二元樹中加入節點 在引線二元樹中加入節點 (( 續續 ))

newnew 會成為會成為 pp 的右子節點的情況共有以下兩種的右子節點的情況共有以下兩種(1) (1) 若若 pp 沒有右子節點 沒有右子節點 ((p-gtrightthread p-gtrightthread 為為1) 1) 如圖如圖 ((a) a) 則 則 new new 插入後應形成如圖插入後應形成如圖 ((b) b) 所示所示

(2)(2) 若若 pp 有右子節點 有右子節點 ((p-gtrightthread p-gtrightthread 為為0) 0) 如圖如圖 ((c) c) 則 則 new new 插入後應形成如插入後應形成如圖圖 ((d) d) 所示所示

程式 5-12  插入節點 new 成為引線二元樹中節點 p的右子節點(上接程式 5-10 5-11 )

1 void InsertRight(struct TBTreeNode p 2 struct TBTreeNode new)3 struct TBTreeNode q4 new-gtrightchild = p-gtrightchild5 new-gtrightthead = p-gtrightthread6 new-gtleftchild = p7 new-gtleftthread = 18 p-gtrightchild = new9 p-gtrightthead = 010 if (new-gtrightthread)11 q = Next(new)12 q-gtleftchild = new13 14

58  堆積bull 「堆積」 (heap) 是一個特殊的完備二元樹節點的資料與其子節點的資料之間有事先定好的大小關係存在bull 定義ndash最大堆疊為一完備二元樹任一節點的資料內容不小於其子節點的資料內容ndash最小堆疊為一完備二元樹任一節點的資料內容不大於其子節點的資料內容

581  插入節點至最大堆積中最大堆積是一完備二元樹所以如圖 (a) 之最大堆積於加入新節點後樹的形狀必須如圖 (b) 所示灰色節點即為新節點若加入的節點資料分別為 1 9 12 則其最大堆積將分別變成如圖 (c) (d) (e) 所示

程式 5-13 新增節點至最大堆積中最大堆積本身是一棵完備二元樹可以用陣列表示之 1 define maxsize 1002 int heap[maxsize]3 int n i4 void InsertHeap(int x)5 if (n == maxsize) HeapFull()6 else7 n++8 i = n9 while ((i gt 1) ampamp (x gt heap[i2]))10 heap[i] = heap[i2]11 i = 212 13 heap[i] = x14 16

程序 InsertHeap 在插入第 n 個資料時需要的時間複雜度為 O(logn)

1 100

2 93

3 84

4 77

5 65

6 81

7 79

8 66

9 70

10 50

100

84

77

93

8165

66 70

79

50

1

2 3

4 5 6 7

8 9 10

1 define maxsize 1002 int heap[maxsize]3 int n i4 void InsertHeap(int x)5 if (n == maxsize) HeapFull()6 else7 n++8 i = n9 while ((i gt 1) ampamp (x gt heap[i2]))10 heap[i] = heap[i2]11 i = 212 13 heap[i] = x14 16

582 刪除最大堆積中的最大資料圖 (a) 中的最大堆積在刪除最大資料後其樹形應變成如圖 (b) 所示將最未一個樹葉節點的資料 6 暫挪至樹根節點內如圖 (c) 再將圖 (c) 中非最大堆積的結構調整成為最大堆積 10 和 8 中選大者與 6對調即成圖 (d)

程式 5-14 刪除最大堆積中的最大資料(上接程式 5-13 )1 int DeleteHeap()2 int x i j3 if (n == 0) HeapEmpty()4 else5 x = heap[1]6 heap[1] = heap[n]7 n--8 i = 19 while (i lt= n2)10 if (heap[2i] gt heap[2i+1])11 j = 2i12 else13 j = 2i+114 if (heap[i]gtheap[j]) break15 swap(heap[i] heap[j])16 i = j17 18 return x19 20

其時間複雜度與其堆積高度相同是為O(logn) 其中 n 為堆積中的元素個數

1 100

2 93

3 84

4 77

5 65

6 81

7 79

8 66

9 70

10 50

100

84

77

93

8165

66 70

79

50

1

2 3

4 5 6 7

8 9 10

1 int DeletHeap()2 int x i j3 if(n == 0) HeapEmpty()4 else5 x = heap[1]6 heap[1] = heap[n]7 n--8 i = 19 while (i lt= n2)10 if ([heap[2i]] gt heap[2i+1])11 j = 2i12 else13 j = 2i+114 if (heap[i]gtheap[j]) break15 swap(heap[i] heap[j])16 i = j17 18 return x19 20

583  優先佇列bull 由於最大堆積(或最小堆積)的結構可提供最大元素(或最小元素)而其維護堆積的成本為 O(logn) n 為總元素個數

bull 堆積常被當成「優先佇列」 (priority queue) 使用mdash此時佇列被組織成完備二元樹若新加入佇列的資料有大小關係優先順序的需求時此優先佇列即以 O(logn) 的代價將新資料放入優先佇列(可能是最大或最小堆積依實際大小關係優先順序的需求而定)中mdash提供依元素的優先順序做為處理順序的服務

bull 一般的佇列為先進先出先到的先行處理優先佇列則是依據其優先順序優先順序高者先行處理與來到的先後無關

範例 5-21bull 在「分時」 (time sharing) 的作業系統中我們不希望使用中央處理器 (Central Processing Unit CPU) 時間短的「處理程序」 (process) 等候過久於是有「最短時間工作優先」 (shortest-job-first) 的排程策略此時等候的處理程序即可組織成之一最小堆積而 CPU 每次即取出存於樹根的處理程序予以處理任何新來的處理程序則以 O(logn) 的時間加入此優先佇列(最小堆積)中

bull 注意之前提過的線性佇列新來的直接加入成為新的尾端元素花O(1) 的時間然線性佇列本身沒有大小的關係每次要取出最大(小)的資料得花O(n) 的代價(在所有元素中挑出最大(小)者) n 為佇列內的元素個數而優先佇列是在新元素加入時要花O(logn) 的時間至於取出最大(小)則只須 O(1) 的時間

59  二元搜尋樹bull 二元搜尋樹是一棵二元樹可能是空二元樹若不為空二元樹則滿足下列性質ndash所有節點內的資料內容是相異的 ndash左子節點的資內容 ( 如果有 ) 比父節點的資料內容小 ndash右子節點的資料內容 ( 如果有 ) 比父節點的資料內容大 ndash以左和右子節點為樹根的左子樹和右子樹也是二元搜尋樹

在二元搜尋樹中搜尋資料二元搜尋樹對資料大小的掌握十分明確若恰與節點 p 資料相等則搜尋成功若欲搜尋的資料 x 比樹節點 p 中的資料小 則只須搜尋節點 p 的左子樹若比節點 p 中的資料大 則只須搜尋 p 的右子樹這樣的判斷可以遞迴地應用在各子樹節點 中

程式 5-15 在二元搜尋樹中搜尋資料(遞迴呼叫)1 struct BSTreeNode2 struct BSTreeNode leftchild3 int data4 struct BSTreeNode rightchild5 6 struct BSTreeNode root7 struct BSTreeNode SearchBST(

struct BSTreeNode tree int x)8 if (tree == NULL) return NULL9 if (x == tree-gtdata) return tree10 if (x lt tree-gtdata)11 return SearchBST(tree-gtleftchild x)

12 return SearchBST(tree-gtrightchild x)13

程式 5-16 搜尋二元搜尋樹的資料(非遞迴呼叫)在二元搜尋樹十分龐大時遞迴呼叫的效率不會太好可利用迴圈來完成搜尋的動作以提高執行效率請看程式 5-16

14 struct BSTreeNode Search BST_iterative15 (struct BSTreeNode node int x)16 while (node = NULL)17 if (x == node-gtdata) return node18 if (x lt node-gtdata)19 node = node-gtleftchild20 else21 node = node-gtrightchild22 23 return NULL24

592  新增資料於二元搜尋樹中欲新增資料進入一二元搜尋樹中得先決定該資料在二元搜尋樹中應出現的位置這可透過上節提到的搜尋動作來解決

在二元搜尋樹定義的性質 (1) 中我們限定了元素必須相異所以搜尋新資料的結果必是 NULL

必須將搜尋的結果改為新增資料所應在位置的節點指標

程式 5-17  在二元搜尋樹中新增資料25 int InsertBST(int x)26 struct BSTreeNode p q27 p = root q = NULL28 while (p = NULL)29 q = p30 if (x == p-gtdata) return 0

若不考慮重複則可省略之31 if (x lt p-gtdata)32 p = p-gtleftchild33 else34 p = p-gtrightchild35

3636 p = (struct BSTreeNode ) p = (struct BSTreeNode ) malloc (sizeof(BSTreeNode))malloc (sizeof(BSTreeNode))

3737 p-gtdata = x p-gtdata = x3838 p-gtleftchild = p-gtrightchild = p-gtleftchild = p-gtrightchild = NULLNULL3939 if (root == NULL) root = p if (root == NULL) root = p4040 else if (x lt q-gtdata) else if (x lt q-gtdata)4141 q-gtleftchild = p q-gtleftchild = p4242 else else4343 q-gtrightchild = p q-gtrightchild = p4444 return 1 return 145 45

程式 程式 5-175-17  在二元搜尋樹中新增資料  在二元搜尋樹中新增資料 (( 續續 ))

範例 5-23 圖 (b) 為圖 (a) 新增整數 6 之後的結果圖 (c) (d) (e) (f) 則分別是逐步再加入 28 18

10 21 的結果

bull若目前的二元搜尋樹高度為 h 則新增資料得花O(h) 的時間找到其所應在的位置遂新增資料至二元搜尋樹中的時間複雜之度為 O(h)

593 自二元搜尋樹中刪除資料欲自二元搜尋樹中刪除資料可能會面臨兩種情況(1) 刪除節點 p 位於樹葉節點上如下圖所示 這是比較容易處理的情況

(2) 刪除的節點 p 不是樹葉刪除節點 p 後原 p 所指的節點可由 q ( q 中包含了 p 左子樹中的最大節點值)或 r ( r 中包含了 p 右子樹中的最小節點值)來取代以維持二元搜尋樹中資料的大小關係其結果分別如圖 (b) 和 (c) 所示而圖 (d) 則標明了指標會改變的節點位置即 p 會消失 father 的左或右子樹指標會異動 k(可能是 q 或 r )會移到原來 p 的位置其父節點 f 的左或右子樹指標會異動其中 father 為 p 父節點 f 為 k 的父節點

程式 5-18 自二元搜尋樹中刪除資料46 int DeleteBST (int x)47 Struct BSTreeNode p father k f48 p = root father = NULL49 while (p = NULL)50 if (x == p-gtdata) 找到 x 所在的節點 p 了51 if ((p-gtleftchild == NULL) ampamp 52 (p-gtrightchild == NULL))53 k = NULL p為樹葉節點54 else if (p-gtleftchild = NULL )x位於非子節

點55 f = p 找出 p左子樹的最右樹葉56 k = p-gtleftchild57 while (k-gtrightchild =NULL)58 f = k59 k = k-gtrightchild60 k將挪至原 p處61 if (p == f)62 f-gtleftchild = k-gtleftchild 63 else64 f-gtrightchild = k-gtleftchild 65

66 else p無左子樹67 f = p 找出 p右子樹的最左樹葉68 k = p-gtrightchild69 while (k-gtleftchild = NULL)70 f = k71 k = k -gtleftchild72 73 if (p == f)74 f-gtrightchild = k-gtrightchild 75 else76 f-gtleftchild = k-gtrightchild77 78 if (k = NULL) k挪至原 p處繼承 p的左右指標79 k-gtleftchild = p-gtleftchild80 k-gtrightchild = p-gtrightchild81 82 if (father == NULL) root = k83 else if (x lt father-gtdata) 決定 k是 father的左

或右兒子84 father-gtleftchild = k85 else86 father-gtrightchild = k87 free(p)88 return 1 成功地刪除 x於此傳回 1返回呼叫處89 51~89行為找到 x時應有的處理步驟

90 else 尚未找到 x繼續往下一階層找

91 father = p92 if (x lt p-gtdata)93 p = p-gtleftchild94 else95 p = p-gtrightchild96 97 98 return 0 未找到 x傳回 099

510  樹林 (forest)

bull 定義樹林(森林)是由 n 棵沒有交集的樹所構成的集合 n 0

下圖為一包含三棵樹的樹林

5101 轉換樹林為二元樹 之前我們以二元樹來表示樹因為二元樹有較多

好用的性質在此我們也將樹林轉換成二元樹看待

在 522 節中我們嘗試了用左子右兄弟的方式來表示樹這個方法事實上已將樹轉換成二元樹

即用左子右兄弟的結構重新組織樹林中的每一棵樹這些樹的樹根將視為兄弟而最左邊的樹則視為樹林二元樹的樹根如此一來樹林即可轉換成二元樹

範例 5-25右下圖為右上圖三棵樹所對應的三棵左子右兄弟二元樹

下圖則為上圖樹林所對應的一棵樹林二元樹

演算法 5-1  轉換樹林成二元樹輸入樹林 F = T1 T2 Tn

輸出 F 的二元樹表示 BT=(T1 T2 Tn)1 if (n == 0) 輸出空二元樹 2 else

3 root(T1 T2 Tn) = T1

4 leftsubtree(T1 T2 Tn) =

BT(T11 T12 T1m)5 BT(T11 T12 T1m)為 T1的子樹6 rightsubtree(T1 T2 Tn) =

BT(T2 T3 Tn)

樹林轉換成二元樹的過程樹林轉換成二元樹的過程

  • Slide 1
  • Slide 2
  • Slide 3
  • Slide 4
  • Slide 5
  • Slide 6
  • Slide 7
  • Slide 8
  • Slide 9
  • Slide 10
  • Slide 11
  • Slide 12
  • Slide 13
  • Slide 14
  • Slide 15
  • Slide 16
  • Slide 17
  • Slide 18
  • Slide 19
  • Slide 20
  • Slide 21
  • Slide 22
  • Slide 23
  • Slide 24
  • Slide 25
  • Slide 26
  • Slide 27
  • Slide 28
  • Slide 29
  • Slide 30
  • Slide 31
  • Slide 32
  • Slide 33
  • Slide 34
  • Slide 35
  • Slide 36
  • Slide 37
  • Slide 38
  • Slide 39
  • Slide 40
  • Slide 41
  • Slide 42
  • Slide 43
  • Slide 44
  • Slide 45
  • Slide 46
  • Slide 47
  • Slide 48
  • Slide 49
  • Slide 50
  • Slide 51
  • Slide 52
  • Slide 53
  • Slide 54
  • Slide 55
  • Slide 56
  • Slide 57
  • Slide 58
  • Slide 59
  • Slide 60
  • Slide 61
  • Slide 62
  • Slide 63
  • Slide 64
  • Slide 65
  • Slide 66
  • Slide 67
  • Slide 68
  • Slide 69
  • Slide 70
  • Slide 71
  • Slide 72
  • Slide 73
  • Slide 74
  • Slide 75
  • Slide 76
  • Slide 77
  • Slide 78
  • Slide 79
  • Slide 80
  • Slide 81
  • Slide 82
  • Slide 83
  • Slide 84
  • Slide 85
  • Slide 86
  • Slide 87
  • Slide 88
  • Slide 89
  • Slide 90
  • Slide 91
  • Slide 92
  • Slide 93
  • Slide 94
  • Slide 95
  • Slide 96
  • Slide 97
  • Slide 98
  • Slide 99
  • Slide 100
  • Slide 101
  • Slide 102
  • Slide 103
Page 32: 第五章 樹

bull 這樣的二元樹節點要取得兒子節點的指標十分方便但若有取得父節點的需求則嫌不足我們可以加上 parent 欄位指向父節點

55  二元樹的走訪bull 假如一組資料已用二元樹的組織在一起總有需求對其全數資料做動作例如計算所有數目印出所有資料在所有資料中搜尋某項資料hellip等此時即須對此二元樹做走訪 (traversal) 的運算利用走訪二元樹的同時將計算列印或搜尋的動作完成

bull 事實上走訪即在決定二元樹上資料被處理(計算列印或搜尋)的順序我們也希望走訪的演算法對任何節點皆一致是容易撰寫程式實作的

bull 若對一個二元樹上的節點而言 V 表示處理節點上的資料 L 表示走訪其左子樹 R 表示走訪其右子樹

55  二元樹的走訪 ( 續 )bull 因為對稱的緣故我們只考慮先走訪左邊再走訪右邊的情形那麼右圖只剩下三種走訪方式

bull 以 V 所在的相對位置分別對此三種走訪方式取名如下

(1) LVR 中序走訪 (inorder traversal)或中序表示法 (infix notation)

(2) LRV 後序走訪 (postorder traversal) 或後序表示法 (postfix notation)

(3) VLR 前序走訪 (preorder traversal)或前序表示法 (prefix notation)

551  中序走訪bull 利用遞迴的方式撰寫中序走訪的程序希望把二元樹中序走訪的

順序印出來

程式 5-3二元樹的中序走訪1 struct BTreeNode2 struct BTreeNode leftchild3 char data4 struct BTreeNode rightchild5 6 struct BTreeNode root7 void inorder(struct BTreeNode node)8 if (node = NULL)9 inorder(node-gtleftchild)10 cout ltlt node-gtdata11 inorder(node-gtrightchild)12 13

範例 5-12此為運算式二元樹

其對應中序表示運算式為 (A+B)C+D(E-F)

6 struct BTreeNode root7 void inorder(struct BTreeNode node)8 if (node = NULL)9 inorder(node-gtleftchild)10 cout ltlt node-gtdata11 inorder(node-gtrightchild)12 13

6 struct BTreeNode root7 void inorder(struct BTreeNode node)8 if (node = NULL)9 inorder(node-gtleftchild)10 cout ltlt node-gtdata11 inorder(node-gtrightchild)12 13

(A+B)C+D(E-F)

如何加上 ( )

552  後序走訪程式 5-4二元樹的後序走訪14 void postorder(struct BTreeNode node)

15 if (node =NULL)16 postorder(node-gtleftchild)17 postorder(node-gtrightchild)18 cout ltlt node-gtdata19 20

以後序走訪可得到AB+CDEF-+

其正為對應的後序運算式

553  前序走訪程式 5-5二元樹的前序走訪21 void preorder(struct BTreeNode node)

22 if (node = NULL)

23 cout ltlt node-gtdata

24 preorder(node-gtleftchild)

25 preorder(node-gtrightchild)

26

27

以前序走訪可得到++ABCD-EF

其正為對應的前序運算式

554 利用堆疊和迴圈取代遞迴進行二元樹的走訪1 struct BTreeNode 2 struct BTreeNode leftchild3 char data4 struct BtreeNode rightchild5 6 struct BTreeNode root7 struct StackNode8 struct BTreeNode treenode9 struct StackNode next1011 struct StackNode top

12 void push_node(struct BtreeNode node )13 struct StackNode old_top14 old_top = top15 top = (struct StackNode ) malloc

(sizeof(struct StackNode))16 top-gttreenode = node17 top-gtnext = old_top18 19 struct BTreeNode pop_node()20 struct BTreeNode Tnode21 struct StackNode old_top22 if (top == NULL)23 StackEmpty()24 else25 Tnode = top-gttreenode26 old_top = top27 top = top-gtnext28 free(old_top)29 return Tnode30 31

32 void inorder_Stack(struct BtreeNode node)33 do34 while (node = NULL)35 push_node(node)36 node = node-gtleftchild37 push all left children38 if (top = NULL)39 node = pop_node()40 cout ltlt node-gtdata41 node = node-gtrightchild42 inorder printing and check right child43 while ((top=NULL)||(node=NULL))44 top==NULL 堆疊已空 node==NULL沒有右子

6 struct BTreeNode root7 void inorder(struct BTreeNode node)8 if (node = NULL)9 inorder(node-gtleftchild)10 cout ltlt node-gtdata11 inorder(node-gtrightchild)12 13

中序表示為DBGEHAFIC

32 void inorder_Stack()33 struct BTreeNode node34 node = root35 do36 while (node = NULL)37 push_node(node)38 node = node-gtleftchild39 40 if (top = NULL)41 node = pop_node()42 cout ltlt node-gtdata43 node = node-gtrightchild44 45 while(top=NULL)||(node=NULL))46

45 void Preorder_Stack(struct BSTreeNode node)46 do47 while (node = NULL)48 cout ltlt node-gtdata)49 push_node(node)50 node = node-gtleftchild51 push all left children52 if (top = NULL)53 node = pop_node()54 node = node-gtrightchild55 56 while ((top=NULL)||(node=NULL))57

程式 5-5二元樹的前序走訪21 void preorder(struct BTreeNode node)22 if (node = NULL)23 cout ltlt node-gtdata24 preorder(node-gtleftchild)25 preorder(node-gtrightchild)26 27

58 void Postorder_Stack(struct BTreeNode node)59 do60 while (node = NULL)61 cout ltlt node-gtdata62 push_node(node)63 node = node-gtrightchild64 push all left children65 if (top = NULL)66 node = pop_node()67 node = node-gtleftchild68 69 while ((top=NULL)||(node=NULL))70

程式 5-4二元樹的後序走訪14 void postorder(struct BTreeNode node)15 if (node =NULL)16 postorder(node-gtleftchild)17 postorder(node-gtrightchild)18 cout ltlt node-gtdata19 20

555  階層走訪bull 「階層走訪」 (level-order traversal) 是依階層

的順序進行二元樹的走訪先走訪階層小的節點後走訪階層大的節點同一階層者則依自左向右的順序走訪

bull 對下圖的二元樹進行階層走訪的結果為ABCDEFGHI

bull 對同一階層而言先走訪的節點其子節點亦在下一階層中先被走訪

bull 這種「先進先出」的特性恰可用佇列予以儲存與處理

程式 5-7 利用佇列進行二元樹的階層走訪

1 struct BTreeNode2 struct BTreeNode leftchild3 char data4 struct BTreeNode rightchild5 6 struct BTreeNode root7 struct QNode8 struct BTreeNode treenode9 struct QNode next10 11 struct QNode front rear

12 void AddQueue(struct BTreeNode Tnode)13 struct QNode node14 node=(struct QNode )malloc(sizeof(struct QNode))15 node-gttreenode = Tnode16 node-gtnext = NULL17 if (front == NULL) front = node19 else rear-gtnext = node20 rear = node21

22 struct BTreeNode DeleteQueue()23 struct BTreeNode Tnode24 struct QNode old_front25 if (rear == NULL)26 QueueEmpty()27 else28 Tnode = front-gttreenode29 old_front = front30 front = front-gtnext31 free(old_front)32 return Tnode33 34 35 void LevelOrder (struct BTreeNode node)36 AddQueue(node)37 while (front =NULL)38 node = DeleteQueue()39 cout ltlt node-gtdata40 if (node-gtleftchild = NULL)41 AddQueue(node-gtleftchild)42 if (node-gtrightchild = NULL)43 AddQueue(node-gtrightchild)44 45

35 void LevelOrder (struct BTreeNode node)36 AddQueue(node)37 while (front =NULL)38 node = DeleteQueue()39 cout ltlt node-gtdata40 if (node-gtleftchild = NULL)41 AddQueue(node-gtleftchild)42 if (node-gtrightchild = NULL)43 AddQueue(node-gtrightchild)44 45

bull Infix ABCbull Postfix ABC CBA ACB BCA

BAC CAB

B

A CA

B

C

B

A

C

C

B

AB

A

C

二元樹中序與前序式中序與後序式bull 每一棵二元樹皆有唯一的一對中序與前序表示法也有唯一的中序與後序次序

bull 換句話說給予一對中序與前序或中序與後序即可決定一棵二元樹

bull 然而給予前序和後序次序並不能決定唯一的二元樹可能會產生兩棵不同的二元樹

bull 想想階層走訪和中序走訪順序可否決定唯一的二元樹

A

B C

H J D E

F G

prefix ABHJCDFGEinfix HBJAFDGCEpostfix HJBFGDECA

VLRLVRLRV

A

prefix BHJinfix HBJ

prefix CDFGEinfix FDGCE

prefix ABHJCDFGEinfix HBJAFDGCE

A

B C

H J D E

F G

prefix ABHJCDFGE

infix HBJAFDGCEk-1

struct BTreeNode BTree_InfixPrefix (AnsiString infix AnsiString prefix)

int k struct BTreeNode node if (infixLength==0) return NULLnode = (struct BTreeNode ) malloc (sizeof(struct BTreeNode))node-gtdata = prefix[1]k = find(prefix[1] infix)node-gtleftchild = BTree_InfixPrefix

(infix[1k-1] prefix[2k])node-gtrightchild = BTree_InfixPrefix(infix[k+1infixLength]prefix[k+1prefixLength])

VLR

LVRk1

k2 k+1~

k+1~

1

struct BTreeNode BTree_InfixPostfix (AnsiString infix AnsiString prefix)

int k struct BTreeNode node if (infixLength==0) return NULLnode = (struct BTreeNode ) malloc (sizeof(struct BTreeNode))node-gtdata = postfix[postfixLength]k = find(postfix[postfixLength] infix)node-gtleftchild = BTree_InfixPrefix

(infix[1k-1] postfix[1k-1])node-gtrightchild = BTree_InfixPrefix(infix[k+1infixLength]postfix[k+1postfixLength-1])

56  二元樹的複製與相等測試程式 5-8 二元樹的複製1 struct BTreeNode2 struct BTreeNode leftchild3 char data4 struct BTreeNode rightchild5 6 struct BTreeNode root7 struct BTreeNode Copy(struct BTreeNode TreeRoot)8 struct BTreeNode CopyRoot9 if (TreeRoot == NULL)10 return NULL11 CopyRoot =(struct BTreeNode ) malloc (sizeof(BTreeNode))12 CopyRoot-gtdata = TreeRoot-gtdata13 CopyRoot-gtleftchild = Copy(TreeRoot-gtleftchild)14 CopyRoot-gtrightchild = Copy(TreeRoot-

gtrightchild)15 return CopyRoot16

程式 5-9 檢測兩棵二元樹是否相等1 struct BTreeNode2 struct BTreeNode leftchild3 char data4 struct BTreeNode rightchild5 6 struct BtreeNode rightchild7 int Equal(struct BTreeNode ustruct BTreeNode v)8 if ((u==NULL) ampamp (v==NULL)) return 19 if ((u=NULL) ampamp (v=NULL)10 ampamp (u-gtdata == v-gtdata )11 ampamp Equal(u-gtleftchildv-gtleftchild)12 ampamp Equal(u-gtrightchildv-gtrightchild))

13 return 114 return 015

Counting Nodes in a Binary Tree

int CountingNodes(struct BTreeNodes node)

if (node == NULL) return 0 int leftchildren = CountingNodes(node-gtleftchild)

int rightchildren= CountingNodes(node-gtrightchild)

return leftchildren+rightchildren

Counting Leaves in a Binary Tree

int CountingLeaves(struct BTreeNodes node)

if (node == NULL) return 0 int leftleaves = CountingLeaves(node-gtleftchild)

int rightchildren= CountingLeaves(node-gtrightchild)

if (leftleaves==0 ampamp leftleaves==0) return 1

return leftleaves + leftleaves

57  引線二元樹bull 在二元樹的鍵結表示中樹葉節點的兩個子樹指標皆指向 NULL 由定理 5-1 得知 n = E+1 其中 n 為節點個數 E 為分支數

bull 而 n 個鍵結節點有 2n 個指標空間每個分支恰佔用一個指標空間共有 E (=n-1) 個指標有用到(不是空的)而共有 n+1 個指標空間是 NULL 比非空的節點還多

bull 有學者提出對這些「放空指標的空間」加以利用的概念mdash與其放空指標不如放指向其它節點的指標稱之為引線 (thread) 使得某些運算(如走訪hellip等)可以加快

bull 這個概念發展出了引線二元樹 (threaded binary tree) 這種資料結構

範例 5-17下圖為加了引線的二元樹

加入的引線將使二元樹的中序走訪更加便利沿著上右圖箭頭所指示的順序走訪即可完成此二元樹的中序走訪GDHBAIECF 為此二元樹的中序表示

bull 範例 5-17 中所加的引線目的在方便二元樹的中序走訪

bull 對樹葉節點 p 而言令其資料為 P 則引線指標的規則如下

(1) p 的左子樹指標指向中序表示中 P 的前一個 元素(前接元素 (predecessor) )

(2) p 的右子樹指標指向中序表示中 P 的後一個 元素(後繼元素 (successor) )

為了區別節點指標放的是一般指標(內部節點)抑或是引線指標(樹葉節點)我們另以 2 個欄位分別區別左和右子樹指標空間存放的對象其節點的記憶體配置有如下圖所示

其中 leftthread (rightthread) 為 0 時表示 leftchild (rightchild) 存放的是一般節點指標而 leftthread (rightthread) 為 1 時表示 leftchild (rightchild) 存放的是引線指標1 struct TBTreeNode2 int leftthread3 struct TBTreeNode leftchild4 char data5 struct TBTreeNode rightchild6 int rightthread7

由右側的引線二元樹可知有最左和最右兩條引線尚無妥善的安排我們另設計一空的引線節點做為樹根如下圖所示

571  引線二元樹的中序走訪決定節點 p 在中序表示中的後繼節點 q (簡稱中序後繼點)

程式 5-10決定節點 node的中序後繼點1 struct TBTreeNode2 int leftthread3 struct TBTreeNode leftchild4 char data5 struct TBTreeNode rightchild6 int rightthread7 8 struct TBTreeNode root9 struct TBTreeNode Next(struct TBTreeNode node)

10 struct TBTreeNode temp11 temp = node-gtrightchild12 if (node-gtrightthead) return temp13 while (temp-gtleftthead) temp=temp-gtleftchild

14 return temp15

程式 5-11引線二元樹的中序表示(接程式 5-10)1 void InorderTBTree()2 struct TBTreeNode node3 for(node=Next(root)node=rootnode=Next(node))

4 cout ltlt node-gtdata5

bull只要 node 是引線二元樹上的任一節點透過呼叫 Next(node) 即可找到其在中序表示中的後繼節點bull(試想若程式 5-10 中的程序 Next 所傳入的參數指標正是 root Next(root) 是否可正常運作請思考)bull於是只須從中序表示中的第一節點起讓每一節點 node 都執行 Next(node) 程序即可得引線二元樹的中序表示

572  在引線二元樹中加入節點在引線二元樹中樹葉節點節點的右子樹指標所指向或內部節點右子樹的最左樹葉為其中序後繼節點而且任一節點若無左子樹則其左子樹指標乃指向其中序前接節點hellip這些性質必須在節點新增進入引線二元樹時也要保持接下來討論節點 new 插入成為引線二元樹的節點 p 的右子節點的情形至於插入成為左子節點的情形則讓各位模擬推敲

572572  在引線二元樹中加入節點 在引線二元樹中加入節點 (( 續續 ))

newnew 會成為會成為 pp 的右子節點的情況共有以下兩種的右子節點的情況共有以下兩種(1) (1) 若若 pp 沒有右子節點 沒有右子節點 ((p-gtrightthread p-gtrightthread 為為1) 1) 如圖如圖 ((a) a) 則 則 new new 插入後應形成如圖插入後應形成如圖 ((b) b) 所示所示

(2)(2) 若若 pp 有右子節點 有右子節點 ((p-gtrightthread p-gtrightthread 為為0) 0) 如圖如圖 ((c) c) 則 則 new new 插入後應形成如插入後應形成如圖圖 ((d) d) 所示所示

程式 5-12  插入節點 new 成為引線二元樹中節點 p的右子節點(上接程式 5-10 5-11 )

1 void InsertRight(struct TBTreeNode p 2 struct TBTreeNode new)3 struct TBTreeNode q4 new-gtrightchild = p-gtrightchild5 new-gtrightthead = p-gtrightthread6 new-gtleftchild = p7 new-gtleftthread = 18 p-gtrightchild = new9 p-gtrightthead = 010 if (new-gtrightthread)11 q = Next(new)12 q-gtleftchild = new13 14

58  堆積bull 「堆積」 (heap) 是一個特殊的完備二元樹節點的資料與其子節點的資料之間有事先定好的大小關係存在bull 定義ndash最大堆疊為一完備二元樹任一節點的資料內容不小於其子節點的資料內容ndash最小堆疊為一完備二元樹任一節點的資料內容不大於其子節點的資料內容

581  插入節點至最大堆積中最大堆積是一完備二元樹所以如圖 (a) 之最大堆積於加入新節點後樹的形狀必須如圖 (b) 所示灰色節點即為新節點若加入的節點資料分別為 1 9 12 則其最大堆積將分別變成如圖 (c) (d) (e) 所示

程式 5-13 新增節點至最大堆積中最大堆積本身是一棵完備二元樹可以用陣列表示之 1 define maxsize 1002 int heap[maxsize]3 int n i4 void InsertHeap(int x)5 if (n == maxsize) HeapFull()6 else7 n++8 i = n9 while ((i gt 1) ampamp (x gt heap[i2]))10 heap[i] = heap[i2]11 i = 212 13 heap[i] = x14 16

程序 InsertHeap 在插入第 n 個資料時需要的時間複雜度為 O(logn)

1 100

2 93

3 84

4 77

5 65

6 81

7 79

8 66

9 70

10 50

100

84

77

93

8165

66 70

79

50

1

2 3

4 5 6 7

8 9 10

1 define maxsize 1002 int heap[maxsize]3 int n i4 void InsertHeap(int x)5 if (n == maxsize) HeapFull()6 else7 n++8 i = n9 while ((i gt 1) ampamp (x gt heap[i2]))10 heap[i] = heap[i2]11 i = 212 13 heap[i] = x14 16

582 刪除最大堆積中的最大資料圖 (a) 中的最大堆積在刪除最大資料後其樹形應變成如圖 (b) 所示將最未一個樹葉節點的資料 6 暫挪至樹根節點內如圖 (c) 再將圖 (c) 中非最大堆積的結構調整成為最大堆積 10 和 8 中選大者與 6對調即成圖 (d)

程式 5-14 刪除最大堆積中的最大資料(上接程式 5-13 )1 int DeleteHeap()2 int x i j3 if (n == 0) HeapEmpty()4 else5 x = heap[1]6 heap[1] = heap[n]7 n--8 i = 19 while (i lt= n2)10 if (heap[2i] gt heap[2i+1])11 j = 2i12 else13 j = 2i+114 if (heap[i]gtheap[j]) break15 swap(heap[i] heap[j])16 i = j17 18 return x19 20

其時間複雜度與其堆積高度相同是為O(logn) 其中 n 為堆積中的元素個數

1 100

2 93

3 84

4 77

5 65

6 81

7 79

8 66

9 70

10 50

100

84

77

93

8165

66 70

79

50

1

2 3

4 5 6 7

8 9 10

1 int DeletHeap()2 int x i j3 if(n == 0) HeapEmpty()4 else5 x = heap[1]6 heap[1] = heap[n]7 n--8 i = 19 while (i lt= n2)10 if ([heap[2i]] gt heap[2i+1])11 j = 2i12 else13 j = 2i+114 if (heap[i]gtheap[j]) break15 swap(heap[i] heap[j])16 i = j17 18 return x19 20

583  優先佇列bull 由於最大堆積(或最小堆積)的結構可提供最大元素(或最小元素)而其維護堆積的成本為 O(logn) n 為總元素個數

bull 堆積常被當成「優先佇列」 (priority queue) 使用mdash此時佇列被組織成完備二元樹若新加入佇列的資料有大小關係優先順序的需求時此優先佇列即以 O(logn) 的代價將新資料放入優先佇列(可能是最大或最小堆積依實際大小關係優先順序的需求而定)中mdash提供依元素的優先順序做為處理順序的服務

bull 一般的佇列為先進先出先到的先行處理優先佇列則是依據其優先順序優先順序高者先行處理與來到的先後無關

範例 5-21bull 在「分時」 (time sharing) 的作業系統中我們不希望使用中央處理器 (Central Processing Unit CPU) 時間短的「處理程序」 (process) 等候過久於是有「最短時間工作優先」 (shortest-job-first) 的排程策略此時等候的處理程序即可組織成之一最小堆積而 CPU 每次即取出存於樹根的處理程序予以處理任何新來的處理程序則以 O(logn) 的時間加入此優先佇列(最小堆積)中

bull 注意之前提過的線性佇列新來的直接加入成為新的尾端元素花O(1) 的時間然線性佇列本身沒有大小的關係每次要取出最大(小)的資料得花O(n) 的代價(在所有元素中挑出最大(小)者) n 為佇列內的元素個數而優先佇列是在新元素加入時要花O(logn) 的時間至於取出最大(小)則只須 O(1) 的時間

59  二元搜尋樹bull 二元搜尋樹是一棵二元樹可能是空二元樹若不為空二元樹則滿足下列性質ndash所有節點內的資料內容是相異的 ndash左子節點的資內容 ( 如果有 ) 比父節點的資料內容小 ndash右子節點的資料內容 ( 如果有 ) 比父節點的資料內容大 ndash以左和右子節點為樹根的左子樹和右子樹也是二元搜尋樹

在二元搜尋樹中搜尋資料二元搜尋樹對資料大小的掌握十分明確若恰與節點 p 資料相等則搜尋成功若欲搜尋的資料 x 比樹節點 p 中的資料小 則只須搜尋節點 p 的左子樹若比節點 p 中的資料大 則只須搜尋 p 的右子樹這樣的判斷可以遞迴地應用在各子樹節點 中

程式 5-15 在二元搜尋樹中搜尋資料(遞迴呼叫)1 struct BSTreeNode2 struct BSTreeNode leftchild3 int data4 struct BSTreeNode rightchild5 6 struct BSTreeNode root7 struct BSTreeNode SearchBST(

struct BSTreeNode tree int x)8 if (tree == NULL) return NULL9 if (x == tree-gtdata) return tree10 if (x lt tree-gtdata)11 return SearchBST(tree-gtleftchild x)

12 return SearchBST(tree-gtrightchild x)13

程式 5-16 搜尋二元搜尋樹的資料(非遞迴呼叫)在二元搜尋樹十分龐大時遞迴呼叫的效率不會太好可利用迴圈來完成搜尋的動作以提高執行效率請看程式 5-16

14 struct BSTreeNode Search BST_iterative15 (struct BSTreeNode node int x)16 while (node = NULL)17 if (x == node-gtdata) return node18 if (x lt node-gtdata)19 node = node-gtleftchild20 else21 node = node-gtrightchild22 23 return NULL24

592  新增資料於二元搜尋樹中欲新增資料進入一二元搜尋樹中得先決定該資料在二元搜尋樹中應出現的位置這可透過上節提到的搜尋動作來解決

在二元搜尋樹定義的性質 (1) 中我們限定了元素必須相異所以搜尋新資料的結果必是 NULL

必須將搜尋的結果改為新增資料所應在位置的節點指標

程式 5-17  在二元搜尋樹中新增資料25 int InsertBST(int x)26 struct BSTreeNode p q27 p = root q = NULL28 while (p = NULL)29 q = p30 if (x == p-gtdata) return 0

若不考慮重複則可省略之31 if (x lt p-gtdata)32 p = p-gtleftchild33 else34 p = p-gtrightchild35

3636 p = (struct BSTreeNode ) p = (struct BSTreeNode ) malloc (sizeof(BSTreeNode))malloc (sizeof(BSTreeNode))

3737 p-gtdata = x p-gtdata = x3838 p-gtleftchild = p-gtrightchild = p-gtleftchild = p-gtrightchild = NULLNULL3939 if (root == NULL) root = p if (root == NULL) root = p4040 else if (x lt q-gtdata) else if (x lt q-gtdata)4141 q-gtleftchild = p q-gtleftchild = p4242 else else4343 q-gtrightchild = p q-gtrightchild = p4444 return 1 return 145 45

程式 程式 5-175-17  在二元搜尋樹中新增資料  在二元搜尋樹中新增資料 (( 續續 ))

範例 5-23 圖 (b) 為圖 (a) 新增整數 6 之後的結果圖 (c) (d) (e) (f) 則分別是逐步再加入 28 18

10 21 的結果

bull若目前的二元搜尋樹高度為 h 則新增資料得花O(h) 的時間找到其所應在的位置遂新增資料至二元搜尋樹中的時間複雜之度為 O(h)

593 自二元搜尋樹中刪除資料欲自二元搜尋樹中刪除資料可能會面臨兩種情況(1) 刪除節點 p 位於樹葉節點上如下圖所示 這是比較容易處理的情況

(2) 刪除的節點 p 不是樹葉刪除節點 p 後原 p 所指的節點可由 q ( q 中包含了 p 左子樹中的最大節點值)或 r ( r 中包含了 p 右子樹中的最小節點值)來取代以維持二元搜尋樹中資料的大小關係其結果分別如圖 (b) 和 (c) 所示而圖 (d) 則標明了指標會改變的節點位置即 p 會消失 father 的左或右子樹指標會異動 k(可能是 q 或 r )會移到原來 p 的位置其父節點 f 的左或右子樹指標會異動其中 father 為 p 父節點 f 為 k 的父節點

程式 5-18 自二元搜尋樹中刪除資料46 int DeleteBST (int x)47 Struct BSTreeNode p father k f48 p = root father = NULL49 while (p = NULL)50 if (x == p-gtdata) 找到 x 所在的節點 p 了51 if ((p-gtleftchild == NULL) ampamp 52 (p-gtrightchild == NULL))53 k = NULL p為樹葉節點54 else if (p-gtleftchild = NULL )x位於非子節

點55 f = p 找出 p左子樹的最右樹葉56 k = p-gtleftchild57 while (k-gtrightchild =NULL)58 f = k59 k = k-gtrightchild60 k將挪至原 p處61 if (p == f)62 f-gtleftchild = k-gtleftchild 63 else64 f-gtrightchild = k-gtleftchild 65

66 else p無左子樹67 f = p 找出 p右子樹的最左樹葉68 k = p-gtrightchild69 while (k-gtleftchild = NULL)70 f = k71 k = k -gtleftchild72 73 if (p == f)74 f-gtrightchild = k-gtrightchild 75 else76 f-gtleftchild = k-gtrightchild77 78 if (k = NULL) k挪至原 p處繼承 p的左右指標79 k-gtleftchild = p-gtleftchild80 k-gtrightchild = p-gtrightchild81 82 if (father == NULL) root = k83 else if (x lt father-gtdata) 決定 k是 father的左

或右兒子84 father-gtleftchild = k85 else86 father-gtrightchild = k87 free(p)88 return 1 成功地刪除 x於此傳回 1返回呼叫處89 51~89行為找到 x時應有的處理步驟

90 else 尚未找到 x繼續往下一階層找

91 father = p92 if (x lt p-gtdata)93 p = p-gtleftchild94 else95 p = p-gtrightchild96 97 98 return 0 未找到 x傳回 099

510  樹林 (forest)

bull 定義樹林(森林)是由 n 棵沒有交集的樹所構成的集合 n 0

下圖為一包含三棵樹的樹林

5101 轉換樹林為二元樹 之前我們以二元樹來表示樹因為二元樹有較多

好用的性質在此我們也將樹林轉換成二元樹看待

在 522 節中我們嘗試了用左子右兄弟的方式來表示樹這個方法事實上已將樹轉換成二元樹

即用左子右兄弟的結構重新組織樹林中的每一棵樹這些樹的樹根將視為兄弟而最左邊的樹則視為樹林二元樹的樹根如此一來樹林即可轉換成二元樹

範例 5-25右下圖為右上圖三棵樹所對應的三棵左子右兄弟二元樹

下圖則為上圖樹林所對應的一棵樹林二元樹

演算法 5-1  轉換樹林成二元樹輸入樹林 F = T1 T2 Tn

輸出 F 的二元樹表示 BT=(T1 T2 Tn)1 if (n == 0) 輸出空二元樹 2 else

3 root(T1 T2 Tn) = T1

4 leftsubtree(T1 T2 Tn) =

BT(T11 T12 T1m)5 BT(T11 T12 T1m)為 T1的子樹6 rightsubtree(T1 T2 Tn) =

BT(T2 T3 Tn)

樹林轉換成二元樹的過程樹林轉換成二元樹的過程

  • Slide 1
  • Slide 2
  • Slide 3
  • Slide 4
  • Slide 5
  • Slide 6
  • Slide 7
  • Slide 8
  • Slide 9
  • Slide 10
  • Slide 11
  • Slide 12
  • Slide 13
  • Slide 14
  • Slide 15
  • Slide 16
  • Slide 17
  • Slide 18
  • Slide 19
  • Slide 20
  • Slide 21
  • Slide 22
  • Slide 23
  • Slide 24
  • Slide 25
  • Slide 26
  • Slide 27
  • Slide 28
  • Slide 29
  • Slide 30
  • Slide 31
  • Slide 32
  • Slide 33
  • Slide 34
  • Slide 35
  • Slide 36
  • Slide 37
  • Slide 38
  • Slide 39
  • Slide 40
  • Slide 41
  • Slide 42
  • Slide 43
  • Slide 44
  • Slide 45
  • Slide 46
  • Slide 47
  • Slide 48
  • Slide 49
  • Slide 50
  • Slide 51
  • Slide 52
  • Slide 53
  • Slide 54
  • Slide 55
  • Slide 56
  • Slide 57
  • Slide 58
  • Slide 59
  • Slide 60
  • Slide 61
  • Slide 62
  • Slide 63
  • Slide 64
  • Slide 65
  • Slide 66
  • Slide 67
  • Slide 68
  • Slide 69
  • Slide 70
  • Slide 71
  • Slide 72
  • Slide 73
  • Slide 74
  • Slide 75
  • Slide 76
  • Slide 77
  • Slide 78
  • Slide 79
  • Slide 80
  • Slide 81
  • Slide 82
  • Slide 83
  • Slide 84
  • Slide 85
  • Slide 86
  • Slide 87
  • Slide 88
  • Slide 89
  • Slide 90
  • Slide 91
  • Slide 92
  • Slide 93
  • Slide 94
  • Slide 95
  • Slide 96
  • Slide 97
  • Slide 98
  • Slide 99
  • Slide 100
  • Slide 101
  • Slide 102
  • Slide 103
Page 33: 第五章 樹

55  二元樹的走訪bull 假如一組資料已用二元樹的組織在一起總有需求對其全數資料做動作例如計算所有數目印出所有資料在所有資料中搜尋某項資料hellip等此時即須對此二元樹做走訪 (traversal) 的運算利用走訪二元樹的同時將計算列印或搜尋的動作完成

bull 事實上走訪即在決定二元樹上資料被處理(計算列印或搜尋)的順序我們也希望走訪的演算法對任何節點皆一致是容易撰寫程式實作的

bull 若對一個二元樹上的節點而言 V 表示處理節點上的資料 L 表示走訪其左子樹 R 表示走訪其右子樹

55  二元樹的走訪 ( 續 )bull 因為對稱的緣故我們只考慮先走訪左邊再走訪右邊的情形那麼右圖只剩下三種走訪方式

bull 以 V 所在的相對位置分別對此三種走訪方式取名如下

(1) LVR 中序走訪 (inorder traversal)或中序表示法 (infix notation)

(2) LRV 後序走訪 (postorder traversal) 或後序表示法 (postfix notation)

(3) VLR 前序走訪 (preorder traversal)或前序表示法 (prefix notation)

551  中序走訪bull 利用遞迴的方式撰寫中序走訪的程序希望把二元樹中序走訪的

順序印出來

程式 5-3二元樹的中序走訪1 struct BTreeNode2 struct BTreeNode leftchild3 char data4 struct BTreeNode rightchild5 6 struct BTreeNode root7 void inorder(struct BTreeNode node)8 if (node = NULL)9 inorder(node-gtleftchild)10 cout ltlt node-gtdata11 inorder(node-gtrightchild)12 13

範例 5-12此為運算式二元樹

其對應中序表示運算式為 (A+B)C+D(E-F)

6 struct BTreeNode root7 void inorder(struct BTreeNode node)8 if (node = NULL)9 inorder(node-gtleftchild)10 cout ltlt node-gtdata11 inorder(node-gtrightchild)12 13

6 struct BTreeNode root7 void inorder(struct BTreeNode node)8 if (node = NULL)9 inorder(node-gtleftchild)10 cout ltlt node-gtdata11 inorder(node-gtrightchild)12 13

(A+B)C+D(E-F)

如何加上 ( )

552  後序走訪程式 5-4二元樹的後序走訪14 void postorder(struct BTreeNode node)

15 if (node =NULL)16 postorder(node-gtleftchild)17 postorder(node-gtrightchild)18 cout ltlt node-gtdata19 20

以後序走訪可得到AB+CDEF-+

其正為對應的後序運算式

553  前序走訪程式 5-5二元樹的前序走訪21 void preorder(struct BTreeNode node)

22 if (node = NULL)

23 cout ltlt node-gtdata

24 preorder(node-gtleftchild)

25 preorder(node-gtrightchild)

26

27

以前序走訪可得到++ABCD-EF

其正為對應的前序運算式

554 利用堆疊和迴圈取代遞迴進行二元樹的走訪1 struct BTreeNode 2 struct BTreeNode leftchild3 char data4 struct BtreeNode rightchild5 6 struct BTreeNode root7 struct StackNode8 struct BTreeNode treenode9 struct StackNode next1011 struct StackNode top

12 void push_node(struct BtreeNode node )13 struct StackNode old_top14 old_top = top15 top = (struct StackNode ) malloc

(sizeof(struct StackNode))16 top-gttreenode = node17 top-gtnext = old_top18 19 struct BTreeNode pop_node()20 struct BTreeNode Tnode21 struct StackNode old_top22 if (top == NULL)23 StackEmpty()24 else25 Tnode = top-gttreenode26 old_top = top27 top = top-gtnext28 free(old_top)29 return Tnode30 31

32 void inorder_Stack(struct BtreeNode node)33 do34 while (node = NULL)35 push_node(node)36 node = node-gtleftchild37 push all left children38 if (top = NULL)39 node = pop_node()40 cout ltlt node-gtdata41 node = node-gtrightchild42 inorder printing and check right child43 while ((top=NULL)||(node=NULL))44 top==NULL 堆疊已空 node==NULL沒有右子

6 struct BTreeNode root7 void inorder(struct BTreeNode node)8 if (node = NULL)9 inorder(node-gtleftchild)10 cout ltlt node-gtdata11 inorder(node-gtrightchild)12 13

中序表示為DBGEHAFIC

32 void inorder_Stack()33 struct BTreeNode node34 node = root35 do36 while (node = NULL)37 push_node(node)38 node = node-gtleftchild39 40 if (top = NULL)41 node = pop_node()42 cout ltlt node-gtdata43 node = node-gtrightchild44 45 while(top=NULL)||(node=NULL))46

45 void Preorder_Stack(struct BSTreeNode node)46 do47 while (node = NULL)48 cout ltlt node-gtdata)49 push_node(node)50 node = node-gtleftchild51 push all left children52 if (top = NULL)53 node = pop_node()54 node = node-gtrightchild55 56 while ((top=NULL)||(node=NULL))57

程式 5-5二元樹的前序走訪21 void preorder(struct BTreeNode node)22 if (node = NULL)23 cout ltlt node-gtdata24 preorder(node-gtleftchild)25 preorder(node-gtrightchild)26 27

58 void Postorder_Stack(struct BTreeNode node)59 do60 while (node = NULL)61 cout ltlt node-gtdata62 push_node(node)63 node = node-gtrightchild64 push all left children65 if (top = NULL)66 node = pop_node()67 node = node-gtleftchild68 69 while ((top=NULL)||(node=NULL))70

程式 5-4二元樹的後序走訪14 void postorder(struct BTreeNode node)15 if (node =NULL)16 postorder(node-gtleftchild)17 postorder(node-gtrightchild)18 cout ltlt node-gtdata19 20

555  階層走訪bull 「階層走訪」 (level-order traversal) 是依階層

的順序進行二元樹的走訪先走訪階層小的節點後走訪階層大的節點同一階層者則依自左向右的順序走訪

bull 對下圖的二元樹進行階層走訪的結果為ABCDEFGHI

bull 對同一階層而言先走訪的節點其子節點亦在下一階層中先被走訪

bull 這種「先進先出」的特性恰可用佇列予以儲存與處理

程式 5-7 利用佇列進行二元樹的階層走訪

1 struct BTreeNode2 struct BTreeNode leftchild3 char data4 struct BTreeNode rightchild5 6 struct BTreeNode root7 struct QNode8 struct BTreeNode treenode9 struct QNode next10 11 struct QNode front rear

12 void AddQueue(struct BTreeNode Tnode)13 struct QNode node14 node=(struct QNode )malloc(sizeof(struct QNode))15 node-gttreenode = Tnode16 node-gtnext = NULL17 if (front == NULL) front = node19 else rear-gtnext = node20 rear = node21

22 struct BTreeNode DeleteQueue()23 struct BTreeNode Tnode24 struct QNode old_front25 if (rear == NULL)26 QueueEmpty()27 else28 Tnode = front-gttreenode29 old_front = front30 front = front-gtnext31 free(old_front)32 return Tnode33 34 35 void LevelOrder (struct BTreeNode node)36 AddQueue(node)37 while (front =NULL)38 node = DeleteQueue()39 cout ltlt node-gtdata40 if (node-gtleftchild = NULL)41 AddQueue(node-gtleftchild)42 if (node-gtrightchild = NULL)43 AddQueue(node-gtrightchild)44 45

35 void LevelOrder (struct BTreeNode node)36 AddQueue(node)37 while (front =NULL)38 node = DeleteQueue()39 cout ltlt node-gtdata40 if (node-gtleftchild = NULL)41 AddQueue(node-gtleftchild)42 if (node-gtrightchild = NULL)43 AddQueue(node-gtrightchild)44 45

bull Infix ABCbull Postfix ABC CBA ACB BCA

BAC CAB

B

A CA

B

C

B

A

C

C

B

AB

A

C

二元樹中序與前序式中序與後序式bull 每一棵二元樹皆有唯一的一對中序與前序表示法也有唯一的中序與後序次序

bull 換句話說給予一對中序與前序或中序與後序即可決定一棵二元樹

bull 然而給予前序和後序次序並不能決定唯一的二元樹可能會產生兩棵不同的二元樹

bull 想想階層走訪和中序走訪順序可否決定唯一的二元樹

A

B C

H J D E

F G

prefix ABHJCDFGEinfix HBJAFDGCEpostfix HJBFGDECA

VLRLVRLRV

A

prefix BHJinfix HBJ

prefix CDFGEinfix FDGCE

prefix ABHJCDFGEinfix HBJAFDGCE

A

B C

H J D E

F G

prefix ABHJCDFGE

infix HBJAFDGCEk-1

struct BTreeNode BTree_InfixPrefix (AnsiString infix AnsiString prefix)

int k struct BTreeNode node if (infixLength==0) return NULLnode = (struct BTreeNode ) malloc (sizeof(struct BTreeNode))node-gtdata = prefix[1]k = find(prefix[1] infix)node-gtleftchild = BTree_InfixPrefix

(infix[1k-1] prefix[2k])node-gtrightchild = BTree_InfixPrefix(infix[k+1infixLength]prefix[k+1prefixLength])

VLR

LVRk1

k2 k+1~

k+1~

1

struct BTreeNode BTree_InfixPostfix (AnsiString infix AnsiString prefix)

int k struct BTreeNode node if (infixLength==0) return NULLnode = (struct BTreeNode ) malloc (sizeof(struct BTreeNode))node-gtdata = postfix[postfixLength]k = find(postfix[postfixLength] infix)node-gtleftchild = BTree_InfixPrefix

(infix[1k-1] postfix[1k-1])node-gtrightchild = BTree_InfixPrefix(infix[k+1infixLength]postfix[k+1postfixLength-1])

56  二元樹的複製與相等測試程式 5-8 二元樹的複製1 struct BTreeNode2 struct BTreeNode leftchild3 char data4 struct BTreeNode rightchild5 6 struct BTreeNode root7 struct BTreeNode Copy(struct BTreeNode TreeRoot)8 struct BTreeNode CopyRoot9 if (TreeRoot == NULL)10 return NULL11 CopyRoot =(struct BTreeNode ) malloc (sizeof(BTreeNode))12 CopyRoot-gtdata = TreeRoot-gtdata13 CopyRoot-gtleftchild = Copy(TreeRoot-gtleftchild)14 CopyRoot-gtrightchild = Copy(TreeRoot-

gtrightchild)15 return CopyRoot16

程式 5-9 檢測兩棵二元樹是否相等1 struct BTreeNode2 struct BTreeNode leftchild3 char data4 struct BTreeNode rightchild5 6 struct BtreeNode rightchild7 int Equal(struct BTreeNode ustruct BTreeNode v)8 if ((u==NULL) ampamp (v==NULL)) return 19 if ((u=NULL) ampamp (v=NULL)10 ampamp (u-gtdata == v-gtdata )11 ampamp Equal(u-gtleftchildv-gtleftchild)12 ampamp Equal(u-gtrightchildv-gtrightchild))

13 return 114 return 015

Counting Nodes in a Binary Tree

int CountingNodes(struct BTreeNodes node)

if (node == NULL) return 0 int leftchildren = CountingNodes(node-gtleftchild)

int rightchildren= CountingNodes(node-gtrightchild)

return leftchildren+rightchildren

Counting Leaves in a Binary Tree

int CountingLeaves(struct BTreeNodes node)

if (node == NULL) return 0 int leftleaves = CountingLeaves(node-gtleftchild)

int rightchildren= CountingLeaves(node-gtrightchild)

if (leftleaves==0 ampamp leftleaves==0) return 1

return leftleaves + leftleaves

57  引線二元樹bull 在二元樹的鍵結表示中樹葉節點的兩個子樹指標皆指向 NULL 由定理 5-1 得知 n = E+1 其中 n 為節點個數 E 為分支數

bull 而 n 個鍵結節點有 2n 個指標空間每個分支恰佔用一個指標空間共有 E (=n-1) 個指標有用到(不是空的)而共有 n+1 個指標空間是 NULL 比非空的節點還多

bull 有學者提出對這些「放空指標的空間」加以利用的概念mdash與其放空指標不如放指向其它節點的指標稱之為引線 (thread) 使得某些運算(如走訪hellip等)可以加快

bull 這個概念發展出了引線二元樹 (threaded binary tree) 這種資料結構

範例 5-17下圖為加了引線的二元樹

加入的引線將使二元樹的中序走訪更加便利沿著上右圖箭頭所指示的順序走訪即可完成此二元樹的中序走訪GDHBAIECF 為此二元樹的中序表示

bull 範例 5-17 中所加的引線目的在方便二元樹的中序走訪

bull 對樹葉節點 p 而言令其資料為 P 則引線指標的規則如下

(1) p 的左子樹指標指向中序表示中 P 的前一個 元素(前接元素 (predecessor) )

(2) p 的右子樹指標指向中序表示中 P 的後一個 元素(後繼元素 (successor) )

為了區別節點指標放的是一般指標(內部節點)抑或是引線指標(樹葉節點)我們另以 2 個欄位分別區別左和右子樹指標空間存放的對象其節點的記憶體配置有如下圖所示

其中 leftthread (rightthread) 為 0 時表示 leftchild (rightchild) 存放的是一般節點指標而 leftthread (rightthread) 為 1 時表示 leftchild (rightchild) 存放的是引線指標1 struct TBTreeNode2 int leftthread3 struct TBTreeNode leftchild4 char data5 struct TBTreeNode rightchild6 int rightthread7

由右側的引線二元樹可知有最左和最右兩條引線尚無妥善的安排我們另設計一空的引線節點做為樹根如下圖所示

571  引線二元樹的中序走訪決定節點 p 在中序表示中的後繼節點 q (簡稱中序後繼點)

程式 5-10決定節點 node的中序後繼點1 struct TBTreeNode2 int leftthread3 struct TBTreeNode leftchild4 char data5 struct TBTreeNode rightchild6 int rightthread7 8 struct TBTreeNode root9 struct TBTreeNode Next(struct TBTreeNode node)

10 struct TBTreeNode temp11 temp = node-gtrightchild12 if (node-gtrightthead) return temp13 while (temp-gtleftthead) temp=temp-gtleftchild

14 return temp15

程式 5-11引線二元樹的中序表示(接程式 5-10)1 void InorderTBTree()2 struct TBTreeNode node3 for(node=Next(root)node=rootnode=Next(node))

4 cout ltlt node-gtdata5

bull只要 node 是引線二元樹上的任一節點透過呼叫 Next(node) 即可找到其在中序表示中的後繼節點bull(試想若程式 5-10 中的程序 Next 所傳入的參數指標正是 root Next(root) 是否可正常運作請思考)bull於是只須從中序表示中的第一節點起讓每一節點 node 都執行 Next(node) 程序即可得引線二元樹的中序表示

572  在引線二元樹中加入節點在引線二元樹中樹葉節點節點的右子樹指標所指向或內部節點右子樹的最左樹葉為其中序後繼節點而且任一節點若無左子樹則其左子樹指標乃指向其中序前接節點hellip這些性質必須在節點新增進入引線二元樹時也要保持接下來討論節點 new 插入成為引線二元樹的節點 p 的右子節點的情形至於插入成為左子節點的情形則讓各位模擬推敲

572572  在引線二元樹中加入節點 在引線二元樹中加入節點 (( 續續 ))

newnew 會成為會成為 pp 的右子節點的情況共有以下兩種的右子節點的情況共有以下兩種(1) (1) 若若 pp 沒有右子節點 沒有右子節點 ((p-gtrightthread p-gtrightthread 為為1) 1) 如圖如圖 ((a) a) 則 則 new new 插入後應形成如圖插入後應形成如圖 ((b) b) 所示所示

(2)(2) 若若 pp 有右子節點 有右子節點 ((p-gtrightthread p-gtrightthread 為為0) 0) 如圖如圖 ((c) c) 則 則 new new 插入後應形成如插入後應形成如圖圖 ((d) d) 所示所示

程式 5-12  插入節點 new 成為引線二元樹中節點 p的右子節點(上接程式 5-10 5-11 )

1 void InsertRight(struct TBTreeNode p 2 struct TBTreeNode new)3 struct TBTreeNode q4 new-gtrightchild = p-gtrightchild5 new-gtrightthead = p-gtrightthread6 new-gtleftchild = p7 new-gtleftthread = 18 p-gtrightchild = new9 p-gtrightthead = 010 if (new-gtrightthread)11 q = Next(new)12 q-gtleftchild = new13 14

58  堆積bull 「堆積」 (heap) 是一個特殊的完備二元樹節點的資料與其子節點的資料之間有事先定好的大小關係存在bull 定義ndash最大堆疊為一完備二元樹任一節點的資料內容不小於其子節點的資料內容ndash最小堆疊為一完備二元樹任一節點的資料內容不大於其子節點的資料內容

581  插入節點至最大堆積中最大堆積是一完備二元樹所以如圖 (a) 之最大堆積於加入新節點後樹的形狀必須如圖 (b) 所示灰色節點即為新節點若加入的節點資料分別為 1 9 12 則其最大堆積將分別變成如圖 (c) (d) (e) 所示

程式 5-13 新增節點至最大堆積中最大堆積本身是一棵完備二元樹可以用陣列表示之 1 define maxsize 1002 int heap[maxsize]3 int n i4 void InsertHeap(int x)5 if (n == maxsize) HeapFull()6 else7 n++8 i = n9 while ((i gt 1) ampamp (x gt heap[i2]))10 heap[i] = heap[i2]11 i = 212 13 heap[i] = x14 16

程序 InsertHeap 在插入第 n 個資料時需要的時間複雜度為 O(logn)

1 100

2 93

3 84

4 77

5 65

6 81

7 79

8 66

9 70

10 50

100

84

77

93

8165

66 70

79

50

1

2 3

4 5 6 7

8 9 10

1 define maxsize 1002 int heap[maxsize]3 int n i4 void InsertHeap(int x)5 if (n == maxsize) HeapFull()6 else7 n++8 i = n9 while ((i gt 1) ampamp (x gt heap[i2]))10 heap[i] = heap[i2]11 i = 212 13 heap[i] = x14 16

582 刪除最大堆積中的最大資料圖 (a) 中的最大堆積在刪除最大資料後其樹形應變成如圖 (b) 所示將最未一個樹葉節點的資料 6 暫挪至樹根節點內如圖 (c) 再將圖 (c) 中非最大堆積的結構調整成為最大堆積 10 和 8 中選大者與 6對調即成圖 (d)

程式 5-14 刪除最大堆積中的最大資料(上接程式 5-13 )1 int DeleteHeap()2 int x i j3 if (n == 0) HeapEmpty()4 else5 x = heap[1]6 heap[1] = heap[n]7 n--8 i = 19 while (i lt= n2)10 if (heap[2i] gt heap[2i+1])11 j = 2i12 else13 j = 2i+114 if (heap[i]gtheap[j]) break15 swap(heap[i] heap[j])16 i = j17 18 return x19 20

其時間複雜度與其堆積高度相同是為O(logn) 其中 n 為堆積中的元素個數

1 100

2 93

3 84

4 77

5 65

6 81

7 79

8 66

9 70

10 50

100

84

77

93

8165

66 70

79

50

1

2 3

4 5 6 7

8 9 10

1 int DeletHeap()2 int x i j3 if(n == 0) HeapEmpty()4 else5 x = heap[1]6 heap[1] = heap[n]7 n--8 i = 19 while (i lt= n2)10 if ([heap[2i]] gt heap[2i+1])11 j = 2i12 else13 j = 2i+114 if (heap[i]gtheap[j]) break15 swap(heap[i] heap[j])16 i = j17 18 return x19 20

583  優先佇列bull 由於最大堆積(或最小堆積)的結構可提供最大元素(或最小元素)而其維護堆積的成本為 O(logn) n 為總元素個數

bull 堆積常被當成「優先佇列」 (priority queue) 使用mdash此時佇列被組織成完備二元樹若新加入佇列的資料有大小關係優先順序的需求時此優先佇列即以 O(logn) 的代價將新資料放入優先佇列(可能是最大或最小堆積依實際大小關係優先順序的需求而定)中mdash提供依元素的優先順序做為處理順序的服務

bull 一般的佇列為先進先出先到的先行處理優先佇列則是依據其優先順序優先順序高者先行處理與來到的先後無關

範例 5-21bull 在「分時」 (time sharing) 的作業系統中我們不希望使用中央處理器 (Central Processing Unit CPU) 時間短的「處理程序」 (process) 等候過久於是有「最短時間工作優先」 (shortest-job-first) 的排程策略此時等候的處理程序即可組織成之一最小堆積而 CPU 每次即取出存於樹根的處理程序予以處理任何新來的處理程序則以 O(logn) 的時間加入此優先佇列(最小堆積)中

bull 注意之前提過的線性佇列新來的直接加入成為新的尾端元素花O(1) 的時間然線性佇列本身沒有大小的關係每次要取出最大(小)的資料得花O(n) 的代價(在所有元素中挑出最大(小)者) n 為佇列內的元素個數而優先佇列是在新元素加入時要花O(logn) 的時間至於取出最大(小)則只須 O(1) 的時間

59  二元搜尋樹bull 二元搜尋樹是一棵二元樹可能是空二元樹若不為空二元樹則滿足下列性質ndash所有節點內的資料內容是相異的 ndash左子節點的資內容 ( 如果有 ) 比父節點的資料內容小 ndash右子節點的資料內容 ( 如果有 ) 比父節點的資料內容大 ndash以左和右子節點為樹根的左子樹和右子樹也是二元搜尋樹

在二元搜尋樹中搜尋資料二元搜尋樹對資料大小的掌握十分明確若恰與節點 p 資料相等則搜尋成功若欲搜尋的資料 x 比樹節點 p 中的資料小 則只須搜尋節點 p 的左子樹若比節點 p 中的資料大 則只須搜尋 p 的右子樹這樣的判斷可以遞迴地應用在各子樹節點 中

程式 5-15 在二元搜尋樹中搜尋資料(遞迴呼叫)1 struct BSTreeNode2 struct BSTreeNode leftchild3 int data4 struct BSTreeNode rightchild5 6 struct BSTreeNode root7 struct BSTreeNode SearchBST(

struct BSTreeNode tree int x)8 if (tree == NULL) return NULL9 if (x == tree-gtdata) return tree10 if (x lt tree-gtdata)11 return SearchBST(tree-gtleftchild x)

12 return SearchBST(tree-gtrightchild x)13

程式 5-16 搜尋二元搜尋樹的資料(非遞迴呼叫)在二元搜尋樹十分龐大時遞迴呼叫的效率不會太好可利用迴圈來完成搜尋的動作以提高執行效率請看程式 5-16

14 struct BSTreeNode Search BST_iterative15 (struct BSTreeNode node int x)16 while (node = NULL)17 if (x == node-gtdata) return node18 if (x lt node-gtdata)19 node = node-gtleftchild20 else21 node = node-gtrightchild22 23 return NULL24

592  新增資料於二元搜尋樹中欲新增資料進入一二元搜尋樹中得先決定該資料在二元搜尋樹中應出現的位置這可透過上節提到的搜尋動作來解決

在二元搜尋樹定義的性質 (1) 中我們限定了元素必須相異所以搜尋新資料的結果必是 NULL

必須將搜尋的結果改為新增資料所應在位置的節點指標

程式 5-17  在二元搜尋樹中新增資料25 int InsertBST(int x)26 struct BSTreeNode p q27 p = root q = NULL28 while (p = NULL)29 q = p30 if (x == p-gtdata) return 0

若不考慮重複則可省略之31 if (x lt p-gtdata)32 p = p-gtleftchild33 else34 p = p-gtrightchild35

3636 p = (struct BSTreeNode ) p = (struct BSTreeNode ) malloc (sizeof(BSTreeNode))malloc (sizeof(BSTreeNode))

3737 p-gtdata = x p-gtdata = x3838 p-gtleftchild = p-gtrightchild = p-gtleftchild = p-gtrightchild = NULLNULL3939 if (root == NULL) root = p if (root == NULL) root = p4040 else if (x lt q-gtdata) else if (x lt q-gtdata)4141 q-gtleftchild = p q-gtleftchild = p4242 else else4343 q-gtrightchild = p q-gtrightchild = p4444 return 1 return 145 45

程式 程式 5-175-17  在二元搜尋樹中新增資料  在二元搜尋樹中新增資料 (( 續續 ))

範例 5-23 圖 (b) 為圖 (a) 新增整數 6 之後的結果圖 (c) (d) (e) (f) 則分別是逐步再加入 28 18

10 21 的結果

bull若目前的二元搜尋樹高度為 h 則新增資料得花O(h) 的時間找到其所應在的位置遂新增資料至二元搜尋樹中的時間複雜之度為 O(h)

593 自二元搜尋樹中刪除資料欲自二元搜尋樹中刪除資料可能會面臨兩種情況(1) 刪除節點 p 位於樹葉節點上如下圖所示 這是比較容易處理的情況

(2) 刪除的節點 p 不是樹葉刪除節點 p 後原 p 所指的節點可由 q ( q 中包含了 p 左子樹中的最大節點值)或 r ( r 中包含了 p 右子樹中的最小節點值)來取代以維持二元搜尋樹中資料的大小關係其結果分別如圖 (b) 和 (c) 所示而圖 (d) 則標明了指標會改變的節點位置即 p 會消失 father 的左或右子樹指標會異動 k(可能是 q 或 r )會移到原來 p 的位置其父節點 f 的左或右子樹指標會異動其中 father 為 p 父節點 f 為 k 的父節點

程式 5-18 自二元搜尋樹中刪除資料46 int DeleteBST (int x)47 Struct BSTreeNode p father k f48 p = root father = NULL49 while (p = NULL)50 if (x == p-gtdata) 找到 x 所在的節點 p 了51 if ((p-gtleftchild == NULL) ampamp 52 (p-gtrightchild == NULL))53 k = NULL p為樹葉節點54 else if (p-gtleftchild = NULL )x位於非子節

點55 f = p 找出 p左子樹的最右樹葉56 k = p-gtleftchild57 while (k-gtrightchild =NULL)58 f = k59 k = k-gtrightchild60 k將挪至原 p處61 if (p == f)62 f-gtleftchild = k-gtleftchild 63 else64 f-gtrightchild = k-gtleftchild 65

66 else p無左子樹67 f = p 找出 p右子樹的最左樹葉68 k = p-gtrightchild69 while (k-gtleftchild = NULL)70 f = k71 k = k -gtleftchild72 73 if (p == f)74 f-gtrightchild = k-gtrightchild 75 else76 f-gtleftchild = k-gtrightchild77 78 if (k = NULL) k挪至原 p處繼承 p的左右指標79 k-gtleftchild = p-gtleftchild80 k-gtrightchild = p-gtrightchild81 82 if (father == NULL) root = k83 else if (x lt father-gtdata) 決定 k是 father的左

或右兒子84 father-gtleftchild = k85 else86 father-gtrightchild = k87 free(p)88 return 1 成功地刪除 x於此傳回 1返回呼叫處89 51~89行為找到 x時應有的處理步驟

90 else 尚未找到 x繼續往下一階層找

91 father = p92 if (x lt p-gtdata)93 p = p-gtleftchild94 else95 p = p-gtrightchild96 97 98 return 0 未找到 x傳回 099

510  樹林 (forest)

bull 定義樹林(森林)是由 n 棵沒有交集的樹所構成的集合 n 0

下圖為一包含三棵樹的樹林

5101 轉換樹林為二元樹 之前我們以二元樹來表示樹因為二元樹有較多

好用的性質在此我們也將樹林轉換成二元樹看待

在 522 節中我們嘗試了用左子右兄弟的方式來表示樹這個方法事實上已將樹轉換成二元樹

即用左子右兄弟的結構重新組織樹林中的每一棵樹這些樹的樹根將視為兄弟而最左邊的樹則視為樹林二元樹的樹根如此一來樹林即可轉換成二元樹

範例 5-25右下圖為右上圖三棵樹所對應的三棵左子右兄弟二元樹

下圖則為上圖樹林所對應的一棵樹林二元樹

演算法 5-1  轉換樹林成二元樹輸入樹林 F = T1 T2 Tn

輸出 F 的二元樹表示 BT=(T1 T2 Tn)1 if (n == 0) 輸出空二元樹 2 else

3 root(T1 T2 Tn) = T1

4 leftsubtree(T1 T2 Tn) =

BT(T11 T12 T1m)5 BT(T11 T12 T1m)為 T1的子樹6 rightsubtree(T1 T2 Tn) =

BT(T2 T3 Tn)

樹林轉換成二元樹的過程樹林轉換成二元樹的過程

  • Slide 1
  • Slide 2
  • Slide 3
  • Slide 4
  • Slide 5
  • Slide 6
  • Slide 7
  • Slide 8
  • Slide 9
  • Slide 10
  • Slide 11
  • Slide 12
  • Slide 13
  • Slide 14
  • Slide 15
  • Slide 16
  • Slide 17
  • Slide 18
  • Slide 19
  • Slide 20
  • Slide 21
  • Slide 22
  • Slide 23
  • Slide 24
  • Slide 25
  • Slide 26
  • Slide 27
  • Slide 28
  • Slide 29
  • Slide 30
  • Slide 31
  • Slide 32
  • Slide 33
  • Slide 34
  • Slide 35
  • Slide 36
  • Slide 37
  • Slide 38
  • Slide 39
  • Slide 40
  • Slide 41
  • Slide 42
  • Slide 43
  • Slide 44
  • Slide 45
  • Slide 46
  • Slide 47
  • Slide 48
  • Slide 49
  • Slide 50
  • Slide 51
  • Slide 52
  • Slide 53
  • Slide 54
  • Slide 55
  • Slide 56
  • Slide 57
  • Slide 58
  • Slide 59
  • Slide 60
  • Slide 61
  • Slide 62
  • Slide 63
  • Slide 64
  • Slide 65
  • Slide 66
  • Slide 67
  • Slide 68
  • Slide 69
  • Slide 70
  • Slide 71
  • Slide 72
  • Slide 73
  • Slide 74
  • Slide 75
  • Slide 76
  • Slide 77
  • Slide 78
  • Slide 79
  • Slide 80
  • Slide 81
  • Slide 82
  • Slide 83
  • Slide 84
  • Slide 85
  • Slide 86
  • Slide 87
  • Slide 88
  • Slide 89
  • Slide 90
  • Slide 91
  • Slide 92
  • Slide 93
  • Slide 94
  • Slide 95
  • Slide 96
  • Slide 97
  • Slide 98
  • Slide 99
  • Slide 100
  • Slide 101
  • Slide 102
  • Slide 103
Page 34: 第五章 樹

55  二元樹的走訪 ( 續 )bull 因為對稱的緣故我們只考慮先走訪左邊再走訪右邊的情形那麼右圖只剩下三種走訪方式

bull 以 V 所在的相對位置分別對此三種走訪方式取名如下

(1) LVR 中序走訪 (inorder traversal)或中序表示法 (infix notation)

(2) LRV 後序走訪 (postorder traversal) 或後序表示法 (postfix notation)

(3) VLR 前序走訪 (preorder traversal)或前序表示法 (prefix notation)

551  中序走訪bull 利用遞迴的方式撰寫中序走訪的程序希望把二元樹中序走訪的

順序印出來

程式 5-3二元樹的中序走訪1 struct BTreeNode2 struct BTreeNode leftchild3 char data4 struct BTreeNode rightchild5 6 struct BTreeNode root7 void inorder(struct BTreeNode node)8 if (node = NULL)9 inorder(node-gtleftchild)10 cout ltlt node-gtdata11 inorder(node-gtrightchild)12 13

範例 5-12此為運算式二元樹

其對應中序表示運算式為 (A+B)C+D(E-F)

6 struct BTreeNode root7 void inorder(struct BTreeNode node)8 if (node = NULL)9 inorder(node-gtleftchild)10 cout ltlt node-gtdata11 inorder(node-gtrightchild)12 13

6 struct BTreeNode root7 void inorder(struct BTreeNode node)8 if (node = NULL)9 inorder(node-gtleftchild)10 cout ltlt node-gtdata11 inorder(node-gtrightchild)12 13

(A+B)C+D(E-F)

如何加上 ( )

552  後序走訪程式 5-4二元樹的後序走訪14 void postorder(struct BTreeNode node)

15 if (node =NULL)16 postorder(node-gtleftchild)17 postorder(node-gtrightchild)18 cout ltlt node-gtdata19 20

以後序走訪可得到AB+CDEF-+

其正為對應的後序運算式

553  前序走訪程式 5-5二元樹的前序走訪21 void preorder(struct BTreeNode node)

22 if (node = NULL)

23 cout ltlt node-gtdata

24 preorder(node-gtleftchild)

25 preorder(node-gtrightchild)

26

27

以前序走訪可得到++ABCD-EF

其正為對應的前序運算式

554 利用堆疊和迴圈取代遞迴進行二元樹的走訪1 struct BTreeNode 2 struct BTreeNode leftchild3 char data4 struct BtreeNode rightchild5 6 struct BTreeNode root7 struct StackNode8 struct BTreeNode treenode9 struct StackNode next1011 struct StackNode top

12 void push_node(struct BtreeNode node )13 struct StackNode old_top14 old_top = top15 top = (struct StackNode ) malloc

(sizeof(struct StackNode))16 top-gttreenode = node17 top-gtnext = old_top18 19 struct BTreeNode pop_node()20 struct BTreeNode Tnode21 struct StackNode old_top22 if (top == NULL)23 StackEmpty()24 else25 Tnode = top-gttreenode26 old_top = top27 top = top-gtnext28 free(old_top)29 return Tnode30 31

32 void inorder_Stack(struct BtreeNode node)33 do34 while (node = NULL)35 push_node(node)36 node = node-gtleftchild37 push all left children38 if (top = NULL)39 node = pop_node()40 cout ltlt node-gtdata41 node = node-gtrightchild42 inorder printing and check right child43 while ((top=NULL)||(node=NULL))44 top==NULL 堆疊已空 node==NULL沒有右子

6 struct BTreeNode root7 void inorder(struct BTreeNode node)8 if (node = NULL)9 inorder(node-gtleftchild)10 cout ltlt node-gtdata11 inorder(node-gtrightchild)12 13

中序表示為DBGEHAFIC

32 void inorder_Stack()33 struct BTreeNode node34 node = root35 do36 while (node = NULL)37 push_node(node)38 node = node-gtleftchild39 40 if (top = NULL)41 node = pop_node()42 cout ltlt node-gtdata43 node = node-gtrightchild44 45 while(top=NULL)||(node=NULL))46

45 void Preorder_Stack(struct BSTreeNode node)46 do47 while (node = NULL)48 cout ltlt node-gtdata)49 push_node(node)50 node = node-gtleftchild51 push all left children52 if (top = NULL)53 node = pop_node()54 node = node-gtrightchild55 56 while ((top=NULL)||(node=NULL))57

程式 5-5二元樹的前序走訪21 void preorder(struct BTreeNode node)22 if (node = NULL)23 cout ltlt node-gtdata24 preorder(node-gtleftchild)25 preorder(node-gtrightchild)26 27

58 void Postorder_Stack(struct BTreeNode node)59 do60 while (node = NULL)61 cout ltlt node-gtdata62 push_node(node)63 node = node-gtrightchild64 push all left children65 if (top = NULL)66 node = pop_node()67 node = node-gtleftchild68 69 while ((top=NULL)||(node=NULL))70

程式 5-4二元樹的後序走訪14 void postorder(struct BTreeNode node)15 if (node =NULL)16 postorder(node-gtleftchild)17 postorder(node-gtrightchild)18 cout ltlt node-gtdata19 20

555  階層走訪bull 「階層走訪」 (level-order traversal) 是依階層

的順序進行二元樹的走訪先走訪階層小的節點後走訪階層大的節點同一階層者則依自左向右的順序走訪

bull 對下圖的二元樹進行階層走訪的結果為ABCDEFGHI

bull 對同一階層而言先走訪的節點其子節點亦在下一階層中先被走訪

bull 這種「先進先出」的特性恰可用佇列予以儲存與處理

程式 5-7 利用佇列進行二元樹的階層走訪

1 struct BTreeNode2 struct BTreeNode leftchild3 char data4 struct BTreeNode rightchild5 6 struct BTreeNode root7 struct QNode8 struct BTreeNode treenode9 struct QNode next10 11 struct QNode front rear

12 void AddQueue(struct BTreeNode Tnode)13 struct QNode node14 node=(struct QNode )malloc(sizeof(struct QNode))15 node-gttreenode = Tnode16 node-gtnext = NULL17 if (front == NULL) front = node19 else rear-gtnext = node20 rear = node21

22 struct BTreeNode DeleteQueue()23 struct BTreeNode Tnode24 struct QNode old_front25 if (rear == NULL)26 QueueEmpty()27 else28 Tnode = front-gttreenode29 old_front = front30 front = front-gtnext31 free(old_front)32 return Tnode33 34 35 void LevelOrder (struct BTreeNode node)36 AddQueue(node)37 while (front =NULL)38 node = DeleteQueue()39 cout ltlt node-gtdata40 if (node-gtleftchild = NULL)41 AddQueue(node-gtleftchild)42 if (node-gtrightchild = NULL)43 AddQueue(node-gtrightchild)44 45

35 void LevelOrder (struct BTreeNode node)36 AddQueue(node)37 while (front =NULL)38 node = DeleteQueue()39 cout ltlt node-gtdata40 if (node-gtleftchild = NULL)41 AddQueue(node-gtleftchild)42 if (node-gtrightchild = NULL)43 AddQueue(node-gtrightchild)44 45

bull Infix ABCbull Postfix ABC CBA ACB BCA

BAC CAB

B

A CA

B

C

B

A

C

C

B

AB

A

C

二元樹中序與前序式中序與後序式bull 每一棵二元樹皆有唯一的一對中序與前序表示法也有唯一的中序與後序次序

bull 換句話說給予一對中序與前序或中序與後序即可決定一棵二元樹

bull 然而給予前序和後序次序並不能決定唯一的二元樹可能會產生兩棵不同的二元樹

bull 想想階層走訪和中序走訪順序可否決定唯一的二元樹

A

B C

H J D E

F G

prefix ABHJCDFGEinfix HBJAFDGCEpostfix HJBFGDECA

VLRLVRLRV

A

prefix BHJinfix HBJ

prefix CDFGEinfix FDGCE

prefix ABHJCDFGEinfix HBJAFDGCE

A

B C

H J D E

F G

prefix ABHJCDFGE

infix HBJAFDGCEk-1

struct BTreeNode BTree_InfixPrefix (AnsiString infix AnsiString prefix)

int k struct BTreeNode node if (infixLength==0) return NULLnode = (struct BTreeNode ) malloc (sizeof(struct BTreeNode))node-gtdata = prefix[1]k = find(prefix[1] infix)node-gtleftchild = BTree_InfixPrefix

(infix[1k-1] prefix[2k])node-gtrightchild = BTree_InfixPrefix(infix[k+1infixLength]prefix[k+1prefixLength])

VLR

LVRk1

k2 k+1~

k+1~

1

struct BTreeNode BTree_InfixPostfix (AnsiString infix AnsiString prefix)

int k struct BTreeNode node if (infixLength==0) return NULLnode = (struct BTreeNode ) malloc (sizeof(struct BTreeNode))node-gtdata = postfix[postfixLength]k = find(postfix[postfixLength] infix)node-gtleftchild = BTree_InfixPrefix

(infix[1k-1] postfix[1k-1])node-gtrightchild = BTree_InfixPrefix(infix[k+1infixLength]postfix[k+1postfixLength-1])

56  二元樹的複製與相等測試程式 5-8 二元樹的複製1 struct BTreeNode2 struct BTreeNode leftchild3 char data4 struct BTreeNode rightchild5 6 struct BTreeNode root7 struct BTreeNode Copy(struct BTreeNode TreeRoot)8 struct BTreeNode CopyRoot9 if (TreeRoot == NULL)10 return NULL11 CopyRoot =(struct BTreeNode ) malloc (sizeof(BTreeNode))12 CopyRoot-gtdata = TreeRoot-gtdata13 CopyRoot-gtleftchild = Copy(TreeRoot-gtleftchild)14 CopyRoot-gtrightchild = Copy(TreeRoot-

gtrightchild)15 return CopyRoot16

程式 5-9 檢測兩棵二元樹是否相等1 struct BTreeNode2 struct BTreeNode leftchild3 char data4 struct BTreeNode rightchild5 6 struct BtreeNode rightchild7 int Equal(struct BTreeNode ustruct BTreeNode v)8 if ((u==NULL) ampamp (v==NULL)) return 19 if ((u=NULL) ampamp (v=NULL)10 ampamp (u-gtdata == v-gtdata )11 ampamp Equal(u-gtleftchildv-gtleftchild)12 ampamp Equal(u-gtrightchildv-gtrightchild))

13 return 114 return 015

Counting Nodes in a Binary Tree

int CountingNodes(struct BTreeNodes node)

if (node == NULL) return 0 int leftchildren = CountingNodes(node-gtleftchild)

int rightchildren= CountingNodes(node-gtrightchild)

return leftchildren+rightchildren

Counting Leaves in a Binary Tree

int CountingLeaves(struct BTreeNodes node)

if (node == NULL) return 0 int leftleaves = CountingLeaves(node-gtleftchild)

int rightchildren= CountingLeaves(node-gtrightchild)

if (leftleaves==0 ampamp leftleaves==0) return 1

return leftleaves + leftleaves

57  引線二元樹bull 在二元樹的鍵結表示中樹葉節點的兩個子樹指標皆指向 NULL 由定理 5-1 得知 n = E+1 其中 n 為節點個數 E 為分支數

bull 而 n 個鍵結節點有 2n 個指標空間每個分支恰佔用一個指標空間共有 E (=n-1) 個指標有用到(不是空的)而共有 n+1 個指標空間是 NULL 比非空的節點還多

bull 有學者提出對這些「放空指標的空間」加以利用的概念mdash與其放空指標不如放指向其它節點的指標稱之為引線 (thread) 使得某些運算(如走訪hellip等)可以加快

bull 這個概念發展出了引線二元樹 (threaded binary tree) 這種資料結構

範例 5-17下圖為加了引線的二元樹

加入的引線將使二元樹的中序走訪更加便利沿著上右圖箭頭所指示的順序走訪即可完成此二元樹的中序走訪GDHBAIECF 為此二元樹的中序表示

bull 範例 5-17 中所加的引線目的在方便二元樹的中序走訪

bull 對樹葉節點 p 而言令其資料為 P 則引線指標的規則如下

(1) p 的左子樹指標指向中序表示中 P 的前一個 元素(前接元素 (predecessor) )

(2) p 的右子樹指標指向中序表示中 P 的後一個 元素(後繼元素 (successor) )

為了區別節點指標放的是一般指標(內部節點)抑或是引線指標(樹葉節點)我們另以 2 個欄位分別區別左和右子樹指標空間存放的對象其節點的記憶體配置有如下圖所示

其中 leftthread (rightthread) 為 0 時表示 leftchild (rightchild) 存放的是一般節點指標而 leftthread (rightthread) 為 1 時表示 leftchild (rightchild) 存放的是引線指標1 struct TBTreeNode2 int leftthread3 struct TBTreeNode leftchild4 char data5 struct TBTreeNode rightchild6 int rightthread7

由右側的引線二元樹可知有最左和最右兩條引線尚無妥善的安排我們另設計一空的引線節點做為樹根如下圖所示

571  引線二元樹的中序走訪決定節點 p 在中序表示中的後繼節點 q (簡稱中序後繼點)

程式 5-10決定節點 node的中序後繼點1 struct TBTreeNode2 int leftthread3 struct TBTreeNode leftchild4 char data5 struct TBTreeNode rightchild6 int rightthread7 8 struct TBTreeNode root9 struct TBTreeNode Next(struct TBTreeNode node)

10 struct TBTreeNode temp11 temp = node-gtrightchild12 if (node-gtrightthead) return temp13 while (temp-gtleftthead) temp=temp-gtleftchild

14 return temp15

程式 5-11引線二元樹的中序表示(接程式 5-10)1 void InorderTBTree()2 struct TBTreeNode node3 for(node=Next(root)node=rootnode=Next(node))

4 cout ltlt node-gtdata5

bull只要 node 是引線二元樹上的任一節點透過呼叫 Next(node) 即可找到其在中序表示中的後繼節點bull(試想若程式 5-10 中的程序 Next 所傳入的參數指標正是 root Next(root) 是否可正常運作請思考)bull於是只須從中序表示中的第一節點起讓每一節點 node 都執行 Next(node) 程序即可得引線二元樹的中序表示

572  在引線二元樹中加入節點在引線二元樹中樹葉節點節點的右子樹指標所指向或內部節點右子樹的最左樹葉為其中序後繼節點而且任一節點若無左子樹則其左子樹指標乃指向其中序前接節點hellip這些性質必須在節點新增進入引線二元樹時也要保持接下來討論節點 new 插入成為引線二元樹的節點 p 的右子節點的情形至於插入成為左子節點的情形則讓各位模擬推敲

572572  在引線二元樹中加入節點 在引線二元樹中加入節點 (( 續續 ))

newnew 會成為會成為 pp 的右子節點的情況共有以下兩種的右子節點的情況共有以下兩種(1) (1) 若若 pp 沒有右子節點 沒有右子節點 ((p-gtrightthread p-gtrightthread 為為1) 1) 如圖如圖 ((a) a) 則 則 new new 插入後應形成如圖插入後應形成如圖 ((b) b) 所示所示

(2)(2) 若若 pp 有右子節點 有右子節點 ((p-gtrightthread p-gtrightthread 為為0) 0) 如圖如圖 ((c) c) 則 則 new new 插入後應形成如插入後應形成如圖圖 ((d) d) 所示所示

程式 5-12  插入節點 new 成為引線二元樹中節點 p的右子節點(上接程式 5-10 5-11 )

1 void InsertRight(struct TBTreeNode p 2 struct TBTreeNode new)3 struct TBTreeNode q4 new-gtrightchild = p-gtrightchild5 new-gtrightthead = p-gtrightthread6 new-gtleftchild = p7 new-gtleftthread = 18 p-gtrightchild = new9 p-gtrightthead = 010 if (new-gtrightthread)11 q = Next(new)12 q-gtleftchild = new13 14

58  堆積bull 「堆積」 (heap) 是一個特殊的完備二元樹節點的資料與其子節點的資料之間有事先定好的大小關係存在bull 定義ndash最大堆疊為一完備二元樹任一節點的資料內容不小於其子節點的資料內容ndash最小堆疊為一完備二元樹任一節點的資料內容不大於其子節點的資料內容

581  插入節點至最大堆積中最大堆積是一完備二元樹所以如圖 (a) 之最大堆積於加入新節點後樹的形狀必須如圖 (b) 所示灰色節點即為新節點若加入的節點資料分別為 1 9 12 則其最大堆積將分別變成如圖 (c) (d) (e) 所示

程式 5-13 新增節點至最大堆積中最大堆積本身是一棵完備二元樹可以用陣列表示之 1 define maxsize 1002 int heap[maxsize]3 int n i4 void InsertHeap(int x)5 if (n == maxsize) HeapFull()6 else7 n++8 i = n9 while ((i gt 1) ampamp (x gt heap[i2]))10 heap[i] = heap[i2]11 i = 212 13 heap[i] = x14 16

程序 InsertHeap 在插入第 n 個資料時需要的時間複雜度為 O(logn)

1 100

2 93

3 84

4 77

5 65

6 81

7 79

8 66

9 70

10 50

100

84

77

93

8165

66 70

79

50

1

2 3

4 5 6 7

8 9 10

1 define maxsize 1002 int heap[maxsize]3 int n i4 void InsertHeap(int x)5 if (n == maxsize) HeapFull()6 else7 n++8 i = n9 while ((i gt 1) ampamp (x gt heap[i2]))10 heap[i] = heap[i2]11 i = 212 13 heap[i] = x14 16

582 刪除最大堆積中的最大資料圖 (a) 中的最大堆積在刪除最大資料後其樹形應變成如圖 (b) 所示將最未一個樹葉節點的資料 6 暫挪至樹根節點內如圖 (c) 再將圖 (c) 中非最大堆積的結構調整成為最大堆積 10 和 8 中選大者與 6對調即成圖 (d)

程式 5-14 刪除最大堆積中的最大資料(上接程式 5-13 )1 int DeleteHeap()2 int x i j3 if (n == 0) HeapEmpty()4 else5 x = heap[1]6 heap[1] = heap[n]7 n--8 i = 19 while (i lt= n2)10 if (heap[2i] gt heap[2i+1])11 j = 2i12 else13 j = 2i+114 if (heap[i]gtheap[j]) break15 swap(heap[i] heap[j])16 i = j17 18 return x19 20

其時間複雜度與其堆積高度相同是為O(logn) 其中 n 為堆積中的元素個數

1 100

2 93

3 84

4 77

5 65

6 81

7 79

8 66

9 70

10 50

100

84

77

93

8165

66 70

79

50

1

2 3

4 5 6 7

8 9 10

1 int DeletHeap()2 int x i j3 if(n == 0) HeapEmpty()4 else5 x = heap[1]6 heap[1] = heap[n]7 n--8 i = 19 while (i lt= n2)10 if ([heap[2i]] gt heap[2i+1])11 j = 2i12 else13 j = 2i+114 if (heap[i]gtheap[j]) break15 swap(heap[i] heap[j])16 i = j17 18 return x19 20

583  優先佇列bull 由於最大堆積(或最小堆積)的結構可提供最大元素(或最小元素)而其維護堆積的成本為 O(logn) n 為總元素個數

bull 堆積常被當成「優先佇列」 (priority queue) 使用mdash此時佇列被組織成完備二元樹若新加入佇列的資料有大小關係優先順序的需求時此優先佇列即以 O(logn) 的代價將新資料放入優先佇列(可能是最大或最小堆積依實際大小關係優先順序的需求而定)中mdash提供依元素的優先順序做為處理順序的服務

bull 一般的佇列為先進先出先到的先行處理優先佇列則是依據其優先順序優先順序高者先行處理與來到的先後無關

範例 5-21bull 在「分時」 (time sharing) 的作業系統中我們不希望使用中央處理器 (Central Processing Unit CPU) 時間短的「處理程序」 (process) 等候過久於是有「最短時間工作優先」 (shortest-job-first) 的排程策略此時等候的處理程序即可組織成之一最小堆積而 CPU 每次即取出存於樹根的處理程序予以處理任何新來的處理程序則以 O(logn) 的時間加入此優先佇列(最小堆積)中

bull 注意之前提過的線性佇列新來的直接加入成為新的尾端元素花O(1) 的時間然線性佇列本身沒有大小的關係每次要取出最大(小)的資料得花O(n) 的代價(在所有元素中挑出最大(小)者) n 為佇列內的元素個數而優先佇列是在新元素加入時要花O(logn) 的時間至於取出最大(小)則只須 O(1) 的時間

59  二元搜尋樹bull 二元搜尋樹是一棵二元樹可能是空二元樹若不為空二元樹則滿足下列性質ndash所有節點內的資料內容是相異的 ndash左子節點的資內容 ( 如果有 ) 比父節點的資料內容小 ndash右子節點的資料內容 ( 如果有 ) 比父節點的資料內容大 ndash以左和右子節點為樹根的左子樹和右子樹也是二元搜尋樹

在二元搜尋樹中搜尋資料二元搜尋樹對資料大小的掌握十分明確若恰與節點 p 資料相等則搜尋成功若欲搜尋的資料 x 比樹節點 p 中的資料小 則只須搜尋節點 p 的左子樹若比節點 p 中的資料大 則只須搜尋 p 的右子樹這樣的判斷可以遞迴地應用在各子樹節點 中

程式 5-15 在二元搜尋樹中搜尋資料(遞迴呼叫)1 struct BSTreeNode2 struct BSTreeNode leftchild3 int data4 struct BSTreeNode rightchild5 6 struct BSTreeNode root7 struct BSTreeNode SearchBST(

struct BSTreeNode tree int x)8 if (tree == NULL) return NULL9 if (x == tree-gtdata) return tree10 if (x lt tree-gtdata)11 return SearchBST(tree-gtleftchild x)

12 return SearchBST(tree-gtrightchild x)13

程式 5-16 搜尋二元搜尋樹的資料(非遞迴呼叫)在二元搜尋樹十分龐大時遞迴呼叫的效率不會太好可利用迴圈來完成搜尋的動作以提高執行效率請看程式 5-16

14 struct BSTreeNode Search BST_iterative15 (struct BSTreeNode node int x)16 while (node = NULL)17 if (x == node-gtdata) return node18 if (x lt node-gtdata)19 node = node-gtleftchild20 else21 node = node-gtrightchild22 23 return NULL24

592  新增資料於二元搜尋樹中欲新增資料進入一二元搜尋樹中得先決定該資料在二元搜尋樹中應出現的位置這可透過上節提到的搜尋動作來解決

在二元搜尋樹定義的性質 (1) 中我們限定了元素必須相異所以搜尋新資料的結果必是 NULL

必須將搜尋的結果改為新增資料所應在位置的節點指標

程式 5-17  在二元搜尋樹中新增資料25 int InsertBST(int x)26 struct BSTreeNode p q27 p = root q = NULL28 while (p = NULL)29 q = p30 if (x == p-gtdata) return 0

若不考慮重複則可省略之31 if (x lt p-gtdata)32 p = p-gtleftchild33 else34 p = p-gtrightchild35

3636 p = (struct BSTreeNode ) p = (struct BSTreeNode ) malloc (sizeof(BSTreeNode))malloc (sizeof(BSTreeNode))

3737 p-gtdata = x p-gtdata = x3838 p-gtleftchild = p-gtrightchild = p-gtleftchild = p-gtrightchild = NULLNULL3939 if (root == NULL) root = p if (root == NULL) root = p4040 else if (x lt q-gtdata) else if (x lt q-gtdata)4141 q-gtleftchild = p q-gtleftchild = p4242 else else4343 q-gtrightchild = p q-gtrightchild = p4444 return 1 return 145 45

程式 程式 5-175-17  在二元搜尋樹中新增資料  在二元搜尋樹中新增資料 (( 續續 ))

範例 5-23 圖 (b) 為圖 (a) 新增整數 6 之後的結果圖 (c) (d) (e) (f) 則分別是逐步再加入 28 18

10 21 的結果

bull若目前的二元搜尋樹高度為 h 則新增資料得花O(h) 的時間找到其所應在的位置遂新增資料至二元搜尋樹中的時間複雜之度為 O(h)

593 自二元搜尋樹中刪除資料欲自二元搜尋樹中刪除資料可能會面臨兩種情況(1) 刪除節點 p 位於樹葉節點上如下圖所示 這是比較容易處理的情況

(2) 刪除的節點 p 不是樹葉刪除節點 p 後原 p 所指的節點可由 q ( q 中包含了 p 左子樹中的最大節點值)或 r ( r 中包含了 p 右子樹中的最小節點值)來取代以維持二元搜尋樹中資料的大小關係其結果分別如圖 (b) 和 (c) 所示而圖 (d) 則標明了指標會改變的節點位置即 p 會消失 father 的左或右子樹指標會異動 k(可能是 q 或 r )會移到原來 p 的位置其父節點 f 的左或右子樹指標會異動其中 father 為 p 父節點 f 為 k 的父節點

程式 5-18 自二元搜尋樹中刪除資料46 int DeleteBST (int x)47 Struct BSTreeNode p father k f48 p = root father = NULL49 while (p = NULL)50 if (x == p-gtdata) 找到 x 所在的節點 p 了51 if ((p-gtleftchild == NULL) ampamp 52 (p-gtrightchild == NULL))53 k = NULL p為樹葉節點54 else if (p-gtleftchild = NULL )x位於非子節

點55 f = p 找出 p左子樹的最右樹葉56 k = p-gtleftchild57 while (k-gtrightchild =NULL)58 f = k59 k = k-gtrightchild60 k將挪至原 p處61 if (p == f)62 f-gtleftchild = k-gtleftchild 63 else64 f-gtrightchild = k-gtleftchild 65

66 else p無左子樹67 f = p 找出 p右子樹的最左樹葉68 k = p-gtrightchild69 while (k-gtleftchild = NULL)70 f = k71 k = k -gtleftchild72 73 if (p == f)74 f-gtrightchild = k-gtrightchild 75 else76 f-gtleftchild = k-gtrightchild77 78 if (k = NULL) k挪至原 p處繼承 p的左右指標79 k-gtleftchild = p-gtleftchild80 k-gtrightchild = p-gtrightchild81 82 if (father == NULL) root = k83 else if (x lt father-gtdata) 決定 k是 father的左

或右兒子84 father-gtleftchild = k85 else86 father-gtrightchild = k87 free(p)88 return 1 成功地刪除 x於此傳回 1返回呼叫處89 51~89行為找到 x時應有的處理步驟

90 else 尚未找到 x繼續往下一階層找

91 father = p92 if (x lt p-gtdata)93 p = p-gtleftchild94 else95 p = p-gtrightchild96 97 98 return 0 未找到 x傳回 099

510  樹林 (forest)

bull 定義樹林(森林)是由 n 棵沒有交集的樹所構成的集合 n 0

下圖為一包含三棵樹的樹林

5101 轉換樹林為二元樹 之前我們以二元樹來表示樹因為二元樹有較多

好用的性質在此我們也將樹林轉換成二元樹看待

在 522 節中我們嘗試了用左子右兄弟的方式來表示樹這個方法事實上已將樹轉換成二元樹

即用左子右兄弟的結構重新組織樹林中的每一棵樹這些樹的樹根將視為兄弟而最左邊的樹則視為樹林二元樹的樹根如此一來樹林即可轉換成二元樹

範例 5-25右下圖為右上圖三棵樹所對應的三棵左子右兄弟二元樹

下圖則為上圖樹林所對應的一棵樹林二元樹

演算法 5-1  轉換樹林成二元樹輸入樹林 F = T1 T2 Tn

輸出 F 的二元樹表示 BT=(T1 T2 Tn)1 if (n == 0) 輸出空二元樹 2 else

3 root(T1 T2 Tn) = T1

4 leftsubtree(T1 T2 Tn) =

BT(T11 T12 T1m)5 BT(T11 T12 T1m)為 T1的子樹6 rightsubtree(T1 T2 Tn) =

BT(T2 T3 Tn)

樹林轉換成二元樹的過程樹林轉換成二元樹的過程

  • Slide 1
  • Slide 2
  • Slide 3
  • Slide 4
  • Slide 5
  • Slide 6
  • Slide 7
  • Slide 8
  • Slide 9
  • Slide 10
  • Slide 11
  • Slide 12
  • Slide 13
  • Slide 14
  • Slide 15
  • Slide 16
  • Slide 17
  • Slide 18
  • Slide 19
  • Slide 20
  • Slide 21
  • Slide 22
  • Slide 23
  • Slide 24
  • Slide 25
  • Slide 26
  • Slide 27
  • Slide 28
  • Slide 29
  • Slide 30
  • Slide 31
  • Slide 32
  • Slide 33
  • Slide 34
  • Slide 35
  • Slide 36
  • Slide 37
  • Slide 38
  • Slide 39
  • Slide 40
  • Slide 41
  • Slide 42
  • Slide 43
  • Slide 44
  • Slide 45
  • Slide 46
  • Slide 47
  • Slide 48
  • Slide 49
  • Slide 50
  • Slide 51
  • Slide 52
  • Slide 53
  • Slide 54
  • Slide 55
  • Slide 56
  • Slide 57
  • Slide 58
  • Slide 59
  • Slide 60
  • Slide 61
  • Slide 62
  • Slide 63
  • Slide 64
  • Slide 65
  • Slide 66
  • Slide 67
  • Slide 68
  • Slide 69
  • Slide 70
  • Slide 71
  • Slide 72
  • Slide 73
  • Slide 74
  • Slide 75
  • Slide 76
  • Slide 77
  • Slide 78
  • Slide 79
  • Slide 80
  • Slide 81
  • Slide 82
  • Slide 83
  • Slide 84
  • Slide 85
  • Slide 86
  • Slide 87
  • Slide 88
  • Slide 89
  • Slide 90
  • Slide 91
  • Slide 92
  • Slide 93
  • Slide 94
  • Slide 95
  • Slide 96
  • Slide 97
  • Slide 98
  • Slide 99
  • Slide 100
  • Slide 101
  • Slide 102
  • Slide 103
Page 35: 第五章 樹

551  中序走訪bull 利用遞迴的方式撰寫中序走訪的程序希望把二元樹中序走訪的

順序印出來

程式 5-3二元樹的中序走訪1 struct BTreeNode2 struct BTreeNode leftchild3 char data4 struct BTreeNode rightchild5 6 struct BTreeNode root7 void inorder(struct BTreeNode node)8 if (node = NULL)9 inorder(node-gtleftchild)10 cout ltlt node-gtdata11 inorder(node-gtrightchild)12 13

範例 5-12此為運算式二元樹

其對應中序表示運算式為 (A+B)C+D(E-F)

6 struct BTreeNode root7 void inorder(struct BTreeNode node)8 if (node = NULL)9 inorder(node-gtleftchild)10 cout ltlt node-gtdata11 inorder(node-gtrightchild)12 13

6 struct BTreeNode root7 void inorder(struct BTreeNode node)8 if (node = NULL)9 inorder(node-gtleftchild)10 cout ltlt node-gtdata11 inorder(node-gtrightchild)12 13

(A+B)C+D(E-F)

如何加上 ( )

552  後序走訪程式 5-4二元樹的後序走訪14 void postorder(struct BTreeNode node)

15 if (node =NULL)16 postorder(node-gtleftchild)17 postorder(node-gtrightchild)18 cout ltlt node-gtdata19 20

以後序走訪可得到AB+CDEF-+

其正為對應的後序運算式

553  前序走訪程式 5-5二元樹的前序走訪21 void preorder(struct BTreeNode node)

22 if (node = NULL)

23 cout ltlt node-gtdata

24 preorder(node-gtleftchild)

25 preorder(node-gtrightchild)

26

27

以前序走訪可得到++ABCD-EF

其正為對應的前序運算式

554 利用堆疊和迴圈取代遞迴進行二元樹的走訪1 struct BTreeNode 2 struct BTreeNode leftchild3 char data4 struct BtreeNode rightchild5 6 struct BTreeNode root7 struct StackNode8 struct BTreeNode treenode9 struct StackNode next1011 struct StackNode top

12 void push_node(struct BtreeNode node )13 struct StackNode old_top14 old_top = top15 top = (struct StackNode ) malloc

(sizeof(struct StackNode))16 top-gttreenode = node17 top-gtnext = old_top18 19 struct BTreeNode pop_node()20 struct BTreeNode Tnode21 struct StackNode old_top22 if (top == NULL)23 StackEmpty()24 else25 Tnode = top-gttreenode26 old_top = top27 top = top-gtnext28 free(old_top)29 return Tnode30 31

32 void inorder_Stack(struct BtreeNode node)33 do34 while (node = NULL)35 push_node(node)36 node = node-gtleftchild37 push all left children38 if (top = NULL)39 node = pop_node()40 cout ltlt node-gtdata41 node = node-gtrightchild42 inorder printing and check right child43 while ((top=NULL)||(node=NULL))44 top==NULL 堆疊已空 node==NULL沒有右子

6 struct BTreeNode root7 void inorder(struct BTreeNode node)8 if (node = NULL)9 inorder(node-gtleftchild)10 cout ltlt node-gtdata11 inorder(node-gtrightchild)12 13

中序表示為DBGEHAFIC

32 void inorder_Stack()33 struct BTreeNode node34 node = root35 do36 while (node = NULL)37 push_node(node)38 node = node-gtleftchild39 40 if (top = NULL)41 node = pop_node()42 cout ltlt node-gtdata43 node = node-gtrightchild44 45 while(top=NULL)||(node=NULL))46

45 void Preorder_Stack(struct BSTreeNode node)46 do47 while (node = NULL)48 cout ltlt node-gtdata)49 push_node(node)50 node = node-gtleftchild51 push all left children52 if (top = NULL)53 node = pop_node()54 node = node-gtrightchild55 56 while ((top=NULL)||(node=NULL))57

程式 5-5二元樹的前序走訪21 void preorder(struct BTreeNode node)22 if (node = NULL)23 cout ltlt node-gtdata24 preorder(node-gtleftchild)25 preorder(node-gtrightchild)26 27

58 void Postorder_Stack(struct BTreeNode node)59 do60 while (node = NULL)61 cout ltlt node-gtdata62 push_node(node)63 node = node-gtrightchild64 push all left children65 if (top = NULL)66 node = pop_node()67 node = node-gtleftchild68 69 while ((top=NULL)||(node=NULL))70

程式 5-4二元樹的後序走訪14 void postorder(struct BTreeNode node)15 if (node =NULL)16 postorder(node-gtleftchild)17 postorder(node-gtrightchild)18 cout ltlt node-gtdata19 20

555  階層走訪bull 「階層走訪」 (level-order traversal) 是依階層

的順序進行二元樹的走訪先走訪階層小的節點後走訪階層大的節點同一階層者則依自左向右的順序走訪

bull 對下圖的二元樹進行階層走訪的結果為ABCDEFGHI

bull 對同一階層而言先走訪的節點其子節點亦在下一階層中先被走訪

bull 這種「先進先出」的特性恰可用佇列予以儲存與處理

程式 5-7 利用佇列進行二元樹的階層走訪

1 struct BTreeNode2 struct BTreeNode leftchild3 char data4 struct BTreeNode rightchild5 6 struct BTreeNode root7 struct QNode8 struct BTreeNode treenode9 struct QNode next10 11 struct QNode front rear

12 void AddQueue(struct BTreeNode Tnode)13 struct QNode node14 node=(struct QNode )malloc(sizeof(struct QNode))15 node-gttreenode = Tnode16 node-gtnext = NULL17 if (front == NULL) front = node19 else rear-gtnext = node20 rear = node21

22 struct BTreeNode DeleteQueue()23 struct BTreeNode Tnode24 struct QNode old_front25 if (rear == NULL)26 QueueEmpty()27 else28 Tnode = front-gttreenode29 old_front = front30 front = front-gtnext31 free(old_front)32 return Tnode33 34 35 void LevelOrder (struct BTreeNode node)36 AddQueue(node)37 while (front =NULL)38 node = DeleteQueue()39 cout ltlt node-gtdata40 if (node-gtleftchild = NULL)41 AddQueue(node-gtleftchild)42 if (node-gtrightchild = NULL)43 AddQueue(node-gtrightchild)44 45

35 void LevelOrder (struct BTreeNode node)36 AddQueue(node)37 while (front =NULL)38 node = DeleteQueue()39 cout ltlt node-gtdata40 if (node-gtleftchild = NULL)41 AddQueue(node-gtleftchild)42 if (node-gtrightchild = NULL)43 AddQueue(node-gtrightchild)44 45

bull Infix ABCbull Postfix ABC CBA ACB BCA

BAC CAB

B

A CA

B

C

B

A

C

C

B

AB

A

C

二元樹中序與前序式中序與後序式bull 每一棵二元樹皆有唯一的一對中序與前序表示法也有唯一的中序與後序次序

bull 換句話說給予一對中序與前序或中序與後序即可決定一棵二元樹

bull 然而給予前序和後序次序並不能決定唯一的二元樹可能會產生兩棵不同的二元樹

bull 想想階層走訪和中序走訪順序可否決定唯一的二元樹

A

B C

H J D E

F G

prefix ABHJCDFGEinfix HBJAFDGCEpostfix HJBFGDECA

VLRLVRLRV

A

prefix BHJinfix HBJ

prefix CDFGEinfix FDGCE

prefix ABHJCDFGEinfix HBJAFDGCE

A

B C

H J D E

F G

prefix ABHJCDFGE

infix HBJAFDGCEk-1

struct BTreeNode BTree_InfixPrefix (AnsiString infix AnsiString prefix)

int k struct BTreeNode node if (infixLength==0) return NULLnode = (struct BTreeNode ) malloc (sizeof(struct BTreeNode))node-gtdata = prefix[1]k = find(prefix[1] infix)node-gtleftchild = BTree_InfixPrefix

(infix[1k-1] prefix[2k])node-gtrightchild = BTree_InfixPrefix(infix[k+1infixLength]prefix[k+1prefixLength])

VLR

LVRk1

k2 k+1~

k+1~

1

struct BTreeNode BTree_InfixPostfix (AnsiString infix AnsiString prefix)

int k struct BTreeNode node if (infixLength==0) return NULLnode = (struct BTreeNode ) malloc (sizeof(struct BTreeNode))node-gtdata = postfix[postfixLength]k = find(postfix[postfixLength] infix)node-gtleftchild = BTree_InfixPrefix

(infix[1k-1] postfix[1k-1])node-gtrightchild = BTree_InfixPrefix(infix[k+1infixLength]postfix[k+1postfixLength-1])

56  二元樹的複製與相等測試程式 5-8 二元樹的複製1 struct BTreeNode2 struct BTreeNode leftchild3 char data4 struct BTreeNode rightchild5 6 struct BTreeNode root7 struct BTreeNode Copy(struct BTreeNode TreeRoot)8 struct BTreeNode CopyRoot9 if (TreeRoot == NULL)10 return NULL11 CopyRoot =(struct BTreeNode ) malloc (sizeof(BTreeNode))12 CopyRoot-gtdata = TreeRoot-gtdata13 CopyRoot-gtleftchild = Copy(TreeRoot-gtleftchild)14 CopyRoot-gtrightchild = Copy(TreeRoot-

gtrightchild)15 return CopyRoot16

程式 5-9 檢測兩棵二元樹是否相等1 struct BTreeNode2 struct BTreeNode leftchild3 char data4 struct BTreeNode rightchild5 6 struct BtreeNode rightchild7 int Equal(struct BTreeNode ustruct BTreeNode v)8 if ((u==NULL) ampamp (v==NULL)) return 19 if ((u=NULL) ampamp (v=NULL)10 ampamp (u-gtdata == v-gtdata )11 ampamp Equal(u-gtleftchildv-gtleftchild)12 ampamp Equal(u-gtrightchildv-gtrightchild))

13 return 114 return 015

Counting Nodes in a Binary Tree

int CountingNodes(struct BTreeNodes node)

if (node == NULL) return 0 int leftchildren = CountingNodes(node-gtleftchild)

int rightchildren= CountingNodes(node-gtrightchild)

return leftchildren+rightchildren

Counting Leaves in a Binary Tree

int CountingLeaves(struct BTreeNodes node)

if (node == NULL) return 0 int leftleaves = CountingLeaves(node-gtleftchild)

int rightchildren= CountingLeaves(node-gtrightchild)

if (leftleaves==0 ampamp leftleaves==0) return 1

return leftleaves + leftleaves

57  引線二元樹bull 在二元樹的鍵結表示中樹葉節點的兩個子樹指標皆指向 NULL 由定理 5-1 得知 n = E+1 其中 n 為節點個數 E 為分支數

bull 而 n 個鍵結節點有 2n 個指標空間每個分支恰佔用一個指標空間共有 E (=n-1) 個指標有用到(不是空的)而共有 n+1 個指標空間是 NULL 比非空的節點還多

bull 有學者提出對這些「放空指標的空間」加以利用的概念mdash與其放空指標不如放指向其它節點的指標稱之為引線 (thread) 使得某些運算(如走訪hellip等)可以加快

bull 這個概念發展出了引線二元樹 (threaded binary tree) 這種資料結構

範例 5-17下圖為加了引線的二元樹

加入的引線將使二元樹的中序走訪更加便利沿著上右圖箭頭所指示的順序走訪即可完成此二元樹的中序走訪GDHBAIECF 為此二元樹的中序表示

bull 範例 5-17 中所加的引線目的在方便二元樹的中序走訪

bull 對樹葉節點 p 而言令其資料為 P 則引線指標的規則如下

(1) p 的左子樹指標指向中序表示中 P 的前一個 元素(前接元素 (predecessor) )

(2) p 的右子樹指標指向中序表示中 P 的後一個 元素(後繼元素 (successor) )

為了區別節點指標放的是一般指標(內部節點)抑或是引線指標(樹葉節點)我們另以 2 個欄位分別區別左和右子樹指標空間存放的對象其節點的記憶體配置有如下圖所示

其中 leftthread (rightthread) 為 0 時表示 leftchild (rightchild) 存放的是一般節點指標而 leftthread (rightthread) 為 1 時表示 leftchild (rightchild) 存放的是引線指標1 struct TBTreeNode2 int leftthread3 struct TBTreeNode leftchild4 char data5 struct TBTreeNode rightchild6 int rightthread7

由右側的引線二元樹可知有最左和最右兩條引線尚無妥善的安排我們另設計一空的引線節點做為樹根如下圖所示

571  引線二元樹的中序走訪決定節點 p 在中序表示中的後繼節點 q (簡稱中序後繼點)

程式 5-10決定節點 node的中序後繼點1 struct TBTreeNode2 int leftthread3 struct TBTreeNode leftchild4 char data5 struct TBTreeNode rightchild6 int rightthread7 8 struct TBTreeNode root9 struct TBTreeNode Next(struct TBTreeNode node)

10 struct TBTreeNode temp11 temp = node-gtrightchild12 if (node-gtrightthead) return temp13 while (temp-gtleftthead) temp=temp-gtleftchild

14 return temp15

程式 5-11引線二元樹的中序表示(接程式 5-10)1 void InorderTBTree()2 struct TBTreeNode node3 for(node=Next(root)node=rootnode=Next(node))

4 cout ltlt node-gtdata5

bull只要 node 是引線二元樹上的任一節點透過呼叫 Next(node) 即可找到其在中序表示中的後繼節點bull(試想若程式 5-10 中的程序 Next 所傳入的參數指標正是 root Next(root) 是否可正常運作請思考)bull於是只須從中序表示中的第一節點起讓每一節點 node 都執行 Next(node) 程序即可得引線二元樹的中序表示

572  在引線二元樹中加入節點在引線二元樹中樹葉節點節點的右子樹指標所指向或內部節點右子樹的最左樹葉為其中序後繼節點而且任一節點若無左子樹則其左子樹指標乃指向其中序前接節點hellip這些性質必須在節點新增進入引線二元樹時也要保持接下來討論節點 new 插入成為引線二元樹的節點 p 的右子節點的情形至於插入成為左子節點的情形則讓各位模擬推敲

572572  在引線二元樹中加入節點 在引線二元樹中加入節點 (( 續續 ))

newnew 會成為會成為 pp 的右子節點的情況共有以下兩種的右子節點的情況共有以下兩種(1) (1) 若若 pp 沒有右子節點 沒有右子節點 ((p-gtrightthread p-gtrightthread 為為1) 1) 如圖如圖 ((a) a) 則 則 new new 插入後應形成如圖插入後應形成如圖 ((b) b) 所示所示

(2)(2) 若若 pp 有右子節點 有右子節點 ((p-gtrightthread p-gtrightthread 為為0) 0) 如圖如圖 ((c) c) 則 則 new new 插入後應形成如插入後應形成如圖圖 ((d) d) 所示所示

程式 5-12  插入節點 new 成為引線二元樹中節點 p的右子節點(上接程式 5-10 5-11 )

1 void InsertRight(struct TBTreeNode p 2 struct TBTreeNode new)3 struct TBTreeNode q4 new-gtrightchild = p-gtrightchild5 new-gtrightthead = p-gtrightthread6 new-gtleftchild = p7 new-gtleftthread = 18 p-gtrightchild = new9 p-gtrightthead = 010 if (new-gtrightthread)11 q = Next(new)12 q-gtleftchild = new13 14

58  堆積bull 「堆積」 (heap) 是一個特殊的完備二元樹節點的資料與其子節點的資料之間有事先定好的大小關係存在bull 定義ndash最大堆疊為一完備二元樹任一節點的資料內容不小於其子節點的資料內容ndash最小堆疊為一完備二元樹任一節點的資料內容不大於其子節點的資料內容

581  插入節點至最大堆積中最大堆積是一完備二元樹所以如圖 (a) 之最大堆積於加入新節點後樹的形狀必須如圖 (b) 所示灰色節點即為新節點若加入的節點資料分別為 1 9 12 則其最大堆積將分別變成如圖 (c) (d) (e) 所示

程式 5-13 新增節點至最大堆積中最大堆積本身是一棵完備二元樹可以用陣列表示之 1 define maxsize 1002 int heap[maxsize]3 int n i4 void InsertHeap(int x)5 if (n == maxsize) HeapFull()6 else7 n++8 i = n9 while ((i gt 1) ampamp (x gt heap[i2]))10 heap[i] = heap[i2]11 i = 212 13 heap[i] = x14 16

程序 InsertHeap 在插入第 n 個資料時需要的時間複雜度為 O(logn)

1 100

2 93

3 84

4 77

5 65

6 81

7 79

8 66

9 70

10 50

100

84

77

93

8165

66 70

79

50

1

2 3

4 5 6 7

8 9 10

1 define maxsize 1002 int heap[maxsize]3 int n i4 void InsertHeap(int x)5 if (n == maxsize) HeapFull()6 else7 n++8 i = n9 while ((i gt 1) ampamp (x gt heap[i2]))10 heap[i] = heap[i2]11 i = 212 13 heap[i] = x14 16

582 刪除最大堆積中的最大資料圖 (a) 中的最大堆積在刪除最大資料後其樹形應變成如圖 (b) 所示將最未一個樹葉節點的資料 6 暫挪至樹根節點內如圖 (c) 再將圖 (c) 中非最大堆積的結構調整成為最大堆積 10 和 8 中選大者與 6對調即成圖 (d)

程式 5-14 刪除最大堆積中的最大資料(上接程式 5-13 )1 int DeleteHeap()2 int x i j3 if (n == 0) HeapEmpty()4 else5 x = heap[1]6 heap[1] = heap[n]7 n--8 i = 19 while (i lt= n2)10 if (heap[2i] gt heap[2i+1])11 j = 2i12 else13 j = 2i+114 if (heap[i]gtheap[j]) break15 swap(heap[i] heap[j])16 i = j17 18 return x19 20

其時間複雜度與其堆積高度相同是為O(logn) 其中 n 為堆積中的元素個數

1 100

2 93

3 84

4 77

5 65

6 81

7 79

8 66

9 70

10 50

100

84

77

93

8165

66 70

79

50

1

2 3

4 5 6 7

8 9 10

1 int DeletHeap()2 int x i j3 if(n == 0) HeapEmpty()4 else5 x = heap[1]6 heap[1] = heap[n]7 n--8 i = 19 while (i lt= n2)10 if ([heap[2i]] gt heap[2i+1])11 j = 2i12 else13 j = 2i+114 if (heap[i]gtheap[j]) break15 swap(heap[i] heap[j])16 i = j17 18 return x19 20

583  優先佇列bull 由於最大堆積(或最小堆積)的結構可提供最大元素(或最小元素)而其維護堆積的成本為 O(logn) n 為總元素個數

bull 堆積常被當成「優先佇列」 (priority queue) 使用mdash此時佇列被組織成完備二元樹若新加入佇列的資料有大小關係優先順序的需求時此優先佇列即以 O(logn) 的代價將新資料放入優先佇列(可能是最大或最小堆積依實際大小關係優先順序的需求而定)中mdash提供依元素的優先順序做為處理順序的服務

bull 一般的佇列為先進先出先到的先行處理優先佇列則是依據其優先順序優先順序高者先行處理與來到的先後無關

範例 5-21bull 在「分時」 (time sharing) 的作業系統中我們不希望使用中央處理器 (Central Processing Unit CPU) 時間短的「處理程序」 (process) 等候過久於是有「最短時間工作優先」 (shortest-job-first) 的排程策略此時等候的處理程序即可組織成之一最小堆積而 CPU 每次即取出存於樹根的處理程序予以處理任何新來的處理程序則以 O(logn) 的時間加入此優先佇列(最小堆積)中

bull 注意之前提過的線性佇列新來的直接加入成為新的尾端元素花O(1) 的時間然線性佇列本身沒有大小的關係每次要取出最大(小)的資料得花O(n) 的代價(在所有元素中挑出最大(小)者) n 為佇列內的元素個數而優先佇列是在新元素加入時要花O(logn) 的時間至於取出最大(小)則只須 O(1) 的時間

59  二元搜尋樹bull 二元搜尋樹是一棵二元樹可能是空二元樹若不為空二元樹則滿足下列性質ndash所有節點內的資料內容是相異的 ndash左子節點的資內容 ( 如果有 ) 比父節點的資料內容小 ndash右子節點的資料內容 ( 如果有 ) 比父節點的資料內容大 ndash以左和右子節點為樹根的左子樹和右子樹也是二元搜尋樹

在二元搜尋樹中搜尋資料二元搜尋樹對資料大小的掌握十分明確若恰與節點 p 資料相等則搜尋成功若欲搜尋的資料 x 比樹節點 p 中的資料小 則只須搜尋節點 p 的左子樹若比節點 p 中的資料大 則只須搜尋 p 的右子樹這樣的判斷可以遞迴地應用在各子樹節點 中

程式 5-15 在二元搜尋樹中搜尋資料(遞迴呼叫)1 struct BSTreeNode2 struct BSTreeNode leftchild3 int data4 struct BSTreeNode rightchild5 6 struct BSTreeNode root7 struct BSTreeNode SearchBST(

struct BSTreeNode tree int x)8 if (tree == NULL) return NULL9 if (x == tree-gtdata) return tree10 if (x lt tree-gtdata)11 return SearchBST(tree-gtleftchild x)

12 return SearchBST(tree-gtrightchild x)13

程式 5-16 搜尋二元搜尋樹的資料(非遞迴呼叫)在二元搜尋樹十分龐大時遞迴呼叫的效率不會太好可利用迴圈來完成搜尋的動作以提高執行效率請看程式 5-16

14 struct BSTreeNode Search BST_iterative15 (struct BSTreeNode node int x)16 while (node = NULL)17 if (x == node-gtdata) return node18 if (x lt node-gtdata)19 node = node-gtleftchild20 else21 node = node-gtrightchild22 23 return NULL24

592  新增資料於二元搜尋樹中欲新增資料進入一二元搜尋樹中得先決定該資料在二元搜尋樹中應出現的位置這可透過上節提到的搜尋動作來解決

在二元搜尋樹定義的性質 (1) 中我們限定了元素必須相異所以搜尋新資料的結果必是 NULL

必須將搜尋的結果改為新增資料所應在位置的節點指標

程式 5-17  在二元搜尋樹中新增資料25 int InsertBST(int x)26 struct BSTreeNode p q27 p = root q = NULL28 while (p = NULL)29 q = p30 if (x == p-gtdata) return 0

若不考慮重複則可省略之31 if (x lt p-gtdata)32 p = p-gtleftchild33 else34 p = p-gtrightchild35

3636 p = (struct BSTreeNode ) p = (struct BSTreeNode ) malloc (sizeof(BSTreeNode))malloc (sizeof(BSTreeNode))

3737 p-gtdata = x p-gtdata = x3838 p-gtleftchild = p-gtrightchild = p-gtleftchild = p-gtrightchild = NULLNULL3939 if (root == NULL) root = p if (root == NULL) root = p4040 else if (x lt q-gtdata) else if (x lt q-gtdata)4141 q-gtleftchild = p q-gtleftchild = p4242 else else4343 q-gtrightchild = p q-gtrightchild = p4444 return 1 return 145 45

程式 程式 5-175-17  在二元搜尋樹中新增資料  在二元搜尋樹中新增資料 (( 續續 ))

範例 5-23 圖 (b) 為圖 (a) 新增整數 6 之後的結果圖 (c) (d) (e) (f) 則分別是逐步再加入 28 18

10 21 的結果

bull若目前的二元搜尋樹高度為 h 則新增資料得花O(h) 的時間找到其所應在的位置遂新增資料至二元搜尋樹中的時間複雜之度為 O(h)

593 自二元搜尋樹中刪除資料欲自二元搜尋樹中刪除資料可能會面臨兩種情況(1) 刪除節點 p 位於樹葉節點上如下圖所示 這是比較容易處理的情況

(2) 刪除的節點 p 不是樹葉刪除節點 p 後原 p 所指的節點可由 q ( q 中包含了 p 左子樹中的最大節點值)或 r ( r 中包含了 p 右子樹中的最小節點值)來取代以維持二元搜尋樹中資料的大小關係其結果分別如圖 (b) 和 (c) 所示而圖 (d) 則標明了指標會改變的節點位置即 p 會消失 father 的左或右子樹指標會異動 k(可能是 q 或 r )會移到原來 p 的位置其父節點 f 的左或右子樹指標會異動其中 father 為 p 父節點 f 為 k 的父節點

程式 5-18 自二元搜尋樹中刪除資料46 int DeleteBST (int x)47 Struct BSTreeNode p father k f48 p = root father = NULL49 while (p = NULL)50 if (x == p-gtdata) 找到 x 所在的節點 p 了51 if ((p-gtleftchild == NULL) ampamp 52 (p-gtrightchild == NULL))53 k = NULL p為樹葉節點54 else if (p-gtleftchild = NULL )x位於非子節

點55 f = p 找出 p左子樹的最右樹葉56 k = p-gtleftchild57 while (k-gtrightchild =NULL)58 f = k59 k = k-gtrightchild60 k將挪至原 p處61 if (p == f)62 f-gtleftchild = k-gtleftchild 63 else64 f-gtrightchild = k-gtleftchild 65

66 else p無左子樹67 f = p 找出 p右子樹的最左樹葉68 k = p-gtrightchild69 while (k-gtleftchild = NULL)70 f = k71 k = k -gtleftchild72 73 if (p == f)74 f-gtrightchild = k-gtrightchild 75 else76 f-gtleftchild = k-gtrightchild77 78 if (k = NULL) k挪至原 p處繼承 p的左右指標79 k-gtleftchild = p-gtleftchild80 k-gtrightchild = p-gtrightchild81 82 if (father == NULL) root = k83 else if (x lt father-gtdata) 決定 k是 father的左

或右兒子84 father-gtleftchild = k85 else86 father-gtrightchild = k87 free(p)88 return 1 成功地刪除 x於此傳回 1返回呼叫處89 51~89行為找到 x時應有的處理步驟

90 else 尚未找到 x繼續往下一階層找

91 father = p92 if (x lt p-gtdata)93 p = p-gtleftchild94 else95 p = p-gtrightchild96 97 98 return 0 未找到 x傳回 099

510  樹林 (forest)

bull 定義樹林(森林)是由 n 棵沒有交集的樹所構成的集合 n 0

下圖為一包含三棵樹的樹林

5101 轉換樹林為二元樹 之前我們以二元樹來表示樹因為二元樹有較多

好用的性質在此我們也將樹林轉換成二元樹看待

在 522 節中我們嘗試了用左子右兄弟的方式來表示樹這個方法事實上已將樹轉換成二元樹

即用左子右兄弟的結構重新組織樹林中的每一棵樹這些樹的樹根將視為兄弟而最左邊的樹則視為樹林二元樹的樹根如此一來樹林即可轉換成二元樹

範例 5-25右下圖為右上圖三棵樹所對應的三棵左子右兄弟二元樹

下圖則為上圖樹林所對應的一棵樹林二元樹

演算法 5-1  轉換樹林成二元樹輸入樹林 F = T1 T2 Tn

輸出 F 的二元樹表示 BT=(T1 T2 Tn)1 if (n == 0) 輸出空二元樹 2 else

3 root(T1 T2 Tn) = T1

4 leftsubtree(T1 T2 Tn) =

BT(T11 T12 T1m)5 BT(T11 T12 T1m)為 T1的子樹6 rightsubtree(T1 T2 Tn) =

BT(T2 T3 Tn)

樹林轉換成二元樹的過程樹林轉換成二元樹的過程

  • Slide 1
  • Slide 2
  • Slide 3
  • Slide 4
  • Slide 5
  • Slide 6
  • Slide 7
  • Slide 8
  • Slide 9
  • Slide 10
  • Slide 11
  • Slide 12
  • Slide 13
  • Slide 14
  • Slide 15
  • Slide 16
  • Slide 17
  • Slide 18
  • Slide 19
  • Slide 20
  • Slide 21
  • Slide 22
  • Slide 23
  • Slide 24
  • Slide 25
  • Slide 26
  • Slide 27
  • Slide 28
  • Slide 29
  • Slide 30
  • Slide 31
  • Slide 32
  • Slide 33
  • Slide 34
  • Slide 35
  • Slide 36
  • Slide 37
  • Slide 38
  • Slide 39
  • Slide 40
  • Slide 41
  • Slide 42
  • Slide 43
  • Slide 44
  • Slide 45
  • Slide 46
  • Slide 47
  • Slide 48
  • Slide 49
  • Slide 50
  • Slide 51
  • Slide 52
  • Slide 53
  • Slide 54
  • Slide 55
  • Slide 56
  • Slide 57
  • Slide 58
  • Slide 59
  • Slide 60
  • Slide 61
  • Slide 62
  • Slide 63
  • Slide 64
  • Slide 65
  • Slide 66
  • Slide 67
  • Slide 68
  • Slide 69
  • Slide 70
  • Slide 71
  • Slide 72
  • Slide 73
  • Slide 74
  • Slide 75
  • Slide 76
  • Slide 77
  • Slide 78
  • Slide 79
  • Slide 80
  • Slide 81
  • Slide 82
  • Slide 83
  • Slide 84
  • Slide 85
  • Slide 86
  • Slide 87
  • Slide 88
  • Slide 89
  • Slide 90
  • Slide 91
  • Slide 92
  • Slide 93
  • Slide 94
  • Slide 95
  • Slide 96
  • Slide 97
  • Slide 98
  • Slide 99
  • Slide 100
  • Slide 101
  • Slide 102
  • Slide 103
Page 36: 第五章 樹

範例 5-12此為運算式二元樹

其對應中序表示運算式為 (A+B)C+D(E-F)

6 struct BTreeNode root7 void inorder(struct BTreeNode node)8 if (node = NULL)9 inorder(node-gtleftchild)10 cout ltlt node-gtdata11 inorder(node-gtrightchild)12 13

6 struct BTreeNode root7 void inorder(struct BTreeNode node)8 if (node = NULL)9 inorder(node-gtleftchild)10 cout ltlt node-gtdata11 inorder(node-gtrightchild)12 13

(A+B)C+D(E-F)

如何加上 ( )

552  後序走訪程式 5-4二元樹的後序走訪14 void postorder(struct BTreeNode node)

15 if (node =NULL)16 postorder(node-gtleftchild)17 postorder(node-gtrightchild)18 cout ltlt node-gtdata19 20

以後序走訪可得到AB+CDEF-+

其正為對應的後序運算式

553  前序走訪程式 5-5二元樹的前序走訪21 void preorder(struct BTreeNode node)

22 if (node = NULL)

23 cout ltlt node-gtdata

24 preorder(node-gtleftchild)

25 preorder(node-gtrightchild)

26

27

以前序走訪可得到++ABCD-EF

其正為對應的前序運算式

554 利用堆疊和迴圈取代遞迴進行二元樹的走訪1 struct BTreeNode 2 struct BTreeNode leftchild3 char data4 struct BtreeNode rightchild5 6 struct BTreeNode root7 struct StackNode8 struct BTreeNode treenode9 struct StackNode next1011 struct StackNode top

12 void push_node(struct BtreeNode node )13 struct StackNode old_top14 old_top = top15 top = (struct StackNode ) malloc

(sizeof(struct StackNode))16 top-gttreenode = node17 top-gtnext = old_top18 19 struct BTreeNode pop_node()20 struct BTreeNode Tnode21 struct StackNode old_top22 if (top == NULL)23 StackEmpty()24 else25 Tnode = top-gttreenode26 old_top = top27 top = top-gtnext28 free(old_top)29 return Tnode30 31

32 void inorder_Stack(struct BtreeNode node)33 do34 while (node = NULL)35 push_node(node)36 node = node-gtleftchild37 push all left children38 if (top = NULL)39 node = pop_node()40 cout ltlt node-gtdata41 node = node-gtrightchild42 inorder printing and check right child43 while ((top=NULL)||(node=NULL))44 top==NULL 堆疊已空 node==NULL沒有右子

6 struct BTreeNode root7 void inorder(struct BTreeNode node)8 if (node = NULL)9 inorder(node-gtleftchild)10 cout ltlt node-gtdata11 inorder(node-gtrightchild)12 13

中序表示為DBGEHAFIC

32 void inorder_Stack()33 struct BTreeNode node34 node = root35 do36 while (node = NULL)37 push_node(node)38 node = node-gtleftchild39 40 if (top = NULL)41 node = pop_node()42 cout ltlt node-gtdata43 node = node-gtrightchild44 45 while(top=NULL)||(node=NULL))46

45 void Preorder_Stack(struct BSTreeNode node)46 do47 while (node = NULL)48 cout ltlt node-gtdata)49 push_node(node)50 node = node-gtleftchild51 push all left children52 if (top = NULL)53 node = pop_node()54 node = node-gtrightchild55 56 while ((top=NULL)||(node=NULL))57

程式 5-5二元樹的前序走訪21 void preorder(struct BTreeNode node)22 if (node = NULL)23 cout ltlt node-gtdata24 preorder(node-gtleftchild)25 preorder(node-gtrightchild)26 27

58 void Postorder_Stack(struct BTreeNode node)59 do60 while (node = NULL)61 cout ltlt node-gtdata62 push_node(node)63 node = node-gtrightchild64 push all left children65 if (top = NULL)66 node = pop_node()67 node = node-gtleftchild68 69 while ((top=NULL)||(node=NULL))70

程式 5-4二元樹的後序走訪14 void postorder(struct BTreeNode node)15 if (node =NULL)16 postorder(node-gtleftchild)17 postorder(node-gtrightchild)18 cout ltlt node-gtdata19 20

555  階層走訪bull 「階層走訪」 (level-order traversal) 是依階層

的順序進行二元樹的走訪先走訪階層小的節點後走訪階層大的節點同一階層者則依自左向右的順序走訪

bull 對下圖的二元樹進行階層走訪的結果為ABCDEFGHI

bull 對同一階層而言先走訪的節點其子節點亦在下一階層中先被走訪

bull 這種「先進先出」的特性恰可用佇列予以儲存與處理

程式 5-7 利用佇列進行二元樹的階層走訪

1 struct BTreeNode2 struct BTreeNode leftchild3 char data4 struct BTreeNode rightchild5 6 struct BTreeNode root7 struct QNode8 struct BTreeNode treenode9 struct QNode next10 11 struct QNode front rear

12 void AddQueue(struct BTreeNode Tnode)13 struct QNode node14 node=(struct QNode )malloc(sizeof(struct QNode))15 node-gttreenode = Tnode16 node-gtnext = NULL17 if (front == NULL) front = node19 else rear-gtnext = node20 rear = node21

22 struct BTreeNode DeleteQueue()23 struct BTreeNode Tnode24 struct QNode old_front25 if (rear == NULL)26 QueueEmpty()27 else28 Tnode = front-gttreenode29 old_front = front30 front = front-gtnext31 free(old_front)32 return Tnode33 34 35 void LevelOrder (struct BTreeNode node)36 AddQueue(node)37 while (front =NULL)38 node = DeleteQueue()39 cout ltlt node-gtdata40 if (node-gtleftchild = NULL)41 AddQueue(node-gtleftchild)42 if (node-gtrightchild = NULL)43 AddQueue(node-gtrightchild)44 45

35 void LevelOrder (struct BTreeNode node)36 AddQueue(node)37 while (front =NULL)38 node = DeleteQueue()39 cout ltlt node-gtdata40 if (node-gtleftchild = NULL)41 AddQueue(node-gtleftchild)42 if (node-gtrightchild = NULL)43 AddQueue(node-gtrightchild)44 45

bull Infix ABCbull Postfix ABC CBA ACB BCA

BAC CAB

B

A CA

B

C

B

A

C

C

B

AB

A

C

二元樹中序與前序式中序與後序式bull 每一棵二元樹皆有唯一的一對中序與前序表示法也有唯一的中序與後序次序

bull 換句話說給予一對中序與前序或中序與後序即可決定一棵二元樹

bull 然而給予前序和後序次序並不能決定唯一的二元樹可能會產生兩棵不同的二元樹

bull 想想階層走訪和中序走訪順序可否決定唯一的二元樹

A

B C

H J D E

F G

prefix ABHJCDFGEinfix HBJAFDGCEpostfix HJBFGDECA

VLRLVRLRV

A

prefix BHJinfix HBJ

prefix CDFGEinfix FDGCE

prefix ABHJCDFGEinfix HBJAFDGCE

A

B C

H J D E

F G

prefix ABHJCDFGE

infix HBJAFDGCEk-1

struct BTreeNode BTree_InfixPrefix (AnsiString infix AnsiString prefix)

int k struct BTreeNode node if (infixLength==0) return NULLnode = (struct BTreeNode ) malloc (sizeof(struct BTreeNode))node-gtdata = prefix[1]k = find(prefix[1] infix)node-gtleftchild = BTree_InfixPrefix

(infix[1k-1] prefix[2k])node-gtrightchild = BTree_InfixPrefix(infix[k+1infixLength]prefix[k+1prefixLength])

VLR

LVRk1

k2 k+1~

k+1~

1

struct BTreeNode BTree_InfixPostfix (AnsiString infix AnsiString prefix)

int k struct BTreeNode node if (infixLength==0) return NULLnode = (struct BTreeNode ) malloc (sizeof(struct BTreeNode))node-gtdata = postfix[postfixLength]k = find(postfix[postfixLength] infix)node-gtleftchild = BTree_InfixPrefix

(infix[1k-1] postfix[1k-1])node-gtrightchild = BTree_InfixPrefix(infix[k+1infixLength]postfix[k+1postfixLength-1])

56  二元樹的複製與相等測試程式 5-8 二元樹的複製1 struct BTreeNode2 struct BTreeNode leftchild3 char data4 struct BTreeNode rightchild5 6 struct BTreeNode root7 struct BTreeNode Copy(struct BTreeNode TreeRoot)8 struct BTreeNode CopyRoot9 if (TreeRoot == NULL)10 return NULL11 CopyRoot =(struct BTreeNode ) malloc (sizeof(BTreeNode))12 CopyRoot-gtdata = TreeRoot-gtdata13 CopyRoot-gtleftchild = Copy(TreeRoot-gtleftchild)14 CopyRoot-gtrightchild = Copy(TreeRoot-

gtrightchild)15 return CopyRoot16

程式 5-9 檢測兩棵二元樹是否相等1 struct BTreeNode2 struct BTreeNode leftchild3 char data4 struct BTreeNode rightchild5 6 struct BtreeNode rightchild7 int Equal(struct BTreeNode ustruct BTreeNode v)8 if ((u==NULL) ampamp (v==NULL)) return 19 if ((u=NULL) ampamp (v=NULL)10 ampamp (u-gtdata == v-gtdata )11 ampamp Equal(u-gtleftchildv-gtleftchild)12 ampamp Equal(u-gtrightchildv-gtrightchild))

13 return 114 return 015

Counting Nodes in a Binary Tree

int CountingNodes(struct BTreeNodes node)

if (node == NULL) return 0 int leftchildren = CountingNodes(node-gtleftchild)

int rightchildren= CountingNodes(node-gtrightchild)

return leftchildren+rightchildren

Counting Leaves in a Binary Tree

int CountingLeaves(struct BTreeNodes node)

if (node == NULL) return 0 int leftleaves = CountingLeaves(node-gtleftchild)

int rightchildren= CountingLeaves(node-gtrightchild)

if (leftleaves==0 ampamp leftleaves==0) return 1

return leftleaves + leftleaves

57  引線二元樹bull 在二元樹的鍵結表示中樹葉節點的兩個子樹指標皆指向 NULL 由定理 5-1 得知 n = E+1 其中 n 為節點個數 E 為分支數

bull 而 n 個鍵結節點有 2n 個指標空間每個分支恰佔用一個指標空間共有 E (=n-1) 個指標有用到(不是空的)而共有 n+1 個指標空間是 NULL 比非空的節點還多

bull 有學者提出對這些「放空指標的空間」加以利用的概念mdash與其放空指標不如放指向其它節點的指標稱之為引線 (thread) 使得某些運算(如走訪hellip等)可以加快

bull 這個概念發展出了引線二元樹 (threaded binary tree) 這種資料結構

範例 5-17下圖為加了引線的二元樹

加入的引線將使二元樹的中序走訪更加便利沿著上右圖箭頭所指示的順序走訪即可完成此二元樹的中序走訪GDHBAIECF 為此二元樹的中序表示

bull 範例 5-17 中所加的引線目的在方便二元樹的中序走訪

bull 對樹葉節點 p 而言令其資料為 P 則引線指標的規則如下

(1) p 的左子樹指標指向中序表示中 P 的前一個 元素(前接元素 (predecessor) )

(2) p 的右子樹指標指向中序表示中 P 的後一個 元素(後繼元素 (successor) )

為了區別節點指標放的是一般指標(內部節點)抑或是引線指標(樹葉節點)我們另以 2 個欄位分別區別左和右子樹指標空間存放的對象其節點的記憶體配置有如下圖所示

其中 leftthread (rightthread) 為 0 時表示 leftchild (rightchild) 存放的是一般節點指標而 leftthread (rightthread) 為 1 時表示 leftchild (rightchild) 存放的是引線指標1 struct TBTreeNode2 int leftthread3 struct TBTreeNode leftchild4 char data5 struct TBTreeNode rightchild6 int rightthread7

由右側的引線二元樹可知有最左和最右兩條引線尚無妥善的安排我們另設計一空的引線節點做為樹根如下圖所示

571  引線二元樹的中序走訪決定節點 p 在中序表示中的後繼節點 q (簡稱中序後繼點)

程式 5-10決定節點 node的中序後繼點1 struct TBTreeNode2 int leftthread3 struct TBTreeNode leftchild4 char data5 struct TBTreeNode rightchild6 int rightthread7 8 struct TBTreeNode root9 struct TBTreeNode Next(struct TBTreeNode node)

10 struct TBTreeNode temp11 temp = node-gtrightchild12 if (node-gtrightthead) return temp13 while (temp-gtleftthead) temp=temp-gtleftchild

14 return temp15

程式 5-11引線二元樹的中序表示(接程式 5-10)1 void InorderTBTree()2 struct TBTreeNode node3 for(node=Next(root)node=rootnode=Next(node))

4 cout ltlt node-gtdata5

bull只要 node 是引線二元樹上的任一節點透過呼叫 Next(node) 即可找到其在中序表示中的後繼節點bull(試想若程式 5-10 中的程序 Next 所傳入的參數指標正是 root Next(root) 是否可正常運作請思考)bull於是只須從中序表示中的第一節點起讓每一節點 node 都執行 Next(node) 程序即可得引線二元樹的中序表示

572  在引線二元樹中加入節點在引線二元樹中樹葉節點節點的右子樹指標所指向或內部節點右子樹的最左樹葉為其中序後繼節點而且任一節點若無左子樹則其左子樹指標乃指向其中序前接節點hellip這些性質必須在節點新增進入引線二元樹時也要保持接下來討論節點 new 插入成為引線二元樹的節點 p 的右子節點的情形至於插入成為左子節點的情形則讓各位模擬推敲

572572  在引線二元樹中加入節點 在引線二元樹中加入節點 (( 續續 ))

newnew 會成為會成為 pp 的右子節點的情況共有以下兩種的右子節點的情況共有以下兩種(1) (1) 若若 pp 沒有右子節點 沒有右子節點 ((p-gtrightthread p-gtrightthread 為為1) 1) 如圖如圖 ((a) a) 則 則 new new 插入後應形成如圖插入後應形成如圖 ((b) b) 所示所示

(2)(2) 若若 pp 有右子節點 有右子節點 ((p-gtrightthread p-gtrightthread 為為0) 0) 如圖如圖 ((c) c) 則 則 new new 插入後應形成如插入後應形成如圖圖 ((d) d) 所示所示

程式 5-12  插入節點 new 成為引線二元樹中節點 p的右子節點(上接程式 5-10 5-11 )

1 void InsertRight(struct TBTreeNode p 2 struct TBTreeNode new)3 struct TBTreeNode q4 new-gtrightchild = p-gtrightchild5 new-gtrightthead = p-gtrightthread6 new-gtleftchild = p7 new-gtleftthread = 18 p-gtrightchild = new9 p-gtrightthead = 010 if (new-gtrightthread)11 q = Next(new)12 q-gtleftchild = new13 14

58  堆積bull 「堆積」 (heap) 是一個特殊的完備二元樹節點的資料與其子節點的資料之間有事先定好的大小關係存在bull 定義ndash最大堆疊為一完備二元樹任一節點的資料內容不小於其子節點的資料內容ndash最小堆疊為一完備二元樹任一節點的資料內容不大於其子節點的資料內容

581  插入節點至最大堆積中最大堆積是一完備二元樹所以如圖 (a) 之最大堆積於加入新節點後樹的形狀必須如圖 (b) 所示灰色節點即為新節點若加入的節點資料分別為 1 9 12 則其最大堆積將分別變成如圖 (c) (d) (e) 所示

程式 5-13 新增節點至最大堆積中最大堆積本身是一棵完備二元樹可以用陣列表示之 1 define maxsize 1002 int heap[maxsize]3 int n i4 void InsertHeap(int x)5 if (n == maxsize) HeapFull()6 else7 n++8 i = n9 while ((i gt 1) ampamp (x gt heap[i2]))10 heap[i] = heap[i2]11 i = 212 13 heap[i] = x14 16

程序 InsertHeap 在插入第 n 個資料時需要的時間複雜度為 O(logn)

1 100

2 93

3 84

4 77

5 65

6 81

7 79

8 66

9 70

10 50

100

84

77

93

8165

66 70

79

50

1

2 3

4 5 6 7

8 9 10

1 define maxsize 1002 int heap[maxsize]3 int n i4 void InsertHeap(int x)5 if (n == maxsize) HeapFull()6 else7 n++8 i = n9 while ((i gt 1) ampamp (x gt heap[i2]))10 heap[i] = heap[i2]11 i = 212 13 heap[i] = x14 16

582 刪除最大堆積中的最大資料圖 (a) 中的最大堆積在刪除最大資料後其樹形應變成如圖 (b) 所示將最未一個樹葉節點的資料 6 暫挪至樹根節點內如圖 (c) 再將圖 (c) 中非最大堆積的結構調整成為最大堆積 10 和 8 中選大者與 6對調即成圖 (d)

程式 5-14 刪除最大堆積中的最大資料(上接程式 5-13 )1 int DeleteHeap()2 int x i j3 if (n == 0) HeapEmpty()4 else5 x = heap[1]6 heap[1] = heap[n]7 n--8 i = 19 while (i lt= n2)10 if (heap[2i] gt heap[2i+1])11 j = 2i12 else13 j = 2i+114 if (heap[i]gtheap[j]) break15 swap(heap[i] heap[j])16 i = j17 18 return x19 20

其時間複雜度與其堆積高度相同是為O(logn) 其中 n 為堆積中的元素個數

1 100

2 93

3 84

4 77

5 65

6 81

7 79

8 66

9 70

10 50

100

84

77

93

8165

66 70

79

50

1

2 3

4 5 6 7

8 9 10

1 int DeletHeap()2 int x i j3 if(n == 0) HeapEmpty()4 else5 x = heap[1]6 heap[1] = heap[n]7 n--8 i = 19 while (i lt= n2)10 if ([heap[2i]] gt heap[2i+1])11 j = 2i12 else13 j = 2i+114 if (heap[i]gtheap[j]) break15 swap(heap[i] heap[j])16 i = j17 18 return x19 20

583  優先佇列bull 由於最大堆積(或最小堆積)的結構可提供最大元素(或最小元素)而其維護堆積的成本為 O(logn) n 為總元素個數

bull 堆積常被當成「優先佇列」 (priority queue) 使用mdash此時佇列被組織成完備二元樹若新加入佇列的資料有大小關係優先順序的需求時此優先佇列即以 O(logn) 的代價將新資料放入優先佇列(可能是最大或最小堆積依實際大小關係優先順序的需求而定)中mdash提供依元素的優先順序做為處理順序的服務

bull 一般的佇列為先進先出先到的先行處理優先佇列則是依據其優先順序優先順序高者先行處理與來到的先後無關

範例 5-21bull 在「分時」 (time sharing) 的作業系統中我們不希望使用中央處理器 (Central Processing Unit CPU) 時間短的「處理程序」 (process) 等候過久於是有「最短時間工作優先」 (shortest-job-first) 的排程策略此時等候的處理程序即可組織成之一最小堆積而 CPU 每次即取出存於樹根的處理程序予以處理任何新來的處理程序則以 O(logn) 的時間加入此優先佇列(最小堆積)中

bull 注意之前提過的線性佇列新來的直接加入成為新的尾端元素花O(1) 的時間然線性佇列本身沒有大小的關係每次要取出最大(小)的資料得花O(n) 的代價(在所有元素中挑出最大(小)者) n 為佇列內的元素個數而優先佇列是在新元素加入時要花O(logn) 的時間至於取出最大(小)則只須 O(1) 的時間

59  二元搜尋樹bull 二元搜尋樹是一棵二元樹可能是空二元樹若不為空二元樹則滿足下列性質ndash所有節點內的資料內容是相異的 ndash左子節點的資內容 ( 如果有 ) 比父節點的資料內容小 ndash右子節點的資料內容 ( 如果有 ) 比父節點的資料內容大 ndash以左和右子節點為樹根的左子樹和右子樹也是二元搜尋樹

在二元搜尋樹中搜尋資料二元搜尋樹對資料大小的掌握十分明確若恰與節點 p 資料相等則搜尋成功若欲搜尋的資料 x 比樹節點 p 中的資料小 則只須搜尋節點 p 的左子樹若比節點 p 中的資料大 則只須搜尋 p 的右子樹這樣的判斷可以遞迴地應用在各子樹節點 中

程式 5-15 在二元搜尋樹中搜尋資料(遞迴呼叫)1 struct BSTreeNode2 struct BSTreeNode leftchild3 int data4 struct BSTreeNode rightchild5 6 struct BSTreeNode root7 struct BSTreeNode SearchBST(

struct BSTreeNode tree int x)8 if (tree == NULL) return NULL9 if (x == tree-gtdata) return tree10 if (x lt tree-gtdata)11 return SearchBST(tree-gtleftchild x)

12 return SearchBST(tree-gtrightchild x)13

程式 5-16 搜尋二元搜尋樹的資料(非遞迴呼叫)在二元搜尋樹十分龐大時遞迴呼叫的效率不會太好可利用迴圈來完成搜尋的動作以提高執行效率請看程式 5-16

14 struct BSTreeNode Search BST_iterative15 (struct BSTreeNode node int x)16 while (node = NULL)17 if (x == node-gtdata) return node18 if (x lt node-gtdata)19 node = node-gtleftchild20 else21 node = node-gtrightchild22 23 return NULL24

592  新增資料於二元搜尋樹中欲新增資料進入一二元搜尋樹中得先決定該資料在二元搜尋樹中應出現的位置這可透過上節提到的搜尋動作來解決

在二元搜尋樹定義的性質 (1) 中我們限定了元素必須相異所以搜尋新資料的結果必是 NULL

必須將搜尋的結果改為新增資料所應在位置的節點指標

程式 5-17  在二元搜尋樹中新增資料25 int InsertBST(int x)26 struct BSTreeNode p q27 p = root q = NULL28 while (p = NULL)29 q = p30 if (x == p-gtdata) return 0

若不考慮重複則可省略之31 if (x lt p-gtdata)32 p = p-gtleftchild33 else34 p = p-gtrightchild35

3636 p = (struct BSTreeNode ) p = (struct BSTreeNode ) malloc (sizeof(BSTreeNode))malloc (sizeof(BSTreeNode))

3737 p-gtdata = x p-gtdata = x3838 p-gtleftchild = p-gtrightchild = p-gtleftchild = p-gtrightchild = NULLNULL3939 if (root == NULL) root = p if (root == NULL) root = p4040 else if (x lt q-gtdata) else if (x lt q-gtdata)4141 q-gtleftchild = p q-gtleftchild = p4242 else else4343 q-gtrightchild = p q-gtrightchild = p4444 return 1 return 145 45

程式 程式 5-175-17  在二元搜尋樹中新增資料  在二元搜尋樹中新增資料 (( 續續 ))

範例 5-23 圖 (b) 為圖 (a) 新增整數 6 之後的結果圖 (c) (d) (e) (f) 則分別是逐步再加入 28 18

10 21 的結果

bull若目前的二元搜尋樹高度為 h 則新增資料得花O(h) 的時間找到其所應在的位置遂新增資料至二元搜尋樹中的時間複雜之度為 O(h)

593 自二元搜尋樹中刪除資料欲自二元搜尋樹中刪除資料可能會面臨兩種情況(1) 刪除節點 p 位於樹葉節點上如下圖所示 這是比較容易處理的情況

(2) 刪除的節點 p 不是樹葉刪除節點 p 後原 p 所指的節點可由 q ( q 中包含了 p 左子樹中的最大節點值)或 r ( r 中包含了 p 右子樹中的最小節點值)來取代以維持二元搜尋樹中資料的大小關係其結果分別如圖 (b) 和 (c) 所示而圖 (d) 則標明了指標會改變的節點位置即 p 會消失 father 的左或右子樹指標會異動 k(可能是 q 或 r )會移到原來 p 的位置其父節點 f 的左或右子樹指標會異動其中 father 為 p 父節點 f 為 k 的父節點

程式 5-18 自二元搜尋樹中刪除資料46 int DeleteBST (int x)47 Struct BSTreeNode p father k f48 p = root father = NULL49 while (p = NULL)50 if (x == p-gtdata) 找到 x 所在的節點 p 了51 if ((p-gtleftchild == NULL) ampamp 52 (p-gtrightchild == NULL))53 k = NULL p為樹葉節點54 else if (p-gtleftchild = NULL )x位於非子節

點55 f = p 找出 p左子樹的最右樹葉56 k = p-gtleftchild57 while (k-gtrightchild =NULL)58 f = k59 k = k-gtrightchild60 k將挪至原 p處61 if (p == f)62 f-gtleftchild = k-gtleftchild 63 else64 f-gtrightchild = k-gtleftchild 65

66 else p無左子樹67 f = p 找出 p右子樹的最左樹葉68 k = p-gtrightchild69 while (k-gtleftchild = NULL)70 f = k71 k = k -gtleftchild72 73 if (p == f)74 f-gtrightchild = k-gtrightchild 75 else76 f-gtleftchild = k-gtrightchild77 78 if (k = NULL) k挪至原 p處繼承 p的左右指標79 k-gtleftchild = p-gtleftchild80 k-gtrightchild = p-gtrightchild81 82 if (father == NULL) root = k83 else if (x lt father-gtdata) 決定 k是 father的左

或右兒子84 father-gtleftchild = k85 else86 father-gtrightchild = k87 free(p)88 return 1 成功地刪除 x於此傳回 1返回呼叫處89 51~89行為找到 x時應有的處理步驟

90 else 尚未找到 x繼續往下一階層找

91 father = p92 if (x lt p-gtdata)93 p = p-gtleftchild94 else95 p = p-gtrightchild96 97 98 return 0 未找到 x傳回 099

510  樹林 (forest)

bull 定義樹林(森林)是由 n 棵沒有交集的樹所構成的集合 n 0

下圖為一包含三棵樹的樹林

5101 轉換樹林為二元樹 之前我們以二元樹來表示樹因為二元樹有較多

好用的性質在此我們也將樹林轉換成二元樹看待

在 522 節中我們嘗試了用左子右兄弟的方式來表示樹這個方法事實上已將樹轉換成二元樹

即用左子右兄弟的結構重新組織樹林中的每一棵樹這些樹的樹根將視為兄弟而最左邊的樹則視為樹林二元樹的樹根如此一來樹林即可轉換成二元樹

範例 5-25右下圖為右上圖三棵樹所對應的三棵左子右兄弟二元樹

下圖則為上圖樹林所對應的一棵樹林二元樹

演算法 5-1  轉換樹林成二元樹輸入樹林 F = T1 T2 Tn

輸出 F 的二元樹表示 BT=(T1 T2 Tn)1 if (n == 0) 輸出空二元樹 2 else

3 root(T1 T2 Tn) = T1

4 leftsubtree(T1 T2 Tn) =

BT(T11 T12 T1m)5 BT(T11 T12 T1m)為 T1的子樹6 rightsubtree(T1 T2 Tn) =

BT(T2 T3 Tn)

樹林轉換成二元樹的過程樹林轉換成二元樹的過程

  • Slide 1
  • Slide 2
  • Slide 3
  • Slide 4
  • Slide 5
  • Slide 6
  • Slide 7
  • Slide 8
  • Slide 9
  • Slide 10
  • Slide 11
  • Slide 12
  • Slide 13
  • Slide 14
  • Slide 15
  • Slide 16
  • Slide 17
  • Slide 18
  • Slide 19
  • Slide 20
  • Slide 21
  • Slide 22
  • Slide 23
  • Slide 24
  • Slide 25
  • Slide 26
  • Slide 27
  • Slide 28
  • Slide 29
  • Slide 30
  • Slide 31
  • Slide 32
  • Slide 33
  • Slide 34
  • Slide 35
  • Slide 36
  • Slide 37
  • Slide 38
  • Slide 39
  • Slide 40
  • Slide 41
  • Slide 42
  • Slide 43
  • Slide 44
  • Slide 45
  • Slide 46
  • Slide 47
  • Slide 48
  • Slide 49
  • Slide 50
  • Slide 51
  • Slide 52
  • Slide 53
  • Slide 54
  • Slide 55
  • Slide 56
  • Slide 57
  • Slide 58
  • Slide 59
  • Slide 60
  • Slide 61
  • Slide 62
  • Slide 63
  • Slide 64
  • Slide 65
  • Slide 66
  • Slide 67
  • Slide 68
  • Slide 69
  • Slide 70
  • Slide 71
  • Slide 72
  • Slide 73
  • Slide 74
  • Slide 75
  • Slide 76
  • Slide 77
  • Slide 78
  • Slide 79
  • Slide 80
  • Slide 81
  • Slide 82
  • Slide 83
  • Slide 84
  • Slide 85
  • Slide 86
  • Slide 87
  • Slide 88
  • Slide 89
  • Slide 90
  • Slide 91
  • Slide 92
  • Slide 93
  • Slide 94
  • Slide 95
  • Slide 96
  • Slide 97
  • Slide 98
  • Slide 99
  • Slide 100
  • Slide 101
  • Slide 102
  • Slide 103
Page 37: 第五章 樹

6 struct BTreeNode root7 void inorder(struct BTreeNode node)8 if (node = NULL)9 inorder(node-gtleftchild)10 cout ltlt node-gtdata11 inorder(node-gtrightchild)12 13

(A+B)C+D(E-F)

如何加上 ( )

552  後序走訪程式 5-4二元樹的後序走訪14 void postorder(struct BTreeNode node)

15 if (node =NULL)16 postorder(node-gtleftchild)17 postorder(node-gtrightchild)18 cout ltlt node-gtdata19 20

以後序走訪可得到AB+CDEF-+

其正為對應的後序運算式

553  前序走訪程式 5-5二元樹的前序走訪21 void preorder(struct BTreeNode node)

22 if (node = NULL)

23 cout ltlt node-gtdata

24 preorder(node-gtleftchild)

25 preorder(node-gtrightchild)

26

27

以前序走訪可得到++ABCD-EF

其正為對應的前序運算式

554 利用堆疊和迴圈取代遞迴進行二元樹的走訪1 struct BTreeNode 2 struct BTreeNode leftchild3 char data4 struct BtreeNode rightchild5 6 struct BTreeNode root7 struct StackNode8 struct BTreeNode treenode9 struct StackNode next1011 struct StackNode top

12 void push_node(struct BtreeNode node )13 struct StackNode old_top14 old_top = top15 top = (struct StackNode ) malloc

(sizeof(struct StackNode))16 top-gttreenode = node17 top-gtnext = old_top18 19 struct BTreeNode pop_node()20 struct BTreeNode Tnode21 struct StackNode old_top22 if (top == NULL)23 StackEmpty()24 else25 Tnode = top-gttreenode26 old_top = top27 top = top-gtnext28 free(old_top)29 return Tnode30 31

32 void inorder_Stack(struct BtreeNode node)33 do34 while (node = NULL)35 push_node(node)36 node = node-gtleftchild37 push all left children38 if (top = NULL)39 node = pop_node()40 cout ltlt node-gtdata41 node = node-gtrightchild42 inorder printing and check right child43 while ((top=NULL)||(node=NULL))44 top==NULL 堆疊已空 node==NULL沒有右子

6 struct BTreeNode root7 void inorder(struct BTreeNode node)8 if (node = NULL)9 inorder(node-gtleftchild)10 cout ltlt node-gtdata11 inorder(node-gtrightchild)12 13

中序表示為DBGEHAFIC

32 void inorder_Stack()33 struct BTreeNode node34 node = root35 do36 while (node = NULL)37 push_node(node)38 node = node-gtleftchild39 40 if (top = NULL)41 node = pop_node()42 cout ltlt node-gtdata43 node = node-gtrightchild44 45 while(top=NULL)||(node=NULL))46

45 void Preorder_Stack(struct BSTreeNode node)46 do47 while (node = NULL)48 cout ltlt node-gtdata)49 push_node(node)50 node = node-gtleftchild51 push all left children52 if (top = NULL)53 node = pop_node()54 node = node-gtrightchild55 56 while ((top=NULL)||(node=NULL))57

程式 5-5二元樹的前序走訪21 void preorder(struct BTreeNode node)22 if (node = NULL)23 cout ltlt node-gtdata24 preorder(node-gtleftchild)25 preorder(node-gtrightchild)26 27

58 void Postorder_Stack(struct BTreeNode node)59 do60 while (node = NULL)61 cout ltlt node-gtdata62 push_node(node)63 node = node-gtrightchild64 push all left children65 if (top = NULL)66 node = pop_node()67 node = node-gtleftchild68 69 while ((top=NULL)||(node=NULL))70

程式 5-4二元樹的後序走訪14 void postorder(struct BTreeNode node)15 if (node =NULL)16 postorder(node-gtleftchild)17 postorder(node-gtrightchild)18 cout ltlt node-gtdata19 20

555  階層走訪bull 「階層走訪」 (level-order traversal) 是依階層

的順序進行二元樹的走訪先走訪階層小的節點後走訪階層大的節點同一階層者則依自左向右的順序走訪

bull 對下圖的二元樹進行階層走訪的結果為ABCDEFGHI

bull 對同一階層而言先走訪的節點其子節點亦在下一階層中先被走訪

bull 這種「先進先出」的特性恰可用佇列予以儲存與處理

程式 5-7 利用佇列進行二元樹的階層走訪

1 struct BTreeNode2 struct BTreeNode leftchild3 char data4 struct BTreeNode rightchild5 6 struct BTreeNode root7 struct QNode8 struct BTreeNode treenode9 struct QNode next10 11 struct QNode front rear

12 void AddQueue(struct BTreeNode Tnode)13 struct QNode node14 node=(struct QNode )malloc(sizeof(struct QNode))15 node-gttreenode = Tnode16 node-gtnext = NULL17 if (front == NULL) front = node19 else rear-gtnext = node20 rear = node21

22 struct BTreeNode DeleteQueue()23 struct BTreeNode Tnode24 struct QNode old_front25 if (rear == NULL)26 QueueEmpty()27 else28 Tnode = front-gttreenode29 old_front = front30 front = front-gtnext31 free(old_front)32 return Tnode33 34 35 void LevelOrder (struct BTreeNode node)36 AddQueue(node)37 while (front =NULL)38 node = DeleteQueue()39 cout ltlt node-gtdata40 if (node-gtleftchild = NULL)41 AddQueue(node-gtleftchild)42 if (node-gtrightchild = NULL)43 AddQueue(node-gtrightchild)44 45

35 void LevelOrder (struct BTreeNode node)36 AddQueue(node)37 while (front =NULL)38 node = DeleteQueue()39 cout ltlt node-gtdata40 if (node-gtleftchild = NULL)41 AddQueue(node-gtleftchild)42 if (node-gtrightchild = NULL)43 AddQueue(node-gtrightchild)44 45

bull Infix ABCbull Postfix ABC CBA ACB BCA

BAC CAB

B

A CA

B

C

B

A

C

C

B

AB

A

C

二元樹中序與前序式中序與後序式bull 每一棵二元樹皆有唯一的一對中序與前序表示法也有唯一的中序與後序次序

bull 換句話說給予一對中序與前序或中序與後序即可決定一棵二元樹

bull 然而給予前序和後序次序並不能決定唯一的二元樹可能會產生兩棵不同的二元樹

bull 想想階層走訪和中序走訪順序可否決定唯一的二元樹

A

B C

H J D E

F G

prefix ABHJCDFGEinfix HBJAFDGCEpostfix HJBFGDECA

VLRLVRLRV

A

prefix BHJinfix HBJ

prefix CDFGEinfix FDGCE

prefix ABHJCDFGEinfix HBJAFDGCE

A

B C

H J D E

F G

prefix ABHJCDFGE

infix HBJAFDGCEk-1

struct BTreeNode BTree_InfixPrefix (AnsiString infix AnsiString prefix)

int k struct BTreeNode node if (infixLength==0) return NULLnode = (struct BTreeNode ) malloc (sizeof(struct BTreeNode))node-gtdata = prefix[1]k = find(prefix[1] infix)node-gtleftchild = BTree_InfixPrefix

(infix[1k-1] prefix[2k])node-gtrightchild = BTree_InfixPrefix(infix[k+1infixLength]prefix[k+1prefixLength])

VLR

LVRk1

k2 k+1~

k+1~

1

struct BTreeNode BTree_InfixPostfix (AnsiString infix AnsiString prefix)

int k struct BTreeNode node if (infixLength==0) return NULLnode = (struct BTreeNode ) malloc (sizeof(struct BTreeNode))node-gtdata = postfix[postfixLength]k = find(postfix[postfixLength] infix)node-gtleftchild = BTree_InfixPrefix

(infix[1k-1] postfix[1k-1])node-gtrightchild = BTree_InfixPrefix(infix[k+1infixLength]postfix[k+1postfixLength-1])

56  二元樹的複製與相等測試程式 5-8 二元樹的複製1 struct BTreeNode2 struct BTreeNode leftchild3 char data4 struct BTreeNode rightchild5 6 struct BTreeNode root7 struct BTreeNode Copy(struct BTreeNode TreeRoot)8 struct BTreeNode CopyRoot9 if (TreeRoot == NULL)10 return NULL11 CopyRoot =(struct BTreeNode ) malloc (sizeof(BTreeNode))12 CopyRoot-gtdata = TreeRoot-gtdata13 CopyRoot-gtleftchild = Copy(TreeRoot-gtleftchild)14 CopyRoot-gtrightchild = Copy(TreeRoot-

gtrightchild)15 return CopyRoot16

程式 5-9 檢測兩棵二元樹是否相等1 struct BTreeNode2 struct BTreeNode leftchild3 char data4 struct BTreeNode rightchild5 6 struct BtreeNode rightchild7 int Equal(struct BTreeNode ustruct BTreeNode v)8 if ((u==NULL) ampamp (v==NULL)) return 19 if ((u=NULL) ampamp (v=NULL)10 ampamp (u-gtdata == v-gtdata )11 ampamp Equal(u-gtleftchildv-gtleftchild)12 ampamp Equal(u-gtrightchildv-gtrightchild))

13 return 114 return 015

Counting Nodes in a Binary Tree

int CountingNodes(struct BTreeNodes node)

if (node == NULL) return 0 int leftchildren = CountingNodes(node-gtleftchild)

int rightchildren= CountingNodes(node-gtrightchild)

return leftchildren+rightchildren

Counting Leaves in a Binary Tree

int CountingLeaves(struct BTreeNodes node)

if (node == NULL) return 0 int leftleaves = CountingLeaves(node-gtleftchild)

int rightchildren= CountingLeaves(node-gtrightchild)

if (leftleaves==0 ampamp leftleaves==0) return 1

return leftleaves + leftleaves

57  引線二元樹bull 在二元樹的鍵結表示中樹葉節點的兩個子樹指標皆指向 NULL 由定理 5-1 得知 n = E+1 其中 n 為節點個數 E 為分支數

bull 而 n 個鍵結節點有 2n 個指標空間每個分支恰佔用一個指標空間共有 E (=n-1) 個指標有用到(不是空的)而共有 n+1 個指標空間是 NULL 比非空的節點還多

bull 有學者提出對這些「放空指標的空間」加以利用的概念mdash與其放空指標不如放指向其它節點的指標稱之為引線 (thread) 使得某些運算(如走訪hellip等)可以加快

bull 這個概念發展出了引線二元樹 (threaded binary tree) 這種資料結構

範例 5-17下圖為加了引線的二元樹

加入的引線將使二元樹的中序走訪更加便利沿著上右圖箭頭所指示的順序走訪即可完成此二元樹的中序走訪GDHBAIECF 為此二元樹的中序表示

bull 範例 5-17 中所加的引線目的在方便二元樹的中序走訪

bull 對樹葉節點 p 而言令其資料為 P 則引線指標的規則如下

(1) p 的左子樹指標指向中序表示中 P 的前一個 元素(前接元素 (predecessor) )

(2) p 的右子樹指標指向中序表示中 P 的後一個 元素(後繼元素 (successor) )

為了區別節點指標放的是一般指標(內部節點)抑或是引線指標(樹葉節點)我們另以 2 個欄位分別區別左和右子樹指標空間存放的對象其節點的記憶體配置有如下圖所示

其中 leftthread (rightthread) 為 0 時表示 leftchild (rightchild) 存放的是一般節點指標而 leftthread (rightthread) 為 1 時表示 leftchild (rightchild) 存放的是引線指標1 struct TBTreeNode2 int leftthread3 struct TBTreeNode leftchild4 char data5 struct TBTreeNode rightchild6 int rightthread7

由右側的引線二元樹可知有最左和最右兩條引線尚無妥善的安排我們另設計一空的引線節點做為樹根如下圖所示

571  引線二元樹的中序走訪決定節點 p 在中序表示中的後繼節點 q (簡稱中序後繼點)

程式 5-10決定節點 node的中序後繼點1 struct TBTreeNode2 int leftthread3 struct TBTreeNode leftchild4 char data5 struct TBTreeNode rightchild6 int rightthread7 8 struct TBTreeNode root9 struct TBTreeNode Next(struct TBTreeNode node)

10 struct TBTreeNode temp11 temp = node-gtrightchild12 if (node-gtrightthead) return temp13 while (temp-gtleftthead) temp=temp-gtleftchild

14 return temp15

程式 5-11引線二元樹的中序表示(接程式 5-10)1 void InorderTBTree()2 struct TBTreeNode node3 for(node=Next(root)node=rootnode=Next(node))

4 cout ltlt node-gtdata5

bull只要 node 是引線二元樹上的任一節點透過呼叫 Next(node) 即可找到其在中序表示中的後繼節點bull(試想若程式 5-10 中的程序 Next 所傳入的參數指標正是 root Next(root) 是否可正常運作請思考)bull於是只須從中序表示中的第一節點起讓每一節點 node 都執行 Next(node) 程序即可得引線二元樹的中序表示

572  在引線二元樹中加入節點在引線二元樹中樹葉節點節點的右子樹指標所指向或內部節點右子樹的最左樹葉為其中序後繼節點而且任一節點若無左子樹則其左子樹指標乃指向其中序前接節點hellip這些性質必須在節點新增進入引線二元樹時也要保持接下來討論節點 new 插入成為引線二元樹的節點 p 的右子節點的情形至於插入成為左子節點的情形則讓各位模擬推敲

572572  在引線二元樹中加入節點 在引線二元樹中加入節點 (( 續續 ))

newnew 會成為會成為 pp 的右子節點的情況共有以下兩種的右子節點的情況共有以下兩種(1) (1) 若若 pp 沒有右子節點 沒有右子節點 ((p-gtrightthread p-gtrightthread 為為1) 1) 如圖如圖 ((a) a) 則 則 new new 插入後應形成如圖插入後應形成如圖 ((b) b) 所示所示

(2)(2) 若若 pp 有右子節點 有右子節點 ((p-gtrightthread p-gtrightthread 為為0) 0) 如圖如圖 ((c) c) 則 則 new new 插入後應形成如插入後應形成如圖圖 ((d) d) 所示所示

程式 5-12  插入節點 new 成為引線二元樹中節點 p的右子節點(上接程式 5-10 5-11 )

1 void InsertRight(struct TBTreeNode p 2 struct TBTreeNode new)3 struct TBTreeNode q4 new-gtrightchild = p-gtrightchild5 new-gtrightthead = p-gtrightthread6 new-gtleftchild = p7 new-gtleftthread = 18 p-gtrightchild = new9 p-gtrightthead = 010 if (new-gtrightthread)11 q = Next(new)12 q-gtleftchild = new13 14

58  堆積bull 「堆積」 (heap) 是一個特殊的完備二元樹節點的資料與其子節點的資料之間有事先定好的大小關係存在bull 定義ndash最大堆疊為一完備二元樹任一節點的資料內容不小於其子節點的資料內容ndash最小堆疊為一完備二元樹任一節點的資料內容不大於其子節點的資料內容

581  插入節點至最大堆積中最大堆積是一完備二元樹所以如圖 (a) 之最大堆積於加入新節點後樹的形狀必須如圖 (b) 所示灰色節點即為新節點若加入的節點資料分別為 1 9 12 則其最大堆積將分別變成如圖 (c) (d) (e) 所示

程式 5-13 新增節點至最大堆積中最大堆積本身是一棵完備二元樹可以用陣列表示之 1 define maxsize 1002 int heap[maxsize]3 int n i4 void InsertHeap(int x)5 if (n == maxsize) HeapFull()6 else7 n++8 i = n9 while ((i gt 1) ampamp (x gt heap[i2]))10 heap[i] = heap[i2]11 i = 212 13 heap[i] = x14 16

程序 InsertHeap 在插入第 n 個資料時需要的時間複雜度為 O(logn)

1 100

2 93

3 84

4 77

5 65

6 81

7 79

8 66

9 70

10 50

100

84

77

93

8165

66 70

79

50

1

2 3

4 5 6 7

8 9 10

1 define maxsize 1002 int heap[maxsize]3 int n i4 void InsertHeap(int x)5 if (n == maxsize) HeapFull()6 else7 n++8 i = n9 while ((i gt 1) ampamp (x gt heap[i2]))10 heap[i] = heap[i2]11 i = 212 13 heap[i] = x14 16

582 刪除最大堆積中的最大資料圖 (a) 中的最大堆積在刪除最大資料後其樹形應變成如圖 (b) 所示將最未一個樹葉節點的資料 6 暫挪至樹根節點內如圖 (c) 再將圖 (c) 中非最大堆積的結構調整成為最大堆積 10 和 8 中選大者與 6對調即成圖 (d)

程式 5-14 刪除最大堆積中的最大資料(上接程式 5-13 )1 int DeleteHeap()2 int x i j3 if (n == 0) HeapEmpty()4 else5 x = heap[1]6 heap[1] = heap[n]7 n--8 i = 19 while (i lt= n2)10 if (heap[2i] gt heap[2i+1])11 j = 2i12 else13 j = 2i+114 if (heap[i]gtheap[j]) break15 swap(heap[i] heap[j])16 i = j17 18 return x19 20

其時間複雜度與其堆積高度相同是為O(logn) 其中 n 為堆積中的元素個數

1 100

2 93

3 84

4 77

5 65

6 81

7 79

8 66

9 70

10 50

100

84

77

93

8165

66 70

79

50

1

2 3

4 5 6 7

8 9 10

1 int DeletHeap()2 int x i j3 if(n == 0) HeapEmpty()4 else5 x = heap[1]6 heap[1] = heap[n]7 n--8 i = 19 while (i lt= n2)10 if ([heap[2i]] gt heap[2i+1])11 j = 2i12 else13 j = 2i+114 if (heap[i]gtheap[j]) break15 swap(heap[i] heap[j])16 i = j17 18 return x19 20

583  優先佇列bull 由於最大堆積(或最小堆積)的結構可提供最大元素(或最小元素)而其維護堆積的成本為 O(logn) n 為總元素個數

bull 堆積常被當成「優先佇列」 (priority queue) 使用mdash此時佇列被組織成完備二元樹若新加入佇列的資料有大小關係優先順序的需求時此優先佇列即以 O(logn) 的代價將新資料放入優先佇列(可能是最大或最小堆積依實際大小關係優先順序的需求而定)中mdash提供依元素的優先順序做為處理順序的服務

bull 一般的佇列為先進先出先到的先行處理優先佇列則是依據其優先順序優先順序高者先行處理與來到的先後無關

範例 5-21bull 在「分時」 (time sharing) 的作業系統中我們不希望使用中央處理器 (Central Processing Unit CPU) 時間短的「處理程序」 (process) 等候過久於是有「最短時間工作優先」 (shortest-job-first) 的排程策略此時等候的處理程序即可組織成之一最小堆積而 CPU 每次即取出存於樹根的處理程序予以處理任何新來的處理程序則以 O(logn) 的時間加入此優先佇列(最小堆積)中

bull 注意之前提過的線性佇列新來的直接加入成為新的尾端元素花O(1) 的時間然線性佇列本身沒有大小的關係每次要取出最大(小)的資料得花O(n) 的代價(在所有元素中挑出最大(小)者) n 為佇列內的元素個數而優先佇列是在新元素加入時要花O(logn) 的時間至於取出最大(小)則只須 O(1) 的時間

59  二元搜尋樹bull 二元搜尋樹是一棵二元樹可能是空二元樹若不為空二元樹則滿足下列性質ndash所有節點內的資料內容是相異的 ndash左子節點的資內容 ( 如果有 ) 比父節點的資料內容小 ndash右子節點的資料內容 ( 如果有 ) 比父節點的資料內容大 ndash以左和右子節點為樹根的左子樹和右子樹也是二元搜尋樹

在二元搜尋樹中搜尋資料二元搜尋樹對資料大小的掌握十分明確若恰與節點 p 資料相等則搜尋成功若欲搜尋的資料 x 比樹節點 p 中的資料小 則只須搜尋節點 p 的左子樹若比節點 p 中的資料大 則只須搜尋 p 的右子樹這樣的判斷可以遞迴地應用在各子樹節點 中

程式 5-15 在二元搜尋樹中搜尋資料(遞迴呼叫)1 struct BSTreeNode2 struct BSTreeNode leftchild3 int data4 struct BSTreeNode rightchild5 6 struct BSTreeNode root7 struct BSTreeNode SearchBST(

struct BSTreeNode tree int x)8 if (tree == NULL) return NULL9 if (x == tree-gtdata) return tree10 if (x lt tree-gtdata)11 return SearchBST(tree-gtleftchild x)

12 return SearchBST(tree-gtrightchild x)13

程式 5-16 搜尋二元搜尋樹的資料(非遞迴呼叫)在二元搜尋樹十分龐大時遞迴呼叫的效率不會太好可利用迴圈來完成搜尋的動作以提高執行效率請看程式 5-16

14 struct BSTreeNode Search BST_iterative15 (struct BSTreeNode node int x)16 while (node = NULL)17 if (x == node-gtdata) return node18 if (x lt node-gtdata)19 node = node-gtleftchild20 else21 node = node-gtrightchild22 23 return NULL24

592  新增資料於二元搜尋樹中欲新增資料進入一二元搜尋樹中得先決定該資料在二元搜尋樹中應出現的位置這可透過上節提到的搜尋動作來解決

在二元搜尋樹定義的性質 (1) 中我們限定了元素必須相異所以搜尋新資料的結果必是 NULL

必須將搜尋的結果改為新增資料所應在位置的節點指標

程式 5-17  在二元搜尋樹中新增資料25 int InsertBST(int x)26 struct BSTreeNode p q27 p = root q = NULL28 while (p = NULL)29 q = p30 if (x == p-gtdata) return 0

若不考慮重複則可省略之31 if (x lt p-gtdata)32 p = p-gtleftchild33 else34 p = p-gtrightchild35

3636 p = (struct BSTreeNode ) p = (struct BSTreeNode ) malloc (sizeof(BSTreeNode))malloc (sizeof(BSTreeNode))

3737 p-gtdata = x p-gtdata = x3838 p-gtleftchild = p-gtrightchild = p-gtleftchild = p-gtrightchild = NULLNULL3939 if (root == NULL) root = p if (root == NULL) root = p4040 else if (x lt q-gtdata) else if (x lt q-gtdata)4141 q-gtleftchild = p q-gtleftchild = p4242 else else4343 q-gtrightchild = p q-gtrightchild = p4444 return 1 return 145 45

程式 程式 5-175-17  在二元搜尋樹中新增資料  在二元搜尋樹中新增資料 (( 續續 ))

範例 5-23 圖 (b) 為圖 (a) 新增整數 6 之後的結果圖 (c) (d) (e) (f) 則分別是逐步再加入 28 18

10 21 的結果

bull若目前的二元搜尋樹高度為 h 則新增資料得花O(h) 的時間找到其所應在的位置遂新增資料至二元搜尋樹中的時間複雜之度為 O(h)

593 自二元搜尋樹中刪除資料欲自二元搜尋樹中刪除資料可能會面臨兩種情況(1) 刪除節點 p 位於樹葉節點上如下圖所示 這是比較容易處理的情況

(2) 刪除的節點 p 不是樹葉刪除節點 p 後原 p 所指的節點可由 q ( q 中包含了 p 左子樹中的最大節點值)或 r ( r 中包含了 p 右子樹中的最小節點值)來取代以維持二元搜尋樹中資料的大小關係其結果分別如圖 (b) 和 (c) 所示而圖 (d) 則標明了指標會改變的節點位置即 p 會消失 father 的左或右子樹指標會異動 k(可能是 q 或 r )會移到原來 p 的位置其父節點 f 的左或右子樹指標會異動其中 father 為 p 父節點 f 為 k 的父節點

程式 5-18 自二元搜尋樹中刪除資料46 int DeleteBST (int x)47 Struct BSTreeNode p father k f48 p = root father = NULL49 while (p = NULL)50 if (x == p-gtdata) 找到 x 所在的節點 p 了51 if ((p-gtleftchild == NULL) ampamp 52 (p-gtrightchild == NULL))53 k = NULL p為樹葉節點54 else if (p-gtleftchild = NULL )x位於非子節

點55 f = p 找出 p左子樹的最右樹葉56 k = p-gtleftchild57 while (k-gtrightchild =NULL)58 f = k59 k = k-gtrightchild60 k將挪至原 p處61 if (p == f)62 f-gtleftchild = k-gtleftchild 63 else64 f-gtrightchild = k-gtleftchild 65

66 else p無左子樹67 f = p 找出 p右子樹的最左樹葉68 k = p-gtrightchild69 while (k-gtleftchild = NULL)70 f = k71 k = k -gtleftchild72 73 if (p == f)74 f-gtrightchild = k-gtrightchild 75 else76 f-gtleftchild = k-gtrightchild77 78 if (k = NULL) k挪至原 p處繼承 p的左右指標79 k-gtleftchild = p-gtleftchild80 k-gtrightchild = p-gtrightchild81 82 if (father == NULL) root = k83 else if (x lt father-gtdata) 決定 k是 father的左

或右兒子84 father-gtleftchild = k85 else86 father-gtrightchild = k87 free(p)88 return 1 成功地刪除 x於此傳回 1返回呼叫處89 51~89行為找到 x時應有的處理步驟

90 else 尚未找到 x繼續往下一階層找

91 father = p92 if (x lt p-gtdata)93 p = p-gtleftchild94 else95 p = p-gtrightchild96 97 98 return 0 未找到 x傳回 099

510  樹林 (forest)

bull 定義樹林(森林)是由 n 棵沒有交集的樹所構成的集合 n 0

下圖為一包含三棵樹的樹林

5101 轉換樹林為二元樹 之前我們以二元樹來表示樹因為二元樹有較多

好用的性質在此我們也將樹林轉換成二元樹看待

在 522 節中我們嘗試了用左子右兄弟的方式來表示樹這個方法事實上已將樹轉換成二元樹

即用左子右兄弟的結構重新組織樹林中的每一棵樹這些樹的樹根將視為兄弟而最左邊的樹則視為樹林二元樹的樹根如此一來樹林即可轉換成二元樹

範例 5-25右下圖為右上圖三棵樹所對應的三棵左子右兄弟二元樹

下圖則為上圖樹林所對應的一棵樹林二元樹

演算法 5-1  轉換樹林成二元樹輸入樹林 F = T1 T2 Tn

輸出 F 的二元樹表示 BT=(T1 T2 Tn)1 if (n == 0) 輸出空二元樹 2 else

3 root(T1 T2 Tn) = T1

4 leftsubtree(T1 T2 Tn) =

BT(T11 T12 T1m)5 BT(T11 T12 T1m)為 T1的子樹6 rightsubtree(T1 T2 Tn) =

BT(T2 T3 Tn)

樹林轉換成二元樹的過程樹林轉換成二元樹的過程

  • Slide 1
  • Slide 2
  • Slide 3
  • Slide 4
  • Slide 5
  • Slide 6
  • Slide 7
  • Slide 8
  • Slide 9
  • Slide 10
  • Slide 11
  • Slide 12
  • Slide 13
  • Slide 14
  • Slide 15
  • Slide 16
  • Slide 17
  • Slide 18
  • Slide 19
  • Slide 20
  • Slide 21
  • Slide 22
  • Slide 23
  • Slide 24
  • Slide 25
  • Slide 26
  • Slide 27
  • Slide 28
  • Slide 29
  • Slide 30
  • Slide 31
  • Slide 32
  • Slide 33
  • Slide 34
  • Slide 35
  • Slide 36
  • Slide 37
  • Slide 38
  • Slide 39
  • Slide 40
  • Slide 41
  • Slide 42
  • Slide 43
  • Slide 44
  • Slide 45
  • Slide 46
  • Slide 47
  • Slide 48
  • Slide 49
  • Slide 50
  • Slide 51
  • Slide 52
  • Slide 53
  • Slide 54
  • Slide 55
  • Slide 56
  • Slide 57
  • Slide 58
  • Slide 59
  • Slide 60
  • Slide 61
  • Slide 62
  • Slide 63
  • Slide 64
  • Slide 65
  • Slide 66
  • Slide 67
  • Slide 68
  • Slide 69
  • Slide 70
  • Slide 71
  • Slide 72
  • Slide 73
  • Slide 74
  • Slide 75
  • Slide 76
  • Slide 77
  • Slide 78
  • Slide 79
  • Slide 80
  • Slide 81
  • Slide 82
  • Slide 83
  • Slide 84
  • Slide 85
  • Slide 86
  • Slide 87
  • Slide 88
  • Slide 89
  • Slide 90
  • Slide 91
  • Slide 92
  • Slide 93
  • Slide 94
  • Slide 95
  • Slide 96
  • Slide 97
  • Slide 98
  • Slide 99
  • Slide 100
  • Slide 101
  • Slide 102
  • Slide 103
Page 38: 第五章 樹

552  後序走訪程式 5-4二元樹的後序走訪14 void postorder(struct BTreeNode node)

15 if (node =NULL)16 postorder(node-gtleftchild)17 postorder(node-gtrightchild)18 cout ltlt node-gtdata19 20

以後序走訪可得到AB+CDEF-+

其正為對應的後序運算式

553  前序走訪程式 5-5二元樹的前序走訪21 void preorder(struct BTreeNode node)

22 if (node = NULL)

23 cout ltlt node-gtdata

24 preorder(node-gtleftchild)

25 preorder(node-gtrightchild)

26

27

以前序走訪可得到++ABCD-EF

其正為對應的前序運算式

554 利用堆疊和迴圈取代遞迴進行二元樹的走訪1 struct BTreeNode 2 struct BTreeNode leftchild3 char data4 struct BtreeNode rightchild5 6 struct BTreeNode root7 struct StackNode8 struct BTreeNode treenode9 struct StackNode next1011 struct StackNode top

12 void push_node(struct BtreeNode node )13 struct StackNode old_top14 old_top = top15 top = (struct StackNode ) malloc

(sizeof(struct StackNode))16 top-gttreenode = node17 top-gtnext = old_top18 19 struct BTreeNode pop_node()20 struct BTreeNode Tnode21 struct StackNode old_top22 if (top == NULL)23 StackEmpty()24 else25 Tnode = top-gttreenode26 old_top = top27 top = top-gtnext28 free(old_top)29 return Tnode30 31

32 void inorder_Stack(struct BtreeNode node)33 do34 while (node = NULL)35 push_node(node)36 node = node-gtleftchild37 push all left children38 if (top = NULL)39 node = pop_node()40 cout ltlt node-gtdata41 node = node-gtrightchild42 inorder printing and check right child43 while ((top=NULL)||(node=NULL))44 top==NULL 堆疊已空 node==NULL沒有右子

6 struct BTreeNode root7 void inorder(struct BTreeNode node)8 if (node = NULL)9 inorder(node-gtleftchild)10 cout ltlt node-gtdata11 inorder(node-gtrightchild)12 13

中序表示為DBGEHAFIC

32 void inorder_Stack()33 struct BTreeNode node34 node = root35 do36 while (node = NULL)37 push_node(node)38 node = node-gtleftchild39 40 if (top = NULL)41 node = pop_node()42 cout ltlt node-gtdata43 node = node-gtrightchild44 45 while(top=NULL)||(node=NULL))46

45 void Preorder_Stack(struct BSTreeNode node)46 do47 while (node = NULL)48 cout ltlt node-gtdata)49 push_node(node)50 node = node-gtleftchild51 push all left children52 if (top = NULL)53 node = pop_node()54 node = node-gtrightchild55 56 while ((top=NULL)||(node=NULL))57

程式 5-5二元樹的前序走訪21 void preorder(struct BTreeNode node)22 if (node = NULL)23 cout ltlt node-gtdata24 preorder(node-gtleftchild)25 preorder(node-gtrightchild)26 27

58 void Postorder_Stack(struct BTreeNode node)59 do60 while (node = NULL)61 cout ltlt node-gtdata62 push_node(node)63 node = node-gtrightchild64 push all left children65 if (top = NULL)66 node = pop_node()67 node = node-gtleftchild68 69 while ((top=NULL)||(node=NULL))70

程式 5-4二元樹的後序走訪14 void postorder(struct BTreeNode node)15 if (node =NULL)16 postorder(node-gtleftchild)17 postorder(node-gtrightchild)18 cout ltlt node-gtdata19 20

555  階層走訪bull 「階層走訪」 (level-order traversal) 是依階層

的順序進行二元樹的走訪先走訪階層小的節點後走訪階層大的節點同一階層者則依自左向右的順序走訪

bull 對下圖的二元樹進行階層走訪的結果為ABCDEFGHI

bull 對同一階層而言先走訪的節點其子節點亦在下一階層中先被走訪

bull 這種「先進先出」的特性恰可用佇列予以儲存與處理

程式 5-7 利用佇列進行二元樹的階層走訪

1 struct BTreeNode2 struct BTreeNode leftchild3 char data4 struct BTreeNode rightchild5 6 struct BTreeNode root7 struct QNode8 struct BTreeNode treenode9 struct QNode next10 11 struct QNode front rear

12 void AddQueue(struct BTreeNode Tnode)13 struct QNode node14 node=(struct QNode )malloc(sizeof(struct QNode))15 node-gttreenode = Tnode16 node-gtnext = NULL17 if (front == NULL) front = node19 else rear-gtnext = node20 rear = node21

22 struct BTreeNode DeleteQueue()23 struct BTreeNode Tnode24 struct QNode old_front25 if (rear == NULL)26 QueueEmpty()27 else28 Tnode = front-gttreenode29 old_front = front30 front = front-gtnext31 free(old_front)32 return Tnode33 34 35 void LevelOrder (struct BTreeNode node)36 AddQueue(node)37 while (front =NULL)38 node = DeleteQueue()39 cout ltlt node-gtdata40 if (node-gtleftchild = NULL)41 AddQueue(node-gtleftchild)42 if (node-gtrightchild = NULL)43 AddQueue(node-gtrightchild)44 45

35 void LevelOrder (struct BTreeNode node)36 AddQueue(node)37 while (front =NULL)38 node = DeleteQueue()39 cout ltlt node-gtdata40 if (node-gtleftchild = NULL)41 AddQueue(node-gtleftchild)42 if (node-gtrightchild = NULL)43 AddQueue(node-gtrightchild)44 45

bull Infix ABCbull Postfix ABC CBA ACB BCA

BAC CAB

B

A CA

B

C

B

A

C

C

B

AB

A

C

二元樹中序與前序式中序與後序式bull 每一棵二元樹皆有唯一的一對中序與前序表示法也有唯一的中序與後序次序

bull 換句話說給予一對中序與前序或中序與後序即可決定一棵二元樹

bull 然而給予前序和後序次序並不能決定唯一的二元樹可能會產生兩棵不同的二元樹

bull 想想階層走訪和中序走訪順序可否決定唯一的二元樹

A

B C

H J D E

F G

prefix ABHJCDFGEinfix HBJAFDGCEpostfix HJBFGDECA

VLRLVRLRV

A

prefix BHJinfix HBJ

prefix CDFGEinfix FDGCE

prefix ABHJCDFGEinfix HBJAFDGCE

A

B C

H J D E

F G

prefix ABHJCDFGE

infix HBJAFDGCEk-1

struct BTreeNode BTree_InfixPrefix (AnsiString infix AnsiString prefix)

int k struct BTreeNode node if (infixLength==0) return NULLnode = (struct BTreeNode ) malloc (sizeof(struct BTreeNode))node-gtdata = prefix[1]k = find(prefix[1] infix)node-gtleftchild = BTree_InfixPrefix

(infix[1k-1] prefix[2k])node-gtrightchild = BTree_InfixPrefix(infix[k+1infixLength]prefix[k+1prefixLength])

VLR

LVRk1

k2 k+1~

k+1~

1

struct BTreeNode BTree_InfixPostfix (AnsiString infix AnsiString prefix)

int k struct BTreeNode node if (infixLength==0) return NULLnode = (struct BTreeNode ) malloc (sizeof(struct BTreeNode))node-gtdata = postfix[postfixLength]k = find(postfix[postfixLength] infix)node-gtleftchild = BTree_InfixPrefix

(infix[1k-1] postfix[1k-1])node-gtrightchild = BTree_InfixPrefix(infix[k+1infixLength]postfix[k+1postfixLength-1])

56  二元樹的複製與相等測試程式 5-8 二元樹的複製1 struct BTreeNode2 struct BTreeNode leftchild3 char data4 struct BTreeNode rightchild5 6 struct BTreeNode root7 struct BTreeNode Copy(struct BTreeNode TreeRoot)8 struct BTreeNode CopyRoot9 if (TreeRoot == NULL)10 return NULL11 CopyRoot =(struct BTreeNode ) malloc (sizeof(BTreeNode))12 CopyRoot-gtdata = TreeRoot-gtdata13 CopyRoot-gtleftchild = Copy(TreeRoot-gtleftchild)14 CopyRoot-gtrightchild = Copy(TreeRoot-

gtrightchild)15 return CopyRoot16

程式 5-9 檢測兩棵二元樹是否相等1 struct BTreeNode2 struct BTreeNode leftchild3 char data4 struct BTreeNode rightchild5 6 struct BtreeNode rightchild7 int Equal(struct BTreeNode ustruct BTreeNode v)8 if ((u==NULL) ampamp (v==NULL)) return 19 if ((u=NULL) ampamp (v=NULL)10 ampamp (u-gtdata == v-gtdata )11 ampamp Equal(u-gtleftchildv-gtleftchild)12 ampamp Equal(u-gtrightchildv-gtrightchild))

13 return 114 return 015

Counting Nodes in a Binary Tree

int CountingNodes(struct BTreeNodes node)

if (node == NULL) return 0 int leftchildren = CountingNodes(node-gtleftchild)

int rightchildren= CountingNodes(node-gtrightchild)

return leftchildren+rightchildren

Counting Leaves in a Binary Tree

int CountingLeaves(struct BTreeNodes node)

if (node == NULL) return 0 int leftleaves = CountingLeaves(node-gtleftchild)

int rightchildren= CountingLeaves(node-gtrightchild)

if (leftleaves==0 ampamp leftleaves==0) return 1

return leftleaves + leftleaves

57  引線二元樹bull 在二元樹的鍵結表示中樹葉節點的兩個子樹指標皆指向 NULL 由定理 5-1 得知 n = E+1 其中 n 為節點個數 E 為分支數

bull 而 n 個鍵結節點有 2n 個指標空間每個分支恰佔用一個指標空間共有 E (=n-1) 個指標有用到(不是空的)而共有 n+1 個指標空間是 NULL 比非空的節點還多

bull 有學者提出對這些「放空指標的空間」加以利用的概念mdash與其放空指標不如放指向其它節點的指標稱之為引線 (thread) 使得某些運算(如走訪hellip等)可以加快

bull 這個概念發展出了引線二元樹 (threaded binary tree) 這種資料結構

範例 5-17下圖為加了引線的二元樹

加入的引線將使二元樹的中序走訪更加便利沿著上右圖箭頭所指示的順序走訪即可完成此二元樹的中序走訪GDHBAIECF 為此二元樹的中序表示

bull 範例 5-17 中所加的引線目的在方便二元樹的中序走訪

bull 對樹葉節點 p 而言令其資料為 P 則引線指標的規則如下

(1) p 的左子樹指標指向中序表示中 P 的前一個 元素(前接元素 (predecessor) )

(2) p 的右子樹指標指向中序表示中 P 的後一個 元素(後繼元素 (successor) )

為了區別節點指標放的是一般指標(內部節點)抑或是引線指標(樹葉節點)我們另以 2 個欄位分別區別左和右子樹指標空間存放的對象其節點的記憶體配置有如下圖所示

其中 leftthread (rightthread) 為 0 時表示 leftchild (rightchild) 存放的是一般節點指標而 leftthread (rightthread) 為 1 時表示 leftchild (rightchild) 存放的是引線指標1 struct TBTreeNode2 int leftthread3 struct TBTreeNode leftchild4 char data5 struct TBTreeNode rightchild6 int rightthread7

由右側的引線二元樹可知有最左和最右兩條引線尚無妥善的安排我們另設計一空的引線節點做為樹根如下圖所示

571  引線二元樹的中序走訪決定節點 p 在中序表示中的後繼節點 q (簡稱中序後繼點)

程式 5-10決定節點 node的中序後繼點1 struct TBTreeNode2 int leftthread3 struct TBTreeNode leftchild4 char data5 struct TBTreeNode rightchild6 int rightthread7 8 struct TBTreeNode root9 struct TBTreeNode Next(struct TBTreeNode node)

10 struct TBTreeNode temp11 temp = node-gtrightchild12 if (node-gtrightthead) return temp13 while (temp-gtleftthead) temp=temp-gtleftchild

14 return temp15

程式 5-11引線二元樹的中序表示(接程式 5-10)1 void InorderTBTree()2 struct TBTreeNode node3 for(node=Next(root)node=rootnode=Next(node))

4 cout ltlt node-gtdata5

bull只要 node 是引線二元樹上的任一節點透過呼叫 Next(node) 即可找到其在中序表示中的後繼節點bull(試想若程式 5-10 中的程序 Next 所傳入的參數指標正是 root Next(root) 是否可正常運作請思考)bull於是只須從中序表示中的第一節點起讓每一節點 node 都執行 Next(node) 程序即可得引線二元樹的中序表示

572  在引線二元樹中加入節點在引線二元樹中樹葉節點節點的右子樹指標所指向或內部節點右子樹的最左樹葉為其中序後繼節點而且任一節點若無左子樹則其左子樹指標乃指向其中序前接節點hellip這些性質必須在節點新增進入引線二元樹時也要保持接下來討論節點 new 插入成為引線二元樹的節點 p 的右子節點的情形至於插入成為左子節點的情形則讓各位模擬推敲

572572  在引線二元樹中加入節點 在引線二元樹中加入節點 (( 續續 ))

newnew 會成為會成為 pp 的右子節點的情況共有以下兩種的右子節點的情況共有以下兩種(1) (1) 若若 pp 沒有右子節點 沒有右子節點 ((p-gtrightthread p-gtrightthread 為為1) 1) 如圖如圖 ((a) a) 則 則 new new 插入後應形成如圖插入後應形成如圖 ((b) b) 所示所示

(2)(2) 若若 pp 有右子節點 有右子節點 ((p-gtrightthread p-gtrightthread 為為0) 0) 如圖如圖 ((c) c) 則 則 new new 插入後應形成如插入後應形成如圖圖 ((d) d) 所示所示

程式 5-12  插入節點 new 成為引線二元樹中節點 p的右子節點(上接程式 5-10 5-11 )

1 void InsertRight(struct TBTreeNode p 2 struct TBTreeNode new)3 struct TBTreeNode q4 new-gtrightchild = p-gtrightchild5 new-gtrightthead = p-gtrightthread6 new-gtleftchild = p7 new-gtleftthread = 18 p-gtrightchild = new9 p-gtrightthead = 010 if (new-gtrightthread)11 q = Next(new)12 q-gtleftchild = new13 14

58  堆積bull 「堆積」 (heap) 是一個特殊的完備二元樹節點的資料與其子節點的資料之間有事先定好的大小關係存在bull 定義ndash最大堆疊為一完備二元樹任一節點的資料內容不小於其子節點的資料內容ndash最小堆疊為一完備二元樹任一節點的資料內容不大於其子節點的資料內容

581  插入節點至最大堆積中最大堆積是一完備二元樹所以如圖 (a) 之最大堆積於加入新節點後樹的形狀必須如圖 (b) 所示灰色節點即為新節點若加入的節點資料分別為 1 9 12 則其最大堆積將分別變成如圖 (c) (d) (e) 所示

程式 5-13 新增節點至最大堆積中最大堆積本身是一棵完備二元樹可以用陣列表示之 1 define maxsize 1002 int heap[maxsize]3 int n i4 void InsertHeap(int x)5 if (n == maxsize) HeapFull()6 else7 n++8 i = n9 while ((i gt 1) ampamp (x gt heap[i2]))10 heap[i] = heap[i2]11 i = 212 13 heap[i] = x14 16

程序 InsertHeap 在插入第 n 個資料時需要的時間複雜度為 O(logn)

1 100

2 93

3 84

4 77

5 65

6 81

7 79

8 66

9 70

10 50

100

84

77

93

8165

66 70

79

50

1

2 3

4 5 6 7

8 9 10

1 define maxsize 1002 int heap[maxsize]3 int n i4 void InsertHeap(int x)5 if (n == maxsize) HeapFull()6 else7 n++8 i = n9 while ((i gt 1) ampamp (x gt heap[i2]))10 heap[i] = heap[i2]11 i = 212 13 heap[i] = x14 16

582 刪除最大堆積中的最大資料圖 (a) 中的最大堆積在刪除最大資料後其樹形應變成如圖 (b) 所示將最未一個樹葉節點的資料 6 暫挪至樹根節點內如圖 (c) 再將圖 (c) 中非最大堆積的結構調整成為最大堆積 10 和 8 中選大者與 6對調即成圖 (d)

程式 5-14 刪除最大堆積中的最大資料(上接程式 5-13 )1 int DeleteHeap()2 int x i j3 if (n == 0) HeapEmpty()4 else5 x = heap[1]6 heap[1] = heap[n]7 n--8 i = 19 while (i lt= n2)10 if (heap[2i] gt heap[2i+1])11 j = 2i12 else13 j = 2i+114 if (heap[i]gtheap[j]) break15 swap(heap[i] heap[j])16 i = j17 18 return x19 20

其時間複雜度與其堆積高度相同是為O(logn) 其中 n 為堆積中的元素個數

1 100

2 93

3 84

4 77

5 65

6 81

7 79

8 66

9 70

10 50

100

84

77

93

8165

66 70

79

50

1

2 3

4 5 6 7

8 9 10

1 int DeletHeap()2 int x i j3 if(n == 0) HeapEmpty()4 else5 x = heap[1]6 heap[1] = heap[n]7 n--8 i = 19 while (i lt= n2)10 if ([heap[2i]] gt heap[2i+1])11 j = 2i12 else13 j = 2i+114 if (heap[i]gtheap[j]) break15 swap(heap[i] heap[j])16 i = j17 18 return x19 20

583  優先佇列bull 由於最大堆積(或最小堆積)的結構可提供最大元素(或最小元素)而其維護堆積的成本為 O(logn) n 為總元素個數

bull 堆積常被當成「優先佇列」 (priority queue) 使用mdash此時佇列被組織成完備二元樹若新加入佇列的資料有大小關係優先順序的需求時此優先佇列即以 O(logn) 的代價將新資料放入優先佇列(可能是最大或最小堆積依實際大小關係優先順序的需求而定)中mdash提供依元素的優先順序做為處理順序的服務

bull 一般的佇列為先進先出先到的先行處理優先佇列則是依據其優先順序優先順序高者先行處理與來到的先後無關

範例 5-21bull 在「分時」 (time sharing) 的作業系統中我們不希望使用中央處理器 (Central Processing Unit CPU) 時間短的「處理程序」 (process) 等候過久於是有「最短時間工作優先」 (shortest-job-first) 的排程策略此時等候的處理程序即可組織成之一最小堆積而 CPU 每次即取出存於樹根的處理程序予以處理任何新來的處理程序則以 O(logn) 的時間加入此優先佇列(最小堆積)中

bull 注意之前提過的線性佇列新來的直接加入成為新的尾端元素花O(1) 的時間然線性佇列本身沒有大小的關係每次要取出最大(小)的資料得花O(n) 的代價(在所有元素中挑出最大(小)者) n 為佇列內的元素個數而優先佇列是在新元素加入時要花O(logn) 的時間至於取出最大(小)則只須 O(1) 的時間

59  二元搜尋樹bull 二元搜尋樹是一棵二元樹可能是空二元樹若不為空二元樹則滿足下列性質ndash所有節點內的資料內容是相異的 ndash左子節點的資內容 ( 如果有 ) 比父節點的資料內容小 ndash右子節點的資料內容 ( 如果有 ) 比父節點的資料內容大 ndash以左和右子節點為樹根的左子樹和右子樹也是二元搜尋樹

在二元搜尋樹中搜尋資料二元搜尋樹對資料大小的掌握十分明確若恰與節點 p 資料相等則搜尋成功若欲搜尋的資料 x 比樹節點 p 中的資料小 則只須搜尋節點 p 的左子樹若比節點 p 中的資料大 則只須搜尋 p 的右子樹這樣的判斷可以遞迴地應用在各子樹節點 中

程式 5-15 在二元搜尋樹中搜尋資料(遞迴呼叫)1 struct BSTreeNode2 struct BSTreeNode leftchild3 int data4 struct BSTreeNode rightchild5 6 struct BSTreeNode root7 struct BSTreeNode SearchBST(

struct BSTreeNode tree int x)8 if (tree == NULL) return NULL9 if (x == tree-gtdata) return tree10 if (x lt tree-gtdata)11 return SearchBST(tree-gtleftchild x)

12 return SearchBST(tree-gtrightchild x)13

程式 5-16 搜尋二元搜尋樹的資料(非遞迴呼叫)在二元搜尋樹十分龐大時遞迴呼叫的效率不會太好可利用迴圈來完成搜尋的動作以提高執行效率請看程式 5-16

14 struct BSTreeNode Search BST_iterative15 (struct BSTreeNode node int x)16 while (node = NULL)17 if (x == node-gtdata) return node18 if (x lt node-gtdata)19 node = node-gtleftchild20 else21 node = node-gtrightchild22 23 return NULL24

592  新增資料於二元搜尋樹中欲新增資料進入一二元搜尋樹中得先決定該資料在二元搜尋樹中應出現的位置這可透過上節提到的搜尋動作來解決

在二元搜尋樹定義的性質 (1) 中我們限定了元素必須相異所以搜尋新資料的結果必是 NULL

必須將搜尋的結果改為新增資料所應在位置的節點指標

程式 5-17  在二元搜尋樹中新增資料25 int InsertBST(int x)26 struct BSTreeNode p q27 p = root q = NULL28 while (p = NULL)29 q = p30 if (x == p-gtdata) return 0

若不考慮重複則可省略之31 if (x lt p-gtdata)32 p = p-gtleftchild33 else34 p = p-gtrightchild35

3636 p = (struct BSTreeNode ) p = (struct BSTreeNode ) malloc (sizeof(BSTreeNode))malloc (sizeof(BSTreeNode))

3737 p-gtdata = x p-gtdata = x3838 p-gtleftchild = p-gtrightchild = p-gtleftchild = p-gtrightchild = NULLNULL3939 if (root == NULL) root = p if (root == NULL) root = p4040 else if (x lt q-gtdata) else if (x lt q-gtdata)4141 q-gtleftchild = p q-gtleftchild = p4242 else else4343 q-gtrightchild = p q-gtrightchild = p4444 return 1 return 145 45

程式 程式 5-175-17  在二元搜尋樹中新增資料  在二元搜尋樹中新增資料 (( 續續 ))

範例 5-23 圖 (b) 為圖 (a) 新增整數 6 之後的結果圖 (c) (d) (e) (f) 則分別是逐步再加入 28 18

10 21 的結果

bull若目前的二元搜尋樹高度為 h 則新增資料得花O(h) 的時間找到其所應在的位置遂新增資料至二元搜尋樹中的時間複雜之度為 O(h)

593 自二元搜尋樹中刪除資料欲自二元搜尋樹中刪除資料可能會面臨兩種情況(1) 刪除節點 p 位於樹葉節點上如下圖所示 這是比較容易處理的情況

(2) 刪除的節點 p 不是樹葉刪除節點 p 後原 p 所指的節點可由 q ( q 中包含了 p 左子樹中的最大節點值)或 r ( r 中包含了 p 右子樹中的最小節點值)來取代以維持二元搜尋樹中資料的大小關係其結果分別如圖 (b) 和 (c) 所示而圖 (d) 則標明了指標會改變的節點位置即 p 會消失 father 的左或右子樹指標會異動 k(可能是 q 或 r )會移到原來 p 的位置其父節點 f 的左或右子樹指標會異動其中 father 為 p 父節點 f 為 k 的父節點

程式 5-18 自二元搜尋樹中刪除資料46 int DeleteBST (int x)47 Struct BSTreeNode p father k f48 p = root father = NULL49 while (p = NULL)50 if (x == p-gtdata) 找到 x 所在的節點 p 了51 if ((p-gtleftchild == NULL) ampamp 52 (p-gtrightchild == NULL))53 k = NULL p為樹葉節點54 else if (p-gtleftchild = NULL )x位於非子節

點55 f = p 找出 p左子樹的最右樹葉56 k = p-gtleftchild57 while (k-gtrightchild =NULL)58 f = k59 k = k-gtrightchild60 k將挪至原 p處61 if (p == f)62 f-gtleftchild = k-gtleftchild 63 else64 f-gtrightchild = k-gtleftchild 65

66 else p無左子樹67 f = p 找出 p右子樹的最左樹葉68 k = p-gtrightchild69 while (k-gtleftchild = NULL)70 f = k71 k = k -gtleftchild72 73 if (p == f)74 f-gtrightchild = k-gtrightchild 75 else76 f-gtleftchild = k-gtrightchild77 78 if (k = NULL) k挪至原 p處繼承 p的左右指標79 k-gtleftchild = p-gtleftchild80 k-gtrightchild = p-gtrightchild81 82 if (father == NULL) root = k83 else if (x lt father-gtdata) 決定 k是 father的左

或右兒子84 father-gtleftchild = k85 else86 father-gtrightchild = k87 free(p)88 return 1 成功地刪除 x於此傳回 1返回呼叫處89 51~89行為找到 x時應有的處理步驟

90 else 尚未找到 x繼續往下一階層找

91 father = p92 if (x lt p-gtdata)93 p = p-gtleftchild94 else95 p = p-gtrightchild96 97 98 return 0 未找到 x傳回 099

510  樹林 (forest)

bull 定義樹林(森林)是由 n 棵沒有交集的樹所構成的集合 n 0

下圖為一包含三棵樹的樹林

5101 轉換樹林為二元樹 之前我們以二元樹來表示樹因為二元樹有較多

好用的性質在此我們也將樹林轉換成二元樹看待

在 522 節中我們嘗試了用左子右兄弟的方式來表示樹這個方法事實上已將樹轉換成二元樹

即用左子右兄弟的結構重新組織樹林中的每一棵樹這些樹的樹根將視為兄弟而最左邊的樹則視為樹林二元樹的樹根如此一來樹林即可轉換成二元樹

範例 5-25右下圖為右上圖三棵樹所對應的三棵左子右兄弟二元樹

下圖則為上圖樹林所對應的一棵樹林二元樹

演算法 5-1  轉換樹林成二元樹輸入樹林 F = T1 T2 Tn

輸出 F 的二元樹表示 BT=(T1 T2 Tn)1 if (n == 0) 輸出空二元樹 2 else

3 root(T1 T2 Tn) = T1

4 leftsubtree(T1 T2 Tn) =

BT(T11 T12 T1m)5 BT(T11 T12 T1m)為 T1的子樹6 rightsubtree(T1 T2 Tn) =

BT(T2 T3 Tn)

樹林轉換成二元樹的過程樹林轉換成二元樹的過程

  • Slide 1
  • Slide 2
  • Slide 3
  • Slide 4
  • Slide 5
  • Slide 6
  • Slide 7
  • Slide 8
  • Slide 9
  • Slide 10
  • Slide 11
  • Slide 12
  • Slide 13
  • Slide 14
  • Slide 15
  • Slide 16
  • Slide 17
  • Slide 18
  • Slide 19
  • Slide 20
  • Slide 21
  • Slide 22
  • Slide 23
  • Slide 24
  • Slide 25
  • Slide 26
  • Slide 27
  • Slide 28
  • Slide 29
  • Slide 30
  • Slide 31
  • Slide 32
  • Slide 33
  • Slide 34
  • Slide 35
  • Slide 36
  • Slide 37
  • Slide 38
  • Slide 39
  • Slide 40
  • Slide 41
  • Slide 42
  • Slide 43
  • Slide 44
  • Slide 45
  • Slide 46
  • Slide 47
  • Slide 48
  • Slide 49
  • Slide 50
  • Slide 51
  • Slide 52
  • Slide 53
  • Slide 54
  • Slide 55
  • Slide 56
  • Slide 57
  • Slide 58
  • Slide 59
  • Slide 60
  • Slide 61
  • Slide 62
  • Slide 63
  • Slide 64
  • Slide 65
  • Slide 66
  • Slide 67
  • Slide 68
  • Slide 69
  • Slide 70
  • Slide 71
  • Slide 72
  • Slide 73
  • Slide 74
  • Slide 75
  • Slide 76
  • Slide 77
  • Slide 78
  • Slide 79
  • Slide 80
  • Slide 81
  • Slide 82
  • Slide 83
  • Slide 84
  • Slide 85
  • Slide 86
  • Slide 87
  • Slide 88
  • Slide 89
  • Slide 90
  • Slide 91
  • Slide 92
  • Slide 93
  • Slide 94
  • Slide 95
  • Slide 96
  • Slide 97
  • Slide 98
  • Slide 99
  • Slide 100
  • Slide 101
  • Slide 102
  • Slide 103
Page 39: 第五章 樹

553  前序走訪程式 5-5二元樹的前序走訪21 void preorder(struct BTreeNode node)

22 if (node = NULL)

23 cout ltlt node-gtdata

24 preorder(node-gtleftchild)

25 preorder(node-gtrightchild)

26

27

以前序走訪可得到++ABCD-EF

其正為對應的前序運算式

554 利用堆疊和迴圈取代遞迴進行二元樹的走訪1 struct BTreeNode 2 struct BTreeNode leftchild3 char data4 struct BtreeNode rightchild5 6 struct BTreeNode root7 struct StackNode8 struct BTreeNode treenode9 struct StackNode next1011 struct StackNode top

12 void push_node(struct BtreeNode node )13 struct StackNode old_top14 old_top = top15 top = (struct StackNode ) malloc

(sizeof(struct StackNode))16 top-gttreenode = node17 top-gtnext = old_top18 19 struct BTreeNode pop_node()20 struct BTreeNode Tnode21 struct StackNode old_top22 if (top == NULL)23 StackEmpty()24 else25 Tnode = top-gttreenode26 old_top = top27 top = top-gtnext28 free(old_top)29 return Tnode30 31

32 void inorder_Stack(struct BtreeNode node)33 do34 while (node = NULL)35 push_node(node)36 node = node-gtleftchild37 push all left children38 if (top = NULL)39 node = pop_node()40 cout ltlt node-gtdata41 node = node-gtrightchild42 inorder printing and check right child43 while ((top=NULL)||(node=NULL))44 top==NULL 堆疊已空 node==NULL沒有右子

6 struct BTreeNode root7 void inorder(struct BTreeNode node)8 if (node = NULL)9 inorder(node-gtleftchild)10 cout ltlt node-gtdata11 inorder(node-gtrightchild)12 13

中序表示為DBGEHAFIC

32 void inorder_Stack()33 struct BTreeNode node34 node = root35 do36 while (node = NULL)37 push_node(node)38 node = node-gtleftchild39 40 if (top = NULL)41 node = pop_node()42 cout ltlt node-gtdata43 node = node-gtrightchild44 45 while(top=NULL)||(node=NULL))46

45 void Preorder_Stack(struct BSTreeNode node)46 do47 while (node = NULL)48 cout ltlt node-gtdata)49 push_node(node)50 node = node-gtleftchild51 push all left children52 if (top = NULL)53 node = pop_node()54 node = node-gtrightchild55 56 while ((top=NULL)||(node=NULL))57

程式 5-5二元樹的前序走訪21 void preorder(struct BTreeNode node)22 if (node = NULL)23 cout ltlt node-gtdata24 preorder(node-gtleftchild)25 preorder(node-gtrightchild)26 27

58 void Postorder_Stack(struct BTreeNode node)59 do60 while (node = NULL)61 cout ltlt node-gtdata62 push_node(node)63 node = node-gtrightchild64 push all left children65 if (top = NULL)66 node = pop_node()67 node = node-gtleftchild68 69 while ((top=NULL)||(node=NULL))70

程式 5-4二元樹的後序走訪14 void postorder(struct BTreeNode node)15 if (node =NULL)16 postorder(node-gtleftchild)17 postorder(node-gtrightchild)18 cout ltlt node-gtdata19 20

555  階層走訪bull 「階層走訪」 (level-order traversal) 是依階層

的順序進行二元樹的走訪先走訪階層小的節點後走訪階層大的節點同一階層者則依自左向右的順序走訪

bull 對下圖的二元樹進行階層走訪的結果為ABCDEFGHI

bull 對同一階層而言先走訪的節點其子節點亦在下一階層中先被走訪

bull 這種「先進先出」的特性恰可用佇列予以儲存與處理

程式 5-7 利用佇列進行二元樹的階層走訪

1 struct BTreeNode2 struct BTreeNode leftchild3 char data4 struct BTreeNode rightchild5 6 struct BTreeNode root7 struct QNode8 struct BTreeNode treenode9 struct QNode next10 11 struct QNode front rear

12 void AddQueue(struct BTreeNode Tnode)13 struct QNode node14 node=(struct QNode )malloc(sizeof(struct QNode))15 node-gttreenode = Tnode16 node-gtnext = NULL17 if (front == NULL) front = node19 else rear-gtnext = node20 rear = node21

22 struct BTreeNode DeleteQueue()23 struct BTreeNode Tnode24 struct QNode old_front25 if (rear == NULL)26 QueueEmpty()27 else28 Tnode = front-gttreenode29 old_front = front30 front = front-gtnext31 free(old_front)32 return Tnode33 34 35 void LevelOrder (struct BTreeNode node)36 AddQueue(node)37 while (front =NULL)38 node = DeleteQueue()39 cout ltlt node-gtdata40 if (node-gtleftchild = NULL)41 AddQueue(node-gtleftchild)42 if (node-gtrightchild = NULL)43 AddQueue(node-gtrightchild)44 45

35 void LevelOrder (struct BTreeNode node)36 AddQueue(node)37 while (front =NULL)38 node = DeleteQueue()39 cout ltlt node-gtdata40 if (node-gtleftchild = NULL)41 AddQueue(node-gtleftchild)42 if (node-gtrightchild = NULL)43 AddQueue(node-gtrightchild)44 45

bull Infix ABCbull Postfix ABC CBA ACB BCA

BAC CAB

B

A CA

B

C

B

A

C

C

B

AB

A

C

二元樹中序與前序式中序與後序式bull 每一棵二元樹皆有唯一的一對中序與前序表示法也有唯一的中序與後序次序

bull 換句話說給予一對中序與前序或中序與後序即可決定一棵二元樹

bull 然而給予前序和後序次序並不能決定唯一的二元樹可能會產生兩棵不同的二元樹

bull 想想階層走訪和中序走訪順序可否決定唯一的二元樹

A

B C

H J D E

F G

prefix ABHJCDFGEinfix HBJAFDGCEpostfix HJBFGDECA

VLRLVRLRV

A

prefix BHJinfix HBJ

prefix CDFGEinfix FDGCE

prefix ABHJCDFGEinfix HBJAFDGCE

A

B C

H J D E

F G

prefix ABHJCDFGE

infix HBJAFDGCEk-1

struct BTreeNode BTree_InfixPrefix (AnsiString infix AnsiString prefix)

int k struct BTreeNode node if (infixLength==0) return NULLnode = (struct BTreeNode ) malloc (sizeof(struct BTreeNode))node-gtdata = prefix[1]k = find(prefix[1] infix)node-gtleftchild = BTree_InfixPrefix

(infix[1k-1] prefix[2k])node-gtrightchild = BTree_InfixPrefix(infix[k+1infixLength]prefix[k+1prefixLength])

VLR

LVRk1

k2 k+1~

k+1~

1

struct BTreeNode BTree_InfixPostfix (AnsiString infix AnsiString prefix)

int k struct BTreeNode node if (infixLength==0) return NULLnode = (struct BTreeNode ) malloc (sizeof(struct BTreeNode))node-gtdata = postfix[postfixLength]k = find(postfix[postfixLength] infix)node-gtleftchild = BTree_InfixPrefix

(infix[1k-1] postfix[1k-1])node-gtrightchild = BTree_InfixPrefix(infix[k+1infixLength]postfix[k+1postfixLength-1])

56  二元樹的複製與相等測試程式 5-8 二元樹的複製1 struct BTreeNode2 struct BTreeNode leftchild3 char data4 struct BTreeNode rightchild5 6 struct BTreeNode root7 struct BTreeNode Copy(struct BTreeNode TreeRoot)8 struct BTreeNode CopyRoot9 if (TreeRoot == NULL)10 return NULL11 CopyRoot =(struct BTreeNode ) malloc (sizeof(BTreeNode))12 CopyRoot-gtdata = TreeRoot-gtdata13 CopyRoot-gtleftchild = Copy(TreeRoot-gtleftchild)14 CopyRoot-gtrightchild = Copy(TreeRoot-

gtrightchild)15 return CopyRoot16

程式 5-9 檢測兩棵二元樹是否相等1 struct BTreeNode2 struct BTreeNode leftchild3 char data4 struct BTreeNode rightchild5 6 struct BtreeNode rightchild7 int Equal(struct BTreeNode ustruct BTreeNode v)8 if ((u==NULL) ampamp (v==NULL)) return 19 if ((u=NULL) ampamp (v=NULL)10 ampamp (u-gtdata == v-gtdata )11 ampamp Equal(u-gtleftchildv-gtleftchild)12 ampamp Equal(u-gtrightchildv-gtrightchild))

13 return 114 return 015

Counting Nodes in a Binary Tree

int CountingNodes(struct BTreeNodes node)

if (node == NULL) return 0 int leftchildren = CountingNodes(node-gtleftchild)

int rightchildren= CountingNodes(node-gtrightchild)

return leftchildren+rightchildren

Counting Leaves in a Binary Tree

int CountingLeaves(struct BTreeNodes node)

if (node == NULL) return 0 int leftleaves = CountingLeaves(node-gtleftchild)

int rightchildren= CountingLeaves(node-gtrightchild)

if (leftleaves==0 ampamp leftleaves==0) return 1

return leftleaves + leftleaves

57  引線二元樹bull 在二元樹的鍵結表示中樹葉節點的兩個子樹指標皆指向 NULL 由定理 5-1 得知 n = E+1 其中 n 為節點個數 E 為分支數

bull 而 n 個鍵結節點有 2n 個指標空間每個分支恰佔用一個指標空間共有 E (=n-1) 個指標有用到(不是空的)而共有 n+1 個指標空間是 NULL 比非空的節點還多

bull 有學者提出對這些「放空指標的空間」加以利用的概念mdash與其放空指標不如放指向其它節點的指標稱之為引線 (thread) 使得某些運算(如走訪hellip等)可以加快

bull 這個概念發展出了引線二元樹 (threaded binary tree) 這種資料結構

範例 5-17下圖為加了引線的二元樹

加入的引線將使二元樹的中序走訪更加便利沿著上右圖箭頭所指示的順序走訪即可完成此二元樹的中序走訪GDHBAIECF 為此二元樹的中序表示

bull 範例 5-17 中所加的引線目的在方便二元樹的中序走訪

bull 對樹葉節點 p 而言令其資料為 P 則引線指標的規則如下

(1) p 的左子樹指標指向中序表示中 P 的前一個 元素(前接元素 (predecessor) )

(2) p 的右子樹指標指向中序表示中 P 的後一個 元素(後繼元素 (successor) )

為了區別節點指標放的是一般指標(內部節點)抑或是引線指標(樹葉節點)我們另以 2 個欄位分別區別左和右子樹指標空間存放的對象其節點的記憶體配置有如下圖所示

其中 leftthread (rightthread) 為 0 時表示 leftchild (rightchild) 存放的是一般節點指標而 leftthread (rightthread) 為 1 時表示 leftchild (rightchild) 存放的是引線指標1 struct TBTreeNode2 int leftthread3 struct TBTreeNode leftchild4 char data5 struct TBTreeNode rightchild6 int rightthread7

由右側的引線二元樹可知有最左和最右兩條引線尚無妥善的安排我們另設計一空的引線節點做為樹根如下圖所示

571  引線二元樹的中序走訪決定節點 p 在中序表示中的後繼節點 q (簡稱中序後繼點)

程式 5-10決定節點 node的中序後繼點1 struct TBTreeNode2 int leftthread3 struct TBTreeNode leftchild4 char data5 struct TBTreeNode rightchild6 int rightthread7 8 struct TBTreeNode root9 struct TBTreeNode Next(struct TBTreeNode node)

10 struct TBTreeNode temp11 temp = node-gtrightchild12 if (node-gtrightthead) return temp13 while (temp-gtleftthead) temp=temp-gtleftchild

14 return temp15

程式 5-11引線二元樹的中序表示(接程式 5-10)1 void InorderTBTree()2 struct TBTreeNode node3 for(node=Next(root)node=rootnode=Next(node))

4 cout ltlt node-gtdata5

bull只要 node 是引線二元樹上的任一節點透過呼叫 Next(node) 即可找到其在中序表示中的後繼節點bull(試想若程式 5-10 中的程序 Next 所傳入的參數指標正是 root Next(root) 是否可正常運作請思考)bull於是只須從中序表示中的第一節點起讓每一節點 node 都執行 Next(node) 程序即可得引線二元樹的中序表示

572  在引線二元樹中加入節點在引線二元樹中樹葉節點節點的右子樹指標所指向或內部節點右子樹的最左樹葉為其中序後繼節點而且任一節點若無左子樹則其左子樹指標乃指向其中序前接節點hellip這些性質必須在節點新增進入引線二元樹時也要保持接下來討論節點 new 插入成為引線二元樹的節點 p 的右子節點的情形至於插入成為左子節點的情形則讓各位模擬推敲

572572  在引線二元樹中加入節點 在引線二元樹中加入節點 (( 續續 ))

newnew 會成為會成為 pp 的右子節點的情況共有以下兩種的右子節點的情況共有以下兩種(1) (1) 若若 pp 沒有右子節點 沒有右子節點 ((p-gtrightthread p-gtrightthread 為為1) 1) 如圖如圖 ((a) a) 則 則 new new 插入後應形成如圖插入後應形成如圖 ((b) b) 所示所示

(2)(2) 若若 pp 有右子節點 有右子節點 ((p-gtrightthread p-gtrightthread 為為0) 0) 如圖如圖 ((c) c) 則 則 new new 插入後應形成如插入後應形成如圖圖 ((d) d) 所示所示

程式 5-12  插入節點 new 成為引線二元樹中節點 p的右子節點(上接程式 5-10 5-11 )

1 void InsertRight(struct TBTreeNode p 2 struct TBTreeNode new)3 struct TBTreeNode q4 new-gtrightchild = p-gtrightchild5 new-gtrightthead = p-gtrightthread6 new-gtleftchild = p7 new-gtleftthread = 18 p-gtrightchild = new9 p-gtrightthead = 010 if (new-gtrightthread)11 q = Next(new)12 q-gtleftchild = new13 14

58  堆積bull 「堆積」 (heap) 是一個特殊的完備二元樹節點的資料與其子節點的資料之間有事先定好的大小關係存在bull 定義ndash最大堆疊為一完備二元樹任一節點的資料內容不小於其子節點的資料內容ndash最小堆疊為一完備二元樹任一節點的資料內容不大於其子節點的資料內容

581  插入節點至最大堆積中最大堆積是一完備二元樹所以如圖 (a) 之最大堆積於加入新節點後樹的形狀必須如圖 (b) 所示灰色節點即為新節點若加入的節點資料分別為 1 9 12 則其最大堆積將分別變成如圖 (c) (d) (e) 所示

程式 5-13 新增節點至最大堆積中最大堆積本身是一棵完備二元樹可以用陣列表示之 1 define maxsize 1002 int heap[maxsize]3 int n i4 void InsertHeap(int x)5 if (n == maxsize) HeapFull()6 else7 n++8 i = n9 while ((i gt 1) ampamp (x gt heap[i2]))10 heap[i] = heap[i2]11 i = 212 13 heap[i] = x14 16

程序 InsertHeap 在插入第 n 個資料時需要的時間複雜度為 O(logn)

1 100

2 93

3 84

4 77

5 65

6 81

7 79

8 66

9 70

10 50

100

84

77

93

8165

66 70

79

50

1

2 3

4 5 6 7

8 9 10

1 define maxsize 1002 int heap[maxsize]3 int n i4 void InsertHeap(int x)5 if (n == maxsize) HeapFull()6 else7 n++8 i = n9 while ((i gt 1) ampamp (x gt heap[i2]))10 heap[i] = heap[i2]11 i = 212 13 heap[i] = x14 16

582 刪除最大堆積中的最大資料圖 (a) 中的最大堆積在刪除最大資料後其樹形應變成如圖 (b) 所示將最未一個樹葉節點的資料 6 暫挪至樹根節點內如圖 (c) 再將圖 (c) 中非最大堆積的結構調整成為最大堆積 10 和 8 中選大者與 6對調即成圖 (d)

程式 5-14 刪除最大堆積中的最大資料(上接程式 5-13 )1 int DeleteHeap()2 int x i j3 if (n == 0) HeapEmpty()4 else5 x = heap[1]6 heap[1] = heap[n]7 n--8 i = 19 while (i lt= n2)10 if (heap[2i] gt heap[2i+1])11 j = 2i12 else13 j = 2i+114 if (heap[i]gtheap[j]) break15 swap(heap[i] heap[j])16 i = j17 18 return x19 20

其時間複雜度與其堆積高度相同是為O(logn) 其中 n 為堆積中的元素個數

1 100

2 93

3 84

4 77

5 65

6 81

7 79

8 66

9 70

10 50

100

84

77

93

8165

66 70

79

50

1

2 3

4 5 6 7

8 9 10

1 int DeletHeap()2 int x i j3 if(n == 0) HeapEmpty()4 else5 x = heap[1]6 heap[1] = heap[n]7 n--8 i = 19 while (i lt= n2)10 if ([heap[2i]] gt heap[2i+1])11 j = 2i12 else13 j = 2i+114 if (heap[i]gtheap[j]) break15 swap(heap[i] heap[j])16 i = j17 18 return x19 20

583  優先佇列bull 由於最大堆積(或最小堆積)的結構可提供最大元素(或最小元素)而其維護堆積的成本為 O(logn) n 為總元素個數

bull 堆積常被當成「優先佇列」 (priority queue) 使用mdash此時佇列被組織成完備二元樹若新加入佇列的資料有大小關係優先順序的需求時此優先佇列即以 O(logn) 的代價將新資料放入優先佇列(可能是最大或最小堆積依實際大小關係優先順序的需求而定)中mdash提供依元素的優先順序做為處理順序的服務

bull 一般的佇列為先進先出先到的先行處理優先佇列則是依據其優先順序優先順序高者先行處理與來到的先後無關

範例 5-21bull 在「分時」 (time sharing) 的作業系統中我們不希望使用中央處理器 (Central Processing Unit CPU) 時間短的「處理程序」 (process) 等候過久於是有「最短時間工作優先」 (shortest-job-first) 的排程策略此時等候的處理程序即可組織成之一最小堆積而 CPU 每次即取出存於樹根的處理程序予以處理任何新來的處理程序則以 O(logn) 的時間加入此優先佇列(最小堆積)中

bull 注意之前提過的線性佇列新來的直接加入成為新的尾端元素花O(1) 的時間然線性佇列本身沒有大小的關係每次要取出最大(小)的資料得花O(n) 的代價(在所有元素中挑出最大(小)者) n 為佇列內的元素個數而優先佇列是在新元素加入時要花O(logn) 的時間至於取出最大(小)則只須 O(1) 的時間

59  二元搜尋樹bull 二元搜尋樹是一棵二元樹可能是空二元樹若不為空二元樹則滿足下列性質ndash所有節點內的資料內容是相異的 ndash左子節點的資內容 ( 如果有 ) 比父節點的資料內容小 ndash右子節點的資料內容 ( 如果有 ) 比父節點的資料內容大 ndash以左和右子節點為樹根的左子樹和右子樹也是二元搜尋樹

在二元搜尋樹中搜尋資料二元搜尋樹對資料大小的掌握十分明確若恰與節點 p 資料相等則搜尋成功若欲搜尋的資料 x 比樹節點 p 中的資料小 則只須搜尋節點 p 的左子樹若比節點 p 中的資料大 則只須搜尋 p 的右子樹這樣的判斷可以遞迴地應用在各子樹節點 中

程式 5-15 在二元搜尋樹中搜尋資料(遞迴呼叫)1 struct BSTreeNode2 struct BSTreeNode leftchild3 int data4 struct BSTreeNode rightchild5 6 struct BSTreeNode root7 struct BSTreeNode SearchBST(

struct BSTreeNode tree int x)8 if (tree == NULL) return NULL9 if (x == tree-gtdata) return tree10 if (x lt tree-gtdata)11 return SearchBST(tree-gtleftchild x)

12 return SearchBST(tree-gtrightchild x)13

程式 5-16 搜尋二元搜尋樹的資料(非遞迴呼叫)在二元搜尋樹十分龐大時遞迴呼叫的效率不會太好可利用迴圈來完成搜尋的動作以提高執行效率請看程式 5-16

14 struct BSTreeNode Search BST_iterative15 (struct BSTreeNode node int x)16 while (node = NULL)17 if (x == node-gtdata) return node18 if (x lt node-gtdata)19 node = node-gtleftchild20 else21 node = node-gtrightchild22 23 return NULL24

592  新增資料於二元搜尋樹中欲新增資料進入一二元搜尋樹中得先決定該資料在二元搜尋樹中應出現的位置這可透過上節提到的搜尋動作來解決

在二元搜尋樹定義的性質 (1) 中我們限定了元素必須相異所以搜尋新資料的結果必是 NULL

必須將搜尋的結果改為新增資料所應在位置的節點指標

程式 5-17  在二元搜尋樹中新增資料25 int InsertBST(int x)26 struct BSTreeNode p q27 p = root q = NULL28 while (p = NULL)29 q = p30 if (x == p-gtdata) return 0

若不考慮重複則可省略之31 if (x lt p-gtdata)32 p = p-gtleftchild33 else34 p = p-gtrightchild35

3636 p = (struct BSTreeNode ) p = (struct BSTreeNode ) malloc (sizeof(BSTreeNode))malloc (sizeof(BSTreeNode))

3737 p-gtdata = x p-gtdata = x3838 p-gtleftchild = p-gtrightchild = p-gtleftchild = p-gtrightchild = NULLNULL3939 if (root == NULL) root = p if (root == NULL) root = p4040 else if (x lt q-gtdata) else if (x lt q-gtdata)4141 q-gtleftchild = p q-gtleftchild = p4242 else else4343 q-gtrightchild = p q-gtrightchild = p4444 return 1 return 145 45

程式 程式 5-175-17  在二元搜尋樹中新增資料  在二元搜尋樹中新增資料 (( 續續 ))

範例 5-23 圖 (b) 為圖 (a) 新增整數 6 之後的結果圖 (c) (d) (e) (f) 則分別是逐步再加入 28 18

10 21 的結果

bull若目前的二元搜尋樹高度為 h 則新增資料得花O(h) 的時間找到其所應在的位置遂新增資料至二元搜尋樹中的時間複雜之度為 O(h)

593 自二元搜尋樹中刪除資料欲自二元搜尋樹中刪除資料可能會面臨兩種情況(1) 刪除節點 p 位於樹葉節點上如下圖所示 這是比較容易處理的情況

(2) 刪除的節點 p 不是樹葉刪除節點 p 後原 p 所指的節點可由 q ( q 中包含了 p 左子樹中的最大節點值)或 r ( r 中包含了 p 右子樹中的最小節點值)來取代以維持二元搜尋樹中資料的大小關係其結果分別如圖 (b) 和 (c) 所示而圖 (d) 則標明了指標會改變的節點位置即 p 會消失 father 的左或右子樹指標會異動 k(可能是 q 或 r )會移到原來 p 的位置其父節點 f 的左或右子樹指標會異動其中 father 為 p 父節點 f 為 k 的父節點

程式 5-18 自二元搜尋樹中刪除資料46 int DeleteBST (int x)47 Struct BSTreeNode p father k f48 p = root father = NULL49 while (p = NULL)50 if (x == p-gtdata) 找到 x 所在的節點 p 了51 if ((p-gtleftchild == NULL) ampamp 52 (p-gtrightchild == NULL))53 k = NULL p為樹葉節點54 else if (p-gtleftchild = NULL )x位於非子節

點55 f = p 找出 p左子樹的最右樹葉56 k = p-gtleftchild57 while (k-gtrightchild =NULL)58 f = k59 k = k-gtrightchild60 k將挪至原 p處61 if (p == f)62 f-gtleftchild = k-gtleftchild 63 else64 f-gtrightchild = k-gtleftchild 65

66 else p無左子樹67 f = p 找出 p右子樹的最左樹葉68 k = p-gtrightchild69 while (k-gtleftchild = NULL)70 f = k71 k = k -gtleftchild72 73 if (p == f)74 f-gtrightchild = k-gtrightchild 75 else76 f-gtleftchild = k-gtrightchild77 78 if (k = NULL) k挪至原 p處繼承 p的左右指標79 k-gtleftchild = p-gtleftchild80 k-gtrightchild = p-gtrightchild81 82 if (father == NULL) root = k83 else if (x lt father-gtdata) 決定 k是 father的左

或右兒子84 father-gtleftchild = k85 else86 father-gtrightchild = k87 free(p)88 return 1 成功地刪除 x於此傳回 1返回呼叫處89 51~89行為找到 x時應有的處理步驟

90 else 尚未找到 x繼續往下一階層找

91 father = p92 if (x lt p-gtdata)93 p = p-gtleftchild94 else95 p = p-gtrightchild96 97 98 return 0 未找到 x傳回 099

510  樹林 (forest)

bull 定義樹林(森林)是由 n 棵沒有交集的樹所構成的集合 n 0

下圖為一包含三棵樹的樹林

5101 轉換樹林為二元樹 之前我們以二元樹來表示樹因為二元樹有較多

好用的性質在此我們也將樹林轉換成二元樹看待

在 522 節中我們嘗試了用左子右兄弟的方式來表示樹這個方法事實上已將樹轉換成二元樹

即用左子右兄弟的結構重新組織樹林中的每一棵樹這些樹的樹根將視為兄弟而最左邊的樹則視為樹林二元樹的樹根如此一來樹林即可轉換成二元樹

範例 5-25右下圖為右上圖三棵樹所對應的三棵左子右兄弟二元樹

下圖則為上圖樹林所對應的一棵樹林二元樹

演算法 5-1  轉換樹林成二元樹輸入樹林 F = T1 T2 Tn

輸出 F 的二元樹表示 BT=(T1 T2 Tn)1 if (n == 0) 輸出空二元樹 2 else

3 root(T1 T2 Tn) = T1

4 leftsubtree(T1 T2 Tn) =

BT(T11 T12 T1m)5 BT(T11 T12 T1m)為 T1的子樹6 rightsubtree(T1 T2 Tn) =

BT(T2 T3 Tn)

樹林轉換成二元樹的過程樹林轉換成二元樹的過程

  • Slide 1
  • Slide 2
  • Slide 3
  • Slide 4
  • Slide 5
  • Slide 6
  • Slide 7
  • Slide 8
  • Slide 9
  • Slide 10
  • Slide 11
  • Slide 12
  • Slide 13
  • Slide 14
  • Slide 15
  • Slide 16
  • Slide 17
  • Slide 18
  • Slide 19
  • Slide 20
  • Slide 21
  • Slide 22
  • Slide 23
  • Slide 24
  • Slide 25
  • Slide 26
  • Slide 27
  • Slide 28
  • Slide 29
  • Slide 30
  • Slide 31
  • Slide 32
  • Slide 33
  • Slide 34
  • Slide 35
  • Slide 36
  • Slide 37
  • Slide 38
  • Slide 39
  • Slide 40
  • Slide 41
  • Slide 42
  • Slide 43
  • Slide 44
  • Slide 45
  • Slide 46
  • Slide 47
  • Slide 48
  • Slide 49
  • Slide 50
  • Slide 51
  • Slide 52
  • Slide 53
  • Slide 54
  • Slide 55
  • Slide 56
  • Slide 57
  • Slide 58
  • Slide 59
  • Slide 60
  • Slide 61
  • Slide 62
  • Slide 63
  • Slide 64
  • Slide 65
  • Slide 66
  • Slide 67
  • Slide 68
  • Slide 69
  • Slide 70
  • Slide 71
  • Slide 72
  • Slide 73
  • Slide 74
  • Slide 75
  • Slide 76
  • Slide 77
  • Slide 78
  • Slide 79
  • Slide 80
  • Slide 81
  • Slide 82
  • Slide 83
  • Slide 84
  • Slide 85
  • Slide 86
  • Slide 87
  • Slide 88
  • Slide 89
  • Slide 90
  • Slide 91
  • Slide 92
  • Slide 93
  • Slide 94
  • Slide 95
  • Slide 96
  • Slide 97
  • Slide 98
  • Slide 99
  • Slide 100
  • Slide 101
  • Slide 102
  • Slide 103
Page 40: 第五章 樹

554 利用堆疊和迴圈取代遞迴進行二元樹的走訪1 struct BTreeNode 2 struct BTreeNode leftchild3 char data4 struct BtreeNode rightchild5 6 struct BTreeNode root7 struct StackNode8 struct BTreeNode treenode9 struct StackNode next1011 struct StackNode top

12 void push_node(struct BtreeNode node )13 struct StackNode old_top14 old_top = top15 top = (struct StackNode ) malloc

(sizeof(struct StackNode))16 top-gttreenode = node17 top-gtnext = old_top18 19 struct BTreeNode pop_node()20 struct BTreeNode Tnode21 struct StackNode old_top22 if (top == NULL)23 StackEmpty()24 else25 Tnode = top-gttreenode26 old_top = top27 top = top-gtnext28 free(old_top)29 return Tnode30 31

32 void inorder_Stack(struct BtreeNode node)33 do34 while (node = NULL)35 push_node(node)36 node = node-gtleftchild37 push all left children38 if (top = NULL)39 node = pop_node()40 cout ltlt node-gtdata41 node = node-gtrightchild42 inorder printing and check right child43 while ((top=NULL)||(node=NULL))44 top==NULL 堆疊已空 node==NULL沒有右子

6 struct BTreeNode root7 void inorder(struct BTreeNode node)8 if (node = NULL)9 inorder(node-gtleftchild)10 cout ltlt node-gtdata11 inorder(node-gtrightchild)12 13

中序表示為DBGEHAFIC

32 void inorder_Stack()33 struct BTreeNode node34 node = root35 do36 while (node = NULL)37 push_node(node)38 node = node-gtleftchild39 40 if (top = NULL)41 node = pop_node()42 cout ltlt node-gtdata43 node = node-gtrightchild44 45 while(top=NULL)||(node=NULL))46

45 void Preorder_Stack(struct BSTreeNode node)46 do47 while (node = NULL)48 cout ltlt node-gtdata)49 push_node(node)50 node = node-gtleftchild51 push all left children52 if (top = NULL)53 node = pop_node()54 node = node-gtrightchild55 56 while ((top=NULL)||(node=NULL))57

程式 5-5二元樹的前序走訪21 void preorder(struct BTreeNode node)22 if (node = NULL)23 cout ltlt node-gtdata24 preorder(node-gtleftchild)25 preorder(node-gtrightchild)26 27

58 void Postorder_Stack(struct BTreeNode node)59 do60 while (node = NULL)61 cout ltlt node-gtdata62 push_node(node)63 node = node-gtrightchild64 push all left children65 if (top = NULL)66 node = pop_node()67 node = node-gtleftchild68 69 while ((top=NULL)||(node=NULL))70

程式 5-4二元樹的後序走訪14 void postorder(struct BTreeNode node)15 if (node =NULL)16 postorder(node-gtleftchild)17 postorder(node-gtrightchild)18 cout ltlt node-gtdata19 20

555  階層走訪bull 「階層走訪」 (level-order traversal) 是依階層

的順序進行二元樹的走訪先走訪階層小的節點後走訪階層大的節點同一階層者則依自左向右的順序走訪

bull 對下圖的二元樹進行階層走訪的結果為ABCDEFGHI

bull 對同一階層而言先走訪的節點其子節點亦在下一階層中先被走訪

bull 這種「先進先出」的特性恰可用佇列予以儲存與處理

程式 5-7 利用佇列進行二元樹的階層走訪

1 struct BTreeNode2 struct BTreeNode leftchild3 char data4 struct BTreeNode rightchild5 6 struct BTreeNode root7 struct QNode8 struct BTreeNode treenode9 struct QNode next10 11 struct QNode front rear

12 void AddQueue(struct BTreeNode Tnode)13 struct QNode node14 node=(struct QNode )malloc(sizeof(struct QNode))15 node-gttreenode = Tnode16 node-gtnext = NULL17 if (front == NULL) front = node19 else rear-gtnext = node20 rear = node21

22 struct BTreeNode DeleteQueue()23 struct BTreeNode Tnode24 struct QNode old_front25 if (rear == NULL)26 QueueEmpty()27 else28 Tnode = front-gttreenode29 old_front = front30 front = front-gtnext31 free(old_front)32 return Tnode33 34 35 void LevelOrder (struct BTreeNode node)36 AddQueue(node)37 while (front =NULL)38 node = DeleteQueue()39 cout ltlt node-gtdata40 if (node-gtleftchild = NULL)41 AddQueue(node-gtleftchild)42 if (node-gtrightchild = NULL)43 AddQueue(node-gtrightchild)44 45

35 void LevelOrder (struct BTreeNode node)36 AddQueue(node)37 while (front =NULL)38 node = DeleteQueue()39 cout ltlt node-gtdata40 if (node-gtleftchild = NULL)41 AddQueue(node-gtleftchild)42 if (node-gtrightchild = NULL)43 AddQueue(node-gtrightchild)44 45

bull Infix ABCbull Postfix ABC CBA ACB BCA

BAC CAB

B

A CA

B

C

B

A

C

C

B

AB

A

C

二元樹中序與前序式中序與後序式bull 每一棵二元樹皆有唯一的一對中序與前序表示法也有唯一的中序與後序次序

bull 換句話說給予一對中序與前序或中序與後序即可決定一棵二元樹

bull 然而給予前序和後序次序並不能決定唯一的二元樹可能會產生兩棵不同的二元樹

bull 想想階層走訪和中序走訪順序可否決定唯一的二元樹

A

B C

H J D E

F G

prefix ABHJCDFGEinfix HBJAFDGCEpostfix HJBFGDECA

VLRLVRLRV

A

prefix BHJinfix HBJ

prefix CDFGEinfix FDGCE

prefix ABHJCDFGEinfix HBJAFDGCE

A

B C

H J D E

F G

prefix ABHJCDFGE

infix HBJAFDGCEk-1

struct BTreeNode BTree_InfixPrefix (AnsiString infix AnsiString prefix)

int k struct BTreeNode node if (infixLength==0) return NULLnode = (struct BTreeNode ) malloc (sizeof(struct BTreeNode))node-gtdata = prefix[1]k = find(prefix[1] infix)node-gtleftchild = BTree_InfixPrefix

(infix[1k-1] prefix[2k])node-gtrightchild = BTree_InfixPrefix(infix[k+1infixLength]prefix[k+1prefixLength])

VLR

LVRk1

k2 k+1~

k+1~

1

struct BTreeNode BTree_InfixPostfix (AnsiString infix AnsiString prefix)

int k struct BTreeNode node if (infixLength==0) return NULLnode = (struct BTreeNode ) malloc (sizeof(struct BTreeNode))node-gtdata = postfix[postfixLength]k = find(postfix[postfixLength] infix)node-gtleftchild = BTree_InfixPrefix

(infix[1k-1] postfix[1k-1])node-gtrightchild = BTree_InfixPrefix(infix[k+1infixLength]postfix[k+1postfixLength-1])

56  二元樹的複製與相等測試程式 5-8 二元樹的複製1 struct BTreeNode2 struct BTreeNode leftchild3 char data4 struct BTreeNode rightchild5 6 struct BTreeNode root7 struct BTreeNode Copy(struct BTreeNode TreeRoot)8 struct BTreeNode CopyRoot9 if (TreeRoot == NULL)10 return NULL11 CopyRoot =(struct BTreeNode ) malloc (sizeof(BTreeNode))12 CopyRoot-gtdata = TreeRoot-gtdata13 CopyRoot-gtleftchild = Copy(TreeRoot-gtleftchild)14 CopyRoot-gtrightchild = Copy(TreeRoot-

gtrightchild)15 return CopyRoot16

程式 5-9 檢測兩棵二元樹是否相等1 struct BTreeNode2 struct BTreeNode leftchild3 char data4 struct BTreeNode rightchild5 6 struct BtreeNode rightchild7 int Equal(struct BTreeNode ustruct BTreeNode v)8 if ((u==NULL) ampamp (v==NULL)) return 19 if ((u=NULL) ampamp (v=NULL)10 ampamp (u-gtdata == v-gtdata )11 ampamp Equal(u-gtleftchildv-gtleftchild)12 ampamp Equal(u-gtrightchildv-gtrightchild))

13 return 114 return 015

Counting Nodes in a Binary Tree

int CountingNodes(struct BTreeNodes node)

if (node == NULL) return 0 int leftchildren = CountingNodes(node-gtleftchild)

int rightchildren= CountingNodes(node-gtrightchild)

return leftchildren+rightchildren

Counting Leaves in a Binary Tree

int CountingLeaves(struct BTreeNodes node)

if (node == NULL) return 0 int leftleaves = CountingLeaves(node-gtleftchild)

int rightchildren= CountingLeaves(node-gtrightchild)

if (leftleaves==0 ampamp leftleaves==0) return 1

return leftleaves + leftleaves

57  引線二元樹bull 在二元樹的鍵結表示中樹葉節點的兩個子樹指標皆指向 NULL 由定理 5-1 得知 n = E+1 其中 n 為節點個數 E 為分支數

bull 而 n 個鍵結節點有 2n 個指標空間每個分支恰佔用一個指標空間共有 E (=n-1) 個指標有用到(不是空的)而共有 n+1 個指標空間是 NULL 比非空的節點還多

bull 有學者提出對這些「放空指標的空間」加以利用的概念mdash與其放空指標不如放指向其它節點的指標稱之為引線 (thread) 使得某些運算(如走訪hellip等)可以加快

bull 這個概念發展出了引線二元樹 (threaded binary tree) 這種資料結構

範例 5-17下圖為加了引線的二元樹

加入的引線將使二元樹的中序走訪更加便利沿著上右圖箭頭所指示的順序走訪即可完成此二元樹的中序走訪GDHBAIECF 為此二元樹的中序表示

bull 範例 5-17 中所加的引線目的在方便二元樹的中序走訪

bull 對樹葉節點 p 而言令其資料為 P 則引線指標的規則如下

(1) p 的左子樹指標指向中序表示中 P 的前一個 元素(前接元素 (predecessor) )

(2) p 的右子樹指標指向中序表示中 P 的後一個 元素(後繼元素 (successor) )

為了區別節點指標放的是一般指標(內部節點)抑或是引線指標(樹葉節點)我們另以 2 個欄位分別區別左和右子樹指標空間存放的對象其節點的記憶體配置有如下圖所示

其中 leftthread (rightthread) 為 0 時表示 leftchild (rightchild) 存放的是一般節點指標而 leftthread (rightthread) 為 1 時表示 leftchild (rightchild) 存放的是引線指標1 struct TBTreeNode2 int leftthread3 struct TBTreeNode leftchild4 char data5 struct TBTreeNode rightchild6 int rightthread7

由右側的引線二元樹可知有最左和最右兩條引線尚無妥善的安排我們另設計一空的引線節點做為樹根如下圖所示

571  引線二元樹的中序走訪決定節點 p 在中序表示中的後繼節點 q (簡稱中序後繼點)

程式 5-10決定節點 node的中序後繼點1 struct TBTreeNode2 int leftthread3 struct TBTreeNode leftchild4 char data5 struct TBTreeNode rightchild6 int rightthread7 8 struct TBTreeNode root9 struct TBTreeNode Next(struct TBTreeNode node)

10 struct TBTreeNode temp11 temp = node-gtrightchild12 if (node-gtrightthead) return temp13 while (temp-gtleftthead) temp=temp-gtleftchild

14 return temp15

程式 5-11引線二元樹的中序表示(接程式 5-10)1 void InorderTBTree()2 struct TBTreeNode node3 for(node=Next(root)node=rootnode=Next(node))

4 cout ltlt node-gtdata5

bull只要 node 是引線二元樹上的任一節點透過呼叫 Next(node) 即可找到其在中序表示中的後繼節點bull(試想若程式 5-10 中的程序 Next 所傳入的參數指標正是 root Next(root) 是否可正常運作請思考)bull於是只須從中序表示中的第一節點起讓每一節點 node 都執行 Next(node) 程序即可得引線二元樹的中序表示

572  在引線二元樹中加入節點在引線二元樹中樹葉節點節點的右子樹指標所指向或內部節點右子樹的最左樹葉為其中序後繼節點而且任一節點若無左子樹則其左子樹指標乃指向其中序前接節點hellip這些性質必須在節點新增進入引線二元樹時也要保持接下來討論節點 new 插入成為引線二元樹的節點 p 的右子節點的情形至於插入成為左子節點的情形則讓各位模擬推敲

572572  在引線二元樹中加入節點 在引線二元樹中加入節點 (( 續續 ))

newnew 會成為會成為 pp 的右子節點的情況共有以下兩種的右子節點的情況共有以下兩種(1) (1) 若若 pp 沒有右子節點 沒有右子節點 ((p-gtrightthread p-gtrightthread 為為1) 1) 如圖如圖 ((a) a) 則 則 new new 插入後應形成如圖插入後應形成如圖 ((b) b) 所示所示

(2)(2) 若若 pp 有右子節點 有右子節點 ((p-gtrightthread p-gtrightthread 為為0) 0) 如圖如圖 ((c) c) 則 則 new new 插入後應形成如插入後應形成如圖圖 ((d) d) 所示所示

程式 5-12  插入節點 new 成為引線二元樹中節點 p的右子節點(上接程式 5-10 5-11 )

1 void InsertRight(struct TBTreeNode p 2 struct TBTreeNode new)3 struct TBTreeNode q4 new-gtrightchild = p-gtrightchild5 new-gtrightthead = p-gtrightthread6 new-gtleftchild = p7 new-gtleftthread = 18 p-gtrightchild = new9 p-gtrightthead = 010 if (new-gtrightthread)11 q = Next(new)12 q-gtleftchild = new13 14

58  堆積bull 「堆積」 (heap) 是一個特殊的完備二元樹節點的資料與其子節點的資料之間有事先定好的大小關係存在bull 定義ndash最大堆疊為一完備二元樹任一節點的資料內容不小於其子節點的資料內容ndash最小堆疊為一完備二元樹任一節點的資料內容不大於其子節點的資料內容

581  插入節點至最大堆積中最大堆積是一完備二元樹所以如圖 (a) 之最大堆積於加入新節點後樹的形狀必須如圖 (b) 所示灰色節點即為新節點若加入的節點資料分別為 1 9 12 則其最大堆積將分別變成如圖 (c) (d) (e) 所示

程式 5-13 新增節點至最大堆積中最大堆積本身是一棵完備二元樹可以用陣列表示之 1 define maxsize 1002 int heap[maxsize]3 int n i4 void InsertHeap(int x)5 if (n == maxsize) HeapFull()6 else7 n++8 i = n9 while ((i gt 1) ampamp (x gt heap[i2]))10 heap[i] = heap[i2]11 i = 212 13 heap[i] = x14 16

程序 InsertHeap 在插入第 n 個資料時需要的時間複雜度為 O(logn)

1 100

2 93

3 84

4 77

5 65

6 81

7 79

8 66

9 70

10 50

100

84

77

93

8165

66 70

79

50

1

2 3

4 5 6 7

8 9 10

1 define maxsize 1002 int heap[maxsize]3 int n i4 void InsertHeap(int x)5 if (n == maxsize) HeapFull()6 else7 n++8 i = n9 while ((i gt 1) ampamp (x gt heap[i2]))10 heap[i] = heap[i2]11 i = 212 13 heap[i] = x14 16

582 刪除最大堆積中的最大資料圖 (a) 中的最大堆積在刪除最大資料後其樹形應變成如圖 (b) 所示將最未一個樹葉節點的資料 6 暫挪至樹根節點內如圖 (c) 再將圖 (c) 中非最大堆積的結構調整成為最大堆積 10 和 8 中選大者與 6對調即成圖 (d)

程式 5-14 刪除最大堆積中的最大資料(上接程式 5-13 )1 int DeleteHeap()2 int x i j3 if (n == 0) HeapEmpty()4 else5 x = heap[1]6 heap[1] = heap[n]7 n--8 i = 19 while (i lt= n2)10 if (heap[2i] gt heap[2i+1])11 j = 2i12 else13 j = 2i+114 if (heap[i]gtheap[j]) break15 swap(heap[i] heap[j])16 i = j17 18 return x19 20

其時間複雜度與其堆積高度相同是為O(logn) 其中 n 為堆積中的元素個數

1 100

2 93

3 84

4 77

5 65

6 81

7 79

8 66

9 70

10 50

100

84

77

93

8165

66 70

79

50

1

2 3

4 5 6 7

8 9 10

1 int DeletHeap()2 int x i j3 if(n == 0) HeapEmpty()4 else5 x = heap[1]6 heap[1] = heap[n]7 n--8 i = 19 while (i lt= n2)10 if ([heap[2i]] gt heap[2i+1])11 j = 2i12 else13 j = 2i+114 if (heap[i]gtheap[j]) break15 swap(heap[i] heap[j])16 i = j17 18 return x19 20

583  優先佇列bull 由於最大堆積(或最小堆積)的結構可提供最大元素(或最小元素)而其維護堆積的成本為 O(logn) n 為總元素個數

bull 堆積常被當成「優先佇列」 (priority queue) 使用mdash此時佇列被組織成完備二元樹若新加入佇列的資料有大小關係優先順序的需求時此優先佇列即以 O(logn) 的代價將新資料放入優先佇列(可能是最大或最小堆積依實際大小關係優先順序的需求而定)中mdash提供依元素的優先順序做為處理順序的服務

bull 一般的佇列為先進先出先到的先行處理優先佇列則是依據其優先順序優先順序高者先行處理與來到的先後無關

範例 5-21bull 在「分時」 (time sharing) 的作業系統中我們不希望使用中央處理器 (Central Processing Unit CPU) 時間短的「處理程序」 (process) 等候過久於是有「最短時間工作優先」 (shortest-job-first) 的排程策略此時等候的處理程序即可組織成之一最小堆積而 CPU 每次即取出存於樹根的處理程序予以處理任何新來的處理程序則以 O(logn) 的時間加入此優先佇列(最小堆積)中

bull 注意之前提過的線性佇列新來的直接加入成為新的尾端元素花O(1) 的時間然線性佇列本身沒有大小的關係每次要取出最大(小)的資料得花O(n) 的代價(在所有元素中挑出最大(小)者) n 為佇列內的元素個數而優先佇列是在新元素加入時要花O(logn) 的時間至於取出最大(小)則只須 O(1) 的時間

59  二元搜尋樹bull 二元搜尋樹是一棵二元樹可能是空二元樹若不為空二元樹則滿足下列性質ndash所有節點內的資料內容是相異的 ndash左子節點的資內容 ( 如果有 ) 比父節點的資料內容小 ndash右子節點的資料內容 ( 如果有 ) 比父節點的資料內容大 ndash以左和右子節點為樹根的左子樹和右子樹也是二元搜尋樹

在二元搜尋樹中搜尋資料二元搜尋樹對資料大小的掌握十分明確若恰與節點 p 資料相等則搜尋成功若欲搜尋的資料 x 比樹節點 p 中的資料小 則只須搜尋節點 p 的左子樹若比節點 p 中的資料大 則只須搜尋 p 的右子樹這樣的判斷可以遞迴地應用在各子樹節點 中

程式 5-15 在二元搜尋樹中搜尋資料(遞迴呼叫)1 struct BSTreeNode2 struct BSTreeNode leftchild3 int data4 struct BSTreeNode rightchild5 6 struct BSTreeNode root7 struct BSTreeNode SearchBST(

struct BSTreeNode tree int x)8 if (tree == NULL) return NULL9 if (x == tree-gtdata) return tree10 if (x lt tree-gtdata)11 return SearchBST(tree-gtleftchild x)

12 return SearchBST(tree-gtrightchild x)13

程式 5-16 搜尋二元搜尋樹的資料(非遞迴呼叫)在二元搜尋樹十分龐大時遞迴呼叫的效率不會太好可利用迴圈來完成搜尋的動作以提高執行效率請看程式 5-16

14 struct BSTreeNode Search BST_iterative15 (struct BSTreeNode node int x)16 while (node = NULL)17 if (x == node-gtdata) return node18 if (x lt node-gtdata)19 node = node-gtleftchild20 else21 node = node-gtrightchild22 23 return NULL24

592  新增資料於二元搜尋樹中欲新增資料進入一二元搜尋樹中得先決定該資料在二元搜尋樹中應出現的位置這可透過上節提到的搜尋動作來解決

在二元搜尋樹定義的性質 (1) 中我們限定了元素必須相異所以搜尋新資料的結果必是 NULL

必須將搜尋的結果改為新增資料所應在位置的節點指標

程式 5-17  在二元搜尋樹中新增資料25 int InsertBST(int x)26 struct BSTreeNode p q27 p = root q = NULL28 while (p = NULL)29 q = p30 if (x == p-gtdata) return 0

若不考慮重複則可省略之31 if (x lt p-gtdata)32 p = p-gtleftchild33 else34 p = p-gtrightchild35

3636 p = (struct BSTreeNode ) p = (struct BSTreeNode ) malloc (sizeof(BSTreeNode))malloc (sizeof(BSTreeNode))

3737 p-gtdata = x p-gtdata = x3838 p-gtleftchild = p-gtrightchild = p-gtleftchild = p-gtrightchild = NULLNULL3939 if (root == NULL) root = p if (root == NULL) root = p4040 else if (x lt q-gtdata) else if (x lt q-gtdata)4141 q-gtleftchild = p q-gtleftchild = p4242 else else4343 q-gtrightchild = p q-gtrightchild = p4444 return 1 return 145 45

程式 程式 5-175-17  在二元搜尋樹中新增資料  在二元搜尋樹中新增資料 (( 續續 ))

範例 5-23 圖 (b) 為圖 (a) 新增整數 6 之後的結果圖 (c) (d) (e) (f) 則分別是逐步再加入 28 18

10 21 的結果

bull若目前的二元搜尋樹高度為 h 則新增資料得花O(h) 的時間找到其所應在的位置遂新增資料至二元搜尋樹中的時間複雜之度為 O(h)

593 自二元搜尋樹中刪除資料欲自二元搜尋樹中刪除資料可能會面臨兩種情況(1) 刪除節點 p 位於樹葉節點上如下圖所示 這是比較容易處理的情況

(2) 刪除的節點 p 不是樹葉刪除節點 p 後原 p 所指的節點可由 q ( q 中包含了 p 左子樹中的最大節點值)或 r ( r 中包含了 p 右子樹中的最小節點值)來取代以維持二元搜尋樹中資料的大小關係其結果分別如圖 (b) 和 (c) 所示而圖 (d) 則標明了指標會改變的節點位置即 p 會消失 father 的左或右子樹指標會異動 k(可能是 q 或 r )會移到原來 p 的位置其父節點 f 的左或右子樹指標會異動其中 father 為 p 父節點 f 為 k 的父節點

程式 5-18 自二元搜尋樹中刪除資料46 int DeleteBST (int x)47 Struct BSTreeNode p father k f48 p = root father = NULL49 while (p = NULL)50 if (x == p-gtdata) 找到 x 所在的節點 p 了51 if ((p-gtleftchild == NULL) ampamp 52 (p-gtrightchild == NULL))53 k = NULL p為樹葉節點54 else if (p-gtleftchild = NULL )x位於非子節

點55 f = p 找出 p左子樹的最右樹葉56 k = p-gtleftchild57 while (k-gtrightchild =NULL)58 f = k59 k = k-gtrightchild60 k將挪至原 p處61 if (p == f)62 f-gtleftchild = k-gtleftchild 63 else64 f-gtrightchild = k-gtleftchild 65

66 else p無左子樹67 f = p 找出 p右子樹的最左樹葉68 k = p-gtrightchild69 while (k-gtleftchild = NULL)70 f = k71 k = k -gtleftchild72 73 if (p == f)74 f-gtrightchild = k-gtrightchild 75 else76 f-gtleftchild = k-gtrightchild77 78 if (k = NULL) k挪至原 p處繼承 p的左右指標79 k-gtleftchild = p-gtleftchild80 k-gtrightchild = p-gtrightchild81 82 if (father == NULL) root = k83 else if (x lt father-gtdata) 決定 k是 father的左

或右兒子84 father-gtleftchild = k85 else86 father-gtrightchild = k87 free(p)88 return 1 成功地刪除 x於此傳回 1返回呼叫處89 51~89行為找到 x時應有的處理步驟

90 else 尚未找到 x繼續往下一階層找

91 father = p92 if (x lt p-gtdata)93 p = p-gtleftchild94 else95 p = p-gtrightchild96 97 98 return 0 未找到 x傳回 099

510  樹林 (forest)

bull 定義樹林(森林)是由 n 棵沒有交集的樹所構成的集合 n 0

下圖為一包含三棵樹的樹林

5101 轉換樹林為二元樹 之前我們以二元樹來表示樹因為二元樹有較多

好用的性質在此我們也將樹林轉換成二元樹看待

在 522 節中我們嘗試了用左子右兄弟的方式來表示樹這個方法事實上已將樹轉換成二元樹

即用左子右兄弟的結構重新組織樹林中的每一棵樹這些樹的樹根將視為兄弟而最左邊的樹則視為樹林二元樹的樹根如此一來樹林即可轉換成二元樹

範例 5-25右下圖為右上圖三棵樹所對應的三棵左子右兄弟二元樹

下圖則為上圖樹林所對應的一棵樹林二元樹

演算法 5-1  轉換樹林成二元樹輸入樹林 F = T1 T2 Tn

輸出 F 的二元樹表示 BT=(T1 T2 Tn)1 if (n == 0) 輸出空二元樹 2 else

3 root(T1 T2 Tn) = T1

4 leftsubtree(T1 T2 Tn) =

BT(T11 T12 T1m)5 BT(T11 T12 T1m)為 T1的子樹6 rightsubtree(T1 T2 Tn) =

BT(T2 T3 Tn)

樹林轉換成二元樹的過程樹林轉換成二元樹的過程

  • Slide 1
  • Slide 2
  • Slide 3
  • Slide 4
  • Slide 5
  • Slide 6
  • Slide 7
  • Slide 8
  • Slide 9
  • Slide 10
  • Slide 11
  • Slide 12
  • Slide 13
  • Slide 14
  • Slide 15
  • Slide 16
  • Slide 17
  • Slide 18
  • Slide 19
  • Slide 20
  • Slide 21
  • Slide 22
  • Slide 23
  • Slide 24
  • Slide 25
  • Slide 26
  • Slide 27
  • Slide 28
  • Slide 29
  • Slide 30
  • Slide 31
  • Slide 32
  • Slide 33
  • Slide 34
  • Slide 35
  • Slide 36
  • Slide 37
  • Slide 38
  • Slide 39
  • Slide 40
  • Slide 41
  • Slide 42
  • Slide 43
  • Slide 44
  • Slide 45
  • Slide 46
  • Slide 47
  • Slide 48
  • Slide 49
  • Slide 50
  • Slide 51
  • Slide 52
  • Slide 53
  • Slide 54
  • Slide 55
  • Slide 56
  • Slide 57
  • Slide 58
  • Slide 59
  • Slide 60
  • Slide 61
  • Slide 62
  • Slide 63
  • Slide 64
  • Slide 65
  • Slide 66
  • Slide 67
  • Slide 68
  • Slide 69
  • Slide 70
  • Slide 71
  • Slide 72
  • Slide 73
  • Slide 74
  • Slide 75
  • Slide 76
  • Slide 77
  • Slide 78
  • Slide 79
  • Slide 80
  • Slide 81
  • Slide 82
  • Slide 83
  • Slide 84
  • Slide 85
  • Slide 86
  • Slide 87
  • Slide 88
  • Slide 89
  • Slide 90
  • Slide 91
  • Slide 92
  • Slide 93
  • Slide 94
  • Slide 95
  • Slide 96
  • Slide 97
  • Slide 98
  • Slide 99
  • Slide 100
  • Slide 101
  • Slide 102
  • Slide 103
Page 41: 第五章 樹

12 void push_node(struct BtreeNode node )13 struct StackNode old_top14 old_top = top15 top = (struct StackNode ) malloc

(sizeof(struct StackNode))16 top-gttreenode = node17 top-gtnext = old_top18 19 struct BTreeNode pop_node()20 struct BTreeNode Tnode21 struct StackNode old_top22 if (top == NULL)23 StackEmpty()24 else25 Tnode = top-gttreenode26 old_top = top27 top = top-gtnext28 free(old_top)29 return Tnode30 31

32 void inorder_Stack(struct BtreeNode node)33 do34 while (node = NULL)35 push_node(node)36 node = node-gtleftchild37 push all left children38 if (top = NULL)39 node = pop_node()40 cout ltlt node-gtdata41 node = node-gtrightchild42 inorder printing and check right child43 while ((top=NULL)||(node=NULL))44 top==NULL 堆疊已空 node==NULL沒有右子

6 struct BTreeNode root7 void inorder(struct BTreeNode node)8 if (node = NULL)9 inorder(node-gtleftchild)10 cout ltlt node-gtdata11 inorder(node-gtrightchild)12 13

中序表示為DBGEHAFIC

32 void inorder_Stack()33 struct BTreeNode node34 node = root35 do36 while (node = NULL)37 push_node(node)38 node = node-gtleftchild39 40 if (top = NULL)41 node = pop_node()42 cout ltlt node-gtdata43 node = node-gtrightchild44 45 while(top=NULL)||(node=NULL))46

45 void Preorder_Stack(struct BSTreeNode node)46 do47 while (node = NULL)48 cout ltlt node-gtdata)49 push_node(node)50 node = node-gtleftchild51 push all left children52 if (top = NULL)53 node = pop_node()54 node = node-gtrightchild55 56 while ((top=NULL)||(node=NULL))57

程式 5-5二元樹的前序走訪21 void preorder(struct BTreeNode node)22 if (node = NULL)23 cout ltlt node-gtdata24 preorder(node-gtleftchild)25 preorder(node-gtrightchild)26 27

58 void Postorder_Stack(struct BTreeNode node)59 do60 while (node = NULL)61 cout ltlt node-gtdata62 push_node(node)63 node = node-gtrightchild64 push all left children65 if (top = NULL)66 node = pop_node()67 node = node-gtleftchild68 69 while ((top=NULL)||(node=NULL))70

程式 5-4二元樹的後序走訪14 void postorder(struct BTreeNode node)15 if (node =NULL)16 postorder(node-gtleftchild)17 postorder(node-gtrightchild)18 cout ltlt node-gtdata19 20

555  階層走訪bull 「階層走訪」 (level-order traversal) 是依階層

的順序進行二元樹的走訪先走訪階層小的節點後走訪階層大的節點同一階層者則依自左向右的順序走訪

bull 對下圖的二元樹進行階層走訪的結果為ABCDEFGHI

bull 對同一階層而言先走訪的節點其子節點亦在下一階層中先被走訪

bull 這種「先進先出」的特性恰可用佇列予以儲存與處理

程式 5-7 利用佇列進行二元樹的階層走訪

1 struct BTreeNode2 struct BTreeNode leftchild3 char data4 struct BTreeNode rightchild5 6 struct BTreeNode root7 struct QNode8 struct BTreeNode treenode9 struct QNode next10 11 struct QNode front rear

12 void AddQueue(struct BTreeNode Tnode)13 struct QNode node14 node=(struct QNode )malloc(sizeof(struct QNode))15 node-gttreenode = Tnode16 node-gtnext = NULL17 if (front == NULL) front = node19 else rear-gtnext = node20 rear = node21

22 struct BTreeNode DeleteQueue()23 struct BTreeNode Tnode24 struct QNode old_front25 if (rear == NULL)26 QueueEmpty()27 else28 Tnode = front-gttreenode29 old_front = front30 front = front-gtnext31 free(old_front)32 return Tnode33 34 35 void LevelOrder (struct BTreeNode node)36 AddQueue(node)37 while (front =NULL)38 node = DeleteQueue()39 cout ltlt node-gtdata40 if (node-gtleftchild = NULL)41 AddQueue(node-gtleftchild)42 if (node-gtrightchild = NULL)43 AddQueue(node-gtrightchild)44 45

35 void LevelOrder (struct BTreeNode node)36 AddQueue(node)37 while (front =NULL)38 node = DeleteQueue()39 cout ltlt node-gtdata40 if (node-gtleftchild = NULL)41 AddQueue(node-gtleftchild)42 if (node-gtrightchild = NULL)43 AddQueue(node-gtrightchild)44 45

bull Infix ABCbull Postfix ABC CBA ACB BCA

BAC CAB

B

A CA

B

C

B

A

C

C

B

AB

A

C

二元樹中序與前序式中序與後序式bull 每一棵二元樹皆有唯一的一對中序與前序表示法也有唯一的中序與後序次序

bull 換句話說給予一對中序與前序或中序與後序即可決定一棵二元樹

bull 然而給予前序和後序次序並不能決定唯一的二元樹可能會產生兩棵不同的二元樹

bull 想想階層走訪和中序走訪順序可否決定唯一的二元樹

A

B C

H J D E

F G

prefix ABHJCDFGEinfix HBJAFDGCEpostfix HJBFGDECA

VLRLVRLRV

A

prefix BHJinfix HBJ

prefix CDFGEinfix FDGCE

prefix ABHJCDFGEinfix HBJAFDGCE

A

B C

H J D E

F G

prefix ABHJCDFGE

infix HBJAFDGCEk-1

struct BTreeNode BTree_InfixPrefix (AnsiString infix AnsiString prefix)

int k struct BTreeNode node if (infixLength==0) return NULLnode = (struct BTreeNode ) malloc (sizeof(struct BTreeNode))node-gtdata = prefix[1]k = find(prefix[1] infix)node-gtleftchild = BTree_InfixPrefix

(infix[1k-1] prefix[2k])node-gtrightchild = BTree_InfixPrefix(infix[k+1infixLength]prefix[k+1prefixLength])

VLR

LVRk1

k2 k+1~

k+1~

1

struct BTreeNode BTree_InfixPostfix (AnsiString infix AnsiString prefix)

int k struct BTreeNode node if (infixLength==0) return NULLnode = (struct BTreeNode ) malloc (sizeof(struct BTreeNode))node-gtdata = postfix[postfixLength]k = find(postfix[postfixLength] infix)node-gtleftchild = BTree_InfixPrefix

(infix[1k-1] postfix[1k-1])node-gtrightchild = BTree_InfixPrefix(infix[k+1infixLength]postfix[k+1postfixLength-1])

56  二元樹的複製與相等測試程式 5-8 二元樹的複製1 struct BTreeNode2 struct BTreeNode leftchild3 char data4 struct BTreeNode rightchild5 6 struct BTreeNode root7 struct BTreeNode Copy(struct BTreeNode TreeRoot)8 struct BTreeNode CopyRoot9 if (TreeRoot == NULL)10 return NULL11 CopyRoot =(struct BTreeNode ) malloc (sizeof(BTreeNode))12 CopyRoot-gtdata = TreeRoot-gtdata13 CopyRoot-gtleftchild = Copy(TreeRoot-gtleftchild)14 CopyRoot-gtrightchild = Copy(TreeRoot-

gtrightchild)15 return CopyRoot16

程式 5-9 檢測兩棵二元樹是否相等1 struct BTreeNode2 struct BTreeNode leftchild3 char data4 struct BTreeNode rightchild5 6 struct BtreeNode rightchild7 int Equal(struct BTreeNode ustruct BTreeNode v)8 if ((u==NULL) ampamp (v==NULL)) return 19 if ((u=NULL) ampamp (v=NULL)10 ampamp (u-gtdata == v-gtdata )11 ampamp Equal(u-gtleftchildv-gtleftchild)12 ampamp Equal(u-gtrightchildv-gtrightchild))

13 return 114 return 015

Counting Nodes in a Binary Tree

int CountingNodes(struct BTreeNodes node)

if (node == NULL) return 0 int leftchildren = CountingNodes(node-gtleftchild)

int rightchildren= CountingNodes(node-gtrightchild)

return leftchildren+rightchildren

Counting Leaves in a Binary Tree

int CountingLeaves(struct BTreeNodes node)

if (node == NULL) return 0 int leftleaves = CountingLeaves(node-gtleftchild)

int rightchildren= CountingLeaves(node-gtrightchild)

if (leftleaves==0 ampamp leftleaves==0) return 1

return leftleaves + leftleaves

57  引線二元樹bull 在二元樹的鍵結表示中樹葉節點的兩個子樹指標皆指向 NULL 由定理 5-1 得知 n = E+1 其中 n 為節點個數 E 為分支數

bull 而 n 個鍵結節點有 2n 個指標空間每個分支恰佔用一個指標空間共有 E (=n-1) 個指標有用到(不是空的)而共有 n+1 個指標空間是 NULL 比非空的節點還多

bull 有學者提出對這些「放空指標的空間」加以利用的概念mdash與其放空指標不如放指向其它節點的指標稱之為引線 (thread) 使得某些運算(如走訪hellip等)可以加快

bull 這個概念發展出了引線二元樹 (threaded binary tree) 這種資料結構

範例 5-17下圖為加了引線的二元樹

加入的引線將使二元樹的中序走訪更加便利沿著上右圖箭頭所指示的順序走訪即可完成此二元樹的中序走訪GDHBAIECF 為此二元樹的中序表示

bull 範例 5-17 中所加的引線目的在方便二元樹的中序走訪

bull 對樹葉節點 p 而言令其資料為 P 則引線指標的規則如下

(1) p 的左子樹指標指向中序表示中 P 的前一個 元素(前接元素 (predecessor) )

(2) p 的右子樹指標指向中序表示中 P 的後一個 元素(後繼元素 (successor) )

為了區別節點指標放的是一般指標(內部節點)抑或是引線指標(樹葉節點)我們另以 2 個欄位分別區別左和右子樹指標空間存放的對象其節點的記憶體配置有如下圖所示

其中 leftthread (rightthread) 為 0 時表示 leftchild (rightchild) 存放的是一般節點指標而 leftthread (rightthread) 為 1 時表示 leftchild (rightchild) 存放的是引線指標1 struct TBTreeNode2 int leftthread3 struct TBTreeNode leftchild4 char data5 struct TBTreeNode rightchild6 int rightthread7

由右側的引線二元樹可知有最左和最右兩條引線尚無妥善的安排我們另設計一空的引線節點做為樹根如下圖所示

571  引線二元樹的中序走訪決定節點 p 在中序表示中的後繼節點 q (簡稱中序後繼點)

程式 5-10決定節點 node的中序後繼點1 struct TBTreeNode2 int leftthread3 struct TBTreeNode leftchild4 char data5 struct TBTreeNode rightchild6 int rightthread7 8 struct TBTreeNode root9 struct TBTreeNode Next(struct TBTreeNode node)

10 struct TBTreeNode temp11 temp = node-gtrightchild12 if (node-gtrightthead) return temp13 while (temp-gtleftthead) temp=temp-gtleftchild

14 return temp15

程式 5-11引線二元樹的中序表示(接程式 5-10)1 void InorderTBTree()2 struct TBTreeNode node3 for(node=Next(root)node=rootnode=Next(node))

4 cout ltlt node-gtdata5

bull只要 node 是引線二元樹上的任一節點透過呼叫 Next(node) 即可找到其在中序表示中的後繼節點bull(試想若程式 5-10 中的程序 Next 所傳入的參數指標正是 root Next(root) 是否可正常運作請思考)bull於是只須從中序表示中的第一節點起讓每一節點 node 都執行 Next(node) 程序即可得引線二元樹的中序表示

572  在引線二元樹中加入節點在引線二元樹中樹葉節點節點的右子樹指標所指向或內部節點右子樹的最左樹葉為其中序後繼節點而且任一節點若無左子樹則其左子樹指標乃指向其中序前接節點hellip這些性質必須在節點新增進入引線二元樹時也要保持接下來討論節點 new 插入成為引線二元樹的節點 p 的右子節點的情形至於插入成為左子節點的情形則讓各位模擬推敲

572572  在引線二元樹中加入節點 在引線二元樹中加入節點 (( 續續 ))

newnew 會成為會成為 pp 的右子節點的情況共有以下兩種的右子節點的情況共有以下兩種(1) (1) 若若 pp 沒有右子節點 沒有右子節點 ((p-gtrightthread p-gtrightthread 為為1) 1) 如圖如圖 ((a) a) 則 則 new new 插入後應形成如圖插入後應形成如圖 ((b) b) 所示所示

(2)(2) 若若 pp 有右子節點 有右子節點 ((p-gtrightthread p-gtrightthread 為為0) 0) 如圖如圖 ((c) c) 則 則 new new 插入後應形成如插入後應形成如圖圖 ((d) d) 所示所示

程式 5-12  插入節點 new 成為引線二元樹中節點 p的右子節點(上接程式 5-10 5-11 )

1 void InsertRight(struct TBTreeNode p 2 struct TBTreeNode new)3 struct TBTreeNode q4 new-gtrightchild = p-gtrightchild5 new-gtrightthead = p-gtrightthread6 new-gtleftchild = p7 new-gtleftthread = 18 p-gtrightchild = new9 p-gtrightthead = 010 if (new-gtrightthread)11 q = Next(new)12 q-gtleftchild = new13 14

58  堆積bull 「堆積」 (heap) 是一個特殊的完備二元樹節點的資料與其子節點的資料之間有事先定好的大小關係存在bull 定義ndash最大堆疊為一完備二元樹任一節點的資料內容不小於其子節點的資料內容ndash最小堆疊為一完備二元樹任一節點的資料內容不大於其子節點的資料內容

581  插入節點至最大堆積中最大堆積是一完備二元樹所以如圖 (a) 之最大堆積於加入新節點後樹的形狀必須如圖 (b) 所示灰色節點即為新節點若加入的節點資料分別為 1 9 12 則其最大堆積將分別變成如圖 (c) (d) (e) 所示

程式 5-13 新增節點至最大堆積中最大堆積本身是一棵完備二元樹可以用陣列表示之 1 define maxsize 1002 int heap[maxsize]3 int n i4 void InsertHeap(int x)5 if (n == maxsize) HeapFull()6 else7 n++8 i = n9 while ((i gt 1) ampamp (x gt heap[i2]))10 heap[i] = heap[i2]11 i = 212 13 heap[i] = x14 16

程序 InsertHeap 在插入第 n 個資料時需要的時間複雜度為 O(logn)

1 100

2 93

3 84

4 77

5 65

6 81

7 79

8 66

9 70

10 50

100

84

77

93

8165

66 70

79

50

1

2 3

4 5 6 7

8 9 10

1 define maxsize 1002 int heap[maxsize]3 int n i4 void InsertHeap(int x)5 if (n == maxsize) HeapFull()6 else7 n++8 i = n9 while ((i gt 1) ampamp (x gt heap[i2]))10 heap[i] = heap[i2]11 i = 212 13 heap[i] = x14 16

582 刪除最大堆積中的最大資料圖 (a) 中的最大堆積在刪除最大資料後其樹形應變成如圖 (b) 所示將最未一個樹葉節點的資料 6 暫挪至樹根節點內如圖 (c) 再將圖 (c) 中非最大堆積的結構調整成為最大堆積 10 和 8 中選大者與 6對調即成圖 (d)

程式 5-14 刪除最大堆積中的最大資料(上接程式 5-13 )1 int DeleteHeap()2 int x i j3 if (n == 0) HeapEmpty()4 else5 x = heap[1]6 heap[1] = heap[n]7 n--8 i = 19 while (i lt= n2)10 if (heap[2i] gt heap[2i+1])11 j = 2i12 else13 j = 2i+114 if (heap[i]gtheap[j]) break15 swap(heap[i] heap[j])16 i = j17 18 return x19 20

其時間複雜度與其堆積高度相同是為O(logn) 其中 n 為堆積中的元素個數

1 100

2 93

3 84

4 77

5 65

6 81

7 79

8 66

9 70

10 50

100

84

77

93

8165

66 70

79

50

1

2 3

4 5 6 7

8 9 10

1 int DeletHeap()2 int x i j3 if(n == 0) HeapEmpty()4 else5 x = heap[1]6 heap[1] = heap[n]7 n--8 i = 19 while (i lt= n2)10 if ([heap[2i]] gt heap[2i+1])11 j = 2i12 else13 j = 2i+114 if (heap[i]gtheap[j]) break15 swap(heap[i] heap[j])16 i = j17 18 return x19 20

583  優先佇列bull 由於最大堆積(或最小堆積)的結構可提供最大元素(或最小元素)而其維護堆積的成本為 O(logn) n 為總元素個數

bull 堆積常被當成「優先佇列」 (priority queue) 使用mdash此時佇列被組織成完備二元樹若新加入佇列的資料有大小關係優先順序的需求時此優先佇列即以 O(logn) 的代價將新資料放入優先佇列(可能是最大或最小堆積依實際大小關係優先順序的需求而定)中mdash提供依元素的優先順序做為處理順序的服務

bull 一般的佇列為先進先出先到的先行處理優先佇列則是依據其優先順序優先順序高者先行處理與來到的先後無關

範例 5-21bull 在「分時」 (time sharing) 的作業系統中我們不希望使用中央處理器 (Central Processing Unit CPU) 時間短的「處理程序」 (process) 等候過久於是有「最短時間工作優先」 (shortest-job-first) 的排程策略此時等候的處理程序即可組織成之一最小堆積而 CPU 每次即取出存於樹根的處理程序予以處理任何新來的處理程序則以 O(logn) 的時間加入此優先佇列(最小堆積)中

bull 注意之前提過的線性佇列新來的直接加入成為新的尾端元素花O(1) 的時間然線性佇列本身沒有大小的關係每次要取出最大(小)的資料得花O(n) 的代價(在所有元素中挑出最大(小)者) n 為佇列內的元素個數而優先佇列是在新元素加入時要花O(logn) 的時間至於取出最大(小)則只須 O(1) 的時間

59  二元搜尋樹bull 二元搜尋樹是一棵二元樹可能是空二元樹若不為空二元樹則滿足下列性質ndash所有節點內的資料內容是相異的 ndash左子節點的資內容 ( 如果有 ) 比父節點的資料內容小 ndash右子節點的資料內容 ( 如果有 ) 比父節點的資料內容大 ndash以左和右子節點為樹根的左子樹和右子樹也是二元搜尋樹

在二元搜尋樹中搜尋資料二元搜尋樹對資料大小的掌握十分明確若恰與節點 p 資料相等則搜尋成功若欲搜尋的資料 x 比樹節點 p 中的資料小 則只須搜尋節點 p 的左子樹若比節點 p 中的資料大 則只須搜尋 p 的右子樹這樣的判斷可以遞迴地應用在各子樹節點 中

程式 5-15 在二元搜尋樹中搜尋資料(遞迴呼叫)1 struct BSTreeNode2 struct BSTreeNode leftchild3 int data4 struct BSTreeNode rightchild5 6 struct BSTreeNode root7 struct BSTreeNode SearchBST(

struct BSTreeNode tree int x)8 if (tree == NULL) return NULL9 if (x == tree-gtdata) return tree10 if (x lt tree-gtdata)11 return SearchBST(tree-gtleftchild x)

12 return SearchBST(tree-gtrightchild x)13

程式 5-16 搜尋二元搜尋樹的資料(非遞迴呼叫)在二元搜尋樹十分龐大時遞迴呼叫的效率不會太好可利用迴圈來完成搜尋的動作以提高執行效率請看程式 5-16

14 struct BSTreeNode Search BST_iterative15 (struct BSTreeNode node int x)16 while (node = NULL)17 if (x == node-gtdata) return node18 if (x lt node-gtdata)19 node = node-gtleftchild20 else21 node = node-gtrightchild22 23 return NULL24

592  新增資料於二元搜尋樹中欲新增資料進入一二元搜尋樹中得先決定該資料在二元搜尋樹中應出現的位置這可透過上節提到的搜尋動作來解決

在二元搜尋樹定義的性質 (1) 中我們限定了元素必須相異所以搜尋新資料的結果必是 NULL

必須將搜尋的結果改為新增資料所應在位置的節點指標

程式 5-17  在二元搜尋樹中新增資料25 int InsertBST(int x)26 struct BSTreeNode p q27 p = root q = NULL28 while (p = NULL)29 q = p30 if (x == p-gtdata) return 0

若不考慮重複則可省略之31 if (x lt p-gtdata)32 p = p-gtleftchild33 else34 p = p-gtrightchild35

3636 p = (struct BSTreeNode ) p = (struct BSTreeNode ) malloc (sizeof(BSTreeNode))malloc (sizeof(BSTreeNode))

3737 p-gtdata = x p-gtdata = x3838 p-gtleftchild = p-gtrightchild = p-gtleftchild = p-gtrightchild = NULLNULL3939 if (root == NULL) root = p if (root == NULL) root = p4040 else if (x lt q-gtdata) else if (x lt q-gtdata)4141 q-gtleftchild = p q-gtleftchild = p4242 else else4343 q-gtrightchild = p q-gtrightchild = p4444 return 1 return 145 45

程式 程式 5-175-17  在二元搜尋樹中新增資料  在二元搜尋樹中新增資料 (( 續續 ))

範例 5-23 圖 (b) 為圖 (a) 新增整數 6 之後的結果圖 (c) (d) (e) (f) 則分別是逐步再加入 28 18

10 21 的結果

bull若目前的二元搜尋樹高度為 h 則新增資料得花O(h) 的時間找到其所應在的位置遂新增資料至二元搜尋樹中的時間複雜之度為 O(h)

593 自二元搜尋樹中刪除資料欲自二元搜尋樹中刪除資料可能會面臨兩種情況(1) 刪除節點 p 位於樹葉節點上如下圖所示 這是比較容易處理的情況

(2) 刪除的節點 p 不是樹葉刪除節點 p 後原 p 所指的節點可由 q ( q 中包含了 p 左子樹中的最大節點值)或 r ( r 中包含了 p 右子樹中的最小節點值)來取代以維持二元搜尋樹中資料的大小關係其結果分別如圖 (b) 和 (c) 所示而圖 (d) 則標明了指標會改變的節點位置即 p 會消失 father 的左或右子樹指標會異動 k(可能是 q 或 r )會移到原來 p 的位置其父節點 f 的左或右子樹指標會異動其中 father 為 p 父節點 f 為 k 的父節點

程式 5-18 自二元搜尋樹中刪除資料46 int DeleteBST (int x)47 Struct BSTreeNode p father k f48 p = root father = NULL49 while (p = NULL)50 if (x == p-gtdata) 找到 x 所在的節點 p 了51 if ((p-gtleftchild == NULL) ampamp 52 (p-gtrightchild == NULL))53 k = NULL p為樹葉節點54 else if (p-gtleftchild = NULL )x位於非子節

點55 f = p 找出 p左子樹的最右樹葉56 k = p-gtleftchild57 while (k-gtrightchild =NULL)58 f = k59 k = k-gtrightchild60 k將挪至原 p處61 if (p == f)62 f-gtleftchild = k-gtleftchild 63 else64 f-gtrightchild = k-gtleftchild 65

66 else p無左子樹67 f = p 找出 p右子樹的最左樹葉68 k = p-gtrightchild69 while (k-gtleftchild = NULL)70 f = k71 k = k -gtleftchild72 73 if (p == f)74 f-gtrightchild = k-gtrightchild 75 else76 f-gtleftchild = k-gtrightchild77 78 if (k = NULL) k挪至原 p處繼承 p的左右指標79 k-gtleftchild = p-gtleftchild80 k-gtrightchild = p-gtrightchild81 82 if (father == NULL) root = k83 else if (x lt father-gtdata) 決定 k是 father的左

或右兒子84 father-gtleftchild = k85 else86 father-gtrightchild = k87 free(p)88 return 1 成功地刪除 x於此傳回 1返回呼叫處89 51~89行為找到 x時應有的處理步驟

90 else 尚未找到 x繼續往下一階層找

91 father = p92 if (x lt p-gtdata)93 p = p-gtleftchild94 else95 p = p-gtrightchild96 97 98 return 0 未找到 x傳回 099

510  樹林 (forest)

bull 定義樹林(森林)是由 n 棵沒有交集的樹所構成的集合 n 0

下圖為一包含三棵樹的樹林

5101 轉換樹林為二元樹 之前我們以二元樹來表示樹因為二元樹有較多

好用的性質在此我們也將樹林轉換成二元樹看待

在 522 節中我們嘗試了用左子右兄弟的方式來表示樹這個方法事實上已將樹轉換成二元樹

即用左子右兄弟的結構重新組織樹林中的每一棵樹這些樹的樹根將視為兄弟而最左邊的樹則視為樹林二元樹的樹根如此一來樹林即可轉換成二元樹

範例 5-25右下圖為右上圖三棵樹所對應的三棵左子右兄弟二元樹

下圖則為上圖樹林所對應的一棵樹林二元樹

演算法 5-1  轉換樹林成二元樹輸入樹林 F = T1 T2 Tn

輸出 F 的二元樹表示 BT=(T1 T2 Tn)1 if (n == 0) 輸出空二元樹 2 else

3 root(T1 T2 Tn) = T1

4 leftsubtree(T1 T2 Tn) =

BT(T11 T12 T1m)5 BT(T11 T12 T1m)為 T1的子樹6 rightsubtree(T1 T2 Tn) =

BT(T2 T3 Tn)

樹林轉換成二元樹的過程樹林轉換成二元樹的過程

  • Slide 1
  • Slide 2
  • Slide 3
  • Slide 4
  • Slide 5
  • Slide 6
  • Slide 7
  • Slide 8
  • Slide 9
  • Slide 10
  • Slide 11
  • Slide 12
  • Slide 13
  • Slide 14
  • Slide 15
  • Slide 16
  • Slide 17
  • Slide 18
  • Slide 19
  • Slide 20
  • Slide 21
  • Slide 22
  • Slide 23
  • Slide 24
  • Slide 25
  • Slide 26
  • Slide 27
  • Slide 28
  • Slide 29
  • Slide 30
  • Slide 31
  • Slide 32
  • Slide 33
  • Slide 34
  • Slide 35
  • Slide 36
  • Slide 37
  • Slide 38
  • Slide 39
  • Slide 40
  • Slide 41
  • Slide 42
  • Slide 43
  • Slide 44
  • Slide 45
  • Slide 46
  • Slide 47
  • Slide 48
  • Slide 49
  • Slide 50
  • Slide 51
  • Slide 52
  • Slide 53
  • Slide 54
  • Slide 55
  • Slide 56
  • Slide 57
  • Slide 58
  • Slide 59
  • Slide 60
  • Slide 61
  • Slide 62
  • Slide 63
  • Slide 64
  • Slide 65
  • Slide 66
  • Slide 67
  • Slide 68
  • Slide 69
  • Slide 70
  • Slide 71
  • Slide 72
  • Slide 73
  • Slide 74
  • Slide 75
  • Slide 76
  • Slide 77
  • Slide 78
  • Slide 79
  • Slide 80
  • Slide 81
  • Slide 82
  • Slide 83
  • Slide 84
  • Slide 85
  • Slide 86
  • Slide 87
  • Slide 88
  • Slide 89
  • Slide 90
  • Slide 91
  • Slide 92
  • Slide 93
  • Slide 94
  • Slide 95
  • Slide 96
  • Slide 97
  • Slide 98
  • Slide 99
  • Slide 100
  • Slide 101
  • Slide 102
  • Slide 103
Page 42: 第五章 樹

32 void inorder_Stack(struct BtreeNode node)33 do34 while (node = NULL)35 push_node(node)36 node = node-gtleftchild37 push all left children38 if (top = NULL)39 node = pop_node()40 cout ltlt node-gtdata41 node = node-gtrightchild42 inorder printing and check right child43 while ((top=NULL)||(node=NULL))44 top==NULL 堆疊已空 node==NULL沒有右子

6 struct BTreeNode root7 void inorder(struct BTreeNode node)8 if (node = NULL)9 inorder(node-gtleftchild)10 cout ltlt node-gtdata11 inorder(node-gtrightchild)12 13

中序表示為DBGEHAFIC

32 void inorder_Stack()33 struct BTreeNode node34 node = root35 do36 while (node = NULL)37 push_node(node)38 node = node-gtleftchild39 40 if (top = NULL)41 node = pop_node()42 cout ltlt node-gtdata43 node = node-gtrightchild44 45 while(top=NULL)||(node=NULL))46

45 void Preorder_Stack(struct BSTreeNode node)46 do47 while (node = NULL)48 cout ltlt node-gtdata)49 push_node(node)50 node = node-gtleftchild51 push all left children52 if (top = NULL)53 node = pop_node()54 node = node-gtrightchild55 56 while ((top=NULL)||(node=NULL))57

程式 5-5二元樹的前序走訪21 void preorder(struct BTreeNode node)22 if (node = NULL)23 cout ltlt node-gtdata24 preorder(node-gtleftchild)25 preorder(node-gtrightchild)26 27

58 void Postorder_Stack(struct BTreeNode node)59 do60 while (node = NULL)61 cout ltlt node-gtdata62 push_node(node)63 node = node-gtrightchild64 push all left children65 if (top = NULL)66 node = pop_node()67 node = node-gtleftchild68 69 while ((top=NULL)||(node=NULL))70

程式 5-4二元樹的後序走訪14 void postorder(struct BTreeNode node)15 if (node =NULL)16 postorder(node-gtleftchild)17 postorder(node-gtrightchild)18 cout ltlt node-gtdata19 20

555  階層走訪bull 「階層走訪」 (level-order traversal) 是依階層

的順序進行二元樹的走訪先走訪階層小的節點後走訪階層大的節點同一階層者則依自左向右的順序走訪

bull 對下圖的二元樹進行階層走訪的結果為ABCDEFGHI

bull 對同一階層而言先走訪的節點其子節點亦在下一階層中先被走訪

bull 這種「先進先出」的特性恰可用佇列予以儲存與處理

程式 5-7 利用佇列進行二元樹的階層走訪

1 struct BTreeNode2 struct BTreeNode leftchild3 char data4 struct BTreeNode rightchild5 6 struct BTreeNode root7 struct QNode8 struct BTreeNode treenode9 struct QNode next10 11 struct QNode front rear

12 void AddQueue(struct BTreeNode Tnode)13 struct QNode node14 node=(struct QNode )malloc(sizeof(struct QNode))15 node-gttreenode = Tnode16 node-gtnext = NULL17 if (front == NULL) front = node19 else rear-gtnext = node20 rear = node21

22 struct BTreeNode DeleteQueue()23 struct BTreeNode Tnode24 struct QNode old_front25 if (rear == NULL)26 QueueEmpty()27 else28 Tnode = front-gttreenode29 old_front = front30 front = front-gtnext31 free(old_front)32 return Tnode33 34 35 void LevelOrder (struct BTreeNode node)36 AddQueue(node)37 while (front =NULL)38 node = DeleteQueue()39 cout ltlt node-gtdata40 if (node-gtleftchild = NULL)41 AddQueue(node-gtleftchild)42 if (node-gtrightchild = NULL)43 AddQueue(node-gtrightchild)44 45

35 void LevelOrder (struct BTreeNode node)36 AddQueue(node)37 while (front =NULL)38 node = DeleteQueue()39 cout ltlt node-gtdata40 if (node-gtleftchild = NULL)41 AddQueue(node-gtleftchild)42 if (node-gtrightchild = NULL)43 AddQueue(node-gtrightchild)44 45

bull Infix ABCbull Postfix ABC CBA ACB BCA

BAC CAB

B

A CA

B

C

B

A

C

C

B

AB

A

C

二元樹中序與前序式中序與後序式bull 每一棵二元樹皆有唯一的一對中序與前序表示法也有唯一的中序與後序次序

bull 換句話說給予一對中序與前序或中序與後序即可決定一棵二元樹

bull 然而給予前序和後序次序並不能決定唯一的二元樹可能會產生兩棵不同的二元樹

bull 想想階層走訪和中序走訪順序可否決定唯一的二元樹

A

B C

H J D E

F G

prefix ABHJCDFGEinfix HBJAFDGCEpostfix HJBFGDECA

VLRLVRLRV

A

prefix BHJinfix HBJ

prefix CDFGEinfix FDGCE

prefix ABHJCDFGEinfix HBJAFDGCE

A

B C

H J D E

F G

prefix ABHJCDFGE

infix HBJAFDGCEk-1

struct BTreeNode BTree_InfixPrefix (AnsiString infix AnsiString prefix)

int k struct BTreeNode node if (infixLength==0) return NULLnode = (struct BTreeNode ) malloc (sizeof(struct BTreeNode))node-gtdata = prefix[1]k = find(prefix[1] infix)node-gtleftchild = BTree_InfixPrefix

(infix[1k-1] prefix[2k])node-gtrightchild = BTree_InfixPrefix(infix[k+1infixLength]prefix[k+1prefixLength])

VLR

LVRk1

k2 k+1~

k+1~

1

struct BTreeNode BTree_InfixPostfix (AnsiString infix AnsiString prefix)

int k struct BTreeNode node if (infixLength==0) return NULLnode = (struct BTreeNode ) malloc (sizeof(struct BTreeNode))node-gtdata = postfix[postfixLength]k = find(postfix[postfixLength] infix)node-gtleftchild = BTree_InfixPrefix

(infix[1k-1] postfix[1k-1])node-gtrightchild = BTree_InfixPrefix(infix[k+1infixLength]postfix[k+1postfixLength-1])

56  二元樹的複製與相等測試程式 5-8 二元樹的複製1 struct BTreeNode2 struct BTreeNode leftchild3 char data4 struct BTreeNode rightchild5 6 struct BTreeNode root7 struct BTreeNode Copy(struct BTreeNode TreeRoot)8 struct BTreeNode CopyRoot9 if (TreeRoot == NULL)10 return NULL11 CopyRoot =(struct BTreeNode ) malloc (sizeof(BTreeNode))12 CopyRoot-gtdata = TreeRoot-gtdata13 CopyRoot-gtleftchild = Copy(TreeRoot-gtleftchild)14 CopyRoot-gtrightchild = Copy(TreeRoot-

gtrightchild)15 return CopyRoot16

程式 5-9 檢測兩棵二元樹是否相等1 struct BTreeNode2 struct BTreeNode leftchild3 char data4 struct BTreeNode rightchild5 6 struct BtreeNode rightchild7 int Equal(struct BTreeNode ustruct BTreeNode v)8 if ((u==NULL) ampamp (v==NULL)) return 19 if ((u=NULL) ampamp (v=NULL)10 ampamp (u-gtdata == v-gtdata )11 ampamp Equal(u-gtleftchildv-gtleftchild)12 ampamp Equal(u-gtrightchildv-gtrightchild))

13 return 114 return 015

Counting Nodes in a Binary Tree

int CountingNodes(struct BTreeNodes node)

if (node == NULL) return 0 int leftchildren = CountingNodes(node-gtleftchild)

int rightchildren= CountingNodes(node-gtrightchild)

return leftchildren+rightchildren

Counting Leaves in a Binary Tree

int CountingLeaves(struct BTreeNodes node)

if (node == NULL) return 0 int leftleaves = CountingLeaves(node-gtleftchild)

int rightchildren= CountingLeaves(node-gtrightchild)

if (leftleaves==0 ampamp leftleaves==0) return 1

return leftleaves + leftleaves

57  引線二元樹bull 在二元樹的鍵結表示中樹葉節點的兩個子樹指標皆指向 NULL 由定理 5-1 得知 n = E+1 其中 n 為節點個數 E 為分支數

bull 而 n 個鍵結節點有 2n 個指標空間每個分支恰佔用一個指標空間共有 E (=n-1) 個指標有用到(不是空的)而共有 n+1 個指標空間是 NULL 比非空的節點還多

bull 有學者提出對這些「放空指標的空間」加以利用的概念mdash與其放空指標不如放指向其它節點的指標稱之為引線 (thread) 使得某些運算(如走訪hellip等)可以加快

bull 這個概念發展出了引線二元樹 (threaded binary tree) 這種資料結構

範例 5-17下圖為加了引線的二元樹

加入的引線將使二元樹的中序走訪更加便利沿著上右圖箭頭所指示的順序走訪即可完成此二元樹的中序走訪GDHBAIECF 為此二元樹的中序表示

bull 範例 5-17 中所加的引線目的在方便二元樹的中序走訪

bull 對樹葉節點 p 而言令其資料為 P 則引線指標的規則如下

(1) p 的左子樹指標指向中序表示中 P 的前一個 元素(前接元素 (predecessor) )

(2) p 的右子樹指標指向中序表示中 P 的後一個 元素(後繼元素 (successor) )

為了區別節點指標放的是一般指標(內部節點)抑或是引線指標(樹葉節點)我們另以 2 個欄位分別區別左和右子樹指標空間存放的對象其節點的記憶體配置有如下圖所示

其中 leftthread (rightthread) 為 0 時表示 leftchild (rightchild) 存放的是一般節點指標而 leftthread (rightthread) 為 1 時表示 leftchild (rightchild) 存放的是引線指標1 struct TBTreeNode2 int leftthread3 struct TBTreeNode leftchild4 char data5 struct TBTreeNode rightchild6 int rightthread7

由右側的引線二元樹可知有最左和最右兩條引線尚無妥善的安排我們另設計一空的引線節點做為樹根如下圖所示

571  引線二元樹的中序走訪決定節點 p 在中序表示中的後繼節點 q (簡稱中序後繼點)

程式 5-10決定節點 node的中序後繼點1 struct TBTreeNode2 int leftthread3 struct TBTreeNode leftchild4 char data5 struct TBTreeNode rightchild6 int rightthread7 8 struct TBTreeNode root9 struct TBTreeNode Next(struct TBTreeNode node)

10 struct TBTreeNode temp11 temp = node-gtrightchild12 if (node-gtrightthead) return temp13 while (temp-gtleftthead) temp=temp-gtleftchild

14 return temp15

程式 5-11引線二元樹的中序表示(接程式 5-10)1 void InorderTBTree()2 struct TBTreeNode node3 for(node=Next(root)node=rootnode=Next(node))

4 cout ltlt node-gtdata5

bull只要 node 是引線二元樹上的任一節點透過呼叫 Next(node) 即可找到其在中序表示中的後繼節點bull(試想若程式 5-10 中的程序 Next 所傳入的參數指標正是 root Next(root) 是否可正常運作請思考)bull於是只須從中序表示中的第一節點起讓每一節點 node 都執行 Next(node) 程序即可得引線二元樹的中序表示

572  在引線二元樹中加入節點在引線二元樹中樹葉節點節點的右子樹指標所指向或內部節點右子樹的最左樹葉為其中序後繼節點而且任一節點若無左子樹則其左子樹指標乃指向其中序前接節點hellip這些性質必須在節點新增進入引線二元樹時也要保持接下來討論節點 new 插入成為引線二元樹的節點 p 的右子節點的情形至於插入成為左子節點的情形則讓各位模擬推敲

572572  在引線二元樹中加入節點 在引線二元樹中加入節點 (( 續續 ))

newnew 會成為會成為 pp 的右子節點的情況共有以下兩種的右子節點的情況共有以下兩種(1) (1) 若若 pp 沒有右子節點 沒有右子節點 ((p-gtrightthread p-gtrightthread 為為1) 1) 如圖如圖 ((a) a) 則 則 new new 插入後應形成如圖插入後應形成如圖 ((b) b) 所示所示

(2)(2) 若若 pp 有右子節點 有右子節點 ((p-gtrightthread p-gtrightthread 為為0) 0) 如圖如圖 ((c) c) 則 則 new new 插入後應形成如插入後應形成如圖圖 ((d) d) 所示所示

程式 5-12  插入節點 new 成為引線二元樹中節點 p的右子節點(上接程式 5-10 5-11 )

1 void InsertRight(struct TBTreeNode p 2 struct TBTreeNode new)3 struct TBTreeNode q4 new-gtrightchild = p-gtrightchild5 new-gtrightthead = p-gtrightthread6 new-gtleftchild = p7 new-gtleftthread = 18 p-gtrightchild = new9 p-gtrightthead = 010 if (new-gtrightthread)11 q = Next(new)12 q-gtleftchild = new13 14

58  堆積bull 「堆積」 (heap) 是一個特殊的完備二元樹節點的資料與其子節點的資料之間有事先定好的大小關係存在bull 定義ndash最大堆疊為一完備二元樹任一節點的資料內容不小於其子節點的資料內容ndash最小堆疊為一完備二元樹任一節點的資料內容不大於其子節點的資料內容

581  插入節點至最大堆積中最大堆積是一完備二元樹所以如圖 (a) 之最大堆積於加入新節點後樹的形狀必須如圖 (b) 所示灰色節點即為新節點若加入的節點資料分別為 1 9 12 則其最大堆積將分別變成如圖 (c) (d) (e) 所示

程式 5-13 新增節點至最大堆積中最大堆積本身是一棵完備二元樹可以用陣列表示之 1 define maxsize 1002 int heap[maxsize]3 int n i4 void InsertHeap(int x)5 if (n == maxsize) HeapFull()6 else7 n++8 i = n9 while ((i gt 1) ampamp (x gt heap[i2]))10 heap[i] = heap[i2]11 i = 212 13 heap[i] = x14 16

程序 InsertHeap 在插入第 n 個資料時需要的時間複雜度為 O(logn)

1 100

2 93

3 84

4 77

5 65

6 81

7 79

8 66

9 70

10 50

100

84

77

93

8165

66 70

79

50

1

2 3

4 5 6 7

8 9 10

1 define maxsize 1002 int heap[maxsize]3 int n i4 void InsertHeap(int x)5 if (n == maxsize) HeapFull()6 else7 n++8 i = n9 while ((i gt 1) ampamp (x gt heap[i2]))10 heap[i] = heap[i2]11 i = 212 13 heap[i] = x14 16

582 刪除最大堆積中的最大資料圖 (a) 中的最大堆積在刪除最大資料後其樹形應變成如圖 (b) 所示將最未一個樹葉節點的資料 6 暫挪至樹根節點內如圖 (c) 再將圖 (c) 中非最大堆積的結構調整成為最大堆積 10 和 8 中選大者與 6對調即成圖 (d)

程式 5-14 刪除最大堆積中的最大資料(上接程式 5-13 )1 int DeleteHeap()2 int x i j3 if (n == 0) HeapEmpty()4 else5 x = heap[1]6 heap[1] = heap[n]7 n--8 i = 19 while (i lt= n2)10 if (heap[2i] gt heap[2i+1])11 j = 2i12 else13 j = 2i+114 if (heap[i]gtheap[j]) break15 swap(heap[i] heap[j])16 i = j17 18 return x19 20

其時間複雜度與其堆積高度相同是為O(logn) 其中 n 為堆積中的元素個數

1 100

2 93

3 84

4 77

5 65

6 81

7 79

8 66

9 70

10 50

100

84

77

93

8165

66 70

79

50

1

2 3

4 5 6 7

8 9 10

1 int DeletHeap()2 int x i j3 if(n == 0) HeapEmpty()4 else5 x = heap[1]6 heap[1] = heap[n]7 n--8 i = 19 while (i lt= n2)10 if ([heap[2i]] gt heap[2i+1])11 j = 2i12 else13 j = 2i+114 if (heap[i]gtheap[j]) break15 swap(heap[i] heap[j])16 i = j17 18 return x19 20

583  優先佇列bull 由於最大堆積(或最小堆積)的結構可提供最大元素(或最小元素)而其維護堆積的成本為 O(logn) n 為總元素個數

bull 堆積常被當成「優先佇列」 (priority queue) 使用mdash此時佇列被組織成完備二元樹若新加入佇列的資料有大小關係優先順序的需求時此優先佇列即以 O(logn) 的代價將新資料放入優先佇列(可能是最大或最小堆積依實際大小關係優先順序的需求而定)中mdash提供依元素的優先順序做為處理順序的服務

bull 一般的佇列為先進先出先到的先行處理優先佇列則是依據其優先順序優先順序高者先行處理與來到的先後無關

範例 5-21bull 在「分時」 (time sharing) 的作業系統中我們不希望使用中央處理器 (Central Processing Unit CPU) 時間短的「處理程序」 (process) 等候過久於是有「最短時間工作優先」 (shortest-job-first) 的排程策略此時等候的處理程序即可組織成之一最小堆積而 CPU 每次即取出存於樹根的處理程序予以處理任何新來的處理程序則以 O(logn) 的時間加入此優先佇列(最小堆積)中

bull 注意之前提過的線性佇列新來的直接加入成為新的尾端元素花O(1) 的時間然線性佇列本身沒有大小的關係每次要取出最大(小)的資料得花O(n) 的代價(在所有元素中挑出最大(小)者) n 為佇列內的元素個數而優先佇列是在新元素加入時要花O(logn) 的時間至於取出最大(小)則只須 O(1) 的時間

59  二元搜尋樹bull 二元搜尋樹是一棵二元樹可能是空二元樹若不為空二元樹則滿足下列性質ndash所有節點內的資料內容是相異的 ndash左子節點的資內容 ( 如果有 ) 比父節點的資料內容小 ndash右子節點的資料內容 ( 如果有 ) 比父節點的資料內容大 ndash以左和右子節點為樹根的左子樹和右子樹也是二元搜尋樹

在二元搜尋樹中搜尋資料二元搜尋樹對資料大小的掌握十分明確若恰與節點 p 資料相等則搜尋成功若欲搜尋的資料 x 比樹節點 p 中的資料小 則只須搜尋節點 p 的左子樹若比節點 p 中的資料大 則只須搜尋 p 的右子樹這樣的判斷可以遞迴地應用在各子樹節點 中

程式 5-15 在二元搜尋樹中搜尋資料(遞迴呼叫)1 struct BSTreeNode2 struct BSTreeNode leftchild3 int data4 struct BSTreeNode rightchild5 6 struct BSTreeNode root7 struct BSTreeNode SearchBST(

struct BSTreeNode tree int x)8 if (tree == NULL) return NULL9 if (x == tree-gtdata) return tree10 if (x lt tree-gtdata)11 return SearchBST(tree-gtleftchild x)

12 return SearchBST(tree-gtrightchild x)13

程式 5-16 搜尋二元搜尋樹的資料(非遞迴呼叫)在二元搜尋樹十分龐大時遞迴呼叫的效率不會太好可利用迴圈來完成搜尋的動作以提高執行效率請看程式 5-16

14 struct BSTreeNode Search BST_iterative15 (struct BSTreeNode node int x)16 while (node = NULL)17 if (x == node-gtdata) return node18 if (x lt node-gtdata)19 node = node-gtleftchild20 else21 node = node-gtrightchild22 23 return NULL24

592  新增資料於二元搜尋樹中欲新增資料進入一二元搜尋樹中得先決定該資料在二元搜尋樹中應出現的位置這可透過上節提到的搜尋動作來解決

在二元搜尋樹定義的性質 (1) 中我們限定了元素必須相異所以搜尋新資料的結果必是 NULL

必須將搜尋的結果改為新增資料所應在位置的節點指標

程式 5-17  在二元搜尋樹中新增資料25 int InsertBST(int x)26 struct BSTreeNode p q27 p = root q = NULL28 while (p = NULL)29 q = p30 if (x == p-gtdata) return 0

若不考慮重複則可省略之31 if (x lt p-gtdata)32 p = p-gtleftchild33 else34 p = p-gtrightchild35

3636 p = (struct BSTreeNode ) p = (struct BSTreeNode ) malloc (sizeof(BSTreeNode))malloc (sizeof(BSTreeNode))

3737 p-gtdata = x p-gtdata = x3838 p-gtleftchild = p-gtrightchild = p-gtleftchild = p-gtrightchild = NULLNULL3939 if (root == NULL) root = p if (root == NULL) root = p4040 else if (x lt q-gtdata) else if (x lt q-gtdata)4141 q-gtleftchild = p q-gtleftchild = p4242 else else4343 q-gtrightchild = p q-gtrightchild = p4444 return 1 return 145 45

程式 程式 5-175-17  在二元搜尋樹中新增資料  在二元搜尋樹中新增資料 (( 續續 ))

範例 5-23 圖 (b) 為圖 (a) 新增整數 6 之後的結果圖 (c) (d) (e) (f) 則分別是逐步再加入 28 18

10 21 的結果

bull若目前的二元搜尋樹高度為 h 則新增資料得花O(h) 的時間找到其所應在的位置遂新增資料至二元搜尋樹中的時間複雜之度為 O(h)

593 自二元搜尋樹中刪除資料欲自二元搜尋樹中刪除資料可能會面臨兩種情況(1) 刪除節點 p 位於樹葉節點上如下圖所示 這是比較容易處理的情況

(2) 刪除的節點 p 不是樹葉刪除節點 p 後原 p 所指的節點可由 q ( q 中包含了 p 左子樹中的最大節點值)或 r ( r 中包含了 p 右子樹中的最小節點值)來取代以維持二元搜尋樹中資料的大小關係其結果分別如圖 (b) 和 (c) 所示而圖 (d) 則標明了指標會改變的節點位置即 p 會消失 father 的左或右子樹指標會異動 k(可能是 q 或 r )會移到原來 p 的位置其父節點 f 的左或右子樹指標會異動其中 father 為 p 父節點 f 為 k 的父節點

程式 5-18 自二元搜尋樹中刪除資料46 int DeleteBST (int x)47 Struct BSTreeNode p father k f48 p = root father = NULL49 while (p = NULL)50 if (x == p-gtdata) 找到 x 所在的節點 p 了51 if ((p-gtleftchild == NULL) ampamp 52 (p-gtrightchild == NULL))53 k = NULL p為樹葉節點54 else if (p-gtleftchild = NULL )x位於非子節

點55 f = p 找出 p左子樹的最右樹葉56 k = p-gtleftchild57 while (k-gtrightchild =NULL)58 f = k59 k = k-gtrightchild60 k將挪至原 p處61 if (p == f)62 f-gtleftchild = k-gtleftchild 63 else64 f-gtrightchild = k-gtleftchild 65

66 else p無左子樹67 f = p 找出 p右子樹的最左樹葉68 k = p-gtrightchild69 while (k-gtleftchild = NULL)70 f = k71 k = k -gtleftchild72 73 if (p == f)74 f-gtrightchild = k-gtrightchild 75 else76 f-gtleftchild = k-gtrightchild77 78 if (k = NULL) k挪至原 p處繼承 p的左右指標79 k-gtleftchild = p-gtleftchild80 k-gtrightchild = p-gtrightchild81 82 if (father == NULL) root = k83 else if (x lt father-gtdata) 決定 k是 father的左

或右兒子84 father-gtleftchild = k85 else86 father-gtrightchild = k87 free(p)88 return 1 成功地刪除 x於此傳回 1返回呼叫處89 51~89行為找到 x時應有的處理步驟

90 else 尚未找到 x繼續往下一階層找

91 father = p92 if (x lt p-gtdata)93 p = p-gtleftchild94 else95 p = p-gtrightchild96 97 98 return 0 未找到 x傳回 099

510  樹林 (forest)

bull 定義樹林(森林)是由 n 棵沒有交集的樹所構成的集合 n 0

下圖為一包含三棵樹的樹林

5101 轉換樹林為二元樹 之前我們以二元樹來表示樹因為二元樹有較多

好用的性質在此我們也將樹林轉換成二元樹看待

在 522 節中我們嘗試了用左子右兄弟的方式來表示樹這個方法事實上已將樹轉換成二元樹

即用左子右兄弟的結構重新組織樹林中的每一棵樹這些樹的樹根將視為兄弟而最左邊的樹則視為樹林二元樹的樹根如此一來樹林即可轉換成二元樹

範例 5-25右下圖為右上圖三棵樹所對應的三棵左子右兄弟二元樹

下圖則為上圖樹林所對應的一棵樹林二元樹

演算法 5-1  轉換樹林成二元樹輸入樹林 F = T1 T2 Tn

輸出 F 的二元樹表示 BT=(T1 T2 Tn)1 if (n == 0) 輸出空二元樹 2 else

3 root(T1 T2 Tn) = T1

4 leftsubtree(T1 T2 Tn) =

BT(T11 T12 T1m)5 BT(T11 T12 T1m)為 T1的子樹6 rightsubtree(T1 T2 Tn) =

BT(T2 T3 Tn)

樹林轉換成二元樹的過程樹林轉換成二元樹的過程

  • Slide 1
  • Slide 2
  • Slide 3
  • Slide 4
  • Slide 5
  • Slide 6
  • Slide 7
  • Slide 8
  • Slide 9
  • Slide 10
  • Slide 11
  • Slide 12
  • Slide 13
  • Slide 14
  • Slide 15
  • Slide 16
  • Slide 17
  • Slide 18
  • Slide 19
  • Slide 20
  • Slide 21
  • Slide 22
  • Slide 23
  • Slide 24
  • Slide 25
  • Slide 26
  • Slide 27
  • Slide 28
  • Slide 29
  • Slide 30
  • Slide 31
  • Slide 32
  • Slide 33
  • Slide 34
  • Slide 35
  • Slide 36
  • Slide 37
  • Slide 38
  • Slide 39
  • Slide 40
  • Slide 41
  • Slide 42
  • Slide 43
  • Slide 44
  • Slide 45
  • Slide 46
  • Slide 47
  • Slide 48
  • Slide 49
  • Slide 50
  • Slide 51
  • Slide 52
  • Slide 53
  • Slide 54
  • Slide 55
  • Slide 56
  • Slide 57
  • Slide 58
  • Slide 59
  • Slide 60
  • Slide 61
  • Slide 62
  • Slide 63
  • Slide 64
  • Slide 65
  • Slide 66
  • Slide 67
  • Slide 68
  • Slide 69
  • Slide 70
  • Slide 71
  • Slide 72
  • Slide 73
  • Slide 74
  • Slide 75
  • Slide 76
  • Slide 77
  • Slide 78
  • Slide 79
  • Slide 80
  • Slide 81
  • Slide 82
  • Slide 83
  • Slide 84
  • Slide 85
  • Slide 86
  • Slide 87
  • Slide 88
  • Slide 89
  • Slide 90
  • Slide 91
  • Slide 92
  • Slide 93
  • Slide 94
  • Slide 95
  • Slide 96
  • Slide 97
  • Slide 98
  • Slide 99
  • Slide 100
  • Slide 101
  • Slide 102
  • Slide 103
Page 43: 第五章 樹

中序表示為DBGEHAFIC

32 void inorder_Stack()33 struct BTreeNode node34 node = root35 do36 while (node = NULL)37 push_node(node)38 node = node-gtleftchild39 40 if (top = NULL)41 node = pop_node()42 cout ltlt node-gtdata43 node = node-gtrightchild44 45 while(top=NULL)||(node=NULL))46

45 void Preorder_Stack(struct BSTreeNode node)46 do47 while (node = NULL)48 cout ltlt node-gtdata)49 push_node(node)50 node = node-gtleftchild51 push all left children52 if (top = NULL)53 node = pop_node()54 node = node-gtrightchild55 56 while ((top=NULL)||(node=NULL))57

程式 5-5二元樹的前序走訪21 void preorder(struct BTreeNode node)22 if (node = NULL)23 cout ltlt node-gtdata24 preorder(node-gtleftchild)25 preorder(node-gtrightchild)26 27

58 void Postorder_Stack(struct BTreeNode node)59 do60 while (node = NULL)61 cout ltlt node-gtdata62 push_node(node)63 node = node-gtrightchild64 push all left children65 if (top = NULL)66 node = pop_node()67 node = node-gtleftchild68 69 while ((top=NULL)||(node=NULL))70

程式 5-4二元樹的後序走訪14 void postorder(struct BTreeNode node)15 if (node =NULL)16 postorder(node-gtleftchild)17 postorder(node-gtrightchild)18 cout ltlt node-gtdata19 20

555  階層走訪bull 「階層走訪」 (level-order traversal) 是依階層

的順序進行二元樹的走訪先走訪階層小的節點後走訪階層大的節點同一階層者則依自左向右的順序走訪

bull 對下圖的二元樹進行階層走訪的結果為ABCDEFGHI

bull 對同一階層而言先走訪的節點其子節點亦在下一階層中先被走訪

bull 這種「先進先出」的特性恰可用佇列予以儲存與處理

程式 5-7 利用佇列進行二元樹的階層走訪

1 struct BTreeNode2 struct BTreeNode leftchild3 char data4 struct BTreeNode rightchild5 6 struct BTreeNode root7 struct QNode8 struct BTreeNode treenode9 struct QNode next10 11 struct QNode front rear

12 void AddQueue(struct BTreeNode Tnode)13 struct QNode node14 node=(struct QNode )malloc(sizeof(struct QNode))15 node-gttreenode = Tnode16 node-gtnext = NULL17 if (front == NULL) front = node19 else rear-gtnext = node20 rear = node21

22 struct BTreeNode DeleteQueue()23 struct BTreeNode Tnode24 struct QNode old_front25 if (rear == NULL)26 QueueEmpty()27 else28 Tnode = front-gttreenode29 old_front = front30 front = front-gtnext31 free(old_front)32 return Tnode33 34 35 void LevelOrder (struct BTreeNode node)36 AddQueue(node)37 while (front =NULL)38 node = DeleteQueue()39 cout ltlt node-gtdata40 if (node-gtleftchild = NULL)41 AddQueue(node-gtleftchild)42 if (node-gtrightchild = NULL)43 AddQueue(node-gtrightchild)44 45

35 void LevelOrder (struct BTreeNode node)36 AddQueue(node)37 while (front =NULL)38 node = DeleteQueue()39 cout ltlt node-gtdata40 if (node-gtleftchild = NULL)41 AddQueue(node-gtleftchild)42 if (node-gtrightchild = NULL)43 AddQueue(node-gtrightchild)44 45

bull Infix ABCbull Postfix ABC CBA ACB BCA

BAC CAB

B

A CA

B

C

B

A

C

C

B

AB

A

C

二元樹中序與前序式中序與後序式bull 每一棵二元樹皆有唯一的一對中序與前序表示法也有唯一的中序與後序次序

bull 換句話說給予一對中序與前序或中序與後序即可決定一棵二元樹

bull 然而給予前序和後序次序並不能決定唯一的二元樹可能會產生兩棵不同的二元樹

bull 想想階層走訪和中序走訪順序可否決定唯一的二元樹

A

B C

H J D E

F G

prefix ABHJCDFGEinfix HBJAFDGCEpostfix HJBFGDECA

VLRLVRLRV

A

prefix BHJinfix HBJ

prefix CDFGEinfix FDGCE

prefix ABHJCDFGEinfix HBJAFDGCE

A

B C

H J D E

F G

prefix ABHJCDFGE

infix HBJAFDGCEk-1

struct BTreeNode BTree_InfixPrefix (AnsiString infix AnsiString prefix)

int k struct BTreeNode node if (infixLength==0) return NULLnode = (struct BTreeNode ) malloc (sizeof(struct BTreeNode))node-gtdata = prefix[1]k = find(prefix[1] infix)node-gtleftchild = BTree_InfixPrefix

(infix[1k-1] prefix[2k])node-gtrightchild = BTree_InfixPrefix(infix[k+1infixLength]prefix[k+1prefixLength])

VLR

LVRk1

k2 k+1~

k+1~

1

struct BTreeNode BTree_InfixPostfix (AnsiString infix AnsiString prefix)

int k struct BTreeNode node if (infixLength==0) return NULLnode = (struct BTreeNode ) malloc (sizeof(struct BTreeNode))node-gtdata = postfix[postfixLength]k = find(postfix[postfixLength] infix)node-gtleftchild = BTree_InfixPrefix

(infix[1k-1] postfix[1k-1])node-gtrightchild = BTree_InfixPrefix(infix[k+1infixLength]postfix[k+1postfixLength-1])

56  二元樹的複製與相等測試程式 5-8 二元樹的複製1 struct BTreeNode2 struct BTreeNode leftchild3 char data4 struct BTreeNode rightchild5 6 struct BTreeNode root7 struct BTreeNode Copy(struct BTreeNode TreeRoot)8 struct BTreeNode CopyRoot9 if (TreeRoot == NULL)10 return NULL11 CopyRoot =(struct BTreeNode ) malloc (sizeof(BTreeNode))12 CopyRoot-gtdata = TreeRoot-gtdata13 CopyRoot-gtleftchild = Copy(TreeRoot-gtleftchild)14 CopyRoot-gtrightchild = Copy(TreeRoot-

gtrightchild)15 return CopyRoot16

程式 5-9 檢測兩棵二元樹是否相等1 struct BTreeNode2 struct BTreeNode leftchild3 char data4 struct BTreeNode rightchild5 6 struct BtreeNode rightchild7 int Equal(struct BTreeNode ustruct BTreeNode v)8 if ((u==NULL) ampamp (v==NULL)) return 19 if ((u=NULL) ampamp (v=NULL)10 ampamp (u-gtdata == v-gtdata )11 ampamp Equal(u-gtleftchildv-gtleftchild)12 ampamp Equal(u-gtrightchildv-gtrightchild))

13 return 114 return 015

Counting Nodes in a Binary Tree

int CountingNodes(struct BTreeNodes node)

if (node == NULL) return 0 int leftchildren = CountingNodes(node-gtleftchild)

int rightchildren= CountingNodes(node-gtrightchild)

return leftchildren+rightchildren

Counting Leaves in a Binary Tree

int CountingLeaves(struct BTreeNodes node)

if (node == NULL) return 0 int leftleaves = CountingLeaves(node-gtleftchild)

int rightchildren= CountingLeaves(node-gtrightchild)

if (leftleaves==0 ampamp leftleaves==0) return 1

return leftleaves + leftleaves

57  引線二元樹bull 在二元樹的鍵結表示中樹葉節點的兩個子樹指標皆指向 NULL 由定理 5-1 得知 n = E+1 其中 n 為節點個數 E 為分支數

bull 而 n 個鍵結節點有 2n 個指標空間每個分支恰佔用一個指標空間共有 E (=n-1) 個指標有用到(不是空的)而共有 n+1 個指標空間是 NULL 比非空的節點還多

bull 有學者提出對這些「放空指標的空間」加以利用的概念mdash與其放空指標不如放指向其它節點的指標稱之為引線 (thread) 使得某些運算(如走訪hellip等)可以加快

bull 這個概念發展出了引線二元樹 (threaded binary tree) 這種資料結構

範例 5-17下圖為加了引線的二元樹

加入的引線將使二元樹的中序走訪更加便利沿著上右圖箭頭所指示的順序走訪即可完成此二元樹的中序走訪GDHBAIECF 為此二元樹的中序表示

bull 範例 5-17 中所加的引線目的在方便二元樹的中序走訪

bull 對樹葉節點 p 而言令其資料為 P 則引線指標的規則如下

(1) p 的左子樹指標指向中序表示中 P 的前一個 元素(前接元素 (predecessor) )

(2) p 的右子樹指標指向中序表示中 P 的後一個 元素(後繼元素 (successor) )

為了區別節點指標放的是一般指標(內部節點)抑或是引線指標(樹葉節點)我們另以 2 個欄位分別區別左和右子樹指標空間存放的對象其節點的記憶體配置有如下圖所示

其中 leftthread (rightthread) 為 0 時表示 leftchild (rightchild) 存放的是一般節點指標而 leftthread (rightthread) 為 1 時表示 leftchild (rightchild) 存放的是引線指標1 struct TBTreeNode2 int leftthread3 struct TBTreeNode leftchild4 char data5 struct TBTreeNode rightchild6 int rightthread7

由右側的引線二元樹可知有最左和最右兩條引線尚無妥善的安排我們另設計一空的引線節點做為樹根如下圖所示

571  引線二元樹的中序走訪決定節點 p 在中序表示中的後繼節點 q (簡稱中序後繼點)

程式 5-10決定節點 node的中序後繼點1 struct TBTreeNode2 int leftthread3 struct TBTreeNode leftchild4 char data5 struct TBTreeNode rightchild6 int rightthread7 8 struct TBTreeNode root9 struct TBTreeNode Next(struct TBTreeNode node)

10 struct TBTreeNode temp11 temp = node-gtrightchild12 if (node-gtrightthead) return temp13 while (temp-gtleftthead) temp=temp-gtleftchild

14 return temp15

程式 5-11引線二元樹的中序表示(接程式 5-10)1 void InorderTBTree()2 struct TBTreeNode node3 for(node=Next(root)node=rootnode=Next(node))

4 cout ltlt node-gtdata5

bull只要 node 是引線二元樹上的任一節點透過呼叫 Next(node) 即可找到其在中序表示中的後繼節點bull(試想若程式 5-10 中的程序 Next 所傳入的參數指標正是 root Next(root) 是否可正常運作請思考)bull於是只須從中序表示中的第一節點起讓每一節點 node 都執行 Next(node) 程序即可得引線二元樹的中序表示

572  在引線二元樹中加入節點在引線二元樹中樹葉節點節點的右子樹指標所指向或內部節點右子樹的最左樹葉為其中序後繼節點而且任一節點若無左子樹則其左子樹指標乃指向其中序前接節點hellip這些性質必須在節點新增進入引線二元樹時也要保持接下來討論節點 new 插入成為引線二元樹的節點 p 的右子節點的情形至於插入成為左子節點的情形則讓各位模擬推敲

572572  在引線二元樹中加入節點 在引線二元樹中加入節點 (( 續續 ))

newnew 會成為會成為 pp 的右子節點的情況共有以下兩種的右子節點的情況共有以下兩種(1) (1) 若若 pp 沒有右子節點 沒有右子節點 ((p-gtrightthread p-gtrightthread 為為1) 1) 如圖如圖 ((a) a) 則 則 new new 插入後應形成如圖插入後應形成如圖 ((b) b) 所示所示

(2)(2) 若若 pp 有右子節點 有右子節點 ((p-gtrightthread p-gtrightthread 為為0) 0) 如圖如圖 ((c) c) 則 則 new new 插入後應形成如插入後應形成如圖圖 ((d) d) 所示所示

程式 5-12  插入節點 new 成為引線二元樹中節點 p的右子節點(上接程式 5-10 5-11 )

1 void InsertRight(struct TBTreeNode p 2 struct TBTreeNode new)3 struct TBTreeNode q4 new-gtrightchild = p-gtrightchild5 new-gtrightthead = p-gtrightthread6 new-gtleftchild = p7 new-gtleftthread = 18 p-gtrightchild = new9 p-gtrightthead = 010 if (new-gtrightthread)11 q = Next(new)12 q-gtleftchild = new13 14

58  堆積bull 「堆積」 (heap) 是一個特殊的完備二元樹節點的資料與其子節點的資料之間有事先定好的大小關係存在bull 定義ndash最大堆疊為一完備二元樹任一節點的資料內容不小於其子節點的資料內容ndash最小堆疊為一完備二元樹任一節點的資料內容不大於其子節點的資料內容

581  插入節點至最大堆積中最大堆積是一完備二元樹所以如圖 (a) 之最大堆積於加入新節點後樹的形狀必須如圖 (b) 所示灰色節點即為新節點若加入的節點資料分別為 1 9 12 則其最大堆積將分別變成如圖 (c) (d) (e) 所示

程式 5-13 新增節點至最大堆積中最大堆積本身是一棵完備二元樹可以用陣列表示之 1 define maxsize 1002 int heap[maxsize]3 int n i4 void InsertHeap(int x)5 if (n == maxsize) HeapFull()6 else7 n++8 i = n9 while ((i gt 1) ampamp (x gt heap[i2]))10 heap[i] = heap[i2]11 i = 212 13 heap[i] = x14 16

程序 InsertHeap 在插入第 n 個資料時需要的時間複雜度為 O(logn)

1 100

2 93

3 84

4 77

5 65

6 81

7 79

8 66

9 70

10 50

100

84

77

93

8165

66 70

79

50

1

2 3

4 5 6 7

8 9 10

1 define maxsize 1002 int heap[maxsize]3 int n i4 void InsertHeap(int x)5 if (n == maxsize) HeapFull()6 else7 n++8 i = n9 while ((i gt 1) ampamp (x gt heap[i2]))10 heap[i] = heap[i2]11 i = 212 13 heap[i] = x14 16

582 刪除最大堆積中的最大資料圖 (a) 中的最大堆積在刪除最大資料後其樹形應變成如圖 (b) 所示將最未一個樹葉節點的資料 6 暫挪至樹根節點內如圖 (c) 再將圖 (c) 中非最大堆積的結構調整成為最大堆積 10 和 8 中選大者與 6對調即成圖 (d)

程式 5-14 刪除最大堆積中的最大資料(上接程式 5-13 )1 int DeleteHeap()2 int x i j3 if (n == 0) HeapEmpty()4 else5 x = heap[1]6 heap[1] = heap[n]7 n--8 i = 19 while (i lt= n2)10 if (heap[2i] gt heap[2i+1])11 j = 2i12 else13 j = 2i+114 if (heap[i]gtheap[j]) break15 swap(heap[i] heap[j])16 i = j17 18 return x19 20

其時間複雜度與其堆積高度相同是為O(logn) 其中 n 為堆積中的元素個數

1 100

2 93

3 84

4 77

5 65

6 81

7 79

8 66

9 70

10 50

100

84

77

93

8165

66 70

79

50

1

2 3

4 5 6 7

8 9 10

1 int DeletHeap()2 int x i j3 if(n == 0) HeapEmpty()4 else5 x = heap[1]6 heap[1] = heap[n]7 n--8 i = 19 while (i lt= n2)10 if ([heap[2i]] gt heap[2i+1])11 j = 2i12 else13 j = 2i+114 if (heap[i]gtheap[j]) break15 swap(heap[i] heap[j])16 i = j17 18 return x19 20

583  優先佇列bull 由於最大堆積(或最小堆積)的結構可提供最大元素(或最小元素)而其維護堆積的成本為 O(logn) n 為總元素個數

bull 堆積常被當成「優先佇列」 (priority queue) 使用mdash此時佇列被組織成完備二元樹若新加入佇列的資料有大小關係優先順序的需求時此優先佇列即以 O(logn) 的代價將新資料放入優先佇列(可能是最大或最小堆積依實際大小關係優先順序的需求而定)中mdash提供依元素的優先順序做為處理順序的服務

bull 一般的佇列為先進先出先到的先行處理優先佇列則是依據其優先順序優先順序高者先行處理與來到的先後無關

範例 5-21bull 在「分時」 (time sharing) 的作業系統中我們不希望使用中央處理器 (Central Processing Unit CPU) 時間短的「處理程序」 (process) 等候過久於是有「最短時間工作優先」 (shortest-job-first) 的排程策略此時等候的處理程序即可組織成之一最小堆積而 CPU 每次即取出存於樹根的處理程序予以處理任何新來的處理程序則以 O(logn) 的時間加入此優先佇列(最小堆積)中

bull 注意之前提過的線性佇列新來的直接加入成為新的尾端元素花O(1) 的時間然線性佇列本身沒有大小的關係每次要取出最大(小)的資料得花O(n) 的代價(在所有元素中挑出最大(小)者) n 為佇列內的元素個數而優先佇列是在新元素加入時要花O(logn) 的時間至於取出最大(小)則只須 O(1) 的時間

59  二元搜尋樹bull 二元搜尋樹是一棵二元樹可能是空二元樹若不為空二元樹則滿足下列性質ndash所有節點內的資料內容是相異的 ndash左子節點的資內容 ( 如果有 ) 比父節點的資料內容小 ndash右子節點的資料內容 ( 如果有 ) 比父節點的資料內容大 ndash以左和右子節點為樹根的左子樹和右子樹也是二元搜尋樹

在二元搜尋樹中搜尋資料二元搜尋樹對資料大小的掌握十分明確若恰與節點 p 資料相等則搜尋成功若欲搜尋的資料 x 比樹節點 p 中的資料小 則只須搜尋節點 p 的左子樹若比節點 p 中的資料大 則只須搜尋 p 的右子樹這樣的判斷可以遞迴地應用在各子樹節點 中

程式 5-15 在二元搜尋樹中搜尋資料(遞迴呼叫)1 struct BSTreeNode2 struct BSTreeNode leftchild3 int data4 struct BSTreeNode rightchild5 6 struct BSTreeNode root7 struct BSTreeNode SearchBST(

struct BSTreeNode tree int x)8 if (tree == NULL) return NULL9 if (x == tree-gtdata) return tree10 if (x lt tree-gtdata)11 return SearchBST(tree-gtleftchild x)

12 return SearchBST(tree-gtrightchild x)13

程式 5-16 搜尋二元搜尋樹的資料(非遞迴呼叫)在二元搜尋樹十分龐大時遞迴呼叫的效率不會太好可利用迴圈來完成搜尋的動作以提高執行效率請看程式 5-16

14 struct BSTreeNode Search BST_iterative15 (struct BSTreeNode node int x)16 while (node = NULL)17 if (x == node-gtdata) return node18 if (x lt node-gtdata)19 node = node-gtleftchild20 else21 node = node-gtrightchild22 23 return NULL24

592  新增資料於二元搜尋樹中欲新增資料進入一二元搜尋樹中得先決定該資料在二元搜尋樹中應出現的位置這可透過上節提到的搜尋動作來解決

在二元搜尋樹定義的性質 (1) 中我們限定了元素必須相異所以搜尋新資料的結果必是 NULL

必須將搜尋的結果改為新增資料所應在位置的節點指標

程式 5-17  在二元搜尋樹中新增資料25 int InsertBST(int x)26 struct BSTreeNode p q27 p = root q = NULL28 while (p = NULL)29 q = p30 if (x == p-gtdata) return 0

若不考慮重複則可省略之31 if (x lt p-gtdata)32 p = p-gtleftchild33 else34 p = p-gtrightchild35

3636 p = (struct BSTreeNode ) p = (struct BSTreeNode ) malloc (sizeof(BSTreeNode))malloc (sizeof(BSTreeNode))

3737 p-gtdata = x p-gtdata = x3838 p-gtleftchild = p-gtrightchild = p-gtleftchild = p-gtrightchild = NULLNULL3939 if (root == NULL) root = p if (root == NULL) root = p4040 else if (x lt q-gtdata) else if (x lt q-gtdata)4141 q-gtleftchild = p q-gtleftchild = p4242 else else4343 q-gtrightchild = p q-gtrightchild = p4444 return 1 return 145 45

程式 程式 5-175-17  在二元搜尋樹中新增資料  在二元搜尋樹中新增資料 (( 續續 ))

範例 5-23 圖 (b) 為圖 (a) 新增整數 6 之後的結果圖 (c) (d) (e) (f) 則分別是逐步再加入 28 18

10 21 的結果

bull若目前的二元搜尋樹高度為 h 則新增資料得花O(h) 的時間找到其所應在的位置遂新增資料至二元搜尋樹中的時間複雜之度為 O(h)

593 自二元搜尋樹中刪除資料欲自二元搜尋樹中刪除資料可能會面臨兩種情況(1) 刪除節點 p 位於樹葉節點上如下圖所示 這是比較容易處理的情況

(2) 刪除的節點 p 不是樹葉刪除節點 p 後原 p 所指的節點可由 q ( q 中包含了 p 左子樹中的最大節點值)或 r ( r 中包含了 p 右子樹中的最小節點值)來取代以維持二元搜尋樹中資料的大小關係其結果分別如圖 (b) 和 (c) 所示而圖 (d) 則標明了指標會改變的節點位置即 p 會消失 father 的左或右子樹指標會異動 k(可能是 q 或 r )會移到原來 p 的位置其父節點 f 的左或右子樹指標會異動其中 father 為 p 父節點 f 為 k 的父節點

程式 5-18 自二元搜尋樹中刪除資料46 int DeleteBST (int x)47 Struct BSTreeNode p father k f48 p = root father = NULL49 while (p = NULL)50 if (x == p-gtdata) 找到 x 所在的節點 p 了51 if ((p-gtleftchild == NULL) ampamp 52 (p-gtrightchild == NULL))53 k = NULL p為樹葉節點54 else if (p-gtleftchild = NULL )x位於非子節

點55 f = p 找出 p左子樹的最右樹葉56 k = p-gtleftchild57 while (k-gtrightchild =NULL)58 f = k59 k = k-gtrightchild60 k將挪至原 p處61 if (p == f)62 f-gtleftchild = k-gtleftchild 63 else64 f-gtrightchild = k-gtleftchild 65

66 else p無左子樹67 f = p 找出 p右子樹的最左樹葉68 k = p-gtrightchild69 while (k-gtleftchild = NULL)70 f = k71 k = k -gtleftchild72 73 if (p == f)74 f-gtrightchild = k-gtrightchild 75 else76 f-gtleftchild = k-gtrightchild77 78 if (k = NULL) k挪至原 p處繼承 p的左右指標79 k-gtleftchild = p-gtleftchild80 k-gtrightchild = p-gtrightchild81 82 if (father == NULL) root = k83 else if (x lt father-gtdata) 決定 k是 father的左

或右兒子84 father-gtleftchild = k85 else86 father-gtrightchild = k87 free(p)88 return 1 成功地刪除 x於此傳回 1返回呼叫處89 51~89行為找到 x時應有的處理步驟

90 else 尚未找到 x繼續往下一階層找

91 father = p92 if (x lt p-gtdata)93 p = p-gtleftchild94 else95 p = p-gtrightchild96 97 98 return 0 未找到 x傳回 099

510  樹林 (forest)

bull 定義樹林(森林)是由 n 棵沒有交集的樹所構成的集合 n 0

下圖為一包含三棵樹的樹林

5101 轉換樹林為二元樹 之前我們以二元樹來表示樹因為二元樹有較多

好用的性質在此我們也將樹林轉換成二元樹看待

在 522 節中我們嘗試了用左子右兄弟的方式來表示樹這個方法事實上已將樹轉換成二元樹

即用左子右兄弟的結構重新組織樹林中的每一棵樹這些樹的樹根將視為兄弟而最左邊的樹則視為樹林二元樹的樹根如此一來樹林即可轉換成二元樹

範例 5-25右下圖為右上圖三棵樹所對應的三棵左子右兄弟二元樹

下圖則為上圖樹林所對應的一棵樹林二元樹

演算法 5-1  轉換樹林成二元樹輸入樹林 F = T1 T2 Tn

輸出 F 的二元樹表示 BT=(T1 T2 Tn)1 if (n == 0) 輸出空二元樹 2 else

3 root(T1 T2 Tn) = T1

4 leftsubtree(T1 T2 Tn) =

BT(T11 T12 T1m)5 BT(T11 T12 T1m)為 T1的子樹6 rightsubtree(T1 T2 Tn) =

BT(T2 T3 Tn)

樹林轉換成二元樹的過程樹林轉換成二元樹的過程

  • Slide 1
  • Slide 2
  • Slide 3
  • Slide 4
  • Slide 5
  • Slide 6
  • Slide 7
  • Slide 8
  • Slide 9
  • Slide 10
  • Slide 11
  • Slide 12
  • Slide 13
  • Slide 14
  • Slide 15
  • Slide 16
  • Slide 17
  • Slide 18
  • Slide 19
  • Slide 20
  • Slide 21
  • Slide 22
  • Slide 23
  • Slide 24
  • Slide 25
  • Slide 26
  • Slide 27
  • Slide 28
  • Slide 29
  • Slide 30
  • Slide 31
  • Slide 32
  • Slide 33
  • Slide 34
  • Slide 35
  • Slide 36
  • Slide 37
  • Slide 38
  • Slide 39
  • Slide 40
  • Slide 41
  • Slide 42
  • Slide 43
  • Slide 44
  • Slide 45
  • Slide 46
  • Slide 47
  • Slide 48
  • Slide 49
  • Slide 50
  • Slide 51
  • Slide 52
  • Slide 53
  • Slide 54
  • Slide 55
  • Slide 56
  • Slide 57
  • Slide 58
  • Slide 59
  • Slide 60
  • Slide 61
  • Slide 62
  • Slide 63
  • Slide 64
  • Slide 65
  • Slide 66
  • Slide 67
  • Slide 68
  • Slide 69
  • Slide 70
  • Slide 71
  • Slide 72
  • Slide 73
  • Slide 74
  • Slide 75
  • Slide 76
  • Slide 77
  • Slide 78
  • Slide 79
  • Slide 80
  • Slide 81
  • Slide 82
  • Slide 83
  • Slide 84
  • Slide 85
  • Slide 86
  • Slide 87
  • Slide 88
  • Slide 89
  • Slide 90
  • Slide 91
  • Slide 92
  • Slide 93
  • Slide 94
  • Slide 95
  • Slide 96
  • Slide 97
  • Slide 98
  • Slide 99
  • Slide 100
  • Slide 101
  • Slide 102
  • Slide 103
Page 44: 第五章 樹

45 void Preorder_Stack(struct BSTreeNode node)46 do47 while (node = NULL)48 cout ltlt node-gtdata)49 push_node(node)50 node = node-gtleftchild51 push all left children52 if (top = NULL)53 node = pop_node()54 node = node-gtrightchild55 56 while ((top=NULL)||(node=NULL))57

程式 5-5二元樹的前序走訪21 void preorder(struct BTreeNode node)22 if (node = NULL)23 cout ltlt node-gtdata24 preorder(node-gtleftchild)25 preorder(node-gtrightchild)26 27

58 void Postorder_Stack(struct BTreeNode node)59 do60 while (node = NULL)61 cout ltlt node-gtdata62 push_node(node)63 node = node-gtrightchild64 push all left children65 if (top = NULL)66 node = pop_node()67 node = node-gtleftchild68 69 while ((top=NULL)||(node=NULL))70

程式 5-4二元樹的後序走訪14 void postorder(struct BTreeNode node)15 if (node =NULL)16 postorder(node-gtleftchild)17 postorder(node-gtrightchild)18 cout ltlt node-gtdata19 20

555  階層走訪bull 「階層走訪」 (level-order traversal) 是依階層

的順序進行二元樹的走訪先走訪階層小的節點後走訪階層大的節點同一階層者則依自左向右的順序走訪

bull 對下圖的二元樹進行階層走訪的結果為ABCDEFGHI

bull 對同一階層而言先走訪的節點其子節點亦在下一階層中先被走訪

bull 這種「先進先出」的特性恰可用佇列予以儲存與處理

程式 5-7 利用佇列進行二元樹的階層走訪

1 struct BTreeNode2 struct BTreeNode leftchild3 char data4 struct BTreeNode rightchild5 6 struct BTreeNode root7 struct QNode8 struct BTreeNode treenode9 struct QNode next10 11 struct QNode front rear

12 void AddQueue(struct BTreeNode Tnode)13 struct QNode node14 node=(struct QNode )malloc(sizeof(struct QNode))15 node-gttreenode = Tnode16 node-gtnext = NULL17 if (front == NULL) front = node19 else rear-gtnext = node20 rear = node21

22 struct BTreeNode DeleteQueue()23 struct BTreeNode Tnode24 struct QNode old_front25 if (rear == NULL)26 QueueEmpty()27 else28 Tnode = front-gttreenode29 old_front = front30 front = front-gtnext31 free(old_front)32 return Tnode33 34 35 void LevelOrder (struct BTreeNode node)36 AddQueue(node)37 while (front =NULL)38 node = DeleteQueue()39 cout ltlt node-gtdata40 if (node-gtleftchild = NULL)41 AddQueue(node-gtleftchild)42 if (node-gtrightchild = NULL)43 AddQueue(node-gtrightchild)44 45

35 void LevelOrder (struct BTreeNode node)36 AddQueue(node)37 while (front =NULL)38 node = DeleteQueue()39 cout ltlt node-gtdata40 if (node-gtleftchild = NULL)41 AddQueue(node-gtleftchild)42 if (node-gtrightchild = NULL)43 AddQueue(node-gtrightchild)44 45

bull Infix ABCbull Postfix ABC CBA ACB BCA

BAC CAB

B

A CA

B

C

B

A

C

C

B

AB

A

C

二元樹中序與前序式中序與後序式bull 每一棵二元樹皆有唯一的一對中序與前序表示法也有唯一的中序與後序次序

bull 換句話說給予一對中序與前序或中序與後序即可決定一棵二元樹

bull 然而給予前序和後序次序並不能決定唯一的二元樹可能會產生兩棵不同的二元樹

bull 想想階層走訪和中序走訪順序可否決定唯一的二元樹

A

B C

H J D E

F G

prefix ABHJCDFGEinfix HBJAFDGCEpostfix HJBFGDECA

VLRLVRLRV

A

prefix BHJinfix HBJ

prefix CDFGEinfix FDGCE

prefix ABHJCDFGEinfix HBJAFDGCE

A

B C

H J D E

F G

prefix ABHJCDFGE

infix HBJAFDGCEk-1

struct BTreeNode BTree_InfixPrefix (AnsiString infix AnsiString prefix)

int k struct BTreeNode node if (infixLength==0) return NULLnode = (struct BTreeNode ) malloc (sizeof(struct BTreeNode))node-gtdata = prefix[1]k = find(prefix[1] infix)node-gtleftchild = BTree_InfixPrefix

(infix[1k-1] prefix[2k])node-gtrightchild = BTree_InfixPrefix(infix[k+1infixLength]prefix[k+1prefixLength])

VLR

LVRk1

k2 k+1~

k+1~

1

struct BTreeNode BTree_InfixPostfix (AnsiString infix AnsiString prefix)

int k struct BTreeNode node if (infixLength==0) return NULLnode = (struct BTreeNode ) malloc (sizeof(struct BTreeNode))node-gtdata = postfix[postfixLength]k = find(postfix[postfixLength] infix)node-gtleftchild = BTree_InfixPrefix

(infix[1k-1] postfix[1k-1])node-gtrightchild = BTree_InfixPrefix(infix[k+1infixLength]postfix[k+1postfixLength-1])

56  二元樹的複製與相等測試程式 5-8 二元樹的複製1 struct BTreeNode2 struct BTreeNode leftchild3 char data4 struct BTreeNode rightchild5 6 struct BTreeNode root7 struct BTreeNode Copy(struct BTreeNode TreeRoot)8 struct BTreeNode CopyRoot9 if (TreeRoot == NULL)10 return NULL11 CopyRoot =(struct BTreeNode ) malloc (sizeof(BTreeNode))12 CopyRoot-gtdata = TreeRoot-gtdata13 CopyRoot-gtleftchild = Copy(TreeRoot-gtleftchild)14 CopyRoot-gtrightchild = Copy(TreeRoot-

gtrightchild)15 return CopyRoot16

程式 5-9 檢測兩棵二元樹是否相等1 struct BTreeNode2 struct BTreeNode leftchild3 char data4 struct BTreeNode rightchild5 6 struct BtreeNode rightchild7 int Equal(struct BTreeNode ustruct BTreeNode v)8 if ((u==NULL) ampamp (v==NULL)) return 19 if ((u=NULL) ampamp (v=NULL)10 ampamp (u-gtdata == v-gtdata )11 ampamp Equal(u-gtleftchildv-gtleftchild)12 ampamp Equal(u-gtrightchildv-gtrightchild))

13 return 114 return 015

Counting Nodes in a Binary Tree

int CountingNodes(struct BTreeNodes node)

if (node == NULL) return 0 int leftchildren = CountingNodes(node-gtleftchild)

int rightchildren= CountingNodes(node-gtrightchild)

return leftchildren+rightchildren

Counting Leaves in a Binary Tree

int CountingLeaves(struct BTreeNodes node)

if (node == NULL) return 0 int leftleaves = CountingLeaves(node-gtleftchild)

int rightchildren= CountingLeaves(node-gtrightchild)

if (leftleaves==0 ampamp leftleaves==0) return 1

return leftleaves + leftleaves

57  引線二元樹bull 在二元樹的鍵結表示中樹葉節點的兩個子樹指標皆指向 NULL 由定理 5-1 得知 n = E+1 其中 n 為節點個數 E 為分支數

bull 而 n 個鍵結節點有 2n 個指標空間每個分支恰佔用一個指標空間共有 E (=n-1) 個指標有用到(不是空的)而共有 n+1 個指標空間是 NULL 比非空的節點還多

bull 有學者提出對這些「放空指標的空間」加以利用的概念mdash與其放空指標不如放指向其它節點的指標稱之為引線 (thread) 使得某些運算(如走訪hellip等)可以加快

bull 這個概念發展出了引線二元樹 (threaded binary tree) 這種資料結構

範例 5-17下圖為加了引線的二元樹

加入的引線將使二元樹的中序走訪更加便利沿著上右圖箭頭所指示的順序走訪即可完成此二元樹的中序走訪GDHBAIECF 為此二元樹的中序表示

bull 範例 5-17 中所加的引線目的在方便二元樹的中序走訪

bull 對樹葉節點 p 而言令其資料為 P 則引線指標的規則如下

(1) p 的左子樹指標指向中序表示中 P 的前一個 元素(前接元素 (predecessor) )

(2) p 的右子樹指標指向中序表示中 P 的後一個 元素(後繼元素 (successor) )

為了區別節點指標放的是一般指標(內部節點)抑或是引線指標(樹葉節點)我們另以 2 個欄位分別區別左和右子樹指標空間存放的對象其節點的記憶體配置有如下圖所示

其中 leftthread (rightthread) 為 0 時表示 leftchild (rightchild) 存放的是一般節點指標而 leftthread (rightthread) 為 1 時表示 leftchild (rightchild) 存放的是引線指標1 struct TBTreeNode2 int leftthread3 struct TBTreeNode leftchild4 char data5 struct TBTreeNode rightchild6 int rightthread7

由右側的引線二元樹可知有最左和最右兩條引線尚無妥善的安排我們另設計一空的引線節點做為樹根如下圖所示

571  引線二元樹的中序走訪決定節點 p 在中序表示中的後繼節點 q (簡稱中序後繼點)

程式 5-10決定節點 node的中序後繼點1 struct TBTreeNode2 int leftthread3 struct TBTreeNode leftchild4 char data5 struct TBTreeNode rightchild6 int rightthread7 8 struct TBTreeNode root9 struct TBTreeNode Next(struct TBTreeNode node)

10 struct TBTreeNode temp11 temp = node-gtrightchild12 if (node-gtrightthead) return temp13 while (temp-gtleftthead) temp=temp-gtleftchild

14 return temp15

程式 5-11引線二元樹的中序表示(接程式 5-10)1 void InorderTBTree()2 struct TBTreeNode node3 for(node=Next(root)node=rootnode=Next(node))

4 cout ltlt node-gtdata5

bull只要 node 是引線二元樹上的任一節點透過呼叫 Next(node) 即可找到其在中序表示中的後繼節點bull(試想若程式 5-10 中的程序 Next 所傳入的參數指標正是 root Next(root) 是否可正常運作請思考)bull於是只須從中序表示中的第一節點起讓每一節點 node 都執行 Next(node) 程序即可得引線二元樹的中序表示

572  在引線二元樹中加入節點在引線二元樹中樹葉節點節點的右子樹指標所指向或內部節點右子樹的最左樹葉為其中序後繼節點而且任一節點若無左子樹則其左子樹指標乃指向其中序前接節點hellip這些性質必須在節點新增進入引線二元樹時也要保持接下來討論節點 new 插入成為引線二元樹的節點 p 的右子節點的情形至於插入成為左子節點的情形則讓各位模擬推敲

572572  在引線二元樹中加入節點 在引線二元樹中加入節點 (( 續續 ))

newnew 會成為會成為 pp 的右子節點的情況共有以下兩種的右子節點的情況共有以下兩種(1) (1) 若若 pp 沒有右子節點 沒有右子節點 ((p-gtrightthread p-gtrightthread 為為1) 1) 如圖如圖 ((a) a) 則 則 new new 插入後應形成如圖插入後應形成如圖 ((b) b) 所示所示

(2)(2) 若若 pp 有右子節點 有右子節點 ((p-gtrightthread p-gtrightthread 為為0) 0) 如圖如圖 ((c) c) 則 則 new new 插入後應形成如插入後應形成如圖圖 ((d) d) 所示所示

程式 5-12  插入節點 new 成為引線二元樹中節點 p的右子節點(上接程式 5-10 5-11 )

1 void InsertRight(struct TBTreeNode p 2 struct TBTreeNode new)3 struct TBTreeNode q4 new-gtrightchild = p-gtrightchild5 new-gtrightthead = p-gtrightthread6 new-gtleftchild = p7 new-gtleftthread = 18 p-gtrightchild = new9 p-gtrightthead = 010 if (new-gtrightthread)11 q = Next(new)12 q-gtleftchild = new13 14

58  堆積bull 「堆積」 (heap) 是一個特殊的完備二元樹節點的資料與其子節點的資料之間有事先定好的大小關係存在bull 定義ndash最大堆疊為一完備二元樹任一節點的資料內容不小於其子節點的資料內容ndash最小堆疊為一完備二元樹任一節點的資料內容不大於其子節點的資料內容

581  插入節點至最大堆積中最大堆積是一完備二元樹所以如圖 (a) 之最大堆積於加入新節點後樹的形狀必須如圖 (b) 所示灰色節點即為新節點若加入的節點資料分別為 1 9 12 則其最大堆積將分別變成如圖 (c) (d) (e) 所示

程式 5-13 新增節點至最大堆積中最大堆積本身是一棵完備二元樹可以用陣列表示之 1 define maxsize 1002 int heap[maxsize]3 int n i4 void InsertHeap(int x)5 if (n == maxsize) HeapFull()6 else7 n++8 i = n9 while ((i gt 1) ampamp (x gt heap[i2]))10 heap[i] = heap[i2]11 i = 212 13 heap[i] = x14 16

程序 InsertHeap 在插入第 n 個資料時需要的時間複雜度為 O(logn)

1 100

2 93

3 84

4 77

5 65

6 81

7 79

8 66

9 70

10 50

100

84

77

93

8165

66 70

79

50

1

2 3

4 5 6 7

8 9 10

1 define maxsize 1002 int heap[maxsize]3 int n i4 void InsertHeap(int x)5 if (n == maxsize) HeapFull()6 else7 n++8 i = n9 while ((i gt 1) ampamp (x gt heap[i2]))10 heap[i] = heap[i2]11 i = 212 13 heap[i] = x14 16

582 刪除最大堆積中的最大資料圖 (a) 中的最大堆積在刪除最大資料後其樹形應變成如圖 (b) 所示將最未一個樹葉節點的資料 6 暫挪至樹根節點內如圖 (c) 再將圖 (c) 中非最大堆積的結構調整成為最大堆積 10 和 8 中選大者與 6對調即成圖 (d)

程式 5-14 刪除最大堆積中的最大資料(上接程式 5-13 )1 int DeleteHeap()2 int x i j3 if (n == 0) HeapEmpty()4 else5 x = heap[1]6 heap[1] = heap[n]7 n--8 i = 19 while (i lt= n2)10 if (heap[2i] gt heap[2i+1])11 j = 2i12 else13 j = 2i+114 if (heap[i]gtheap[j]) break15 swap(heap[i] heap[j])16 i = j17 18 return x19 20

其時間複雜度與其堆積高度相同是為O(logn) 其中 n 為堆積中的元素個數

1 100

2 93

3 84

4 77

5 65

6 81

7 79

8 66

9 70

10 50

100

84

77

93

8165

66 70

79

50

1

2 3

4 5 6 7

8 9 10

1 int DeletHeap()2 int x i j3 if(n == 0) HeapEmpty()4 else5 x = heap[1]6 heap[1] = heap[n]7 n--8 i = 19 while (i lt= n2)10 if ([heap[2i]] gt heap[2i+1])11 j = 2i12 else13 j = 2i+114 if (heap[i]gtheap[j]) break15 swap(heap[i] heap[j])16 i = j17 18 return x19 20

583  優先佇列bull 由於最大堆積(或最小堆積)的結構可提供最大元素(或最小元素)而其維護堆積的成本為 O(logn) n 為總元素個數

bull 堆積常被當成「優先佇列」 (priority queue) 使用mdash此時佇列被組織成完備二元樹若新加入佇列的資料有大小關係優先順序的需求時此優先佇列即以 O(logn) 的代價將新資料放入優先佇列(可能是最大或最小堆積依實際大小關係優先順序的需求而定)中mdash提供依元素的優先順序做為處理順序的服務

bull 一般的佇列為先進先出先到的先行處理優先佇列則是依據其優先順序優先順序高者先行處理與來到的先後無關

範例 5-21bull 在「分時」 (time sharing) 的作業系統中我們不希望使用中央處理器 (Central Processing Unit CPU) 時間短的「處理程序」 (process) 等候過久於是有「最短時間工作優先」 (shortest-job-first) 的排程策略此時等候的處理程序即可組織成之一最小堆積而 CPU 每次即取出存於樹根的處理程序予以處理任何新來的處理程序則以 O(logn) 的時間加入此優先佇列(最小堆積)中

bull 注意之前提過的線性佇列新來的直接加入成為新的尾端元素花O(1) 的時間然線性佇列本身沒有大小的關係每次要取出最大(小)的資料得花O(n) 的代價(在所有元素中挑出最大(小)者) n 為佇列內的元素個數而優先佇列是在新元素加入時要花O(logn) 的時間至於取出最大(小)則只須 O(1) 的時間

59  二元搜尋樹bull 二元搜尋樹是一棵二元樹可能是空二元樹若不為空二元樹則滿足下列性質ndash所有節點內的資料內容是相異的 ndash左子節點的資內容 ( 如果有 ) 比父節點的資料內容小 ndash右子節點的資料內容 ( 如果有 ) 比父節點的資料內容大 ndash以左和右子節點為樹根的左子樹和右子樹也是二元搜尋樹

在二元搜尋樹中搜尋資料二元搜尋樹對資料大小的掌握十分明確若恰與節點 p 資料相等則搜尋成功若欲搜尋的資料 x 比樹節點 p 中的資料小 則只須搜尋節點 p 的左子樹若比節點 p 中的資料大 則只須搜尋 p 的右子樹這樣的判斷可以遞迴地應用在各子樹節點 中

程式 5-15 在二元搜尋樹中搜尋資料(遞迴呼叫)1 struct BSTreeNode2 struct BSTreeNode leftchild3 int data4 struct BSTreeNode rightchild5 6 struct BSTreeNode root7 struct BSTreeNode SearchBST(

struct BSTreeNode tree int x)8 if (tree == NULL) return NULL9 if (x == tree-gtdata) return tree10 if (x lt tree-gtdata)11 return SearchBST(tree-gtleftchild x)

12 return SearchBST(tree-gtrightchild x)13

程式 5-16 搜尋二元搜尋樹的資料(非遞迴呼叫)在二元搜尋樹十分龐大時遞迴呼叫的效率不會太好可利用迴圈來完成搜尋的動作以提高執行效率請看程式 5-16

14 struct BSTreeNode Search BST_iterative15 (struct BSTreeNode node int x)16 while (node = NULL)17 if (x == node-gtdata) return node18 if (x lt node-gtdata)19 node = node-gtleftchild20 else21 node = node-gtrightchild22 23 return NULL24

592  新增資料於二元搜尋樹中欲新增資料進入一二元搜尋樹中得先決定該資料在二元搜尋樹中應出現的位置這可透過上節提到的搜尋動作來解決

在二元搜尋樹定義的性質 (1) 中我們限定了元素必須相異所以搜尋新資料的結果必是 NULL

必須將搜尋的結果改為新增資料所應在位置的節點指標

程式 5-17  在二元搜尋樹中新增資料25 int InsertBST(int x)26 struct BSTreeNode p q27 p = root q = NULL28 while (p = NULL)29 q = p30 if (x == p-gtdata) return 0

若不考慮重複則可省略之31 if (x lt p-gtdata)32 p = p-gtleftchild33 else34 p = p-gtrightchild35

3636 p = (struct BSTreeNode ) p = (struct BSTreeNode ) malloc (sizeof(BSTreeNode))malloc (sizeof(BSTreeNode))

3737 p-gtdata = x p-gtdata = x3838 p-gtleftchild = p-gtrightchild = p-gtleftchild = p-gtrightchild = NULLNULL3939 if (root == NULL) root = p if (root == NULL) root = p4040 else if (x lt q-gtdata) else if (x lt q-gtdata)4141 q-gtleftchild = p q-gtleftchild = p4242 else else4343 q-gtrightchild = p q-gtrightchild = p4444 return 1 return 145 45

程式 程式 5-175-17  在二元搜尋樹中新增資料  在二元搜尋樹中新增資料 (( 續續 ))

範例 5-23 圖 (b) 為圖 (a) 新增整數 6 之後的結果圖 (c) (d) (e) (f) 則分別是逐步再加入 28 18

10 21 的結果

bull若目前的二元搜尋樹高度為 h 則新增資料得花O(h) 的時間找到其所應在的位置遂新增資料至二元搜尋樹中的時間複雜之度為 O(h)

593 自二元搜尋樹中刪除資料欲自二元搜尋樹中刪除資料可能會面臨兩種情況(1) 刪除節點 p 位於樹葉節點上如下圖所示 這是比較容易處理的情況

(2) 刪除的節點 p 不是樹葉刪除節點 p 後原 p 所指的節點可由 q ( q 中包含了 p 左子樹中的最大節點值)或 r ( r 中包含了 p 右子樹中的最小節點值)來取代以維持二元搜尋樹中資料的大小關係其結果分別如圖 (b) 和 (c) 所示而圖 (d) 則標明了指標會改變的節點位置即 p 會消失 father 的左或右子樹指標會異動 k(可能是 q 或 r )會移到原來 p 的位置其父節點 f 的左或右子樹指標會異動其中 father 為 p 父節點 f 為 k 的父節點

程式 5-18 自二元搜尋樹中刪除資料46 int DeleteBST (int x)47 Struct BSTreeNode p father k f48 p = root father = NULL49 while (p = NULL)50 if (x == p-gtdata) 找到 x 所在的節點 p 了51 if ((p-gtleftchild == NULL) ampamp 52 (p-gtrightchild == NULL))53 k = NULL p為樹葉節點54 else if (p-gtleftchild = NULL )x位於非子節

點55 f = p 找出 p左子樹的最右樹葉56 k = p-gtleftchild57 while (k-gtrightchild =NULL)58 f = k59 k = k-gtrightchild60 k將挪至原 p處61 if (p == f)62 f-gtleftchild = k-gtleftchild 63 else64 f-gtrightchild = k-gtleftchild 65

66 else p無左子樹67 f = p 找出 p右子樹的最左樹葉68 k = p-gtrightchild69 while (k-gtleftchild = NULL)70 f = k71 k = k -gtleftchild72 73 if (p == f)74 f-gtrightchild = k-gtrightchild 75 else76 f-gtleftchild = k-gtrightchild77 78 if (k = NULL) k挪至原 p處繼承 p的左右指標79 k-gtleftchild = p-gtleftchild80 k-gtrightchild = p-gtrightchild81 82 if (father == NULL) root = k83 else if (x lt father-gtdata) 決定 k是 father的左

或右兒子84 father-gtleftchild = k85 else86 father-gtrightchild = k87 free(p)88 return 1 成功地刪除 x於此傳回 1返回呼叫處89 51~89行為找到 x時應有的處理步驟

90 else 尚未找到 x繼續往下一階層找

91 father = p92 if (x lt p-gtdata)93 p = p-gtleftchild94 else95 p = p-gtrightchild96 97 98 return 0 未找到 x傳回 099

510  樹林 (forest)

bull 定義樹林(森林)是由 n 棵沒有交集的樹所構成的集合 n 0

下圖為一包含三棵樹的樹林

5101 轉換樹林為二元樹 之前我們以二元樹來表示樹因為二元樹有較多

好用的性質在此我們也將樹林轉換成二元樹看待

在 522 節中我們嘗試了用左子右兄弟的方式來表示樹這個方法事實上已將樹轉換成二元樹

即用左子右兄弟的結構重新組織樹林中的每一棵樹這些樹的樹根將視為兄弟而最左邊的樹則視為樹林二元樹的樹根如此一來樹林即可轉換成二元樹

範例 5-25右下圖為右上圖三棵樹所對應的三棵左子右兄弟二元樹

下圖則為上圖樹林所對應的一棵樹林二元樹

演算法 5-1  轉換樹林成二元樹輸入樹林 F = T1 T2 Tn

輸出 F 的二元樹表示 BT=(T1 T2 Tn)1 if (n == 0) 輸出空二元樹 2 else

3 root(T1 T2 Tn) = T1

4 leftsubtree(T1 T2 Tn) =

BT(T11 T12 T1m)5 BT(T11 T12 T1m)為 T1的子樹6 rightsubtree(T1 T2 Tn) =

BT(T2 T3 Tn)

樹林轉換成二元樹的過程樹林轉換成二元樹的過程

  • Slide 1
  • Slide 2
  • Slide 3
  • Slide 4
  • Slide 5
  • Slide 6
  • Slide 7
  • Slide 8
  • Slide 9
  • Slide 10
  • Slide 11
  • Slide 12
  • Slide 13
  • Slide 14
  • Slide 15
  • Slide 16
  • Slide 17
  • Slide 18
  • Slide 19
  • Slide 20
  • Slide 21
  • Slide 22
  • Slide 23
  • Slide 24
  • Slide 25
  • Slide 26
  • Slide 27
  • Slide 28
  • Slide 29
  • Slide 30
  • Slide 31
  • Slide 32
  • Slide 33
  • Slide 34
  • Slide 35
  • Slide 36
  • Slide 37
  • Slide 38
  • Slide 39
  • Slide 40
  • Slide 41
  • Slide 42
  • Slide 43
  • Slide 44
  • Slide 45
  • Slide 46
  • Slide 47
  • Slide 48
  • Slide 49
  • Slide 50
  • Slide 51
  • Slide 52
  • Slide 53
  • Slide 54
  • Slide 55
  • Slide 56
  • Slide 57
  • Slide 58
  • Slide 59
  • Slide 60
  • Slide 61
  • Slide 62
  • Slide 63
  • Slide 64
  • Slide 65
  • Slide 66
  • Slide 67
  • Slide 68
  • Slide 69
  • Slide 70
  • Slide 71
  • Slide 72
  • Slide 73
  • Slide 74
  • Slide 75
  • Slide 76
  • Slide 77
  • Slide 78
  • Slide 79
  • Slide 80
  • Slide 81
  • Slide 82
  • Slide 83
  • Slide 84
  • Slide 85
  • Slide 86
  • Slide 87
  • Slide 88
  • Slide 89
  • Slide 90
  • Slide 91
  • Slide 92
  • Slide 93
  • Slide 94
  • Slide 95
  • Slide 96
  • Slide 97
  • Slide 98
  • Slide 99
  • Slide 100
  • Slide 101
  • Slide 102
  • Slide 103
Page 45: 第五章 樹

58 void Postorder_Stack(struct BTreeNode node)59 do60 while (node = NULL)61 cout ltlt node-gtdata62 push_node(node)63 node = node-gtrightchild64 push all left children65 if (top = NULL)66 node = pop_node()67 node = node-gtleftchild68 69 while ((top=NULL)||(node=NULL))70

程式 5-4二元樹的後序走訪14 void postorder(struct BTreeNode node)15 if (node =NULL)16 postorder(node-gtleftchild)17 postorder(node-gtrightchild)18 cout ltlt node-gtdata19 20

555  階層走訪bull 「階層走訪」 (level-order traversal) 是依階層

的順序進行二元樹的走訪先走訪階層小的節點後走訪階層大的節點同一階層者則依自左向右的順序走訪

bull 對下圖的二元樹進行階層走訪的結果為ABCDEFGHI

bull 對同一階層而言先走訪的節點其子節點亦在下一階層中先被走訪

bull 這種「先進先出」的特性恰可用佇列予以儲存與處理

程式 5-7 利用佇列進行二元樹的階層走訪

1 struct BTreeNode2 struct BTreeNode leftchild3 char data4 struct BTreeNode rightchild5 6 struct BTreeNode root7 struct QNode8 struct BTreeNode treenode9 struct QNode next10 11 struct QNode front rear

12 void AddQueue(struct BTreeNode Tnode)13 struct QNode node14 node=(struct QNode )malloc(sizeof(struct QNode))15 node-gttreenode = Tnode16 node-gtnext = NULL17 if (front == NULL) front = node19 else rear-gtnext = node20 rear = node21

22 struct BTreeNode DeleteQueue()23 struct BTreeNode Tnode24 struct QNode old_front25 if (rear == NULL)26 QueueEmpty()27 else28 Tnode = front-gttreenode29 old_front = front30 front = front-gtnext31 free(old_front)32 return Tnode33 34 35 void LevelOrder (struct BTreeNode node)36 AddQueue(node)37 while (front =NULL)38 node = DeleteQueue()39 cout ltlt node-gtdata40 if (node-gtleftchild = NULL)41 AddQueue(node-gtleftchild)42 if (node-gtrightchild = NULL)43 AddQueue(node-gtrightchild)44 45

35 void LevelOrder (struct BTreeNode node)36 AddQueue(node)37 while (front =NULL)38 node = DeleteQueue()39 cout ltlt node-gtdata40 if (node-gtleftchild = NULL)41 AddQueue(node-gtleftchild)42 if (node-gtrightchild = NULL)43 AddQueue(node-gtrightchild)44 45

bull Infix ABCbull Postfix ABC CBA ACB BCA

BAC CAB

B

A CA

B

C

B

A

C

C

B

AB

A

C

二元樹中序與前序式中序與後序式bull 每一棵二元樹皆有唯一的一對中序與前序表示法也有唯一的中序與後序次序

bull 換句話說給予一對中序與前序或中序與後序即可決定一棵二元樹

bull 然而給予前序和後序次序並不能決定唯一的二元樹可能會產生兩棵不同的二元樹

bull 想想階層走訪和中序走訪順序可否決定唯一的二元樹

A

B C

H J D E

F G

prefix ABHJCDFGEinfix HBJAFDGCEpostfix HJBFGDECA

VLRLVRLRV

A

prefix BHJinfix HBJ

prefix CDFGEinfix FDGCE

prefix ABHJCDFGEinfix HBJAFDGCE

A

B C

H J D E

F G

prefix ABHJCDFGE

infix HBJAFDGCEk-1

struct BTreeNode BTree_InfixPrefix (AnsiString infix AnsiString prefix)

int k struct BTreeNode node if (infixLength==0) return NULLnode = (struct BTreeNode ) malloc (sizeof(struct BTreeNode))node-gtdata = prefix[1]k = find(prefix[1] infix)node-gtleftchild = BTree_InfixPrefix

(infix[1k-1] prefix[2k])node-gtrightchild = BTree_InfixPrefix(infix[k+1infixLength]prefix[k+1prefixLength])

VLR

LVRk1

k2 k+1~

k+1~

1

struct BTreeNode BTree_InfixPostfix (AnsiString infix AnsiString prefix)

int k struct BTreeNode node if (infixLength==0) return NULLnode = (struct BTreeNode ) malloc (sizeof(struct BTreeNode))node-gtdata = postfix[postfixLength]k = find(postfix[postfixLength] infix)node-gtleftchild = BTree_InfixPrefix

(infix[1k-1] postfix[1k-1])node-gtrightchild = BTree_InfixPrefix(infix[k+1infixLength]postfix[k+1postfixLength-1])

56  二元樹的複製與相等測試程式 5-8 二元樹的複製1 struct BTreeNode2 struct BTreeNode leftchild3 char data4 struct BTreeNode rightchild5 6 struct BTreeNode root7 struct BTreeNode Copy(struct BTreeNode TreeRoot)8 struct BTreeNode CopyRoot9 if (TreeRoot == NULL)10 return NULL11 CopyRoot =(struct BTreeNode ) malloc (sizeof(BTreeNode))12 CopyRoot-gtdata = TreeRoot-gtdata13 CopyRoot-gtleftchild = Copy(TreeRoot-gtleftchild)14 CopyRoot-gtrightchild = Copy(TreeRoot-

gtrightchild)15 return CopyRoot16

程式 5-9 檢測兩棵二元樹是否相等1 struct BTreeNode2 struct BTreeNode leftchild3 char data4 struct BTreeNode rightchild5 6 struct BtreeNode rightchild7 int Equal(struct BTreeNode ustruct BTreeNode v)8 if ((u==NULL) ampamp (v==NULL)) return 19 if ((u=NULL) ampamp (v=NULL)10 ampamp (u-gtdata == v-gtdata )11 ampamp Equal(u-gtleftchildv-gtleftchild)12 ampamp Equal(u-gtrightchildv-gtrightchild))

13 return 114 return 015

Counting Nodes in a Binary Tree

int CountingNodes(struct BTreeNodes node)

if (node == NULL) return 0 int leftchildren = CountingNodes(node-gtleftchild)

int rightchildren= CountingNodes(node-gtrightchild)

return leftchildren+rightchildren

Counting Leaves in a Binary Tree

int CountingLeaves(struct BTreeNodes node)

if (node == NULL) return 0 int leftleaves = CountingLeaves(node-gtleftchild)

int rightchildren= CountingLeaves(node-gtrightchild)

if (leftleaves==0 ampamp leftleaves==0) return 1

return leftleaves + leftleaves

57  引線二元樹bull 在二元樹的鍵結表示中樹葉節點的兩個子樹指標皆指向 NULL 由定理 5-1 得知 n = E+1 其中 n 為節點個數 E 為分支數

bull 而 n 個鍵結節點有 2n 個指標空間每個分支恰佔用一個指標空間共有 E (=n-1) 個指標有用到(不是空的)而共有 n+1 個指標空間是 NULL 比非空的節點還多

bull 有學者提出對這些「放空指標的空間」加以利用的概念mdash與其放空指標不如放指向其它節點的指標稱之為引線 (thread) 使得某些運算(如走訪hellip等)可以加快

bull 這個概念發展出了引線二元樹 (threaded binary tree) 這種資料結構

範例 5-17下圖為加了引線的二元樹

加入的引線將使二元樹的中序走訪更加便利沿著上右圖箭頭所指示的順序走訪即可完成此二元樹的中序走訪GDHBAIECF 為此二元樹的中序表示

bull 範例 5-17 中所加的引線目的在方便二元樹的中序走訪

bull 對樹葉節點 p 而言令其資料為 P 則引線指標的規則如下

(1) p 的左子樹指標指向中序表示中 P 的前一個 元素(前接元素 (predecessor) )

(2) p 的右子樹指標指向中序表示中 P 的後一個 元素(後繼元素 (successor) )

為了區別節點指標放的是一般指標(內部節點)抑或是引線指標(樹葉節點)我們另以 2 個欄位分別區別左和右子樹指標空間存放的對象其節點的記憶體配置有如下圖所示

其中 leftthread (rightthread) 為 0 時表示 leftchild (rightchild) 存放的是一般節點指標而 leftthread (rightthread) 為 1 時表示 leftchild (rightchild) 存放的是引線指標1 struct TBTreeNode2 int leftthread3 struct TBTreeNode leftchild4 char data5 struct TBTreeNode rightchild6 int rightthread7

由右側的引線二元樹可知有最左和最右兩條引線尚無妥善的安排我們另設計一空的引線節點做為樹根如下圖所示

571  引線二元樹的中序走訪決定節點 p 在中序表示中的後繼節點 q (簡稱中序後繼點)

程式 5-10決定節點 node的中序後繼點1 struct TBTreeNode2 int leftthread3 struct TBTreeNode leftchild4 char data5 struct TBTreeNode rightchild6 int rightthread7 8 struct TBTreeNode root9 struct TBTreeNode Next(struct TBTreeNode node)

10 struct TBTreeNode temp11 temp = node-gtrightchild12 if (node-gtrightthead) return temp13 while (temp-gtleftthead) temp=temp-gtleftchild

14 return temp15

程式 5-11引線二元樹的中序表示(接程式 5-10)1 void InorderTBTree()2 struct TBTreeNode node3 for(node=Next(root)node=rootnode=Next(node))

4 cout ltlt node-gtdata5

bull只要 node 是引線二元樹上的任一節點透過呼叫 Next(node) 即可找到其在中序表示中的後繼節點bull(試想若程式 5-10 中的程序 Next 所傳入的參數指標正是 root Next(root) 是否可正常運作請思考)bull於是只須從中序表示中的第一節點起讓每一節點 node 都執行 Next(node) 程序即可得引線二元樹的中序表示

572  在引線二元樹中加入節點在引線二元樹中樹葉節點節點的右子樹指標所指向或內部節點右子樹的最左樹葉為其中序後繼節點而且任一節點若無左子樹則其左子樹指標乃指向其中序前接節點hellip這些性質必須在節點新增進入引線二元樹時也要保持接下來討論節點 new 插入成為引線二元樹的節點 p 的右子節點的情形至於插入成為左子節點的情形則讓各位模擬推敲

572572  在引線二元樹中加入節點 在引線二元樹中加入節點 (( 續續 ))

newnew 會成為會成為 pp 的右子節點的情況共有以下兩種的右子節點的情況共有以下兩種(1) (1) 若若 pp 沒有右子節點 沒有右子節點 ((p-gtrightthread p-gtrightthread 為為1) 1) 如圖如圖 ((a) a) 則 則 new new 插入後應形成如圖插入後應形成如圖 ((b) b) 所示所示

(2)(2) 若若 pp 有右子節點 有右子節點 ((p-gtrightthread p-gtrightthread 為為0) 0) 如圖如圖 ((c) c) 則 則 new new 插入後應形成如插入後應形成如圖圖 ((d) d) 所示所示

程式 5-12  插入節點 new 成為引線二元樹中節點 p的右子節點(上接程式 5-10 5-11 )

1 void InsertRight(struct TBTreeNode p 2 struct TBTreeNode new)3 struct TBTreeNode q4 new-gtrightchild = p-gtrightchild5 new-gtrightthead = p-gtrightthread6 new-gtleftchild = p7 new-gtleftthread = 18 p-gtrightchild = new9 p-gtrightthead = 010 if (new-gtrightthread)11 q = Next(new)12 q-gtleftchild = new13 14

58  堆積bull 「堆積」 (heap) 是一個特殊的完備二元樹節點的資料與其子節點的資料之間有事先定好的大小關係存在bull 定義ndash最大堆疊為一完備二元樹任一節點的資料內容不小於其子節點的資料內容ndash最小堆疊為一完備二元樹任一節點的資料內容不大於其子節點的資料內容

581  插入節點至最大堆積中最大堆積是一完備二元樹所以如圖 (a) 之最大堆積於加入新節點後樹的形狀必須如圖 (b) 所示灰色節點即為新節點若加入的節點資料分別為 1 9 12 則其最大堆積將分別變成如圖 (c) (d) (e) 所示

程式 5-13 新增節點至最大堆積中最大堆積本身是一棵完備二元樹可以用陣列表示之 1 define maxsize 1002 int heap[maxsize]3 int n i4 void InsertHeap(int x)5 if (n == maxsize) HeapFull()6 else7 n++8 i = n9 while ((i gt 1) ampamp (x gt heap[i2]))10 heap[i] = heap[i2]11 i = 212 13 heap[i] = x14 16

程序 InsertHeap 在插入第 n 個資料時需要的時間複雜度為 O(logn)

1 100

2 93

3 84

4 77

5 65

6 81

7 79

8 66

9 70

10 50

100

84

77

93

8165

66 70

79

50

1

2 3

4 5 6 7

8 9 10

1 define maxsize 1002 int heap[maxsize]3 int n i4 void InsertHeap(int x)5 if (n == maxsize) HeapFull()6 else7 n++8 i = n9 while ((i gt 1) ampamp (x gt heap[i2]))10 heap[i] = heap[i2]11 i = 212 13 heap[i] = x14 16

582 刪除最大堆積中的最大資料圖 (a) 中的最大堆積在刪除最大資料後其樹形應變成如圖 (b) 所示將最未一個樹葉節點的資料 6 暫挪至樹根節點內如圖 (c) 再將圖 (c) 中非最大堆積的結構調整成為最大堆積 10 和 8 中選大者與 6對調即成圖 (d)

程式 5-14 刪除最大堆積中的最大資料(上接程式 5-13 )1 int DeleteHeap()2 int x i j3 if (n == 0) HeapEmpty()4 else5 x = heap[1]6 heap[1] = heap[n]7 n--8 i = 19 while (i lt= n2)10 if (heap[2i] gt heap[2i+1])11 j = 2i12 else13 j = 2i+114 if (heap[i]gtheap[j]) break15 swap(heap[i] heap[j])16 i = j17 18 return x19 20

其時間複雜度與其堆積高度相同是為O(logn) 其中 n 為堆積中的元素個數

1 100

2 93

3 84

4 77

5 65

6 81

7 79

8 66

9 70

10 50

100

84

77

93

8165

66 70

79

50

1

2 3

4 5 6 7

8 9 10

1 int DeletHeap()2 int x i j3 if(n == 0) HeapEmpty()4 else5 x = heap[1]6 heap[1] = heap[n]7 n--8 i = 19 while (i lt= n2)10 if ([heap[2i]] gt heap[2i+1])11 j = 2i12 else13 j = 2i+114 if (heap[i]gtheap[j]) break15 swap(heap[i] heap[j])16 i = j17 18 return x19 20

583  優先佇列bull 由於最大堆積(或最小堆積)的結構可提供最大元素(或最小元素)而其維護堆積的成本為 O(logn) n 為總元素個數

bull 堆積常被當成「優先佇列」 (priority queue) 使用mdash此時佇列被組織成完備二元樹若新加入佇列的資料有大小關係優先順序的需求時此優先佇列即以 O(logn) 的代價將新資料放入優先佇列(可能是最大或最小堆積依實際大小關係優先順序的需求而定)中mdash提供依元素的優先順序做為處理順序的服務

bull 一般的佇列為先進先出先到的先行處理優先佇列則是依據其優先順序優先順序高者先行處理與來到的先後無關

範例 5-21bull 在「分時」 (time sharing) 的作業系統中我們不希望使用中央處理器 (Central Processing Unit CPU) 時間短的「處理程序」 (process) 等候過久於是有「最短時間工作優先」 (shortest-job-first) 的排程策略此時等候的處理程序即可組織成之一最小堆積而 CPU 每次即取出存於樹根的處理程序予以處理任何新來的處理程序則以 O(logn) 的時間加入此優先佇列(最小堆積)中

bull 注意之前提過的線性佇列新來的直接加入成為新的尾端元素花O(1) 的時間然線性佇列本身沒有大小的關係每次要取出最大(小)的資料得花O(n) 的代價(在所有元素中挑出最大(小)者) n 為佇列內的元素個數而優先佇列是在新元素加入時要花O(logn) 的時間至於取出最大(小)則只須 O(1) 的時間

59  二元搜尋樹bull 二元搜尋樹是一棵二元樹可能是空二元樹若不為空二元樹則滿足下列性質ndash所有節點內的資料內容是相異的 ndash左子節點的資內容 ( 如果有 ) 比父節點的資料內容小 ndash右子節點的資料內容 ( 如果有 ) 比父節點的資料內容大 ndash以左和右子節點為樹根的左子樹和右子樹也是二元搜尋樹

在二元搜尋樹中搜尋資料二元搜尋樹對資料大小的掌握十分明確若恰與節點 p 資料相等則搜尋成功若欲搜尋的資料 x 比樹節點 p 中的資料小 則只須搜尋節點 p 的左子樹若比節點 p 中的資料大 則只須搜尋 p 的右子樹這樣的判斷可以遞迴地應用在各子樹節點 中

程式 5-15 在二元搜尋樹中搜尋資料(遞迴呼叫)1 struct BSTreeNode2 struct BSTreeNode leftchild3 int data4 struct BSTreeNode rightchild5 6 struct BSTreeNode root7 struct BSTreeNode SearchBST(

struct BSTreeNode tree int x)8 if (tree == NULL) return NULL9 if (x == tree-gtdata) return tree10 if (x lt tree-gtdata)11 return SearchBST(tree-gtleftchild x)

12 return SearchBST(tree-gtrightchild x)13

程式 5-16 搜尋二元搜尋樹的資料(非遞迴呼叫)在二元搜尋樹十分龐大時遞迴呼叫的效率不會太好可利用迴圈來完成搜尋的動作以提高執行效率請看程式 5-16

14 struct BSTreeNode Search BST_iterative15 (struct BSTreeNode node int x)16 while (node = NULL)17 if (x == node-gtdata) return node18 if (x lt node-gtdata)19 node = node-gtleftchild20 else21 node = node-gtrightchild22 23 return NULL24

592  新增資料於二元搜尋樹中欲新增資料進入一二元搜尋樹中得先決定該資料在二元搜尋樹中應出現的位置這可透過上節提到的搜尋動作來解決

在二元搜尋樹定義的性質 (1) 中我們限定了元素必須相異所以搜尋新資料的結果必是 NULL

必須將搜尋的結果改為新增資料所應在位置的節點指標

程式 5-17  在二元搜尋樹中新增資料25 int InsertBST(int x)26 struct BSTreeNode p q27 p = root q = NULL28 while (p = NULL)29 q = p30 if (x == p-gtdata) return 0

若不考慮重複則可省略之31 if (x lt p-gtdata)32 p = p-gtleftchild33 else34 p = p-gtrightchild35

3636 p = (struct BSTreeNode ) p = (struct BSTreeNode ) malloc (sizeof(BSTreeNode))malloc (sizeof(BSTreeNode))

3737 p-gtdata = x p-gtdata = x3838 p-gtleftchild = p-gtrightchild = p-gtleftchild = p-gtrightchild = NULLNULL3939 if (root == NULL) root = p if (root == NULL) root = p4040 else if (x lt q-gtdata) else if (x lt q-gtdata)4141 q-gtleftchild = p q-gtleftchild = p4242 else else4343 q-gtrightchild = p q-gtrightchild = p4444 return 1 return 145 45

程式 程式 5-175-17  在二元搜尋樹中新增資料  在二元搜尋樹中新增資料 (( 續續 ))

範例 5-23 圖 (b) 為圖 (a) 新增整數 6 之後的結果圖 (c) (d) (e) (f) 則分別是逐步再加入 28 18

10 21 的結果

bull若目前的二元搜尋樹高度為 h 則新增資料得花O(h) 的時間找到其所應在的位置遂新增資料至二元搜尋樹中的時間複雜之度為 O(h)

593 自二元搜尋樹中刪除資料欲自二元搜尋樹中刪除資料可能會面臨兩種情況(1) 刪除節點 p 位於樹葉節點上如下圖所示 這是比較容易處理的情況

(2) 刪除的節點 p 不是樹葉刪除節點 p 後原 p 所指的節點可由 q ( q 中包含了 p 左子樹中的最大節點值)或 r ( r 中包含了 p 右子樹中的最小節點值)來取代以維持二元搜尋樹中資料的大小關係其結果分別如圖 (b) 和 (c) 所示而圖 (d) 則標明了指標會改變的節點位置即 p 會消失 father 的左或右子樹指標會異動 k(可能是 q 或 r )會移到原來 p 的位置其父節點 f 的左或右子樹指標會異動其中 father 為 p 父節點 f 為 k 的父節點

程式 5-18 自二元搜尋樹中刪除資料46 int DeleteBST (int x)47 Struct BSTreeNode p father k f48 p = root father = NULL49 while (p = NULL)50 if (x == p-gtdata) 找到 x 所在的節點 p 了51 if ((p-gtleftchild == NULL) ampamp 52 (p-gtrightchild == NULL))53 k = NULL p為樹葉節點54 else if (p-gtleftchild = NULL )x位於非子節

點55 f = p 找出 p左子樹的最右樹葉56 k = p-gtleftchild57 while (k-gtrightchild =NULL)58 f = k59 k = k-gtrightchild60 k將挪至原 p處61 if (p == f)62 f-gtleftchild = k-gtleftchild 63 else64 f-gtrightchild = k-gtleftchild 65

66 else p無左子樹67 f = p 找出 p右子樹的最左樹葉68 k = p-gtrightchild69 while (k-gtleftchild = NULL)70 f = k71 k = k -gtleftchild72 73 if (p == f)74 f-gtrightchild = k-gtrightchild 75 else76 f-gtleftchild = k-gtrightchild77 78 if (k = NULL) k挪至原 p處繼承 p的左右指標79 k-gtleftchild = p-gtleftchild80 k-gtrightchild = p-gtrightchild81 82 if (father == NULL) root = k83 else if (x lt father-gtdata) 決定 k是 father的左

或右兒子84 father-gtleftchild = k85 else86 father-gtrightchild = k87 free(p)88 return 1 成功地刪除 x於此傳回 1返回呼叫處89 51~89行為找到 x時應有的處理步驟

90 else 尚未找到 x繼續往下一階層找

91 father = p92 if (x lt p-gtdata)93 p = p-gtleftchild94 else95 p = p-gtrightchild96 97 98 return 0 未找到 x傳回 099

510  樹林 (forest)

bull 定義樹林(森林)是由 n 棵沒有交集的樹所構成的集合 n 0

下圖為一包含三棵樹的樹林

5101 轉換樹林為二元樹 之前我們以二元樹來表示樹因為二元樹有較多

好用的性質在此我們也將樹林轉換成二元樹看待

在 522 節中我們嘗試了用左子右兄弟的方式來表示樹這個方法事實上已將樹轉換成二元樹

即用左子右兄弟的結構重新組織樹林中的每一棵樹這些樹的樹根將視為兄弟而最左邊的樹則視為樹林二元樹的樹根如此一來樹林即可轉換成二元樹

範例 5-25右下圖為右上圖三棵樹所對應的三棵左子右兄弟二元樹

下圖則為上圖樹林所對應的一棵樹林二元樹

演算法 5-1  轉換樹林成二元樹輸入樹林 F = T1 T2 Tn

輸出 F 的二元樹表示 BT=(T1 T2 Tn)1 if (n == 0) 輸出空二元樹 2 else

3 root(T1 T2 Tn) = T1

4 leftsubtree(T1 T2 Tn) =

BT(T11 T12 T1m)5 BT(T11 T12 T1m)為 T1的子樹6 rightsubtree(T1 T2 Tn) =

BT(T2 T3 Tn)

樹林轉換成二元樹的過程樹林轉換成二元樹的過程

  • Slide 1
  • Slide 2
  • Slide 3
  • Slide 4
  • Slide 5
  • Slide 6
  • Slide 7
  • Slide 8
  • Slide 9
  • Slide 10
  • Slide 11
  • Slide 12
  • Slide 13
  • Slide 14
  • Slide 15
  • Slide 16
  • Slide 17
  • Slide 18
  • Slide 19
  • Slide 20
  • Slide 21
  • Slide 22
  • Slide 23
  • Slide 24
  • Slide 25
  • Slide 26
  • Slide 27
  • Slide 28
  • Slide 29
  • Slide 30
  • Slide 31
  • Slide 32
  • Slide 33
  • Slide 34
  • Slide 35
  • Slide 36
  • Slide 37
  • Slide 38
  • Slide 39
  • Slide 40
  • Slide 41
  • Slide 42
  • Slide 43
  • Slide 44
  • Slide 45
  • Slide 46
  • Slide 47
  • Slide 48
  • Slide 49
  • Slide 50
  • Slide 51
  • Slide 52
  • Slide 53
  • Slide 54
  • Slide 55
  • Slide 56
  • Slide 57
  • Slide 58
  • Slide 59
  • Slide 60
  • Slide 61
  • Slide 62
  • Slide 63
  • Slide 64
  • Slide 65
  • Slide 66
  • Slide 67
  • Slide 68
  • Slide 69
  • Slide 70
  • Slide 71
  • Slide 72
  • Slide 73
  • Slide 74
  • Slide 75
  • Slide 76
  • Slide 77
  • Slide 78
  • Slide 79
  • Slide 80
  • Slide 81
  • Slide 82
  • Slide 83
  • Slide 84
  • Slide 85
  • Slide 86
  • Slide 87
  • Slide 88
  • Slide 89
  • Slide 90
  • Slide 91
  • Slide 92
  • Slide 93
  • Slide 94
  • Slide 95
  • Slide 96
  • Slide 97
  • Slide 98
  • Slide 99
  • Slide 100
  • Slide 101
  • Slide 102
  • Slide 103
Page 46: 第五章 樹

555  階層走訪bull 「階層走訪」 (level-order traversal) 是依階層

的順序進行二元樹的走訪先走訪階層小的節點後走訪階層大的節點同一階層者則依自左向右的順序走訪

bull 對下圖的二元樹進行階層走訪的結果為ABCDEFGHI

bull 對同一階層而言先走訪的節點其子節點亦在下一階層中先被走訪

bull 這種「先進先出」的特性恰可用佇列予以儲存與處理

程式 5-7 利用佇列進行二元樹的階層走訪

1 struct BTreeNode2 struct BTreeNode leftchild3 char data4 struct BTreeNode rightchild5 6 struct BTreeNode root7 struct QNode8 struct BTreeNode treenode9 struct QNode next10 11 struct QNode front rear

12 void AddQueue(struct BTreeNode Tnode)13 struct QNode node14 node=(struct QNode )malloc(sizeof(struct QNode))15 node-gttreenode = Tnode16 node-gtnext = NULL17 if (front == NULL) front = node19 else rear-gtnext = node20 rear = node21

22 struct BTreeNode DeleteQueue()23 struct BTreeNode Tnode24 struct QNode old_front25 if (rear == NULL)26 QueueEmpty()27 else28 Tnode = front-gttreenode29 old_front = front30 front = front-gtnext31 free(old_front)32 return Tnode33 34 35 void LevelOrder (struct BTreeNode node)36 AddQueue(node)37 while (front =NULL)38 node = DeleteQueue()39 cout ltlt node-gtdata40 if (node-gtleftchild = NULL)41 AddQueue(node-gtleftchild)42 if (node-gtrightchild = NULL)43 AddQueue(node-gtrightchild)44 45

35 void LevelOrder (struct BTreeNode node)36 AddQueue(node)37 while (front =NULL)38 node = DeleteQueue()39 cout ltlt node-gtdata40 if (node-gtleftchild = NULL)41 AddQueue(node-gtleftchild)42 if (node-gtrightchild = NULL)43 AddQueue(node-gtrightchild)44 45

bull Infix ABCbull Postfix ABC CBA ACB BCA

BAC CAB

B

A CA

B

C

B

A

C

C

B

AB

A

C

二元樹中序與前序式中序與後序式bull 每一棵二元樹皆有唯一的一對中序與前序表示法也有唯一的中序與後序次序

bull 換句話說給予一對中序與前序或中序與後序即可決定一棵二元樹

bull 然而給予前序和後序次序並不能決定唯一的二元樹可能會產生兩棵不同的二元樹

bull 想想階層走訪和中序走訪順序可否決定唯一的二元樹

A

B C

H J D E

F G

prefix ABHJCDFGEinfix HBJAFDGCEpostfix HJBFGDECA

VLRLVRLRV

A

prefix BHJinfix HBJ

prefix CDFGEinfix FDGCE

prefix ABHJCDFGEinfix HBJAFDGCE

A

B C

H J D E

F G

prefix ABHJCDFGE

infix HBJAFDGCEk-1

struct BTreeNode BTree_InfixPrefix (AnsiString infix AnsiString prefix)

int k struct BTreeNode node if (infixLength==0) return NULLnode = (struct BTreeNode ) malloc (sizeof(struct BTreeNode))node-gtdata = prefix[1]k = find(prefix[1] infix)node-gtleftchild = BTree_InfixPrefix

(infix[1k-1] prefix[2k])node-gtrightchild = BTree_InfixPrefix(infix[k+1infixLength]prefix[k+1prefixLength])

VLR

LVRk1

k2 k+1~

k+1~

1

struct BTreeNode BTree_InfixPostfix (AnsiString infix AnsiString prefix)

int k struct BTreeNode node if (infixLength==0) return NULLnode = (struct BTreeNode ) malloc (sizeof(struct BTreeNode))node-gtdata = postfix[postfixLength]k = find(postfix[postfixLength] infix)node-gtleftchild = BTree_InfixPrefix

(infix[1k-1] postfix[1k-1])node-gtrightchild = BTree_InfixPrefix(infix[k+1infixLength]postfix[k+1postfixLength-1])

56  二元樹的複製與相等測試程式 5-8 二元樹的複製1 struct BTreeNode2 struct BTreeNode leftchild3 char data4 struct BTreeNode rightchild5 6 struct BTreeNode root7 struct BTreeNode Copy(struct BTreeNode TreeRoot)8 struct BTreeNode CopyRoot9 if (TreeRoot == NULL)10 return NULL11 CopyRoot =(struct BTreeNode ) malloc (sizeof(BTreeNode))12 CopyRoot-gtdata = TreeRoot-gtdata13 CopyRoot-gtleftchild = Copy(TreeRoot-gtleftchild)14 CopyRoot-gtrightchild = Copy(TreeRoot-

gtrightchild)15 return CopyRoot16

程式 5-9 檢測兩棵二元樹是否相等1 struct BTreeNode2 struct BTreeNode leftchild3 char data4 struct BTreeNode rightchild5 6 struct BtreeNode rightchild7 int Equal(struct BTreeNode ustruct BTreeNode v)8 if ((u==NULL) ampamp (v==NULL)) return 19 if ((u=NULL) ampamp (v=NULL)10 ampamp (u-gtdata == v-gtdata )11 ampamp Equal(u-gtleftchildv-gtleftchild)12 ampamp Equal(u-gtrightchildv-gtrightchild))

13 return 114 return 015

Counting Nodes in a Binary Tree

int CountingNodes(struct BTreeNodes node)

if (node == NULL) return 0 int leftchildren = CountingNodes(node-gtleftchild)

int rightchildren= CountingNodes(node-gtrightchild)

return leftchildren+rightchildren

Counting Leaves in a Binary Tree

int CountingLeaves(struct BTreeNodes node)

if (node == NULL) return 0 int leftleaves = CountingLeaves(node-gtleftchild)

int rightchildren= CountingLeaves(node-gtrightchild)

if (leftleaves==0 ampamp leftleaves==0) return 1

return leftleaves + leftleaves

57  引線二元樹bull 在二元樹的鍵結表示中樹葉節點的兩個子樹指標皆指向 NULL 由定理 5-1 得知 n = E+1 其中 n 為節點個數 E 為分支數

bull 而 n 個鍵結節點有 2n 個指標空間每個分支恰佔用一個指標空間共有 E (=n-1) 個指標有用到(不是空的)而共有 n+1 個指標空間是 NULL 比非空的節點還多

bull 有學者提出對這些「放空指標的空間」加以利用的概念mdash與其放空指標不如放指向其它節點的指標稱之為引線 (thread) 使得某些運算(如走訪hellip等)可以加快

bull 這個概念發展出了引線二元樹 (threaded binary tree) 這種資料結構

範例 5-17下圖為加了引線的二元樹

加入的引線將使二元樹的中序走訪更加便利沿著上右圖箭頭所指示的順序走訪即可完成此二元樹的中序走訪GDHBAIECF 為此二元樹的中序表示

bull 範例 5-17 中所加的引線目的在方便二元樹的中序走訪

bull 對樹葉節點 p 而言令其資料為 P 則引線指標的規則如下

(1) p 的左子樹指標指向中序表示中 P 的前一個 元素(前接元素 (predecessor) )

(2) p 的右子樹指標指向中序表示中 P 的後一個 元素(後繼元素 (successor) )

為了區別節點指標放的是一般指標(內部節點)抑或是引線指標(樹葉節點)我們另以 2 個欄位分別區別左和右子樹指標空間存放的對象其節點的記憶體配置有如下圖所示

其中 leftthread (rightthread) 為 0 時表示 leftchild (rightchild) 存放的是一般節點指標而 leftthread (rightthread) 為 1 時表示 leftchild (rightchild) 存放的是引線指標1 struct TBTreeNode2 int leftthread3 struct TBTreeNode leftchild4 char data5 struct TBTreeNode rightchild6 int rightthread7

由右側的引線二元樹可知有最左和最右兩條引線尚無妥善的安排我們另設計一空的引線節點做為樹根如下圖所示

571  引線二元樹的中序走訪決定節點 p 在中序表示中的後繼節點 q (簡稱中序後繼點)

程式 5-10決定節點 node的中序後繼點1 struct TBTreeNode2 int leftthread3 struct TBTreeNode leftchild4 char data5 struct TBTreeNode rightchild6 int rightthread7 8 struct TBTreeNode root9 struct TBTreeNode Next(struct TBTreeNode node)

10 struct TBTreeNode temp11 temp = node-gtrightchild12 if (node-gtrightthead) return temp13 while (temp-gtleftthead) temp=temp-gtleftchild

14 return temp15

程式 5-11引線二元樹的中序表示(接程式 5-10)1 void InorderTBTree()2 struct TBTreeNode node3 for(node=Next(root)node=rootnode=Next(node))

4 cout ltlt node-gtdata5

bull只要 node 是引線二元樹上的任一節點透過呼叫 Next(node) 即可找到其在中序表示中的後繼節點bull(試想若程式 5-10 中的程序 Next 所傳入的參數指標正是 root Next(root) 是否可正常運作請思考)bull於是只須從中序表示中的第一節點起讓每一節點 node 都執行 Next(node) 程序即可得引線二元樹的中序表示

572  在引線二元樹中加入節點在引線二元樹中樹葉節點節點的右子樹指標所指向或內部節點右子樹的最左樹葉為其中序後繼節點而且任一節點若無左子樹則其左子樹指標乃指向其中序前接節點hellip這些性質必須在節點新增進入引線二元樹時也要保持接下來討論節點 new 插入成為引線二元樹的節點 p 的右子節點的情形至於插入成為左子節點的情形則讓各位模擬推敲

572572  在引線二元樹中加入節點 在引線二元樹中加入節點 (( 續續 ))

newnew 會成為會成為 pp 的右子節點的情況共有以下兩種的右子節點的情況共有以下兩種(1) (1) 若若 pp 沒有右子節點 沒有右子節點 ((p-gtrightthread p-gtrightthread 為為1) 1) 如圖如圖 ((a) a) 則 則 new new 插入後應形成如圖插入後應形成如圖 ((b) b) 所示所示

(2)(2) 若若 pp 有右子節點 有右子節點 ((p-gtrightthread p-gtrightthread 為為0) 0) 如圖如圖 ((c) c) 則 則 new new 插入後應形成如插入後應形成如圖圖 ((d) d) 所示所示

程式 5-12  插入節點 new 成為引線二元樹中節點 p的右子節點(上接程式 5-10 5-11 )

1 void InsertRight(struct TBTreeNode p 2 struct TBTreeNode new)3 struct TBTreeNode q4 new-gtrightchild = p-gtrightchild5 new-gtrightthead = p-gtrightthread6 new-gtleftchild = p7 new-gtleftthread = 18 p-gtrightchild = new9 p-gtrightthead = 010 if (new-gtrightthread)11 q = Next(new)12 q-gtleftchild = new13 14

58  堆積bull 「堆積」 (heap) 是一個特殊的完備二元樹節點的資料與其子節點的資料之間有事先定好的大小關係存在bull 定義ndash最大堆疊為一完備二元樹任一節點的資料內容不小於其子節點的資料內容ndash最小堆疊為一完備二元樹任一節點的資料內容不大於其子節點的資料內容

581  插入節點至最大堆積中最大堆積是一完備二元樹所以如圖 (a) 之最大堆積於加入新節點後樹的形狀必須如圖 (b) 所示灰色節點即為新節點若加入的節點資料分別為 1 9 12 則其最大堆積將分別變成如圖 (c) (d) (e) 所示

程式 5-13 新增節點至最大堆積中最大堆積本身是一棵完備二元樹可以用陣列表示之 1 define maxsize 1002 int heap[maxsize]3 int n i4 void InsertHeap(int x)5 if (n == maxsize) HeapFull()6 else7 n++8 i = n9 while ((i gt 1) ampamp (x gt heap[i2]))10 heap[i] = heap[i2]11 i = 212 13 heap[i] = x14 16

程序 InsertHeap 在插入第 n 個資料時需要的時間複雜度為 O(logn)

1 100

2 93

3 84

4 77

5 65

6 81

7 79

8 66

9 70

10 50

100

84

77

93

8165

66 70

79

50

1

2 3

4 5 6 7

8 9 10

1 define maxsize 1002 int heap[maxsize]3 int n i4 void InsertHeap(int x)5 if (n == maxsize) HeapFull()6 else7 n++8 i = n9 while ((i gt 1) ampamp (x gt heap[i2]))10 heap[i] = heap[i2]11 i = 212 13 heap[i] = x14 16

582 刪除最大堆積中的最大資料圖 (a) 中的最大堆積在刪除最大資料後其樹形應變成如圖 (b) 所示將最未一個樹葉節點的資料 6 暫挪至樹根節點內如圖 (c) 再將圖 (c) 中非最大堆積的結構調整成為最大堆積 10 和 8 中選大者與 6對調即成圖 (d)

程式 5-14 刪除最大堆積中的最大資料(上接程式 5-13 )1 int DeleteHeap()2 int x i j3 if (n == 0) HeapEmpty()4 else5 x = heap[1]6 heap[1] = heap[n]7 n--8 i = 19 while (i lt= n2)10 if (heap[2i] gt heap[2i+1])11 j = 2i12 else13 j = 2i+114 if (heap[i]gtheap[j]) break15 swap(heap[i] heap[j])16 i = j17 18 return x19 20

其時間複雜度與其堆積高度相同是為O(logn) 其中 n 為堆積中的元素個數

1 100

2 93

3 84

4 77

5 65

6 81

7 79

8 66

9 70

10 50

100

84

77

93

8165

66 70

79

50

1

2 3

4 5 6 7

8 9 10

1 int DeletHeap()2 int x i j3 if(n == 0) HeapEmpty()4 else5 x = heap[1]6 heap[1] = heap[n]7 n--8 i = 19 while (i lt= n2)10 if ([heap[2i]] gt heap[2i+1])11 j = 2i12 else13 j = 2i+114 if (heap[i]gtheap[j]) break15 swap(heap[i] heap[j])16 i = j17 18 return x19 20

583  優先佇列bull 由於最大堆積(或最小堆積)的結構可提供最大元素(或最小元素)而其維護堆積的成本為 O(logn) n 為總元素個數

bull 堆積常被當成「優先佇列」 (priority queue) 使用mdash此時佇列被組織成完備二元樹若新加入佇列的資料有大小關係優先順序的需求時此優先佇列即以 O(logn) 的代價將新資料放入優先佇列(可能是最大或最小堆積依實際大小關係優先順序的需求而定)中mdash提供依元素的優先順序做為處理順序的服務

bull 一般的佇列為先進先出先到的先行處理優先佇列則是依據其優先順序優先順序高者先行處理與來到的先後無關

範例 5-21bull 在「分時」 (time sharing) 的作業系統中我們不希望使用中央處理器 (Central Processing Unit CPU) 時間短的「處理程序」 (process) 等候過久於是有「最短時間工作優先」 (shortest-job-first) 的排程策略此時等候的處理程序即可組織成之一最小堆積而 CPU 每次即取出存於樹根的處理程序予以處理任何新來的處理程序則以 O(logn) 的時間加入此優先佇列(最小堆積)中

bull 注意之前提過的線性佇列新來的直接加入成為新的尾端元素花O(1) 的時間然線性佇列本身沒有大小的關係每次要取出最大(小)的資料得花O(n) 的代價(在所有元素中挑出最大(小)者) n 為佇列內的元素個數而優先佇列是在新元素加入時要花O(logn) 的時間至於取出最大(小)則只須 O(1) 的時間

59  二元搜尋樹bull 二元搜尋樹是一棵二元樹可能是空二元樹若不為空二元樹則滿足下列性質ndash所有節點內的資料內容是相異的 ndash左子節點的資內容 ( 如果有 ) 比父節點的資料內容小 ndash右子節點的資料內容 ( 如果有 ) 比父節點的資料內容大 ndash以左和右子節點為樹根的左子樹和右子樹也是二元搜尋樹

在二元搜尋樹中搜尋資料二元搜尋樹對資料大小的掌握十分明確若恰與節點 p 資料相等則搜尋成功若欲搜尋的資料 x 比樹節點 p 中的資料小 則只須搜尋節點 p 的左子樹若比節點 p 中的資料大 則只須搜尋 p 的右子樹這樣的判斷可以遞迴地應用在各子樹節點 中

程式 5-15 在二元搜尋樹中搜尋資料(遞迴呼叫)1 struct BSTreeNode2 struct BSTreeNode leftchild3 int data4 struct BSTreeNode rightchild5 6 struct BSTreeNode root7 struct BSTreeNode SearchBST(

struct BSTreeNode tree int x)8 if (tree == NULL) return NULL9 if (x == tree-gtdata) return tree10 if (x lt tree-gtdata)11 return SearchBST(tree-gtleftchild x)

12 return SearchBST(tree-gtrightchild x)13

程式 5-16 搜尋二元搜尋樹的資料(非遞迴呼叫)在二元搜尋樹十分龐大時遞迴呼叫的效率不會太好可利用迴圈來完成搜尋的動作以提高執行效率請看程式 5-16

14 struct BSTreeNode Search BST_iterative15 (struct BSTreeNode node int x)16 while (node = NULL)17 if (x == node-gtdata) return node18 if (x lt node-gtdata)19 node = node-gtleftchild20 else21 node = node-gtrightchild22 23 return NULL24

592  新增資料於二元搜尋樹中欲新增資料進入一二元搜尋樹中得先決定該資料在二元搜尋樹中應出現的位置這可透過上節提到的搜尋動作來解決

在二元搜尋樹定義的性質 (1) 中我們限定了元素必須相異所以搜尋新資料的結果必是 NULL

必須將搜尋的結果改為新增資料所應在位置的節點指標

程式 5-17  在二元搜尋樹中新增資料25 int InsertBST(int x)26 struct BSTreeNode p q27 p = root q = NULL28 while (p = NULL)29 q = p30 if (x == p-gtdata) return 0

若不考慮重複則可省略之31 if (x lt p-gtdata)32 p = p-gtleftchild33 else34 p = p-gtrightchild35

3636 p = (struct BSTreeNode ) p = (struct BSTreeNode ) malloc (sizeof(BSTreeNode))malloc (sizeof(BSTreeNode))

3737 p-gtdata = x p-gtdata = x3838 p-gtleftchild = p-gtrightchild = p-gtleftchild = p-gtrightchild = NULLNULL3939 if (root == NULL) root = p if (root == NULL) root = p4040 else if (x lt q-gtdata) else if (x lt q-gtdata)4141 q-gtleftchild = p q-gtleftchild = p4242 else else4343 q-gtrightchild = p q-gtrightchild = p4444 return 1 return 145 45

程式 程式 5-175-17  在二元搜尋樹中新增資料  在二元搜尋樹中新增資料 (( 續續 ))

範例 5-23 圖 (b) 為圖 (a) 新增整數 6 之後的結果圖 (c) (d) (e) (f) 則分別是逐步再加入 28 18

10 21 的結果

bull若目前的二元搜尋樹高度為 h 則新增資料得花O(h) 的時間找到其所應在的位置遂新增資料至二元搜尋樹中的時間複雜之度為 O(h)

593 自二元搜尋樹中刪除資料欲自二元搜尋樹中刪除資料可能會面臨兩種情況(1) 刪除節點 p 位於樹葉節點上如下圖所示 這是比較容易處理的情況

(2) 刪除的節點 p 不是樹葉刪除節點 p 後原 p 所指的節點可由 q ( q 中包含了 p 左子樹中的最大節點值)或 r ( r 中包含了 p 右子樹中的最小節點值)來取代以維持二元搜尋樹中資料的大小關係其結果分別如圖 (b) 和 (c) 所示而圖 (d) 則標明了指標會改變的節點位置即 p 會消失 father 的左或右子樹指標會異動 k(可能是 q 或 r )會移到原來 p 的位置其父節點 f 的左或右子樹指標會異動其中 father 為 p 父節點 f 為 k 的父節點

程式 5-18 自二元搜尋樹中刪除資料46 int DeleteBST (int x)47 Struct BSTreeNode p father k f48 p = root father = NULL49 while (p = NULL)50 if (x == p-gtdata) 找到 x 所在的節點 p 了51 if ((p-gtleftchild == NULL) ampamp 52 (p-gtrightchild == NULL))53 k = NULL p為樹葉節點54 else if (p-gtleftchild = NULL )x位於非子節

點55 f = p 找出 p左子樹的最右樹葉56 k = p-gtleftchild57 while (k-gtrightchild =NULL)58 f = k59 k = k-gtrightchild60 k將挪至原 p處61 if (p == f)62 f-gtleftchild = k-gtleftchild 63 else64 f-gtrightchild = k-gtleftchild 65

66 else p無左子樹67 f = p 找出 p右子樹的最左樹葉68 k = p-gtrightchild69 while (k-gtleftchild = NULL)70 f = k71 k = k -gtleftchild72 73 if (p == f)74 f-gtrightchild = k-gtrightchild 75 else76 f-gtleftchild = k-gtrightchild77 78 if (k = NULL) k挪至原 p處繼承 p的左右指標79 k-gtleftchild = p-gtleftchild80 k-gtrightchild = p-gtrightchild81 82 if (father == NULL) root = k83 else if (x lt father-gtdata) 決定 k是 father的左

或右兒子84 father-gtleftchild = k85 else86 father-gtrightchild = k87 free(p)88 return 1 成功地刪除 x於此傳回 1返回呼叫處89 51~89行為找到 x時應有的處理步驟

90 else 尚未找到 x繼續往下一階層找

91 father = p92 if (x lt p-gtdata)93 p = p-gtleftchild94 else95 p = p-gtrightchild96 97 98 return 0 未找到 x傳回 099

510  樹林 (forest)

bull 定義樹林(森林)是由 n 棵沒有交集的樹所構成的集合 n 0

下圖為一包含三棵樹的樹林

5101 轉換樹林為二元樹 之前我們以二元樹來表示樹因為二元樹有較多

好用的性質在此我們也將樹林轉換成二元樹看待

在 522 節中我們嘗試了用左子右兄弟的方式來表示樹這個方法事實上已將樹轉換成二元樹

即用左子右兄弟的結構重新組織樹林中的每一棵樹這些樹的樹根將視為兄弟而最左邊的樹則視為樹林二元樹的樹根如此一來樹林即可轉換成二元樹

範例 5-25右下圖為右上圖三棵樹所對應的三棵左子右兄弟二元樹

下圖則為上圖樹林所對應的一棵樹林二元樹

演算法 5-1  轉換樹林成二元樹輸入樹林 F = T1 T2 Tn

輸出 F 的二元樹表示 BT=(T1 T2 Tn)1 if (n == 0) 輸出空二元樹 2 else

3 root(T1 T2 Tn) = T1

4 leftsubtree(T1 T2 Tn) =

BT(T11 T12 T1m)5 BT(T11 T12 T1m)為 T1的子樹6 rightsubtree(T1 T2 Tn) =

BT(T2 T3 Tn)

樹林轉換成二元樹的過程樹林轉換成二元樹的過程

  • Slide 1
  • Slide 2
  • Slide 3
  • Slide 4
  • Slide 5
  • Slide 6
  • Slide 7
  • Slide 8
  • Slide 9
  • Slide 10
  • Slide 11
  • Slide 12
  • Slide 13
  • Slide 14
  • Slide 15
  • Slide 16
  • Slide 17
  • Slide 18
  • Slide 19
  • Slide 20
  • Slide 21
  • Slide 22
  • Slide 23
  • Slide 24
  • Slide 25
  • Slide 26
  • Slide 27
  • Slide 28
  • Slide 29
  • Slide 30
  • Slide 31
  • Slide 32
  • Slide 33
  • Slide 34
  • Slide 35
  • Slide 36
  • Slide 37
  • Slide 38
  • Slide 39
  • Slide 40
  • Slide 41
  • Slide 42
  • Slide 43
  • Slide 44
  • Slide 45
  • Slide 46
  • Slide 47
  • Slide 48
  • Slide 49
  • Slide 50
  • Slide 51
  • Slide 52
  • Slide 53
  • Slide 54
  • Slide 55
  • Slide 56
  • Slide 57
  • Slide 58
  • Slide 59
  • Slide 60
  • Slide 61
  • Slide 62
  • Slide 63
  • Slide 64
  • Slide 65
  • Slide 66
  • Slide 67
  • Slide 68
  • Slide 69
  • Slide 70
  • Slide 71
  • Slide 72
  • Slide 73
  • Slide 74
  • Slide 75
  • Slide 76
  • Slide 77
  • Slide 78
  • Slide 79
  • Slide 80
  • Slide 81
  • Slide 82
  • Slide 83
  • Slide 84
  • Slide 85
  • Slide 86
  • Slide 87
  • Slide 88
  • Slide 89
  • Slide 90
  • Slide 91
  • Slide 92
  • Slide 93
  • Slide 94
  • Slide 95
  • Slide 96
  • Slide 97
  • Slide 98
  • Slide 99
  • Slide 100
  • Slide 101
  • Slide 102
  • Slide 103
Page 47: 第五章 樹

程式 5-7 利用佇列進行二元樹的階層走訪

1 struct BTreeNode2 struct BTreeNode leftchild3 char data4 struct BTreeNode rightchild5 6 struct BTreeNode root7 struct QNode8 struct BTreeNode treenode9 struct QNode next10 11 struct QNode front rear

12 void AddQueue(struct BTreeNode Tnode)13 struct QNode node14 node=(struct QNode )malloc(sizeof(struct QNode))15 node-gttreenode = Tnode16 node-gtnext = NULL17 if (front == NULL) front = node19 else rear-gtnext = node20 rear = node21

22 struct BTreeNode DeleteQueue()23 struct BTreeNode Tnode24 struct QNode old_front25 if (rear == NULL)26 QueueEmpty()27 else28 Tnode = front-gttreenode29 old_front = front30 front = front-gtnext31 free(old_front)32 return Tnode33 34 35 void LevelOrder (struct BTreeNode node)36 AddQueue(node)37 while (front =NULL)38 node = DeleteQueue()39 cout ltlt node-gtdata40 if (node-gtleftchild = NULL)41 AddQueue(node-gtleftchild)42 if (node-gtrightchild = NULL)43 AddQueue(node-gtrightchild)44 45

35 void LevelOrder (struct BTreeNode node)36 AddQueue(node)37 while (front =NULL)38 node = DeleteQueue()39 cout ltlt node-gtdata40 if (node-gtleftchild = NULL)41 AddQueue(node-gtleftchild)42 if (node-gtrightchild = NULL)43 AddQueue(node-gtrightchild)44 45

bull Infix ABCbull Postfix ABC CBA ACB BCA

BAC CAB

B

A CA

B

C

B

A

C

C

B

AB

A

C

二元樹中序與前序式中序與後序式bull 每一棵二元樹皆有唯一的一對中序與前序表示法也有唯一的中序與後序次序

bull 換句話說給予一對中序與前序或中序與後序即可決定一棵二元樹

bull 然而給予前序和後序次序並不能決定唯一的二元樹可能會產生兩棵不同的二元樹

bull 想想階層走訪和中序走訪順序可否決定唯一的二元樹

A

B C

H J D E

F G

prefix ABHJCDFGEinfix HBJAFDGCEpostfix HJBFGDECA

VLRLVRLRV

A

prefix BHJinfix HBJ

prefix CDFGEinfix FDGCE

prefix ABHJCDFGEinfix HBJAFDGCE

A

B C

H J D E

F G

prefix ABHJCDFGE

infix HBJAFDGCEk-1

struct BTreeNode BTree_InfixPrefix (AnsiString infix AnsiString prefix)

int k struct BTreeNode node if (infixLength==0) return NULLnode = (struct BTreeNode ) malloc (sizeof(struct BTreeNode))node-gtdata = prefix[1]k = find(prefix[1] infix)node-gtleftchild = BTree_InfixPrefix

(infix[1k-1] prefix[2k])node-gtrightchild = BTree_InfixPrefix(infix[k+1infixLength]prefix[k+1prefixLength])

VLR

LVRk1

k2 k+1~

k+1~

1

struct BTreeNode BTree_InfixPostfix (AnsiString infix AnsiString prefix)

int k struct BTreeNode node if (infixLength==0) return NULLnode = (struct BTreeNode ) malloc (sizeof(struct BTreeNode))node-gtdata = postfix[postfixLength]k = find(postfix[postfixLength] infix)node-gtleftchild = BTree_InfixPrefix

(infix[1k-1] postfix[1k-1])node-gtrightchild = BTree_InfixPrefix(infix[k+1infixLength]postfix[k+1postfixLength-1])

56  二元樹的複製與相等測試程式 5-8 二元樹的複製1 struct BTreeNode2 struct BTreeNode leftchild3 char data4 struct BTreeNode rightchild5 6 struct BTreeNode root7 struct BTreeNode Copy(struct BTreeNode TreeRoot)8 struct BTreeNode CopyRoot9 if (TreeRoot == NULL)10 return NULL11 CopyRoot =(struct BTreeNode ) malloc (sizeof(BTreeNode))12 CopyRoot-gtdata = TreeRoot-gtdata13 CopyRoot-gtleftchild = Copy(TreeRoot-gtleftchild)14 CopyRoot-gtrightchild = Copy(TreeRoot-

gtrightchild)15 return CopyRoot16

程式 5-9 檢測兩棵二元樹是否相等1 struct BTreeNode2 struct BTreeNode leftchild3 char data4 struct BTreeNode rightchild5 6 struct BtreeNode rightchild7 int Equal(struct BTreeNode ustruct BTreeNode v)8 if ((u==NULL) ampamp (v==NULL)) return 19 if ((u=NULL) ampamp (v=NULL)10 ampamp (u-gtdata == v-gtdata )11 ampamp Equal(u-gtleftchildv-gtleftchild)12 ampamp Equal(u-gtrightchildv-gtrightchild))

13 return 114 return 015

Counting Nodes in a Binary Tree

int CountingNodes(struct BTreeNodes node)

if (node == NULL) return 0 int leftchildren = CountingNodes(node-gtleftchild)

int rightchildren= CountingNodes(node-gtrightchild)

return leftchildren+rightchildren

Counting Leaves in a Binary Tree

int CountingLeaves(struct BTreeNodes node)

if (node == NULL) return 0 int leftleaves = CountingLeaves(node-gtleftchild)

int rightchildren= CountingLeaves(node-gtrightchild)

if (leftleaves==0 ampamp leftleaves==0) return 1

return leftleaves + leftleaves

57  引線二元樹bull 在二元樹的鍵結表示中樹葉節點的兩個子樹指標皆指向 NULL 由定理 5-1 得知 n = E+1 其中 n 為節點個數 E 為分支數

bull 而 n 個鍵結節點有 2n 個指標空間每個分支恰佔用一個指標空間共有 E (=n-1) 個指標有用到(不是空的)而共有 n+1 個指標空間是 NULL 比非空的節點還多

bull 有學者提出對這些「放空指標的空間」加以利用的概念mdash與其放空指標不如放指向其它節點的指標稱之為引線 (thread) 使得某些運算(如走訪hellip等)可以加快

bull 這個概念發展出了引線二元樹 (threaded binary tree) 這種資料結構

範例 5-17下圖為加了引線的二元樹

加入的引線將使二元樹的中序走訪更加便利沿著上右圖箭頭所指示的順序走訪即可完成此二元樹的中序走訪GDHBAIECF 為此二元樹的中序表示

bull 範例 5-17 中所加的引線目的在方便二元樹的中序走訪

bull 對樹葉節點 p 而言令其資料為 P 則引線指標的規則如下

(1) p 的左子樹指標指向中序表示中 P 的前一個 元素(前接元素 (predecessor) )

(2) p 的右子樹指標指向中序表示中 P 的後一個 元素(後繼元素 (successor) )

為了區別節點指標放的是一般指標(內部節點)抑或是引線指標(樹葉節點)我們另以 2 個欄位分別區別左和右子樹指標空間存放的對象其節點的記憶體配置有如下圖所示

其中 leftthread (rightthread) 為 0 時表示 leftchild (rightchild) 存放的是一般節點指標而 leftthread (rightthread) 為 1 時表示 leftchild (rightchild) 存放的是引線指標1 struct TBTreeNode2 int leftthread3 struct TBTreeNode leftchild4 char data5 struct TBTreeNode rightchild6 int rightthread7

由右側的引線二元樹可知有最左和最右兩條引線尚無妥善的安排我們另設計一空的引線節點做為樹根如下圖所示

571  引線二元樹的中序走訪決定節點 p 在中序表示中的後繼節點 q (簡稱中序後繼點)

程式 5-10決定節點 node的中序後繼點1 struct TBTreeNode2 int leftthread3 struct TBTreeNode leftchild4 char data5 struct TBTreeNode rightchild6 int rightthread7 8 struct TBTreeNode root9 struct TBTreeNode Next(struct TBTreeNode node)

10 struct TBTreeNode temp11 temp = node-gtrightchild12 if (node-gtrightthead) return temp13 while (temp-gtleftthead) temp=temp-gtleftchild

14 return temp15

程式 5-11引線二元樹的中序表示(接程式 5-10)1 void InorderTBTree()2 struct TBTreeNode node3 for(node=Next(root)node=rootnode=Next(node))

4 cout ltlt node-gtdata5

bull只要 node 是引線二元樹上的任一節點透過呼叫 Next(node) 即可找到其在中序表示中的後繼節點bull(試想若程式 5-10 中的程序 Next 所傳入的參數指標正是 root Next(root) 是否可正常運作請思考)bull於是只須從中序表示中的第一節點起讓每一節點 node 都執行 Next(node) 程序即可得引線二元樹的中序表示

572  在引線二元樹中加入節點在引線二元樹中樹葉節點節點的右子樹指標所指向或內部節點右子樹的最左樹葉為其中序後繼節點而且任一節點若無左子樹則其左子樹指標乃指向其中序前接節點hellip這些性質必須在節點新增進入引線二元樹時也要保持接下來討論節點 new 插入成為引線二元樹的節點 p 的右子節點的情形至於插入成為左子節點的情形則讓各位模擬推敲

572572  在引線二元樹中加入節點 在引線二元樹中加入節點 (( 續續 ))

newnew 會成為會成為 pp 的右子節點的情況共有以下兩種的右子節點的情況共有以下兩種(1) (1) 若若 pp 沒有右子節點 沒有右子節點 ((p-gtrightthread p-gtrightthread 為為1) 1) 如圖如圖 ((a) a) 則 則 new new 插入後應形成如圖插入後應形成如圖 ((b) b) 所示所示

(2)(2) 若若 pp 有右子節點 有右子節點 ((p-gtrightthread p-gtrightthread 為為0) 0) 如圖如圖 ((c) c) 則 則 new new 插入後應形成如插入後應形成如圖圖 ((d) d) 所示所示

程式 5-12  插入節點 new 成為引線二元樹中節點 p的右子節點(上接程式 5-10 5-11 )

1 void InsertRight(struct TBTreeNode p 2 struct TBTreeNode new)3 struct TBTreeNode q4 new-gtrightchild = p-gtrightchild5 new-gtrightthead = p-gtrightthread6 new-gtleftchild = p7 new-gtleftthread = 18 p-gtrightchild = new9 p-gtrightthead = 010 if (new-gtrightthread)11 q = Next(new)12 q-gtleftchild = new13 14

58  堆積bull 「堆積」 (heap) 是一個特殊的完備二元樹節點的資料與其子節點的資料之間有事先定好的大小關係存在bull 定義ndash最大堆疊為一完備二元樹任一節點的資料內容不小於其子節點的資料內容ndash最小堆疊為一完備二元樹任一節點的資料內容不大於其子節點的資料內容

581  插入節點至最大堆積中最大堆積是一完備二元樹所以如圖 (a) 之最大堆積於加入新節點後樹的形狀必須如圖 (b) 所示灰色節點即為新節點若加入的節點資料分別為 1 9 12 則其最大堆積將分別變成如圖 (c) (d) (e) 所示

程式 5-13 新增節點至最大堆積中最大堆積本身是一棵完備二元樹可以用陣列表示之 1 define maxsize 1002 int heap[maxsize]3 int n i4 void InsertHeap(int x)5 if (n == maxsize) HeapFull()6 else7 n++8 i = n9 while ((i gt 1) ampamp (x gt heap[i2]))10 heap[i] = heap[i2]11 i = 212 13 heap[i] = x14 16

程序 InsertHeap 在插入第 n 個資料時需要的時間複雜度為 O(logn)

1 100

2 93

3 84

4 77

5 65

6 81

7 79

8 66

9 70

10 50

100

84

77

93

8165

66 70

79

50

1

2 3

4 5 6 7

8 9 10

1 define maxsize 1002 int heap[maxsize]3 int n i4 void InsertHeap(int x)5 if (n == maxsize) HeapFull()6 else7 n++8 i = n9 while ((i gt 1) ampamp (x gt heap[i2]))10 heap[i] = heap[i2]11 i = 212 13 heap[i] = x14 16

582 刪除最大堆積中的最大資料圖 (a) 中的最大堆積在刪除最大資料後其樹形應變成如圖 (b) 所示將最未一個樹葉節點的資料 6 暫挪至樹根節點內如圖 (c) 再將圖 (c) 中非最大堆積的結構調整成為最大堆積 10 和 8 中選大者與 6對調即成圖 (d)

程式 5-14 刪除最大堆積中的最大資料(上接程式 5-13 )1 int DeleteHeap()2 int x i j3 if (n == 0) HeapEmpty()4 else5 x = heap[1]6 heap[1] = heap[n]7 n--8 i = 19 while (i lt= n2)10 if (heap[2i] gt heap[2i+1])11 j = 2i12 else13 j = 2i+114 if (heap[i]gtheap[j]) break15 swap(heap[i] heap[j])16 i = j17 18 return x19 20

其時間複雜度與其堆積高度相同是為O(logn) 其中 n 為堆積中的元素個數

1 100

2 93

3 84

4 77

5 65

6 81

7 79

8 66

9 70

10 50

100

84

77

93

8165

66 70

79

50

1

2 3

4 5 6 7

8 9 10

1 int DeletHeap()2 int x i j3 if(n == 0) HeapEmpty()4 else5 x = heap[1]6 heap[1] = heap[n]7 n--8 i = 19 while (i lt= n2)10 if ([heap[2i]] gt heap[2i+1])11 j = 2i12 else13 j = 2i+114 if (heap[i]gtheap[j]) break15 swap(heap[i] heap[j])16 i = j17 18 return x19 20

583  優先佇列bull 由於最大堆積(或最小堆積)的結構可提供最大元素(或最小元素)而其維護堆積的成本為 O(logn) n 為總元素個數

bull 堆積常被當成「優先佇列」 (priority queue) 使用mdash此時佇列被組織成完備二元樹若新加入佇列的資料有大小關係優先順序的需求時此優先佇列即以 O(logn) 的代價將新資料放入優先佇列(可能是最大或最小堆積依實際大小關係優先順序的需求而定)中mdash提供依元素的優先順序做為處理順序的服務

bull 一般的佇列為先進先出先到的先行處理優先佇列則是依據其優先順序優先順序高者先行處理與來到的先後無關

範例 5-21bull 在「分時」 (time sharing) 的作業系統中我們不希望使用中央處理器 (Central Processing Unit CPU) 時間短的「處理程序」 (process) 等候過久於是有「最短時間工作優先」 (shortest-job-first) 的排程策略此時等候的處理程序即可組織成之一最小堆積而 CPU 每次即取出存於樹根的處理程序予以處理任何新來的處理程序則以 O(logn) 的時間加入此優先佇列(最小堆積)中

bull 注意之前提過的線性佇列新來的直接加入成為新的尾端元素花O(1) 的時間然線性佇列本身沒有大小的關係每次要取出最大(小)的資料得花O(n) 的代價(在所有元素中挑出最大(小)者) n 為佇列內的元素個數而優先佇列是在新元素加入時要花O(logn) 的時間至於取出最大(小)則只須 O(1) 的時間

59  二元搜尋樹bull 二元搜尋樹是一棵二元樹可能是空二元樹若不為空二元樹則滿足下列性質ndash所有節點內的資料內容是相異的 ndash左子節點的資內容 ( 如果有 ) 比父節點的資料內容小 ndash右子節點的資料內容 ( 如果有 ) 比父節點的資料內容大 ndash以左和右子節點為樹根的左子樹和右子樹也是二元搜尋樹

在二元搜尋樹中搜尋資料二元搜尋樹對資料大小的掌握十分明確若恰與節點 p 資料相等則搜尋成功若欲搜尋的資料 x 比樹節點 p 中的資料小 則只須搜尋節點 p 的左子樹若比節點 p 中的資料大 則只須搜尋 p 的右子樹這樣的判斷可以遞迴地應用在各子樹節點 中

程式 5-15 在二元搜尋樹中搜尋資料(遞迴呼叫)1 struct BSTreeNode2 struct BSTreeNode leftchild3 int data4 struct BSTreeNode rightchild5 6 struct BSTreeNode root7 struct BSTreeNode SearchBST(

struct BSTreeNode tree int x)8 if (tree == NULL) return NULL9 if (x == tree-gtdata) return tree10 if (x lt tree-gtdata)11 return SearchBST(tree-gtleftchild x)

12 return SearchBST(tree-gtrightchild x)13

程式 5-16 搜尋二元搜尋樹的資料(非遞迴呼叫)在二元搜尋樹十分龐大時遞迴呼叫的效率不會太好可利用迴圈來完成搜尋的動作以提高執行效率請看程式 5-16

14 struct BSTreeNode Search BST_iterative15 (struct BSTreeNode node int x)16 while (node = NULL)17 if (x == node-gtdata) return node18 if (x lt node-gtdata)19 node = node-gtleftchild20 else21 node = node-gtrightchild22 23 return NULL24

592  新增資料於二元搜尋樹中欲新增資料進入一二元搜尋樹中得先決定該資料在二元搜尋樹中應出現的位置這可透過上節提到的搜尋動作來解決

在二元搜尋樹定義的性質 (1) 中我們限定了元素必須相異所以搜尋新資料的結果必是 NULL

必須將搜尋的結果改為新增資料所應在位置的節點指標

程式 5-17  在二元搜尋樹中新增資料25 int InsertBST(int x)26 struct BSTreeNode p q27 p = root q = NULL28 while (p = NULL)29 q = p30 if (x == p-gtdata) return 0

若不考慮重複則可省略之31 if (x lt p-gtdata)32 p = p-gtleftchild33 else34 p = p-gtrightchild35

3636 p = (struct BSTreeNode ) p = (struct BSTreeNode ) malloc (sizeof(BSTreeNode))malloc (sizeof(BSTreeNode))

3737 p-gtdata = x p-gtdata = x3838 p-gtleftchild = p-gtrightchild = p-gtleftchild = p-gtrightchild = NULLNULL3939 if (root == NULL) root = p if (root == NULL) root = p4040 else if (x lt q-gtdata) else if (x lt q-gtdata)4141 q-gtleftchild = p q-gtleftchild = p4242 else else4343 q-gtrightchild = p q-gtrightchild = p4444 return 1 return 145 45

程式 程式 5-175-17  在二元搜尋樹中新增資料  在二元搜尋樹中新增資料 (( 續續 ))

範例 5-23 圖 (b) 為圖 (a) 新增整數 6 之後的結果圖 (c) (d) (e) (f) 則分別是逐步再加入 28 18

10 21 的結果

bull若目前的二元搜尋樹高度為 h 則新增資料得花O(h) 的時間找到其所應在的位置遂新增資料至二元搜尋樹中的時間複雜之度為 O(h)

593 自二元搜尋樹中刪除資料欲自二元搜尋樹中刪除資料可能會面臨兩種情況(1) 刪除節點 p 位於樹葉節點上如下圖所示 這是比較容易處理的情況

(2) 刪除的節點 p 不是樹葉刪除節點 p 後原 p 所指的節點可由 q ( q 中包含了 p 左子樹中的最大節點值)或 r ( r 中包含了 p 右子樹中的最小節點值)來取代以維持二元搜尋樹中資料的大小關係其結果分別如圖 (b) 和 (c) 所示而圖 (d) 則標明了指標會改變的節點位置即 p 會消失 father 的左或右子樹指標會異動 k(可能是 q 或 r )會移到原來 p 的位置其父節點 f 的左或右子樹指標會異動其中 father 為 p 父節點 f 為 k 的父節點

程式 5-18 自二元搜尋樹中刪除資料46 int DeleteBST (int x)47 Struct BSTreeNode p father k f48 p = root father = NULL49 while (p = NULL)50 if (x == p-gtdata) 找到 x 所在的節點 p 了51 if ((p-gtleftchild == NULL) ampamp 52 (p-gtrightchild == NULL))53 k = NULL p為樹葉節點54 else if (p-gtleftchild = NULL )x位於非子節

點55 f = p 找出 p左子樹的最右樹葉56 k = p-gtleftchild57 while (k-gtrightchild =NULL)58 f = k59 k = k-gtrightchild60 k將挪至原 p處61 if (p == f)62 f-gtleftchild = k-gtleftchild 63 else64 f-gtrightchild = k-gtleftchild 65

66 else p無左子樹67 f = p 找出 p右子樹的最左樹葉68 k = p-gtrightchild69 while (k-gtleftchild = NULL)70 f = k71 k = k -gtleftchild72 73 if (p == f)74 f-gtrightchild = k-gtrightchild 75 else76 f-gtleftchild = k-gtrightchild77 78 if (k = NULL) k挪至原 p處繼承 p的左右指標79 k-gtleftchild = p-gtleftchild80 k-gtrightchild = p-gtrightchild81 82 if (father == NULL) root = k83 else if (x lt father-gtdata) 決定 k是 father的左

或右兒子84 father-gtleftchild = k85 else86 father-gtrightchild = k87 free(p)88 return 1 成功地刪除 x於此傳回 1返回呼叫處89 51~89行為找到 x時應有的處理步驟

90 else 尚未找到 x繼續往下一階層找

91 father = p92 if (x lt p-gtdata)93 p = p-gtleftchild94 else95 p = p-gtrightchild96 97 98 return 0 未找到 x傳回 099

510  樹林 (forest)

bull 定義樹林(森林)是由 n 棵沒有交集的樹所構成的集合 n 0

下圖為一包含三棵樹的樹林

5101 轉換樹林為二元樹 之前我們以二元樹來表示樹因為二元樹有較多

好用的性質在此我們也將樹林轉換成二元樹看待

在 522 節中我們嘗試了用左子右兄弟的方式來表示樹這個方法事實上已將樹轉換成二元樹

即用左子右兄弟的結構重新組織樹林中的每一棵樹這些樹的樹根將視為兄弟而最左邊的樹則視為樹林二元樹的樹根如此一來樹林即可轉換成二元樹

範例 5-25右下圖為右上圖三棵樹所對應的三棵左子右兄弟二元樹

下圖則為上圖樹林所對應的一棵樹林二元樹

演算法 5-1  轉換樹林成二元樹輸入樹林 F = T1 T2 Tn

輸出 F 的二元樹表示 BT=(T1 T2 Tn)1 if (n == 0) 輸出空二元樹 2 else

3 root(T1 T2 Tn) = T1

4 leftsubtree(T1 T2 Tn) =

BT(T11 T12 T1m)5 BT(T11 T12 T1m)為 T1的子樹6 rightsubtree(T1 T2 Tn) =

BT(T2 T3 Tn)

樹林轉換成二元樹的過程樹林轉換成二元樹的過程

  • Slide 1
  • Slide 2
  • Slide 3
  • Slide 4
  • Slide 5
  • Slide 6
  • Slide 7
  • Slide 8
  • Slide 9
  • Slide 10
  • Slide 11
  • Slide 12
  • Slide 13
  • Slide 14
  • Slide 15
  • Slide 16
  • Slide 17
  • Slide 18
  • Slide 19
  • Slide 20
  • Slide 21
  • Slide 22
  • Slide 23
  • Slide 24
  • Slide 25
  • Slide 26
  • Slide 27
  • Slide 28
  • Slide 29
  • Slide 30
  • Slide 31
  • Slide 32
  • Slide 33
  • Slide 34
  • Slide 35
  • Slide 36
  • Slide 37
  • Slide 38
  • Slide 39
  • Slide 40
  • Slide 41
  • Slide 42
  • Slide 43
  • Slide 44
  • Slide 45
  • Slide 46
  • Slide 47
  • Slide 48
  • Slide 49
  • Slide 50
  • Slide 51
  • Slide 52
  • Slide 53
  • Slide 54
  • Slide 55
  • Slide 56
  • Slide 57
  • Slide 58
  • Slide 59
  • Slide 60
  • Slide 61
  • Slide 62
  • Slide 63
  • Slide 64
  • Slide 65
  • Slide 66
  • Slide 67
  • Slide 68
  • Slide 69
  • Slide 70
  • Slide 71
  • Slide 72
  • Slide 73
  • Slide 74
  • Slide 75
  • Slide 76
  • Slide 77
  • Slide 78
  • Slide 79
  • Slide 80
  • Slide 81
  • Slide 82
  • Slide 83
  • Slide 84
  • Slide 85
  • Slide 86
  • Slide 87
  • Slide 88
  • Slide 89
  • Slide 90
  • Slide 91
  • Slide 92
  • Slide 93
  • Slide 94
  • Slide 95
  • Slide 96
  • Slide 97
  • Slide 98
  • Slide 99
  • Slide 100
  • Slide 101
  • Slide 102
  • Slide 103
Page 48: 第五章 樹

22 struct BTreeNode DeleteQueue()23 struct BTreeNode Tnode24 struct QNode old_front25 if (rear == NULL)26 QueueEmpty()27 else28 Tnode = front-gttreenode29 old_front = front30 front = front-gtnext31 free(old_front)32 return Tnode33 34 35 void LevelOrder (struct BTreeNode node)36 AddQueue(node)37 while (front =NULL)38 node = DeleteQueue()39 cout ltlt node-gtdata40 if (node-gtleftchild = NULL)41 AddQueue(node-gtleftchild)42 if (node-gtrightchild = NULL)43 AddQueue(node-gtrightchild)44 45

35 void LevelOrder (struct BTreeNode node)36 AddQueue(node)37 while (front =NULL)38 node = DeleteQueue()39 cout ltlt node-gtdata40 if (node-gtleftchild = NULL)41 AddQueue(node-gtleftchild)42 if (node-gtrightchild = NULL)43 AddQueue(node-gtrightchild)44 45

bull Infix ABCbull Postfix ABC CBA ACB BCA

BAC CAB

B

A CA

B

C

B

A

C

C

B

AB

A

C

二元樹中序與前序式中序與後序式bull 每一棵二元樹皆有唯一的一對中序與前序表示法也有唯一的中序與後序次序

bull 換句話說給予一對中序與前序或中序與後序即可決定一棵二元樹

bull 然而給予前序和後序次序並不能決定唯一的二元樹可能會產生兩棵不同的二元樹

bull 想想階層走訪和中序走訪順序可否決定唯一的二元樹

A

B C

H J D E

F G

prefix ABHJCDFGEinfix HBJAFDGCEpostfix HJBFGDECA

VLRLVRLRV

A

prefix BHJinfix HBJ

prefix CDFGEinfix FDGCE

prefix ABHJCDFGEinfix HBJAFDGCE

A

B C

H J D E

F G

prefix ABHJCDFGE

infix HBJAFDGCEk-1

struct BTreeNode BTree_InfixPrefix (AnsiString infix AnsiString prefix)

int k struct BTreeNode node if (infixLength==0) return NULLnode = (struct BTreeNode ) malloc (sizeof(struct BTreeNode))node-gtdata = prefix[1]k = find(prefix[1] infix)node-gtleftchild = BTree_InfixPrefix

(infix[1k-1] prefix[2k])node-gtrightchild = BTree_InfixPrefix(infix[k+1infixLength]prefix[k+1prefixLength])

VLR

LVRk1

k2 k+1~

k+1~

1

struct BTreeNode BTree_InfixPostfix (AnsiString infix AnsiString prefix)

int k struct BTreeNode node if (infixLength==0) return NULLnode = (struct BTreeNode ) malloc (sizeof(struct BTreeNode))node-gtdata = postfix[postfixLength]k = find(postfix[postfixLength] infix)node-gtleftchild = BTree_InfixPrefix

(infix[1k-1] postfix[1k-1])node-gtrightchild = BTree_InfixPrefix(infix[k+1infixLength]postfix[k+1postfixLength-1])

56  二元樹的複製與相等測試程式 5-8 二元樹的複製1 struct BTreeNode2 struct BTreeNode leftchild3 char data4 struct BTreeNode rightchild5 6 struct BTreeNode root7 struct BTreeNode Copy(struct BTreeNode TreeRoot)8 struct BTreeNode CopyRoot9 if (TreeRoot == NULL)10 return NULL11 CopyRoot =(struct BTreeNode ) malloc (sizeof(BTreeNode))12 CopyRoot-gtdata = TreeRoot-gtdata13 CopyRoot-gtleftchild = Copy(TreeRoot-gtleftchild)14 CopyRoot-gtrightchild = Copy(TreeRoot-

gtrightchild)15 return CopyRoot16

程式 5-9 檢測兩棵二元樹是否相等1 struct BTreeNode2 struct BTreeNode leftchild3 char data4 struct BTreeNode rightchild5 6 struct BtreeNode rightchild7 int Equal(struct BTreeNode ustruct BTreeNode v)8 if ((u==NULL) ampamp (v==NULL)) return 19 if ((u=NULL) ampamp (v=NULL)10 ampamp (u-gtdata == v-gtdata )11 ampamp Equal(u-gtleftchildv-gtleftchild)12 ampamp Equal(u-gtrightchildv-gtrightchild))

13 return 114 return 015

Counting Nodes in a Binary Tree

int CountingNodes(struct BTreeNodes node)

if (node == NULL) return 0 int leftchildren = CountingNodes(node-gtleftchild)

int rightchildren= CountingNodes(node-gtrightchild)

return leftchildren+rightchildren

Counting Leaves in a Binary Tree

int CountingLeaves(struct BTreeNodes node)

if (node == NULL) return 0 int leftleaves = CountingLeaves(node-gtleftchild)

int rightchildren= CountingLeaves(node-gtrightchild)

if (leftleaves==0 ampamp leftleaves==0) return 1

return leftleaves + leftleaves

57  引線二元樹bull 在二元樹的鍵結表示中樹葉節點的兩個子樹指標皆指向 NULL 由定理 5-1 得知 n = E+1 其中 n 為節點個數 E 為分支數

bull 而 n 個鍵結節點有 2n 個指標空間每個分支恰佔用一個指標空間共有 E (=n-1) 個指標有用到(不是空的)而共有 n+1 個指標空間是 NULL 比非空的節點還多

bull 有學者提出對這些「放空指標的空間」加以利用的概念mdash與其放空指標不如放指向其它節點的指標稱之為引線 (thread) 使得某些運算(如走訪hellip等)可以加快

bull 這個概念發展出了引線二元樹 (threaded binary tree) 這種資料結構

範例 5-17下圖為加了引線的二元樹

加入的引線將使二元樹的中序走訪更加便利沿著上右圖箭頭所指示的順序走訪即可完成此二元樹的中序走訪GDHBAIECF 為此二元樹的中序表示

bull 範例 5-17 中所加的引線目的在方便二元樹的中序走訪

bull 對樹葉節點 p 而言令其資料為 P 則引線指標的規則如下

(1) p 的左子樹指標指向中序表示中 P 的前一個 元素(前接元素 (predecessor) )

(2) p 的右子樹指標指向中序表示中 P 的後一個 元素(後繼元素 (successor) )

為了區別節點指標放的是一般指標(內部節點)抑或是引線指標(樹葉節點)我們另以 2 個欄位分別區別左和右子樹指標空間存放的對象其節點的記憶體配置有如下圖所示

其中 leftthread (rightthread) 為 0 時表示 leftchild (rightchild) 存放的是一般節點指標而 leftthread (rightthread) 為 1 時表示 leftchild (rightchild) 存放的是引線指標1 struct TBTreeNode2 int leftthread3 struct TBTreeNode leftchild4 char data5 struct TBTreeNode rightchild6 int rightthread7

由右側的引線二元樹可知有最左和最右兩條引線尚無妥善的安排我們另設計一空的引線節點做為樹根如下圖所示

571  引線二元樹的中序走訪決定節點 p 在中序表示中的後繼節點 q (簡稱中序後繼點)

程式 5-10決定節點 node的中序後繼點1 struct TBTreeNode2 int leftthread3 struct TBTreeNode leftchild4 char data5 struct TBTreeNode rightchild6 int rightthread7 8 struct TBTreeNode root9 struct TBTreeNode Next(struct TBTreeNode node)

10 struct TBTreeNode temp11 temp = node-gtrightchild12 if (node-gtrightthead) return temp13 while (temp-gtleftthead) temp=temp-gtleftchild

14 return temp15

程式 5-11引線二元樹的中序表示(接程式 5-10)1 void InorderTBTree()2 struct TBTreeNode node3 for(node=Next(root)node=rootnode=Next(node))

4 cout ltlt node-gtdata5

bull只要 node 是引線二元樹上的任一節點透過呼叫 Next(node) 即可找到其在中序表示中的後繼節點bull(試想若程式 5-10 中的程序 Next 所傳入的參數指標正是 root Next(root) 是否可正常運作請思考)bull於是只須從中序表示中的第一節點起讓每一節點 node 都執行 Next(node) 程序即可得引線二元樹的中序表示

572  在引線二元樹中加入節點在引線二元樹中樹葉節點節點的右子樹指標所指向或內部節點右子樹的最左樹葉為其中序後繼節點而且任一節點若無左子樹則其左子樹指標乃指向其中序前接節點hellip這些性質必須在節點新增進入引線二元樹時也要保持接下來討論節點 new 插入成為引線二元樹的節點 p 的右子節點的情形至於插入成為左子節點的情形則讓各位模擬推敲

572572  在引線二元樹中加入節點 在引線二元樹中加入節點 (( 續續 ))

newnew 會成為會成為 pp 的右子節點的情況共有以下兩種的右子節點的情況共有以下兩種(1) (1) 若若 pp 沒有右子節點 沒有右子節點 ((p-gtrightthread p-gtrightthread 為為1) 1) 如圖如圖 ((a) a) 則 則 new new 插入後應形成如圖插入後應形成如圖 ((b) b) 所示所示

(2)(2) 若若 pp 有右子節點 有右子節點 ((p-gtrightthread p-gtrightthread 為為0) 0) 如圖如圖 ((c) c) 則 則 new new 插入後應形成如插入後應形成如圖圖 ((d) d) 所示所示

程式 5-12  插入節點 new 成為引線二元樹中節點 p的右子節點(上接程式 5-10 5-11 )

1 void InsertRight(struct TBTreeNode p 2 struct TBTreeNode new)3 struct TBTreeNode q4 new-gtrightchild = p-gtrightchild5 new-gtrightthead = p-gtrightthread6 new-gtleftchild = p7 new-gtleftthread = 18 p-gtrightchild = new9 p-gtrightthead = 010 if (new-gtrightthread)11 q = Next(new)12 q-gtleftchild = new13 14

58  堆積bull 「堆積」 (heap) 是一個特殊的完備二元樹節點的資料與其子節點的資料之間有事先定好的大小關係存在bull 定義ndash最大堆疊為一完備二元樹任一節點的資料內容不小於其子節點的資料內容ndash最小堆疊為一完備二元樹任一節點的資料內容不大於其子節點的資料內容

581  插入節點至最大堆積中最大堆積是一完備二元樹所以如圖 (a) 之最大堆積於加入新節點後樹的形狀必須如圖 (b) 所示灰色節點即為新節點若加入的節點資料分別為 1 9 12 則其最大堆積將分別變成如圖 (c) (d) (e) 所示

程式 5-13 新增節點至最大堆積中最大堆積本身是一棵完備二元樹可以用陣列表示之 1 define maxsize 1002 int heap[maxsize]3 int n i4 void InsertHeap(int x)5 if (n == maxsize) HeapFull()6 else7 n++8 i = n9 while ((i gt 1) ampamp (x gt heap[i2]))10 heap[i] = heap[i2]11 i = 212 13 heap[i] = x14 16

程序 InsertHeap 在插入第 n 個資料時需要的時間複雜度為 O(logn)

1 100

2 93

3 84

4 77

5 65

6 81

7 79

8 66

9 70

10 50

100

84

77

93

8165

66 70

79

50

1

2 3

4 5 6 7

8 9 10

1 define maxsize 1002 int heap[maxsize]3 int n i4 void InsertHeap(int x)5 if (n == maxsize) HeapFull()6 else7 n++8 i = n9 while ((i gt 1) ampamp (x gt heap[i2]))10 heap[i] = heap[i2]11 i = 212 13 heap[i] = x14 16

582 刪除最大堆積中的最大資料圖 (a) 中的最大堆積在刪除最大資料後其樹形應變成如圖 (b) 所示將最未一個樹葉節點的資料 6 暫挪至樹根節點內如圖 (c) 再將圖 (c) 中非最大堆積的結構調整成為最大堆積 10 和 8 中選大者與 6對調即成圖 (d)

程式 5-14 刪除最大堆積中的最大資料(上接程式 5-13 )1 int DeleteHeap()2 int x i j3 if (n == 0) HeapEmpty()4 else5 x = heap[1]6 heap[1] = heap[n]7 n--8 i = 19 while (i lt= n2)10 if (heap[2i] gt heap[2i+1])11 j = 2i12 else13 j = 2i+114 if (heap[i]gtheap[j]) break15 swap(heap[i] heap[j])16 i = j17 18 return x19 20

其時間複雜度與其堆積高度相同是為O(logn) 其中 n 為堆積中的元素個數

1 100

2 93

3 84

4 77

5 65

6 81

7 79

8 66

9 70

10 50

100

84

77

93

8165

66 70

79

50

1

2 3

4 5 6 7

8 9 10

1 int DeletHeap()2 int x i j3 if(n == 0) HeapEmpty()4 else5 x = heap[1]6 heap[1] = heap[n]7 n--8 i = 19 while (i lt= n2)10 if ([heap[2i]] gt heap[2i+1])11 j = 2i12 else13 j = 2i+114 if (heap[i]gtheap[j]) break15 swap(heap[i] heap[j])16 i = j17 18 return x19 20

583  優先佇列bull 由於最大堆積(或最小堆積)的結構可提供最大元素(或最小元素)而其維護堆積的成本為 O(logn) n 為總元素個數

bull 堆積常被當成「優先佇列」 (priority queue) 使用mdash此時佇列被組織成完備二元樹若新加入佇列的資料有大小關係優先順序的需求時此優先佇列即以 O(logn) 的代價將新資料放入優先佇列(可能是最大或最小堆積依實際大小關係優先順序的需求而定)中mdash提供依元素的優先順序做為處理順序的服務

bull 一般的佇列為先進先出先到的先行處理優先佇列則是依據其優先順序優先順序高者先行處理與來到的先後無關

範例 5-21bull 在「分時」 (time sharing) 的作業系統中我們不希望使用中央處理器 (Central Processing Unit CPU) 時間短的「處理程序」 (process) 等候過久於是有「最短時間工作優先」 (shortest-job-first) 的排程策略此時等候的處理程序即可組織成之一最小堆積而 CPU 每次即取出存於樹根的處理程序予以處理任何新來的處理程序則以 O(logn) 的時間加入此優先佇列(最小堆積)中

bull 注意之前提過的線性佇列新來的直接加入成為新的尾端元素花O(1) 的時間然線性佇列本身沒有大小的關係每次要取出最大(小)的資料得花O(n) 的代價(在所有元素中挑出最大(小)者) n 為佇列內的元素個數而優先佇列是在新元素加入時要花O(logn) 的時間至於取出最大(小)則只須 O(1) 的時間

59  二元搜尋樹bull 二元搜尋樹是一棵二元樹可能是空二元樹若不為空二元樹則滿足下列性質ndash所有節點內的資料內容是相異的 ndash左子節點的資內容 ( 如果有 ) 比父節點的資料內容小 ndash右子節點的資料內容 ( 如果有 ) 比父節點的資料內容大 ndash以左和右子節點為樹根的左子樹和右子樹也是二元搜尋樹

在二元搜尋樹中搜尋資料二元搜尋樹對資料大小的掌握十分明確若恰與節點 p 資料相等則搜尋成功若欲搜尋的資料 x 比樹節點 p 中的資料小 則只須搜尋節點 p 的左子樹若比節點 p 中的資料大 則只須搜尋 p 的右子樹這樣的判斷可以遞迴地應用在各子樹節點 中

程式 5-15 在二元搜尋樹中搜尋資料(遞迴呼叫)1 struct BSTreeNode2 struct BSTreeNode leftchild3 int data4 struct BSTreeNode rightchild5 6 struct BSTreeNode root7 struct BSTreeNode SearchBST(

struct BSTreeNode tree int x)8 if (tree == NULL) return NULL9 if (x == tree-gtdata) return tree10 if (x lt tree-gtdata)11 return SearchBST(tree-gtleftchild x)

12 return SearchBST(tree-gtrightchild x)13

程式 5-16 搜尋二元搜尋樹的資料(非遞迴呼叫)在二元搜尋樹十分龐大時遞迴呼叫的效率不會太好可利用迴圈來完成搜尋的動作以提高執行效率請看程式 5-16

14 struct BSTreeNode Search BST_iterative15 (struct BSTreeNode node int x)16 while (node = NULL)17 if (x == node-gtdata) return node18 if (x lt node-gtdata)19 node = node-gtleftchild20 else21 node = node-gtrightchild22 23 return NULL24

592  新增資料於二元搜尋樹中欲新增資料進入一二元搜尋樹中得先決定該資料在二元搜尋樹中應出現的位置這可透過上節提到的搜尋動作來解決

在二元搜尋樹定義的性質 (1) 中我們限定了元素必須相異所以搜尋新資料的結果必是 NULL

必須將搜尋的結果改為新增資料所應在位置的節點指標

程式 5-17  在二元搜尋樹中新增資料25 int InsertBST(int x)26 struct BSTreeNode p q27 p = root q = NULL28 while (p = NULL)29 q = p30 if (x == p-gtdata) return 0

若不考慮重複則可省略之31 if (x lt p-gtdata)32 p = p-gtleftchild33 else34 p = p-gtrightchild35

3636 p = (struct BSTreeNode ) p = (struct BSTreeNode ) malloc (sizeof(BSTreeNode))malloc (sizeof(BSTreeNode))

3737 p-gtdata = x p-gtdata = x3838 p-gtleftchild = p-gtrightchild = p-gtleftchild = p-gtrightchild = NULLNULL3939 if (root == NULL) root = p if (root == NULL) root = p4040 else if (x lt q-gtdata) else if (x lt q-gtdata)4141 q-gtleftchild = p q-gtleftchild = p4242 else else4343 q-gtrightchild = p q-gtrightchild = p4444 return 1 return 145 45

程式 程式 5-175-17  在二元搜尋樹中新增資料  在二元搜尋樹中新增資料 (( 續續 ))

範例 5-23 圖 (b) 為圖 (a) 新增整數 6 之後的結果圖 (c) (d) (e) (f) 則分別是逐步再加入 28 18

10 21 的結果

bull若目前的二元搜尋樹高度為 h 則新增資料得花O(h) 的時間找到其所應在的位置遂新增資料至二元搜尋樹中的時間複雜之度為 O(h)

593 自二元搜尋樹中刪除資料欲自二元搜尋樹中刪除資料可能會面臨兩種情況(1) 刪除節點 p 位於樹葉節點上如下圖所示 這是比較容易處理的情況

(2) 刪除的節點 p 不是樹葉刪除節點 p 後原 p 所指的節點可由 q ( q 中包含了 p 左子樹中的最大節點值)或 r ( r 中包含了 p 右子樹中的最小節點值)來取代以維持二元搜尋樹中資料的大小關係其結果分別如圖 (b) 和 (c) 所示而圖 (d) 則標明了指標會改變的節點位置即 p 會消失 father 的左或右子樹指標會異動 k(可能是 q 或 r )會移到原來 p 的位置其父節點 f 的左或右子樹指標會異動其中 father 為 p 父節點 f 為 k 的父節點

程式 5-18 自二元搜尋樹中刪除資料46 int DeleteBST (int x)47 Struct BSTreeNode p father k f48 p = root father = NULL49 while (p = NULL)50 if (x == p-gtdata) 找到 x 所在的節點 p 了51 if ((p-gtleftchild == NULL) ampamp 52 (p-gtrightchild == NULL))53 k = NULL p為樹葉節點54 else if (p-gtleftchild = NULL )x位於非子節

點55 f = p 找出 p左子樹的最右樹葉56 k = p-gtleftchild57 while (k-gtrightchild =NULL)58 f = k59 k = k-gtrightchild60 k將挪至原 p處61 if (p == f)62 f-gtleftchild = k-gtleftchild 63 else64 f-gtrightchild = k-gtleftchild 65

66 else p無左子樹67 f = p 找出 p右子樹的最左樹葉68 k = p-gtrightchild69 while (k-gtleftchild = NULL)70 f = k71 k = k -gtleftchild72 73 if (p == f)74 f-gtrightchild = k-gtrightchild 75 else76 f-gtleftchild = k-gtrightchild77 78 if (k = NULL) k挪至原 p處繼承 p的左右指標79 k-gtleftchild = p-gtleftchild80 k-gtrightchild = p-gtrightchild81 82 if (father == NULL) root = k83 else if (x lt father-gtdata) 決定 k是 father的左

或右兒子84 father-gtleftchild = k85 else86 father-gtrightchild = k87 free(p)88 return 1 成功地刪除 x於此傳回 1返回呼叫處89 51~89行為找到 x時應有的處理步驟

90 else 尚未找到 x繼續往下一階層找

91 father = p92 if (x lt p-gtdata)93 p = p-gtleftchild94 else95 p = p-gtrightchild96 97 98 return 0 未找到 x傳回 099

510  樹林 (forest)

bull 定義樹林(森林)是由 n 棵沒有交集的樹所構成的集合 n 0

下圖為一包含三棵樹的樹林

5101 轉換樹林為二元樹 之前我們以二元樹來表示樹因為二元樹有較多

好用的性質在此我們也將樹林轉換成二元樹看待

在 522 節中我們嘗試了用左子右兄弟的方式來表示樹這個方法事實上已將樹轉換成二元樹

即用左子右兄弟的結構重新組織樹林中的每一棵樹這些樹的樹根將視為兄弟而最左邊的樹則視為樹林二元樹的樹根如此一來樹林即可轉換成二元樹

範例 5-25右下圖為右上圖三棵樹所對應的三棵左子右兄弟二元樹

下圖則為上圖樹林所對應的一棵樹林二元樹

演算法 5-1  轉換樹林成二元樹輸入樹林 F = T1 T2 Tn

輸出 F 的二元樹表示 BT=(T1 T2 Tn)1 if (n == 0) 輸出空二元樹 2 else

3 root(T1 T2 Tn) = T1

4 leftsubtree(T1 T2 Tn) =

BT(T11 T12 T1m)5 BT(T11 T12 T1m)為 T1的子樹6 rightsubtree(T1 T2 Tn) =

BT(T2 T3 Tn)

樹林轉換成二元樹的過程樹林轉換成二元樹的過程

  • Slide 1
  • Slide 2
  • Slide 3
  • Slide 4
  • Slide 5
  • Slide 6
  • Slide 7
  • Slide 8
  • Slide 9
  • Slide 10
  • Slide 11
  • Slide 12
  • Slide 13
  • Slide 14
  • Slide 15
  • Slide 16
  • Slide 17
  • Slide 18
  • Slide 19
  • Slide 20
  • Slide 21
  • Slide 22
  • Slide 23
  • Slide 24
  • Slide 25
  • Slide 26
  • Slide 27
  • Slide 28
  • Slide 29
  • Slide 30
  • Slide 31
  • Slide 32
  • Slide 33
  • Slide 34
  • Slide 35
  • Slide 36
  • Slide 37
  • Slide 38
  • Slide 39
  • Slide 40
  • Slide 41
  • Slide 42
  • Slide 43
  • Slide 44
  • Slide 45
  • Slide 46
  • Slide 47
  • Slide 48
  • Slide 49
  • Slide 50
  • Slide 51
  • Slide 52
  • Slide 53
  • Slide 54
  • Slide 55
  • Slide 56
  • Slide 57
  • Slide 58
  • Slide 59
  • Slide 60
  • Slide 61
  • Slide 62
  • Slide 63
  • Slide 64
  • Slide 65
  • Slide 66
  • Slide 67
  • Slide 68
  • Slide 69
  • Slide 70
  • Slide 71
  • Slide 72
  • Slide 73
  • Slide 74
  • Slide 75
  • Slide 76
  • Slide 77
  • Slide 78
  • Slide 79
  • Slide 80
  • Slide 81
  • Slide 82
  • Slide 83
  • Slide 84
  • Slide 85
  • Slide 86
  • Slide 87
  • Slide 88
  • Slide 89
  • Slide 90
  • Slide 91
  • Slide 92
  • Slide 93
  • Slide 94
  • Slide 95
  • Slide 96
  • Slide 97
  • Slide 98
  • Slide 99
  • Slide 100
  • Slide 101
  • Slide 102
  • Slide 103
Page 49: 第五章 樹

35 void LevelOrder (struct BTreeNode node)36 AddQueue(node)37 while (front =NULL)38 node = DeleteQueue()39 cout ltlt node-gtdata40 if (node-gtleftchild = NULL)41 AddQueue(node-gtleftchild)42 if (node-gtrightchild = NULL)43 AddQueue(node-gtrightchild)44 45

bull Infix ABCbull Postfix ABC CBA ACB BCA

BAC CAB

B

A CA

B

C

B

A

C

C

B

AB

A

C

二元樹中序與前序式中序與後序式bull 每一棵二元樹皆有唯一的一對中序與前序表示法也有唯一的中序與後序次序

bull 換句話說給予一對中序與前序或中序與後序即可決定一棵二元樹

bull 然而給予前序和後序次序並不能決定唯一的二元樹可能會產生兩棵不同的二元樹

bull 想想階層走訪和中序走訪順序可否決定唯一的二元樹

A

B C

H J D E

F G

prefix ABHJCDFGEinfix HBJAFDGCEpostfix HJBFGDECA

VLRLVRLRV

A

prefix BHJinfix HBJ

prefix CDFGEinfix FDGCE

prefix ABHJCDFGEinfix HBJAFDGCE

A

B C

H J D E

F G

prefix ABHJCDFGE

infix HBJAFDGCEk-1

struct BTreeNode BTree_InfixPrefix (AnsiString infix AnsiString prefix)

int k struct BTreeNode node if (infixLength==0) return NULLnode = (struct BTreeNode ) malloc (sizeof(struct BTreeNode))node-gtdata = prefix[1]k = find(prefix[1] infix)node-gtleftchild = BTree_InfixPrefix

(infix[1k-1] prefix[2k])node-gtrightchild = BTree_InfixPrefix(infix[k+1infixLength]prefix[k+1prefixLength])

VLR

LVRk1

k2 k+1~

k+1~

1

struct BTreeNode BTree_InfixPostfix (AnsiString infix AnsiString prefix)

int k struct BTreeNode node if (infixLength==0) return NULLnode = (struct BTreeNode ) malloc (sizeof(struct BTreeNode))node-gtdata = postfix[postfixLength]k = find(postfix[postfixLength] infix)node-gtleftchild = BTree_InfixPrefix

(infix[1k-1] postfix[1k-1])node-gtrightchild = BTree_InfixPrefix(infix[k+1infixLength]postfix[k+1postfixLength-1])

56  二元樹的複製與相等測試程式 5-8 二元樹的複製1 struct BTreeNode2 struct BTreeNode leftchild3 char data4 struct BTreeNode rightchild5 6 struct BTreeNode root7 struct BTreeNode Copy(struct BTreeNode TreeRoot)8 struct BTreeNode CopyRoot9 if (TreeRoot == NULL)10 return NULL11 CopyRoot =(struct BTreeNode ) malloc (sizeof(BTreeNode))12 CopyRoot-gtdata = TreeRoot-gtdata13 CopyRoot-gtleftchild = Copy(TreeRoot-gtleftchild)14 CopyRoot-gtrightchild = Copy(TreeRoot-

gtrightchild)15 return CopyRoot16

程式 5-9 檢測兩棵二元樹是否相等1 struct BTreeNode2 struct BTreeNode leftchild3 char data4 struct BTreeNode rightchild5 6 struct BtreeNode rightchild7 int Equal(struct BTreeNode ustruct BTreeNode v)8 if ((u==NULL) ampamp (v==NULL)) return 19 if ((u=NULL) ampamp (v=NULL)10 ampamp (u-gtdata == v-gtdata )11 ampamp Equal(u-gtleftchildv-gtleftchild)12 ampamp Equal(u-gtrightchildv-gtrightchild))

13 return 114 return 015

Counting Nodes in a Binary Tree

int CountingNodes(struct BTreeNodes node)

if (node == NULL) return 0 int leftchildren = CountingNodes(node-gtleftchild)

int rightchildren= CountingNodes(node-gtrightchild)

return leftchildren+rightchildren

Counting Leaves in a Binary Tree

int CountingLeaves(struct BTreeNodes node)

if (node == NULL) return 0 int leftleaves = CountingLeaves(node-gtleftchild)

int rightchildren= CountingLeaves(node-gtrightchild)

if (leftleaves==0 ampamp leftleaves==0) return 1

return leftleaves + leftleaves

57  引線二元樹bull 在二元樹的鍵結表示中樹葉節點的兩個子樹指標皆指向 NULL 由定理 5-1 得知 n = E+1 其中 n 為節點個數 E 為分支數

bull 而 n 個鍵結節點有 2n 個指標空間每個分支恰佔用一個指標空間共有 E (=n-1) 個指標有用到(不是空的)而共有 n+1 個指標空間是 NULL 比非空的節點還多

bull 有學者提出對這些「放空指標的空間」加以利用的概念mdash與其放空指標不如放指向其它節點的指標稱之為引線 (thread) 使得某些運算(如走訪hellip等)可以加快

bull 這個概念發展出了引線二元樹 (threaded binary tree) 這種資料結構

範例 5-17下圖為加了引線的二元樹

加入的引線將使二元樹的中序走訪更加便利沿著上右圖箭頭所指示的順序走訪即可完成此二元樹的中序走訪GDHBAIECF 為此二元樹的中序表示

bull 範例 5-17 中所加的引線目的在方便二元樹的中序走訪

bull 對樹葉節點 p 而言令其資料為 P 則引線指標的規則如下

(1) p 的左子樹指標指向中序表示中 P 的前一個 元素(前接元素 (predecessor) )

(2) p 的右子樹指標指向中序表示中 P 的後一個 元素(後繼元素 (successor) )

為了區別節點指標放的是一般指標(內部節點)抑或是引線指標(樹葉節點)我們另以 2 個欄位分別區別左和右子樹指標空間存放的對象其節點的記憶體配置有如下圖所示

其中 leftthread (rightthread) 為 0 時表示 leftchild (rightchild) 存放的是一般節點指標而 leftthread (rightthread) 為 1 時表示 leftchild (rightchild) 存放的是引線指標1 struct TBTreeNode2 int leftthread3 struct TBTreeNode leftchild4 char data5 struct TBTreeNode rightchild6 int rightthread7

由右側的引線二元樹可知有最左和最右兩條引線尚無妥善的安排我們另設計一空的引線節點做為樹根如下圖所示

571  引線二元樹的中序走訪決定節點 p 在中序表示中的後繼節點 q (簡稱中序後繼點)

程式 5-10決定節點 node的中序後繼點1 struct TBTreeNode2 int leftthread3 struct TBTreeNode leftchild4 char data5 struct TBTreeNode rightchild6 int rightthread7 8 struct TBTreeNode root9 struct TBTreeNode Next(struct TBTreeNode node)

10 struct TBTreeNode temp11 temp = node-gtrightchild12 if (node-gtrightthead) return temp13 while (temp-gtleftthead) temp=temp-gtleftchild

14 return temp15

程式 5-11引線二元樹的中序表示(接程式 5-10)1 void InorderTBTree()2 struct TBTreeNode node3 for(node=Next(root)node=rootnode=Next(node))

4 cout ltlt node-gtdata5

bull只要 node 是引線二元樹上的任一節點透過呼叫 Next(node) 即可找到其在中序表示中的後繼節點bull(試想若程式 5-10 中的程序 Next 所傳入的參數指標正是 root Next(root) 是否可正常運作請思考)bull於是只須從中序表示中的第一節點起讓每一節點 node 都執行 Next(node) 程序即可得引線二元樹的中序表示

572  在引線二元樹中加入節點在引線二元樹中樹葉節點節點的右子樹指標所指向或內部節點右子樹的最左樹葉為其中序後繼節點而且任一節點若無左子樹則其左子樹指標乃指向其中序前接節點hellip這些性質必須在節點新增進入引線二元樹時也要保持接下來討論節點 new 插入成為引線二元樹的節點 p 的右子節點的情形至於插入成為左子節點的情形則讓各位模擬推敲

572572  在引線二元樹中加入節點 在引線二元樹中加入節點 (( 續續 ))

newnew 會成為會成為 pp 的右子節點的情況共有以下兩種的右子節點的情況共有以下兩種(1) (1) 若若 pp 沒有右子節點 沒有右子節點 ((p-gtrightthread p-gtrightthread 為為1) 1) 如圖如圖 ((a) a) 則 則 new new 插入後應形成如圖插入後應形成如圖 ((b) b) 所示所示

(2)(2) 若若 pp 有右子節點 有右子節點 ((p-gtrightthread p-gtrightthread 為為0) 0) 如圖如圖 ((c) c) 則 則 new new 插入後應形成如插入後應形成如圖圖 ((d) d) 所示所示

程式 5-12  插入節點 new 成為引線二元樹中節點 p的右子節點(上接程式 5-10 5-11 )

1 void InsertRight(struct TBTreeNode p 2 struct TBTreeNode new)3 struct TBTreeNode q4 new-gtrightchild = p-gtrightchild5 new-gtrightthead = p-gtrightthread6 new-gtleftchild = p7 new-gtleftthread = 18 p-gtrightchild = new9 p-gtrightthead = 010 if (new-gtrightthread)11 q = Next(new)12 q-gtleftchild = new13 14

58  堆積bull 「堆積」 (heap) 是一個特殊的完備二元樹節點的資料與其子節點的資料之間有事先定好的大小關係存在bull 定義ndash最大堆疊為一完備二元樹任一節點的資料內容不小於其子節點的資料內容ndash最小堆疊為一完備二元樹任一節點的資料內容不大於其子節點的資料內容

581  插入節點至最大堆積中最大堆積是一完備二元樹所以如圖 (a) 之最大堆積於加入新節點後樹的形狀必須如圖 (b) 所示灰色節點即為新節點若加入的節點資料分別為 1 9 12 則其最大堆積將分別變成如圖 (c) (d) (e) 所示

程式 5-13 新增節點至最大堆積中最大堆積本身是一棵完備二元樹可以用陣列表示之 1 define maxsize 1002 int heap[maxsize]3 int n i4 void InsertHeap(int x)5 if (n == maxsize) HeapFull()6 else7 n++8 i = n9 while ((i gt 1) ampamp (x gt heap[i2]))10 heap[i] = heap[i2]11 i = 212 13 heap[i] = x14 16

程序 InsertHeap 在插入第 n 個資料時需要的時間複雜度為 O(logn)

1 100

2 93

3 84

4 77

5 65

6 81

7 79

8 66

9 70

10 50

100

84

77

93

8165

66 70

79

50

1

2 3

4 5 6 7

8 9 10

1 define maxsize 1002 int heap[maxsize]3 int n i4 void InsertHeap(int x)5 if (n == maxsize) HeapFull()6 else7 n++8 i = n9 while ((i gt 1) ampamp (x gt heap[i2]))10 heap[i] = heap[i2]11 i = 212 13 heap[i] = x14 16

582 刪除最大堆積中的最大資料圖 (a) 中的最大堆積在刪除最大資料後其樹形應變成如圖 (b) 所示將最未一個樹葉節點的資料 6 暫挪至樹根節點內如圖 (c) 再將圖 (c) 中非最大堆積的結構調整成為最大堆積 10 和 8 中選大者與 6對調即成圖 (d)

程式 5-14 刪除最大堆積中的最大資料(上接程式 5-13 )1 int DeleteHeap()2 int x i j3 if (n == 0) HeapEmpty()4 else5 x = heap[1]6 heap[1] = heap[n]7 n--8 i = 19 while (i lt= n2)10 if (heap[2i] gt heap[2i+1])11 j = 2i12 else13 j = 2i+114 if (heap[i]gtheap[j]) break15 swap(heap[i] heap[j])16 i = j17 18 return x19 20

其時間複雜度與其堆積高度相同是為O(logn) 其中 n 為堆積中的元素個數

1 100

2 93

3 84

4 77

5 65

6 81

7 79

8 66

9 70

10 50

100

84

77

93

8165

66 70

79

50

1

2 3

4 5 6 7

8 9 10

1 int DeletHeap()2 int x i j3 if(n == 0) HeapEmpty()4 else5 x = heap[1]6 heap[1] = heap[n]7 n--8 i = 19 while (i lt= n2)10 if ([heap[2i]] gt heap[2i+1])11 j = 2i12 else13 j = 2i+114 if (heap[i]gtheap[j]) break15 swap(heap[i] heap[j])16 i = j17 18 return x19 20

583  優先佇列bull 由於最大堆積(或最小堆積)的結構可提供最大元素(或最小元素)而其維護堆積的成本為 O(logn) n 為總元素個數

bull 堆積常被當成「優先佇列」 (priority queue) 使用mdash此時佇列被組織成完備二元樹若新加入佇列的資料有大小關係優先順序的需求時此優先佇列即以 O(logn) 的代價將新資料放入優先佇列(可能是最大或最小堆積依實際大小關係優先順序的需求而定)中mdash提供依元素的優先順序做為處理順序的服務

bull 一般的佇列為先進先出先到的先行處理優先佇列則是依據其優先順序優先順序高者先行處理與來到的先後無關

範例 5-21bull 在「分時」 (time sharing) 的作業系統中我們不希望使用中央處理器 (Central Processing Unit CPU) 時間短的「處理程序」 (process) 等候過久於是有「最短時間工作優先」 (shortest-job-first) 的排程策略此時等候的處理程序即可組織成之一最小堆積而 CPU 每次即取出存於樹根的處理程序予以處理任何新來的處理程序則以 O(logn) 的時間加入此優先佇列(最小堆積)中

bull 注意之前提過的線性佇列新來的直接加入成為新的尾端元素花O(1) 的時間然線性佇列本身沒有大小的關係每次要取出最大(小)的資料得花O(n) 的代價(在所有元素中挑出最大(小)者) n 為佇列內的元素個數而優先佇列是在新元素加入時要花O(logn) 的時間至於取出最大(小)則只須 O(1) 的時間

59  二元搜尋樹bull 二元搜尋樹是一棵二元樹可能是空二元樹若不為空二元樹則滿足下列性質ndash所有節點內的資料內容是相異的 ndash左子節點的資內容 ( 如果有 ) 比父節點的資料內容小 ndash右子節點的資料內容 ( 如果有 ) 比父節點的資料內容大 ndash以左和右子節點為樹根的左子樹和右子樹也是二元搜尋樹

在二元搜尋樹中搜尋資料二元搜尋樹對資料大小的掌握十分明確若恰與節點 p 資料相等則搜尋成功若欲搜尋的資料 x 比樹節點 p 中的資料小 則只須搜尋節點 p 的左子樹若比節點 p 中的資料大 則只須搜尋 p 的右子樹這樣的判斷可以遞迴地應用在各子樹節點 中

程式 5-15 在二元搜尋樹中搜尋資料(遞迴呼叫)1 struct BSTreeNode2 struct BSTreeNode leftchild3 int data4 struct BSTreeNode rightchild5 6 struct BSTreeNode root7 struct BSTreeNode SearchBST(

struct BSTreeNode tree int x)8 if (tree == NULL) return NULL9 if (x == tree-gtdata) return tree10 if (x lt tree-gtdata)11 return SearchBST(tree-gtleftchild x)

12 return SearchBST(tree-gtrightchild x)13

程式 5-16 搜尋二元搜尋樹的資料(非遞迴呼叫)在二元搜尋樹十分龐大時遞迴呼叫的效率不會太好可利用迴圈來完成搜尋的動作以提高執行效率請看程式 5-16

14 struct BSTreeNode Search BST_iterative15 (struct BSTreeNode node int x)16 while (node = NULL)17 if (x == node-gtdata) return node18 if (x lt node-gtdata)19 node = node-gtleftchild20 else21 node = node-gtrightchild22 23 return NULL24

592  新增資料於二元搜尋樹中欲新增資料進入一二元搜尋樹中得先決定該資料在二元搜尋樹中應出現的位置這可透過上節提到的搜尋動作來解決

在二元搜尋樹定義的性質 (1) 中我們限定了元素必須相異所以搜尋新資料的結果必是 NULL

必須將搜尋的結果改為新增資料所應在位置的節點指標

程式 5-17  在二元搜尋樹中新增資料25 int InsertBST(int x)26 struct BSTreeNode p q27 p = root q = NULL28 while (p = NULL)29 q = p30 if (x == p-gtdata) return 0

若不考慮重複則可省略之31 if (x lt p-gtdata)32 p = p-gtleftchild33 else34 p = p-gtrightchild35

3636 p = (struct BSTreeNode ) p = (struct BSTreeNode ) malloc (sizeof(BSTreeNode))malloc (sizeof(BSTreeNode))

3737 p-gtdata = x p-gtdata = x3838 p-gtleftchild = p-gtrightchild = p-gtleftchild = p-gtrightchild = NULLNULL3939 if (root == NULL) root = p if (root == NULL) root = p4040 else if (x lt q-gtdata) else if (x lt q-gtdata)4141 q-gtleftchild = p q-gtleftchild = p4242 else else4343 q-gtrightchild = p q-gtrightchild = p4444 return 1 return 145 45

程式 程式 5-175-17  在二元搜尋樹中新增資料  在二元搜尋樹中新增資料 (( 續續 ))

範例 5-23 圖 (b) 為圖 (a) 新增整數 6 之後的結果圖 (c) (d) (e) (f) 則分別是逐步再加入 28 18

10 21 的結果

bull若目前的二元搜尋樹高度為 h 則新增資料得花O(h) 的時間找到其所應在的位置遂新增資料至二元搜尋樹中的時間複雜之度為 O(h)

593 自二元搜尋樹中刪除資料欲自二元搜尋樹中刪除資料可能會面臨兩種情況(1) 刪除節點 p 位於樹葉節點上如下圖所示 這是比較容易處理的情況

(2) 刪除的節點 p 不是樹葉刪除節點 p 後原 p 所指的節點可由 q ( q 中包含了 p 左子樹中的最大節點值)或 r ( r 中包含了 p 右子樹中的最小節點值)來取代以維持二元搜尋樹中資料的大小關係其結果分別如圖 (b) 和 (c) 所示而圖 (d) 則標明了指標會改變的節點位置即 p 會消失 father 的左或右子樹指標會異動 k(可能是 q 或 r )會移到原來 p 的位置其父節點 f 的左或右子樹指標會異動其中 father 為 p 父節點 f 為 k 的父節點

程式 5-18 自二元搜尋樹中刪除資料46 int DeleteBST (int x)47 Struct BSTreeNode p father k f48 p = root father = NULL49 while (p = NULL)50 if (x == p-gtdata) 找到 x 所在的節點 p 了51 if ((p-gtleftchild == NULL) ampamp 52 (p-gtrightchild == NULL))53 k = NULL p為樹葉節點54 else if (p-gtleftchild = NULL )x位於非子節

點55 f = p 找出 p左子樹的最右樹葉56 k = p-gtleftchild57 while (k-gtrightchild =NULL)58 f = k59 k = k-gtrightchild60 k將挪至原 p處61 if (p == f)62 f-gtleftchild = k-gtleftchild 63 else64 f-gtrightchild = k-gtleftchild 65

66 else p無左子樹67 f = p 找出 p右子樹的最左樹葉68 k = p-gtrightchild69 while (k-gtleftchild = NULL)70 f = k71 k = k -gtleftchild72 73 if (p == f)74 f-gtrightchild = k-gtrightchild 75 else76 f-gtleftchild = k-gtrightchild77 78 if (k = NULL) k挪至原 p處繼承 p的左右指標79 k-gtleftchild = p-gtleftchild80 k-gtrightchild = p-gtrightchild81 82 if (father == NULL) root = k83 else if (x lt father-gtdata) 決定 k是 father的左

或右兒子84 father-gtleftchild = k85 else86 father-gtrightchild = k87 free(p)88 return 1 成功地刪除 x於此傳回 1返回呼叫處89 51~89行為找到 x時應有的處理步驟

90 else 尚未找到 x繼續往下一階層找

91 father = p92 if (x lt p-gtdata)93 p = p-gtleftchild94 else95 p = p-gtrightchild96 97 98 return 0 未找到 x傳回 099

510  樹林 (forest)

bull 定義樹林(森林)是由 n 棵沒有交集的樹所構成的集合 n 0

下圖為一包含三棵樹的樹林

5101 轉換樹林為二元樹 之前我們以二元樹來表示樹因為二元樹有較多

好用的性質在此我們也將樹林轉換成二元樹看待

在 522 節中我們嘗試了用左子右兄弟的方式來表示樹這個方法事實上已將樹轉換成二元樹

即用左子右兄弟的結構重新組織樹林中的每一棵樹這些樹的樹根將視為兄弟而最左邊的樹則視為樹林二元樹的樹根如此一來樹林即可轉換成二元樹

範例 5-25右下圖為右上圖三棵樹所對應的三棵左子右兄弟二元樹

下圖則為上圖樹林所對應的一棵樹林二元樹

演算法 5-1  轉換樹林成二元樹輸入樹林 F = T1 T2 Tn

輸出 F 的二元樹表示 BT=(T1 T2 Tn)1 if (n == 0) 輸出空二元樹 2 else

3 root(T1 T2 Tn) = T1

4 leftsubtree(T1 T2 Tn) =

BT(T11 T12 T1m)5 BT(T11 T12 T1m)為 T1的子樹6 rightsubtree(T1 T2 Tn) =

BT(T2 T3 Tn)

樹林轉換成二元樹的過程樹林轉換成二元樹的過程

  • Slide 1
  • Slide 2
  • Slide 3
  • Slide 4
  • Slide 5
  • Slide 6
  • Slide 7
  • Slide 8
  • Slide 9
  • Slide 10
  • Slide 11
  • Slide 12
  • Slide 13
  • Slide 14
  • Slide 15
  • Slide 16
  • Slide 17
  • Slide 18
  • Slide 19
  • Slide 20
  • Slide 21
  • Slide 22
  • Slide 23
  • Slide 24
  • Slide 25
  • Slide 26
  • Slide 27
  • Slide 28
  • Slide 29
  • Slide 30
  • Slide 31
  • Slide 32
  • Slide 33
  • Slide 34
  • Slide 35
  • Slide 36
  • Slide 37
  • Slide 38
  • Slide 39
  • Slide 40
  • Slide 41
  • Slide 42
  • Slide 43
  • Slide 44
  • Slide 45
  • Slide 46
  • Slide 47
  • Slide 48
  • Slide 49
  • Slide 50
  • Slide 51
  • Slide 52
  • Slide 53
  • Slide 54
  • Slide 55
  • Slide 56
  • Slide 57
  • Slide 58
  • Slide 59
  • Slide 60
  • Slide 61
  • Slide 62
  • Slide 63
  • Slide 64
  • Slide 65
  • Slide 66
  • Slide 67
  • Slide 68
  • Slide 69
  • Slide 70
  • Slide 71
  • Slide 72
  • Slide 73
  • Slide 74
  • Slide 75
  • Slide 76
  • Slide 77
  • Slide 78
  • Slide 79
  • Slide 80
  • Slide 81
  • Slide 82
  • Slide 83
  • Slide 84
  • Slide 85
  • Slide 86
  • Slide 87
  • Slide 88
  • Slide 89
  • Slide 90
  • Slide 91
  • Slide 92
  • Slide 93
  • Slide 94
  • Slide 95
  • Slide 96
  • Slide 97
  • Slide 98
  • Slide 99
  • Slide 100
  • Slide 101
  • Slide 102
  • Slide 103
Page 50: 第五章 樹

bull Infix ABCbull Postfix ABC CBA ACB BCA

BAC CAB

B

A CA

B

C

B

A

C

C

B

AB

A

C

二元樹中序與前序式中序與後序式bull 每一棵二元樹皆有唯一的一對中序與前序表示法也有唯一的中序與後序次序

bull 換句話說給予一對中序與前序或中序與後序即可決定一棵二元樹

bull 然而給予前序和後序次序並不能決定唯一的二元樹可能會產生兩棵不同的二元樹

bull 想想階層走訪和中序走訪順序可否決定唯一的二元樹

A

B C

H J D E

F G

prefix ABHJCDFGEinfix HBJAFDGCEpostfix HJBFGDECA

VLRLVRLRV

A

prefix BHJinfix HBJ

prefix CDFGEinfix FDGCE

prefix ABHJCDFGEinfix HBJAFDGCE

A

B C

H J D E

F G

prefix ABHJCDFGE

infix HBJAFDGCEk-1

struct BTreeNode BTree_InfixPrefix (AnsiString infix AnsiString prefix)

int k struct BTreeNode node if (infixLength==0) return NULLnode = (struct BTreeNode ) malloc (sizeof(struct BTreeNode))node-gtdata = prefix[1]k = find(prefix[1] infix)node-gtleftchild = BTree_InfixPrefix

(infix[1k-1] prefix[2k])node-gtrightchild = BTree_InfixPrefix(infix[k+1infixLength]prefix[k+1prefixLength])

VLR

LVRk1

k2 k+1~

k+1~

1

struct BTreeNode BTree_InfixPostfix (AnsiString infix AnsiString prefix)

int k struct BTreeNode node if (infixLength==0) return NULLnode = (struct BTreeNode ) malloc (sizeof(struct BTreeNode))node-gtdata = postfix[postfixLength]k = find(postfix[postfixLength] infix)node-gtleftchild = BTree_InfixPrefix

(infix[1k-1] postfix[1k-1])node-gtrightchild = BTree_InfixPrefix(infix[k+1infixLength]postfix[k+1postfixLength-1])

56  二元樹的複製與相等測試程式 5-8 二元樹的複製1 struct BTreeNode2 struct BTreeNode leftchild3 char data4 struct BTreeNode rightchild5 6 struct BTreeNode root7 struct BTreeNode Copy(struct BTreeNode TreeRoot)8 struct BTreeNode CopyRoot9 if (TreeRoot == NULL)10 return NULL11 CopyRoot =(struct BTreeNode ) malloc (sizeof(BTreeNode))12 CopyRoot-gtdata = TreeRoot-gtdata13 CopyRoot-gtleftchild = Copy(TreeRoot-gtleftchild)14 CopyRoot-gtrightchild = Copy(TreeRoot-

gtrightchild)15 return CopyRoot16

程式 5-9 檢測兩棵二元樹是否相等1 struct BTreeNode2 struct BTreeNode leftchild3 char data4 struct BTreeNode rightchild5 6 struct BtreeNode rightchild7 int Equal(struct BTreeNode ustruct BTreeNode v)8 if ((u==NULL) ampamp (v==NULL)) return 19 if ((u=NULL) ampamp (v=NULL)10 ampamp (u-gtdata == v-gtdata )11 ampamp Equal(u-gtleftchildv-gtleftchild)12 ampamp Equal(u-gtrightchildv-gtrightchild))

13 return 114 return 015

Counting Nodes in a Binary Tree

int CountingNodes(struct BTreeNodes node)

if (node == NULL) return 0 int leftchildren = CountingNodes(node-gtleftchild)

int rightchildren= CountingNodes(node-gtrightchild)

return leftchildren+rightchildren

Counting Leaves in a Binary Tree

int CountingLeaves(struct BTreeNodes node)

if (node == NULL) return 0 int leftleaves = CountingLeaves(node-gtleftchild)

int rightchildren= CountingLeaves(node-gtrightchild)

if (leftleaves==0 ampamp leftleaves==0) return 1

return leftleaves + leftleaves

57  引線二元樹bull 在二元樹的鍵結表示中樹葉節點的兩個子樹指標皆指向 NULL 由定理 5-1 得知 n = E+1 其中 n 為節點個數 E 為分支數

bull 而 n 個鍵結節點有 2n 個指標空間每個分支恰佔用一個指標空間共有 E (=n-1) 個指標有用到(不是空的)而共有 n+1 個指標空間是 NULL 比非空的節點還多

bull 有學者提出對這些「放空指標的空間」加以利用的概念mdash與其放空指標不如放指向其它節點的指標稱之為引線 (thread) 使得某些運算(如走訪hellip等)可以加快

bull 這個概念發展出了引線二元樹 (threaded binary tree) 這種資料結構

範例 5-17下圖為加了引線的二元樹

加入的引線將使二元樹的中序走訪更加便利沿著上右圖箭頭所指示的順序走訪即可完成此二元樹的中序走訪GDHBAIECF 為此二元樹的中序表示

bull 範例 5-17 中所加的引線目的在方便二元樹的中序走訪

bull 對樹葉節點 p 而言令其資料為 P 則引線指標的規則如下

(1) p 的左子樹指標指向中序表示中 P 的前一個 元素(前接元素 (predecessor) )

(2) p 的右子樹指標指向中序表示中 P 的後一個 元素(後繼元素 (successor) )

為了區別節點指標放的是一般指標(內部節點)抑或是引線指標(樹葉節點)我們另以 2 個欄位分別區別左和右子樹指標空間存放的對象其節點的記憶體配置有如下圖所示

其中 leftthread (rightthread) 為 0 時表示 leftchild (rightchild) 存放的是一般節點指標而 leftthread (rightthread) 為 1 時表示 leftchild (rightchild) 存放的是引線指標1 struct TBTreeNode2 int leftthread3 struct TBTreeNode leftchild4 char data5 struct TBTreeNode rightchild6 int rightthread7

由右側的引線二元樹可知有最左和最右兩條引線尚無妥善的安排我們另設計一空的引線節點做為樹根如下圖所示

571  引線二元樹的中序走訪決定節點 p 在中序表示中的後繼節點 q (簡稱中序後繼點)

程式 5-10決定節點 node的中序後繼點1 struct TBTreeNode2 int leftthread3 struct TBTreeNode leftchild4 char data5 struct TBTreeNode rightchild6 int rightthread7 8 struct TBTreeNode root9 struct TBTreeNode Next(struct TBTreeNode node)

10 struct TBTreeNode temp11 temp = node-gtrightchild12 if (node-gtrightthead) return temp13 while (temp-gtleftthead) temp=temp-gtleftchild

14 return temp15

程式 5-11引線二元樹的中序表示(接程式 5-10)1 void InorderTBTree()2 struct TBTreeNode node3 for(node=Next(root)node=rootnode=Next(node))

4 cout ltlt node-gtdata5

bull只要 node 是引線二元樹上的任一節點透過呼叫 Next(node) 即可找到其在中序表示中的後繼節點bull(試想若程式 5-10 中的程序 Next 所傳入的參數指標正是 root Next(root) 是否可正常運作請思考)bull於是只須從中序表示中的第一節點起讓每一節點 node 都執行 Next(node) 程序即可得引線二元樹的中序表示

572  在引線二元樹中加入節點在引線二元樹中樹葉節點節點的右子樹指標所指向或內部節點右子樹的最左樹葉為其中序後繼節點而且任一節點若無左子樹則其左子樹指標乃指向其中序前接節點hellip這些性質必須在節點新增進入引線二元樹時也要保持接下來討論節點 new 插入成為引線二元樹的節點 p 的右子節點的情形至於插入成為左子節點的情形則讓各位模擬推敲

572572  在引線二元樹中加入節點 在引線二元樹中加入節點 (( 續續 ))

newnew 會成為會成為 pp 的右子節點的情況共有以下兩種的右子節點的情況共有以下兩種(1) (1) 若若 pp 沒有右子節點 沒有右子節點 ((p-gtrightthread p-gtrightthread 為為1) 1) 如圖如圖 ((a) a) 則 則 new new 插入後應形成如圖插入後應形成如圖 ((b) b) 所示所示

(2)(2) 若若 pp 有右子節點 有右子節點 ((p-gtrightthread p-gtrightthread 為為0) 0) 如圖如圖 ((c) c) 則 則 new new 插入後應形成如插入後應形成如圖圖 ((d) d) 所示所示

程式 5-12  插入節點 new 成為引線二元樹中節點 p的右子節點(上接程式 5-10 5-11 )

1 void InsertRight(struct TBTreeNode p 2 struct TBTreeNode new)3 struct TBTreeNode q4 new-gtrightchild = p-gtrightchild5 new-gtrightthead = p-gtrightthread6 new-gtleftchild = p7 new-gtleftthread = 18 p-gtrightchild = new9 p-gtrightthead = 010 if (new-gtrightthread)11 q = Next(new)12 q-gtleftchild = new13 14

58  堆積bull 「堆積」 (heap) 是一個特殊的完備二元樹節點的資料與其子節點的資料之間有事先定好的大小關係存在bull 定義ndash最大堆疊為一完備二元樹任一節點的資料內容不小於其子節點的資料內容ndash最小堆疊為一完備二元樹任一節點的資料內容不大於其子節點的資料內容

581  插入節點至最大堆積中最大堆積是一完備二元樹所以如圖 (a) 之最大堆積於加入新節點後樹的形狀必須如圖 (b) 所示灰色節點即為新節點若加入的節點資料分別為 1 9 12 則其最大堆積將分別變成如圖 (c) (d) (e) 所示

程式 5-13 新增節點至最大堆積中最大堆積本身是一棵完備二元樹可以用陣列表示之 1 define maxsize 1002 int heap[maxsize]3 int n i4 void InsertHeap(int x)5 if (n == maxsize) HeapFull()6 else7 n++8 i = n9 while ((i gt 1) ampamp (x gt heap[i2]))10 heap[i] = heap[i2]11 i = 212 13 heap[i] = x14 16

程序 InsertHeap 在插入第 n 個資料時需要的時間複雜度為 O(logn)

1 100

2 93

3 84

4 77

5 65

6 81

7 79

8 66

9 70

10 50

100

84

77

93

8165

66 70

79

50

1

2 3

4 5 6 7

8 9 10

1 define maxsize 1002 int heap[maxsize]3 int n i4 void InsertHeap(int x)5 if (n == maxsize) HeapFull()6 else7 n++8 i = n9 while ((i gt 1) ampamp (x gt heap[i2]))10 heap[i] = heap[i2]11 i = 212 13 heap[i] = x14 16

582 刪除最大堆積中的最大資料圖 (a) 中的最大堆積在刪除最大資料後其樹形應變成如圖 (b) 所示將最未一個樹葉節點的資料 6 暫挪至樹根節點內如圖 (c) 再將圖 (c) 中非最大堆積的結構調整成為最大堆積 10 和 8 中選大者與 6對調即成圖 (d)

程式 5-14 刪除最大堆積中的最大資料(上接程式 5-13 )1 int DeleteHeap()2 int x i j3 if (n == 0) HeapEmpty()4 else5 x = heap[1]6 heap[1] = heap[n]7 n--8 i = 19 while (i lt= n2)10 if (heap[2i] gt heap[2i+1])11 j = 2i12 else13 j = 2i+114 if (heap[i]gtheap[j]) break15 swap(heap[i] heap[j])16 i = j17 18 return x19 20

其時間複雜度與其堆積高度相同是為O(logn) 其中 n 為堆積中的元素個數

1 100

2 93

3 84

4 77

5 65

6 81

7 79

8 66

9 70

10 50

100

84

77

93

8165

66 70

79

50

1

2 3

4 5 6 7

8 9 10

1 int DeletHeap()2 int x i j3 if(n == 0) HeapEmpty()4 else5 x = heap[1]6 heap[1] = heap[n]7 n--8 i = 19 while (i lt= n2)10 if ([heap[2i]] gt heap[2i+1])11 j = 2i12 else13 j = 2i+114 if (heap[i]gtheap[j]) break15 swap(heap[i] heap[j])16 i = j17 18 return x19 20

583  優先佇列bull 由於最大堆積(或最小堆積)的結構可提供最大元素(或最小元素)而其維護堆積的成本為 O(logn) n 為總元素個數

bull 堆積常被當成「優先佇列」 (priority queue) 使用mdash此時佇列被組織成完備二元樹若新加入佇列的資料有大小關係優先順序的需求時此優先佇列即以 O(logn) 的代價將新資料放入優先佇列(可能是最大或最小堆積依實際大小關係優先順序的需求而定)中mdash提供依元素的優先順序做為處理順序的服務

bull 一般的佇列為先進先出先到的先行處理優先佇列則是依據其優先順序優先順序高者先行處理與來到的先後無關

範例 5-21bull 在「分時」 (time sharing) 的作業系統中我們不希望使用中央處理器 (Central Processing Unit CPU) 時間短的「處理程序」 (process) 等候過久於是有「最短時間工作優先」 (shortest-job-first) 的排程策略此時等候的處理程序即可組織成之一最小堆積而 CPU 每次即取出存於樹根的處理程序予以處理任何新來的處理程序則以 O(logn) 的時間加入此優先佇列(最小堆積)中

bull 注意之前提過的線性佇列新來的直接加入成為新的尾端元素花O(1) 的時間然線性佇列本身沒有大小的關係每次要取出最大(小)的資料得花O(n) 的代價(在所有元素中挑出最大(小)者) n 為佇列內的元素個數而優先佇列是在新元素加入時要花O(logn) 的時間至於取出最大(小)則只須 O(1) 的時間

59  二元搜尋樹bull 二元搜尋樹是一棵二元樹可能是空二元樹若不為空二元樹則滿足下列性質ndash所有節點內的資料內容是相異的 ndash左子節點的資內容 ( 如果有 ) 比父節點的資料內容小 ndash右子節點的資料內容 ( 如果有 ) 比父節點的資料內容大 ndash以左和右子節點為樹根的左子樹和右子樹也是二元搜尋樹

在二元搜尋樹中搜尋資料二元搜尋樹對資料大小的掌握十分明確若恰與節點 p 資料相等則搜尋成功若欲搜尋的資料 x 比樹節點 p 中的資料小 則只須搜尋節點 p 的左子樹若比節點 p 中的資料大 則只須搜尋 p 的右子樹這樣的判斷可以遞迴地應用在各子樹節點 中

程式 5-15 在二元搜尋樹中搜尋資料(遞迴呼叫)1 struct BSTreeNode2 struct BSTreeNode leftchild3 int data4 struct BSTreeNode rightchild5 6 struct BSTreeNode root7 struct BSTreeNode SearchBST(

struct BSTreeNode tree int x)8 if (tree == NULL) return NULL9 if (x == tree-gtdata) return tree10 if (x lt tree-gtdata)11 return SearchBST(tree-gtleftchild x)

12 return SearchBST(tree-gtrightchild x)13

程式 5-16 搜尋二元搜尋樹的資料(非遞迴呼叫)在二元搜尋樹十分龐大時遞迴呼叫的效率不會太好可利用迴圈來完成搜尋的動作以提高執行效率請看程式 5-16

14 struct BSTreeNode Search BST_iterative15 (struct BSTreeNode node int x)16 while (node = NULL)17 if (x == node-gtdata) return node18 if (x lt node-gtdata)19 node = node-gtleftchild20 else21 node = node-gtrightchild22 23 return NULL24

592  新增資料於二元搜尋樹中欲新增資料進入一二元搜尋樹中得先決定該資料在二元搜尋樹中應出現的位置這可透過上節提到的搜尋動作來解決

在二元搜尋樹定義的性質 (1) 中我們限定了元素必須相異所以搜尋新資料的結果必是 NULL

必須將搜尋的結果改為新增資料所應在位置的節點指標

程式 5-17  在二元搜尋樹中新增資料25 int InsertBST(int x)26 struct BSTreeNode p q27 p = root q = NULL28 while (p = NULL)29 q = p30 if (x == p-gtdata) return 0

若不考慮重複則可省略之31 if (x lt p-gtdata)32 p = p-gtleftchild33 else34 p = p-gtrightchild35

3636 p = (struct BSTreeNode ) p = (struct BSTreeNode ) malloc (sizeof(BSTreeNode))malloc (sizeof(BSTreeNode))

3737 p-gtdata = x p-gtdata = x3838 p-gtleftchild = p-gtrightchild = p-gtleftchild = p-gtrightchild = NULLNULL3939 if (root == NULL) root = p if (root == NULL) root = p4040 else if (x lt q-gtdata) else if (x lt q-gtdata)4141 q-gtleftchild = p q-gtleftchild = p4242 else else4343 q-gtrightchild = p q-gtrightchild = p4444 return 1 return 145 45

程式 程式 5-175-17  在二元搜尋樹中新增資料  在二元搜尋樹中新增資料 (( 續續 ))

範例 5-23 圖 (b) 為圖 (a) 新增整數 6 之後的結果圖 (c) (d) (e) (f) 則分別是逐步再加入 28 18

10 21 的結果

bull若目前的二元搜尋樹高度為 h 則新增資料得花O(h) 的時間找到其所應在的位置遂新增資料至二元搜尋樹中的時間複雜之度為 O(h)

593 自二元搜尋樹中刪除資料欲自二元搜尋樹中刪除資料可能會面臨兩種情況(1) 刪除節點 p 位於樹葉節點上如下圖所示 這是比較容易處理的情況

(2) 刪除的節點 p 不是樹葉刪除節點 p 後原 p 所指的節點可由 q ( q 中包含了 p 左子樹中的最大節點值)或 r ( r 中包含了 p 右子樹中的最小節點值)來取代以維持二元搜尋樹中資料的大小關係其結果分別如圖 (b) 和 (c) 所示而圖 (d) 則標明了指標會改變的節點位置即 p 會消失 father 的左或右子樹指標會異動 k(可能是 q 或 r )會移到原來 p 的位置其父節點 f 的左或右子樹指標會異動其中 father 為 p 父節點 f 為 k 的父節點

程式 5-18 自二元搜尋樹中刪除資料46 int DeleteBST (int x)47 Struct BSTreeNode p father k f48 p = root father = NULL49 while (p = NULL)50 if (x == p-gtdata) 找到 x 所在的節點 p 了51 if ((p-gtleftchild == NULL) ampamp 52 (p-gtrightchild == NULL))53 k = NULL p為樹葉節點54 else if (p-gtleftchild = NULL )x位於非子節

點55 f = p 找出 p左子樹的最右樹葉56 k = p-gtleftchild57 while (k-gtrightchild =NULL)58 f = k59 k = k-gtrightchild60 k將挪至原 p處61 if (p == f)62 f-gtleftchild = k-gtleftchild 63 else64 f-gtrightchild = k-gtleftchild 65

66 else p無左子樹67 f = p 找出 p右子樹的最左樹葉68 k = p-gtrightchild69 while (k-gtleftchild = NULL)70 f = k71 k = k -gtleftchild72 73 if (p == f)74 f-gtrightchild = k-gtrightchild 75 else76 f-gtleftchild = k-gtrightchild77 78 if (k = NULL) k挪至原 p處繼承 p的左右指標79 k-gtleftchild = p-gtleftchild80 k-gtrightchild = p-gtrightchild81 82 if (father == NULL) root = k83 else if (x lt father-gtdata) 決定 k是 father的左

或右兒子84 father-gtleftchild = k85 else86 father-gtrightchild = k87 free(p)88 return 1 成功地刪除 x於此傳回 1返回呼叫處89 51~89行為找到 x時應有的處理步驟

90 else 尚未找到 x繼續往下一階層找

91 father = p92 if (x lt p-gtdata)93 p = p-gtleftchild94 else95 p = p-gtrightchild96 97 98 return 0 未找到 x傳回 099

510  樹林 (forest)

bull 定義樹林(森林)是由 n 棵沒有交集的樹所構成的集合 n 0

下圖為一包含三棵樹的樹林

5101 轉換樹林為二元樹 之前我們以二元樹來表示樹因為二元樹有較多

好用的性質在此我們也將樹林轉換成二元樹看待

在 522 節中我們嘗試了用左子右兄弟的方式來表示樹這個方法事實上已將樹轉換成二元樹

即用左子右兄弟的結構重新組織樹林中的每一棵樹這些樹的樹根將視為兄弟而最左邊的樹則視為樹林二元樹的樹根如此一來樹林即可轉換成二元樹

範例 5-25右下圖為右上圖三棵樹所對應的三棵左子右兄弟二元樹

下圖則為上圖樹林所對應的一棵樹林二元樹

演算法 5-1  轉換樹林成二元樹輸入樹林 F = T1 T2 Tn

輸出 F 的二元樹表示 BT=(T1 T2 Tn)1 if (n == 0) 輸出空二元樹 2 else

3 root(T1 T2 Tn) = T1

4 leftsubtree(T1 T2 Tn) =

BT(T11 T12 T1m)5 BT(T11 T12 T1m)為 T1的子樹6 rightsubtree(T1 T2 Tn) =

BT(T2 T3 Tn)

樹林轉換成二元樹的過程樹林轉換成二元樹的過程

  • Slide 1
  • Slide 2
  • Slide 3
  • Slide 4
  • Slide 5
  • Slide 6
  • Slide 7
  • Slide 8
  • Slide 9
  • Slide 10
  • Slide 11
  • Slide 12
  • Slide 13
  • Slide 14
  • Slide 15
  • Slide 16
  • Slide 17
  • Slide 18
  • Slide 19
  • Slide 20
  • Slide 21
  • Slide 22
  • Slide 23
  • Slide 24
  • Slide 25
  • Slide 26
  • Slide 27
  • Slide 28
  • Slide 29
  • Slide 30
  • Slide 31
  • Slide 32
  • Slide 33
  • Slide 34
  • Slide 35
  • Slide 36
  • Slide 37
  • Slide 38
  • Slide 39
  • Slide 40
  • Slide 41
  • Slide 42
  • Slide 43
  • Slide 44
  • Slide 45
  • Slide 46
  • Slide 47
  • Slide 48
  • Slide 49
  • Slide 50
  • Slide 51
  • Slide 52
  • Slide 53
  • Slide 54
  • Slide 55
  • Slide 56
  • Slide 57
  • Slide 58
  • Slide 59
  • Slide 60
  • Slide 61
  • Slide 62
  • Slide 63
  • Slide 64
  • Slide 65
  • Slide 66
  • Slide 67
  • Slide 68
  • Slide 69
  • Slide 70
  • Slide 71
  • Slide 72
  • Slide 73
  • Slide 74
  • Slide 75
  • Slide 76
  • Slide 77
  • Slide 78
  • Slide 79
  • Slide 80
  • Slide 81
  • Slide 82
  • Slide 83
  • Slide 84
  • Slide 85
  • Slide 86
  • Slide 87
  • Slide 88
  • Slide 89
  • Slide 90
  • Slide 91
  • Slide 92
  • Slide 93
  • Slide 94
  • Slide 95
  • Slide 96
  • Slide 97
  • Slide 98
  • Slide 99
  • Slide 100
  • Slide 101
  • Slide 102
  • Slide 103
Page 51: 第五章 樹

二元樹中序與前序式中序與後序式bull 每一棵二元樹皆有唯一的一對中序與前序表示法也有唯一的中序與後序次序

bull 換句話說給予一對中序與前序或中序與後序即可決定一棵二元樹

bull 然而給予前序和後序次序並不能決定唯一的二元樹可能會產生兩棵不同的二元樹

bull 想想階層走訪和中序走訪順序可否決定唯一的二元樹

A

B C

H J D E

F G

prefix ABHJCDFGEinfix HBJAFDGCEpostfix HJBFGDECA

VLRLVRLRV

A

prefix BHJinfix HBJ

prefix CDFGEinfix FDGCE

prefix ABHJCDFGEinfix HBJAFDGCE

A

B C

H J D E

F G

prefix ABHJCDFGE

infix HBJAFDGCEk-1

struct BTreeNode BTree_InfixPrefix (AnsiString infix AnsiString prefix)

int k struct BTreeNode node if (infixLength==0) return NULLnode = (struct BTreeNode ) malloc (sizeof(struct BTreeNode))node-gtdata = prefix[1]k = find(prefix[1] infix)node-gtleftchild = BTree_InfixPrefix

(infix[1k-1] prefix[2k])node-gtrightchild = BTree_InfixPrefix(infix[k+1infixLength]prefix[k+1prefixLength])

VLR

LVRk1

k2 k+1~

k+1~

1

struct BTreeNode BTree_InfixPostfix (AnsiString infix AnsiString prefix)

int k struct BTreeNode node if (infixLength==0) return NULLnode = (struct BTreeNode ) malloc (sizeof(struct BTreeNode))node-gtdata = postfix[postfixLength]k = find(postfix[postfixLength] infix)node-gtleftchild = BTree_InfixPrefix

(infix[1k-1] postfix[1k-1])node-gtrightchild = BTree_InfixPrefix(infix[k+1infixLength]postfix[k+1postfixLength-1])

56  二元樹的複製與相等測試程式 5-8 二元樹的複製1 struct BTreeNode2 struct BTreeNode leftchild3 char data4 struct BTreeNode rightchild5 6 struct BTreeNode root7 struct BTreeNode Copy(struct BTreeNode TreeRoot)8 struct BTreeNode CopyRoot9 if (TreeRoot == NULL)10 return NULL11 CopyRoot =(struct BTreeNode ) malloc (sizeof(BTreeNode))12 CopyRoot-gtdata = TreeRoot-gtdata13 CopyRoot-gtleftchild = Copy(TreeRoot-gtleftchild)14 CopyRoot-gtrightchild = Copy(TreeRoot-

gtrightchild)15 return CopyRoot16

程式 5-9 檢測兩棵二元樹是否相等1 struct BTreeNode2 struct BTreeNode leftchild3 char data4 struct BTreeNode rightchild5 6 struct BtreeNode rightchild7 int Equal(struct BTreeNode ustruct BTreeNode v)8 if ((u==NULL) ampamp (v==NULL)) return 19 if ((u=NULL) ampamp (v=NULL)10 ampamp (u-gtdata == v-gtdata )11 ampamp Equal(u-gtleftchildv-gtleftchild)12 ampamp Equal(u-gtrightchildv-gtrightchild))

13 return 114 return 015

Counting Nodes in a Binary Tree

int CountingNodes(struct BTreeNodes node)

if (node == NULL) return 0 int leftchildren = CountingNodes(node-gtleftchild)

int rightchildren= CountingNodes(node-gtrightchild)

return leftchildren+rightchildren

Counting Leaves in a Binary Tree

int CountingLeaves(struct BTreeNodes node)

if (node == NULL) return 0 int leftleaves = CountingLeaves(node-gtleftchild)

int rightchildren= CountingLeaves(node-gtrightchild)

if (leftleaves==0 ampamp leftleaves==0) return 1

return leftleaves + leftleaves

57  引線二元樹bull 在二元樹的鍵結表示中樹葉節點的兩個子樹指標皆指向 NULL 由定理 5-1 得知 n = E+1 其中 n 為節點個數 E 為分支數

bull 而 n 個鍵結節點有 2n 個指標空間每個分支恰佔用一個指標空間共有 E (=n-1) 個指標有用到(不是空的)而共有 n+1 個指標空間是 NULL 比非空的節點還多

bull 有學者提出對這些「放空指標的空間」加以利用的概念mdash與其放空指標不如放指向其它節點的指標稱之為引線 (thread) 使得某些運算(如走訪hellip等)可以加快

bull 這個概念發展出了引線二元樹 (threaded binary tree) 這種資料結構

範例 5-17下圖為加了引線的二元樹

加入的引線將使二元樹的中序走訪更加便利沿著上右圖箭頭所指示的順序走訪即可完成此二元樹的中序走訪GDHBAIECF 為此二元樹的中序表示

bull 範例 5-17 中所加的引線目的在方便二元樹的中序走訪

bull 對樹葉節點 p 而言令其資料為 P 則引線指標的規則如下

(1) p 的左子樹指標指向中序表示中 P 的前一個 元素(前接元素 (predecessor) )

(2) p 的右子樹指標指向中序表示中 P 的後一個 元素(後繼元素 (successor) )

為了區別節點指標放的是一般指標(內部節點)抑或是引線指標(樹葉節點)我們另以 2 個欄位分別區別左和右子樹指標空間存放的對象其節點的記憶體配置有如下圖所示

其中 leftthread (rightthread) 為 0 時表示 leftchild (rightchild) 存放的是一般節點指標而 leftthread (rightthread) 為 1 時表示 leftchild (rightchild) 存放的是引線指標1 struct TBTreeNode2 int leftthread3 struct TBTreeNode leftchild4 char data5 struct TBTreeNode rightchild6 int rightthread7

由右側的引線二元樹可知有最左和最右兩條引線尚無妥善的安排我們另設計一空的引線節點做為樹根如下圖所示

571  引線二元樹的中序走訪決定節點 p 在中序表示中的後繼節點 q (簡稱中序後繼點)

程式 5-10決定節點 node的中序後繼點1 struct TBTreeNode2 int leftthread3 struct TBTreeNode leftchild4 char data5 struct TBTreeNode rightchild6 int rightthread7 8 struct TBTreeNode root9 struct TBTreeNode Next(struct TBTreeNode node)

10 struct TBTreeNode temp11 temp = node-gtrightchild12 if (node-gtrightthead) return temp13 while (temp-gtleftthead) temp=temp-gtleftchild

14 return temp15

程式 5-11引線二元樹的中序表示(接程式 5-10)1 void InorderTBTree()2 struct TBTreeNode node3 for(node=Next(root)node=rootnode=Next(node))

4 cout ltlt node-gtdata5

bull只要 node 是引線二元樹上的任一節點透過呼叫 Next(node) 即可找到其在中序表示中的後繼節點bull(試想若程式 5-10 中的程序 Next 所傳入的參數指標正是 root Next(root) 是否可正常運作請思考)bull於是只須從中序表示中的第一節點起讓每一節點 node 都執行 Next(node) 程序即可得引線二元樹的中序表示

572  在引線二元樹中加入節點在引線二元樹中樹葉節點節點的右子樹指標所指向或內部節點右子樹的最左樹葉為其中序後繼節點而且任一節點若無左子樹則其左子樹指標乃指向其中序前接節點hellip這些性質必須在節點新增進入引線二元樹時也要保持接下來討論節點 new 插入成為引線二元樹的節點 p 的右子節點的情形至於插入成為左子節點的情形則讓各位模擬推敲

572572  在引線二元樹中加入節點 在引線二元樹中加入節點 (( 續續 ))

newnew 會成為會成為 pp 的右子節點的情況共有以下兩種的右子節點的情況共有以下兩種(1) (1) 若若 pp 沒有右子節點 沒有右子節點 ((p-gtrightthread p-gtrightthread 為為1) 1) 如圖如圖 ((a) a) 則 則 new new 插入後應形成如圖插入後應形成如圖 ((b) b) 所示所示

(2)(2) 若若 pp 有右子節點 有右子節點 ((p-gtrightthread p-gtrightthread 為為0) 0) 如圖如圖 ((c) c) 則 則 new new 插入後應形成如插入後應形成如圖圖 ((d) d) 所示所示

程式 5-12  插入節點 new 成為引線二元樹中節點 p的右子節點(上接程式 5-10 5-11 )

1 void InsertRight(struct TBTreeNode p 2 struct TBTreeNode new)3 struct TBTreeNode q4 new-gtrightchild = p-gtrightchild5 new-gtrightthead = p-gtrightthread6 new-gtleftchild = p7 new-gtleftthread = 18 p-gtrightchild = new9 p-gtrightthead = 010 if (new-gtrightthread)11 q = Next(new)12 q-gtleftchild = new13 14

58  堆積bull 「堆積」 (heap) 是一個特殊的完備二元樹節點的資料與其子節點的資料之間有事先定好的大小關係存在bull 定義ndash最大堆疊為一完備二元樹任一節點的資料內容不小於其子節點的資料內容ndash最小堆疊為一完備二元樹任一節點的資料內容不大於其子節點的資料內容

581  插入節點至最大堆積中最大堆積是一完備二元樹所以如圖 (a) 之最大堆積於加入新節點後樹的形狀必須如圖 (b) 所示灰色節點即為新節點若加入的節點資料分別為 1 9 12 則其最大堆積將分別變成如圖 (c) (d) (e) 所示

程式 5-13 新增節點至最大堆積中最大堆積本身是一棵完備二元樹可以用陣列表示之 1 define maxsize 1002 int heap[maxsize]3 int n i4 void InsertHeap(int x)5 if (n == maxsize) HeapFull()6 else7 n++8 i = n9 while ((i gt 1) ampamp (x gt heap[i2]))10 heap[i] = heap[i2]11 i = 212 13 heap[i] = x14 16

程序 InsertHeap 在插入第 n 個資料時需要的時間複雜度為 O(logn)

1 100

2 93

3 84

4 77

5 65

6 81

7 79

8 66

9 70

10 50

100

84

77

93

8165

66 70

79

50

1

2 3

4 5 6 7

8 9 10

1 define maxsize 1002 int heap[maxsize]3 int n i4 void InsertHeap(int x)5 if (n == maxsize) HeapFull()6 else7 n++8 i = n9 while ((i gt 1) ampamp (x gt heap[i2]))10 heap[i] = heap[i2]11 i = 212 13 heap[i] = x14 16

582 刪除最大堆積中的最大資料圖 (a) 中的最大堆積在刪除最大資料後其樹形應變成如圖 (b) 所示將最未一個樹葉節點的資料 6 暫挪至樹根節點內如圖 (c) 再將圖 (c) 中非最大堆積的結構調整成為最大堆積 10 和 8 中選大者與 6對調即成圖 (d)

程式 5-14 刪除最大堆積中的最大資料(上接程式 5-13 )1 int DeleteHeap()2 int x i j3 if (n == 0) HeapEmpty()4 else5 x = heap[1]6 heap[1] = heap[n]7 n--8 i = 19 while (i lt= n2)10 if (heap[2i] gt heap[2i+1])11 j = 2i12 else13 j = 2i+114 if (heap[i]gtheap[j]) break15 swap(heap[i] heap[j])16 i = j17 18 return x19 20

其時間複雜度與其堆積高度相同是為O(logn) 其中 n 為堆積中的元素個數

1 100

2 93

3 84

4 77

5 65

6 81

7 79

8 66

9 70

10 50

100

84

77

93

8165

66 70

79

50

1

2 3

4 5 6 7

8 9 10

1 int DeletHeap()2 int x i j3 if(n == 0) HeapEmpty()4 else5 x = heap[1]6 heap[1] = heap[n]7 n--8 i = 19 while (i lt= n2)10 if ([heap[2i]] gt heap[2i+1])11 j = 2i12 else13 j = 2i+114 if (heap[i]gtheap[j]) break15 swap(heap[i] heap[j])16 i = j17 18 return x19 20

583  優先佇列bull 由於最大堆積(或最小堆積)的結構可提供最大元素(或最小元素)而其維護堆積的成本為 O(logn) n 為總元素個數

bull 堆積常被當成「優先佇列」 (priority queue) 使用mdash此時佇列被組織成完備二元樹若新加入佇列的資料有大小關係優先順序的需求時此優先佇列即以 O(logn) 的代價將新資料放入優先佇列(可能是最大或最小堆積依實際大小關係優先順序的需求而定)中mdash提供依元素的優先順序做為處理順序的服務

bull 一般的佇列為先進先出先到的先行處理優先佇列則是依據其優先順序優先順序高者先行處理與來到的先後無關

範例 5-21bull 在「分時」 (time sharing) 的作業系統中我們不希望使用中央處理器 (Central Processing Unit CPU) 時間短的「處理程序」 (process) 等候過久於是有「最短時間工作優先」 (shortest-job-first) 的排程策略此時等候的處理程序即可組織成之一最小堆積而 CPU 每次即取出存於樹根的處理程序予以處理任何新來的處理程序則以 O(logn) 的時間加入此優先佇列(最小堆積)中

bull 注意之前提過的線性佇列新來的直接加入成為新的尾端元素花O(1) 的時間然線性佇列本身沒有大小的關係每次要取出最大(小)的資料得花O(n) 的代價(在所有元素中挑出最大(小)者) n 為佇列內的元素個數而優先佇列是在新元素加入時要花O(logn) 的時間至於取出最大(小)則只須 O(1) 的時間

59  二元搜尋樹bull 二元搜尋樹是一棵二元樹可能是空二元樹若不為空二元樹則滿足下列性質ndash所有節點內的資料內容是相異的 ndash左子節點的資內容 ( 如果有 ) 比父節點的資料內容小 ndash右子節點的資料內容 ( 如果有 ) 比父節點的資料內容大 ndash以左和右子節點為樹根的左子樹和右子樹也是二元搜尋樹

在二元搜尋樹中搜尋資料二元搜尋樹對資料大小的掌握十分明確若恰與節點 p 資料相等則搜尋成功若欲搜尋的資料 x 比樹節點 p 中的資料小 則只須搜尋節點 p 的左子樹若比節點 p 中的資料大 則只須搜尋 p 的右子樹這樣的判斷可以遞迴地應用在各子樹節點 中

程式 5-15 在二元搜尋樹中搜尋資料(遞迴呼叫)1 struct BSTreeNode2 struct BSTreeNode leftchild3 int data4 struct BSTreeNode rightchild5 6 struct BSTreeNode root7 struct BSTreeNode SearchBST(

struct BSTreeNode tree int x)8 if (tree == NULL) return NULL9 if (x == tree-gtdata) return tree10 if (x lt tree-gtdata)11 return SearchBST(tree-gtleftchild x)

12 return SearchBST(tree-gtrightchild x)13

程式 5-16 搜尋二元搜尋樹的資料(非遞迴呼叫)在二元搜尋樹十分龐大時遞迴呼叫的效率不會太好可利用迴圈來完成搜尋的動作以提高執行效率請看程式 5-16

14 struct BSTreeNode Search BST_iterative15 (struct BSTreeNode node int x)16 while (node = NULL)17 if (x == node-gtdata) return node18 if (x lt node-gtdata)19 node = node-gtleftchild20 else21 node = node-gtrightchild22 23 return NULL24

592  新增資料於二元搜尋樹中欲新增資料進入一二元搜尋樹中得先決定該資料在二元搜尋樹中應出現的位置這可透過上節提到的搜尋動作來解決

在二元搜尋樹定義的性質 (1) 中我們限定了元素必須相異所以搜尋新資料的結果必是 NULL

必須將搜尋的結果改為新增資料所應在位置的節點指標

程式 5-17  在二元搜尋樹中新增資料25 int InsertBST(int x)26 struct BSTreeNode p q27 p = root q = NULL28 while (p = NULL)29 q = p30 if (x == p-gtdata) return 0

若不考慮重複則可省略之31 if (x lt p-gtdata)32 p = p-gtleftchild33 else34 p = p-gtrightchild35

3636 p = (struct BSTreeNode ) p = (struct BSTreeNode ) malloc (sizeof(BSTreeNode))malloc (sizeof(BSTreeNode))

3737 p-gtdata = x p-gtdata = x3838 p-gtleftchild = p-gtrightchild = p-gtleftchild = p-gtrightchild = NULLNULL3939 if (root == NULL) root = p if (root == NULL) root = p4040 else if (x lt q-gtdata) else if (x lt q-gtdata)4141 q-gtleftchild = p q-gtleftchild = p4242 else else4343 q-gtrightchild = p q-gtrightchild = p4444 return 1 return 145 45

程式 程式 5-175-17  在二元搜尋樹中新增資料  在二元搜尋樹中新增資料 (( 續續 ))

範例 5-23 圖 (b) 為圖 (a) 新增整數 6 之後的結果圖 (c) (d) (e) (f) 則分別是逐步再加入 28 18

10 21 的結果

bull若目前的二元搜尋樹高度為 h 則新增資料得花O(h) 的時間找到其所應在的位置遂新增資料至二元搜尋樹中的時間複雜之度為 O(h)

593 自二元搜尋樹中刪除資料欲自二元搜尋樹中刪除資料可能會面臨兩種情況(1) 刪除節點 p 位於樹葉節點上如下圖所示 這是比較容易處理的情況

(2) 刪除的節點 p 不是樹葉刪除節點 p 後原 p 所指的節點可由 q ( q 中包含了 p 左子樹中的最大節點值)或 r ( r 中包含了 p 右子樹中的最小節點值)來取代以維持二元搜尋樹中資料的大小關係其結果分別如圖 (b) 和 (c) 所示而圖 (d) 則標明了指標會改變的節點位置即 p 會消失 father 的左或右子樹指標會異動 k(可能是 q 或 r )會移到原來 p 的位置其父節點 f 的左或右子樹指標會異動其中 father 為 p 父節點 f 為 k 的父節點

程式 5-18 自二元搜尋樹中刪除資料46 int DeleteBST (int x)47 Struct BSTreeNode p father k f48 p = root father = NULL49 while (p = NULL)50 if (x == p-gtdata) 找到 x 所在的節點 p 了51 if ((p-gtleftchild == NULL) ampamp 52 (p-gtrightchild == NULL))53 k = NULL p為樹葉節點54 else if (p-gtleftchild = NULL )x位於非子節

點55 f = p 找出 p左子樹的最右樹葉56 k = p-gtleftchild57 while (k-gtrightchild =NULL)58 f = k59 k = k-gtrightchild60 k將挪至原 p處61 if (p == f)62 f-gtleftchild = k-gtleftchild 63 else64 f-gtrightchild = k-gtleftchild 65

66 else p無左子樹67 f = p 找出 p右子樹的最左樹葉68 k = p-gtrightchild69 while (k-gtleftchild = NULL)70 f = k71 k = k -gtleftchild72 73 if (p == f)74 f-gtrightchild = k-gtrightchild 75 else76 f-gtleftchild = k-gtrightchild77 78 if (k = NULL) k挪至原 p處繼承 p的左右指標79 k-gtleftchild = p-gtleftchild80 k-gtrightchild = p-gtrightchild81 82 if (father == NULL) root = k83 else if (x lt father-gtdata) 決定 k是 father的左

或右兒子84 father-gtleftchild = k85 else86 father-gtrightchild = k87 free(p)88 return 1 成功地刪除 x於此傳回 1返回呼叫處89 51~89行為找到 x時應有的處理步驟

90 else 尚未找到 x繼續往下一階層找

91 father = p92 if (x lt p-gtdata)93 p = p-gtleftchild94 else95 p = p-gtrightchild96 97 98 return 0 未找到 x傳回 099

510  樹林 (forest)

bull 定義樹林(森林)是由 n 棵沒有交集的樹所構成的集合 n 0

下圖為一包含三棵樹的樹林

5101 轉換樹林為二元樹 之前我們以二元樹來表示樹因為二元樹有較多

好用的性質在此我們也將樹林轉換成二元樹看待

在 522 節中我們嘗試了用左子右兄弟的方式來表示樹這個方法事實上已將樹轉換成二元樹

即用左子右兄弟的結構重新組織樹林中的每一棵樹這些樹的樹根將視為兄弟而最左邊的樹則視為樹林二元樹的樹根如此一來樹林即可轉換成二元樹

範例 5-25右下圖為右上圖三棵樹所對應的三棵左子右兄弟二元樹

下圖則為上圖樹林所對應的一棵樹林二元樹

演算法 5-1  轉換樹林成二元樹輸入樹林 F = T1 T2 Tn

輸出 F 的二元樹表示 BT=(T1 T2 Tn)1 if (n == 0) 輸出空二元樹 2 else

3 root(T1 T2 Tn) = T1

4 leftsubtree(T1 T2 Tn) =

BT(T11 T12 T1m)5 BT(T11 T12 T1m)為 T1的子樹6 rightsubtree(T1 T2 Tn) =

BT(T2 T3 Tn)

樹林轉換成二元樹的過程樹林轉換成二元樹的過程

  • Slide 1
  • Slide 2
  • Slide 3
  • Slide 4
  • Slide 5
  • Slide 6
  • Slide 7
  • Slide 8
  • Slide 9
  • Slide 10
  • Slide 11
  • Slide 12
  • Slide 13
  • Slide 14
  • Slide 15
  • Slide 16
  • Slide 17
  • Slide 18
  • Slide 19
  • Slide 20
  • Slide 21
  • Slide 22
  • Slide 23
  • Slide 24
  • Slide 25
  • Slide 26
  • Slide 27
  • Slide 28
  • Slide 29
  • Slide 30
  • Slide 31
  • Slide 32
  • Slide 33
  • Slide 34
  • Slide 35
  • Slide 36
  • Slide 37
  • Slide 38
  • Slide 39
  • Slide 40
  • Slide 41
  • Slide 42
  • Slide 43
  • Slide 44
  • Slide 45
  • Slide 46
  • Slide 47
  • Slide 48
  • Slide 49
  • Slide 50
  • Slide 51
  • Slide 52
  • Slide 53
  • Slide 54
  • Slide 55
  • Slide 56
  • Slide 57
  • Slide 58
  • Slide 59
  • Slide 60
  • Slide 61
  • Slide 62
  • Slide 63
  • Slide 64
  • Slide 65
  • Slide 66
  • Slide 67
  • Slide 68
  • Slide 69
  • Slide 70
  • Slide 71
  • Slide 72
  • Slide 73
  • Slide 74
  • Slide 75
  • Slide 76
  • Slide 77
  • Slide 78
  • Slide 79
  • Slide 80
  • Slide 81
  • Slide 82
  • Slide 83
  • Slide 84
  • Slide 85
  • Slide 86
  • Slide 87
  • Slide 88
  • Slide 89
  • Slide 90
  • Slide 91
  • Slide 92
  • Slide 93
  • Slide 94
  • Slide 95
  • Slide 96
  • Slide 97
  • Slide 98
  • Slide 99
  • Slide 100
  • Slide 101
  • Slide 102
  • Slide 103
Page 52: 第五章 樹

A

B C

H J D E

F G

prefix ABHJCDFGEinfix HBJAFDGCEpostfix HJBFGDECA

VLRLVRLRV

A

prefix BHJinfix HBJ

prefix CDFGEinfix FDGCE

prefix ABHJCDFGEinfix HBJAFDGCE

A

B C

H J D E

F G

prefix ABHJCDFGE

infix HBJAFDGCEk-1

struct BTreeNode BTree_InfixPrefix (AnsiString infix AnsiString prefix)

int k struct BTreeNode node if (infixLength==0) return NULLnode = (struct BTreeNode ) malloc (sizeof(struct BTreeNode))node-gtdata = prefix[1]k = find(prefix[1] infix)node-gtleftchild = BTree_InfixPrefix

(infix[1k-1] prefix[2k])node-gtrightchild = BTree_InfixPrefix(infix[k+1infixLength]prefix[k+1prefixLength])

VLR

LVRk1

k2 k+1~

k+1~

1

struct BTreeNode BTree_InfixPostfix (AnsiString infix AnsiString prefix)

int k struct BTreeNode node if (infixLength==0) return NULLnode = (struct BTreeNode ) malloc (sizeof(struct BTreeNode))node-gtdata = postfix[postfixLength]k = find(postfix[postfixLength] infix)node-gtleftchild = BTree_InfixPrefix

(infix[1k-1] postfix[1k-1])node-gtrightchild = BTree_InfixPrefix(infix[k+1infixLength]postfix[k+1postfixLength-1])

56  二元樹的複製與相等測試程式 5-8 二元樹的複製1 struct BTreeNode2 struct BTreeNode leftchild3 char data4 struct BTreeNode rightchild5 6 struct BTreeNode root7 struct BTreeNode Copy(struct BTreeNode TreeRoot)8 struct BTreeNode CopyRoot9 if (TreeRoot == NULL)10 return NULL11 CopyRoot =(struct BTreeNode ) malloc (sizeof(BTreeNode))12 CopyRoot-gtdata = TreeRoot-gtdata13 CopyRoot-gtleftchild = Copy(TreeRoot-gtleftchild)14 CopyRoot-gtrightchild = Copy(TreeRoot-

gtrightchild)15 return CopyRoot16

程式 5-9 檢測兩棵二元樹是否相等1 struct BTreeNode2 struct BTreeNode leftchild3 char data4 struct BTreeNode rightchild5 6 struct BtreeNode rightchild7 int Equal(struct BTreeNode ustruct BTreeNode v)8 if ((u==NULL) ampamp (v==NULL)) return 19 if ((u=NULL) ampamp (v=NULL)10 ampamp (u-gtdata == v-gtdata )11 ampamp Equal(u-gtleftchildv-gtleftchild)12 ampamp Equal(u-gtrightchildv-gtrightchild))

13 return 114 return 015

Counting Nodes in a Binary Tree

int CountingNodes(struct BTreeNodes node)

if (node == NULL) return 0 int leftchildren = CountingNodes(node-gtleftchild)

int rightchildren= CountingNodes(node-gtrightchild)

return leftchildren+rightchildren

Counting Leaves in a Binary Tree

int CountingLeaves(struct BTreeNodes node)

if (node == NULL) return 0 int leftleaves = CountingLeaves(node-gtleftchild)

int rightchildren= CountingLeaves(node-gtrightchild)

if (leftleaves==0 ampamp leftleaves==0) return 1

return leftleaves + leftleaves

57  引線二元樹bull 在二元樹的鍵結表示中樹葉節點的兩個子樹指標皆指向 NULL 由定理 5-1 得知 n = E+1 其中 n 為節點個數 E 為分支數

bull 而 n 個鍵結節點有 2n 個指標空間每個分支恰佔用一個指標空間共有 E (=n-1) 個指標有用到(不是空的)而共有 n+1 個指標空間是 NULL 比非空的節點還多

bull 有學者提出對這些「放空指標的空間」加以利用的概念mdash與其放空指標不如放指向其它節點的指標稱之為引線 (thread) 使得某些運算(如走訪hellip等)可以加快

bull 這個概念發展出了引線二元樹 (threaded binary tree) 這種資料結構

範例 5-17下圖為加了引線的二元樹

加入的引線將使二元樹的中序走訪更加便利沿著上右圖箭頭所指示的順序走訪即可完成此二元樹的中序走訪GDHBAIECF 為此二元樹的中序表示

bull 範例 5-17 中所加的引線目的在方便二元樹的中序走訪

bull 對樹葉節點 p 而言令其資料為 P 則引線指標的規則如下

(1) p 的左子樹指標指向中序表示中 P 的前一個 元素(前接元素 (predecessor) )

(2) p 的右子樹指標指向中序表示中 P 的後一個 元素(後繼元素 (successor) )

為了區別節點指標放的是一般指標(內部節點)抑或是引線指標(樹葉節點)我們另以 2 個欄位分別區別左和右子樹指標空間存放的對象其節點的記憶體配置有如下圖所示

其中 leftthread (rightthread) 為 0 時表示 leftchild (rightchild) 存放的是一般節點指標而 leftthread (rightthread) 為 1 時表示 leftchild (rightchild) 存放的是引線指標1 struct TBTreeNode2 int leftthread3 struct TBTreeNode leftchild4 char data5 struct TBTreeNode rightchild6 int rightthread7

由右側的引線二元樹可知有最左和最右兩條引線尚無妥善的安排我們另設計一空的引線節點做為樹根如下圖所示

571  引線二元樹的中序走訪決定節點 p 在中序表示中的後繼節點 q (簡稱中序後繼點)

程式 5-10決定節點 node的中序後繼點1 struct TBTreeNode2 int leftthread3 struct TBTreeNode leftchild4 char data5 struct TBTreeNode rightchild6 int rightthread7 8 struct TBTreeNode root9 struct TBTreeNode Next(struct TBTreeNode node)

10 struct TBTreeNode temp11 temp = node-gtrightchild12 if (node-gtrightthead) return temp13 while (temp-gtleftthead) temp=temp-gtleftchild

14 return temp15

程式 5-11引線二元樹的中序表示(接程式 5-10)1 void InorderTBTree()2 struct TBTreeNode node3 for(node=Next(root)node=rootnode=Next(node))

4 cout ltlt node-gtdata5

bull只要 node 是引線二元樹上的任一節點透過呼叫 Next(node) 即可找到其在中序表示中的後繼節點bull(試想若程式 5-10 中的程序 Next 所傳入的參數指標正是 root Next(root) 是否可正常運作請思考)bull於是只須從中序表示中的第一節點起讓每一節點 node 都執行 Next(node) 程序即可得引線二元樹的中序表示

572  在引線二元樹中加入節點在引線二元樹中樹葉節點節點的右子樹指標所指向或內部節點右子樹的最左樹葉為其中序後繼節點而且任一節點若無左子樹則其左子樹指標乃指向其中序前接節點hellip這些性質必須在節點新增進入引線二元樹時也要保持接下來討論節點 new 插入成為引線二元樹的節點 p 的右子節點的情形至於插入成為左子節點的情形則讓各位模擬推敲

572572  在引線二元樹中加入節點 在引線二元樹中加入節點 (( 續續 ))

newnew 會成為會成為 pp 的右子節點的情況共有以下兩種的右子節點的情況共有以下兩種(1) (1) 若若 pp 沒有右子節點 沒有右子節點 ((p-gtrightthread p-gtrightthread 為為1) 1) 如圖如圖 ((a) a) 則 則 new new 插入後應形成如圖插入後應形成如圖 ((b) b) 所示所示

(2)(2) 若若 pp 有右子節點 有右子節點 ((p-gtrightthread p-gtrightthread 為為0) 0) 如圖如圖 ((c) c) 則 則 new new 插入後應形成如插入後應形成如圖圖 ((d) d) 所示所示

程式 5-12  插入節點 new 成為引線二元樹中節點 p的右子節點(上接程式 5-10 5-11 )

1 void InsertRight(struct TBTreeNode p 2 struct TBTreeNode new)3 struct TBTreeNode q4 new-gtrightchild = p-gtrightchild5 new-gtrightthead = p-gtrightthread6 new-gtleftchild = p7 new-gtleftthread = 18 p-gtrightchild = new9 p-gtrightthead = 010 if (new-gtrightthread)11 q = Next(new)12 q-gtleftchild = new13 14

58  堆積bull 「堆積」 (heap) 是一個特殊的完備二元樹節點的資料與其子節點的資料之間有事先定好的大小關係存在bull 定義ndash最大堆疊為一完備二元樹任一節點的資料內容不小於其子節點的資料內容ndash最小堆疊為一完備二元樹任一節點的資料內容不大於其子節點的資料內容

581  插入節點至最大堆積中最大堆積是一完備二元樹所以如圖 (a) 之最大堆積於加入新節點後樹的形狀必須如圖 (b) 所示灰色節點即為新節點若加入的節點資料分別為 1 9 12 則其最大堆積將分別變成如圖 (c) (d) (e) 所示

程式 5-13 新增節點至最大堆積中最大堆積本身是一棵完備二元樹可以用陣列表示之 1 define maxsize 1002 int heap[maxsize]3 int n i4 void InsertHeap(int x)5 if (n == maxsize) HeapFull()6 else7 n++8 i = n9 while ((i gt 1) ampamp (x gt heap[i2]))10 heap[i] = heap[i2]11 i = 212 13 heap[i] = x14 16

程序 InsertHeap 在插入第 n 個資料時需要的時間複雜度為 O(logn)

1 100

2 93

3 84

4 77

5 65

6 81

7 79

8 66

9 70

10 50

100

84

77

93

8165

66 70

79

50

1

2 3

4 5 6 7

8 9 10

1 define maxsize 1002 int heap[maxsize]3 int n i4 void InsertHeap(int x)5 if (n == maxsize) HeapFull()6 else7 n++8 i = n9 while ((i gt 1) ampamp (x gt heap[i2]))10 heap[i] = heap[i2]11 i = 212 13 heap[i] = x14 16

582 刪除最大堆積中的最大資料圖 (a) 中的最大堆積在刪除最大資料後其樹形應變成如圖 (b) 所示將最未一個樹葉節點的資料 6 暫挪至樹根節點內如圖 (c) 再將圖 (c) 中非最大堆積的結構調整成為最大堆積 10 和 8 中選大者與 6對調即成圖 (d)

程式 5-14 刪除最大堆積中的最大資料(上接程式 5-13 )1 int DeleteHeap()2 int x i j3 if (n == 0) HeapEmpty()4 else5 x = heap[1]6 heap[1] = heap[n]7 n--8 i = 19 while (i lt= n2)10 if (heap[2i] gt heap[2i+1])11 j = 2i12 else13 j = 2i+114 if (heap[i]gtheap[j]) break15 swap(heap[i] heap[j])16 i = j17 18 return x19 20

其時間複雜度與其堆積高度相同是為O(logn) 其中 n 為堆積中的元素個數

1 100

2 93

3 84

4 77

5 65

6 81

7 79

8 66

9 70

10 50

100

84

77

93

8165

66 70

79

50

1

2 3

4 5 6 7

8 9 10

1 int DeletHeap()2 int x i j3 if(n == 0) HeapEmpty()4 else5 x = heap[1]6 heap[1] = heap[n]7 n--8 i = 19 while (i lt= n2)10 if ([heap[2i]] gt heap[2i+1])11 j = 2i12 else13 j = 2i+114 if (heap[i]gtheap[j]) break15 swap(heap[i] heap[j])16 i = j17 18 return x19 20

583  優先佇列bull 由於最大堆積(或最小堆積)的結構可提供最大元素(或最小元素)而其維護堆積的成本為 O(logn) n 為總元素個數

bull 堆積常被當成「優先佇列」 (priority queue) 使用mdash此時佇列被組織成完備二元樹若新加入佇列的資料有大小關係優先順序的需求時此優先佇列即以 O(logn) 的代價將新資料放入優先佇列(可能是最大或最小堆積依實際大小關係優先順序的需求而定)中mdash提供依元素的優先順序做為處理順序的服務

bull 一般的佇列為先進先出先到的先行處理優先佇列則是依據其優先順序優先順序高者先行處理與來到的先後無關

範例 5-21bull 在「分時」 (time sharing) 的作業系統中我們不希望使用中央處理器 (Central Processing Unit CPU) 時間短的「處理程序」 (process) 等候過久於是有「最短時間工作優先」 (shortest-job-first) 的排程策略此時等候的處理程序即可組織成之一最小堆積而 CPU 每次即取出存於樹根的處理程序予以處理任何新來的處理程序則以 O(logn) 的時間加入此優先佇列(最小堆積)中

bull 注意之前提過的線性佇列新來的直接加入成為新的尾端元素花O(1) 的時間然線性佇列本身沒有大小的關係每次要取出最大(小)的資料得花O(n) 的代價(在所有元素中挑出最大(小)者) n 為佇列內的元素個數而優先佇列是在新元素加入時要花O(logn) 的時間至於取出最大(小)則只須 O(1) 的時間

59  二元搜尋樹bull 二元搜尋樹是一棵二元樹可能是空二元樹若不為空二元樹則滿足下列性質ndash所有節點內的資料內容是相異的 ndash左子節點的資內容 ( 如果有 ) 比父節點的資料內容小 ndash右子節點的資料內容 ( 如果有 ) 比父節點的資料內容大 ndash以左和右子節點為樹根的左子樹和右子樹也是二元搜尋樹

在二元搜尋樹中搜尋資料二元搜尋樹對資料大小的掌握十分明確若恰與節點 p 資料相等則搜尋成功若欲搜尋的資料 x 比樹節點 p 中的資料小 則只須搜尋節點 p 的左子樹若比節點 p 中的資料大 則只須搜尋 p 的右子樹這樣的判斷可以遞迴地應用在各子樹節點 中

程式 5-15 在二元搜尋樹中搜尋資料(遞迴呼叫)1 struct BSTreeNode2 struct BSTreeNode leftchild3 int data4 struct BSTreeNode rightchild5 6 struct BSTreeNode root7 struct BSTreeNode SearchBST(

struct BSTreeNode tree int x)8 if (tree == NULL) return NULL9 if (x == tree-gtdata) return tree10 if (x lt tree-gtdata)11 return SearchBST(tree-gtleftchild x)

12 return SearchBST(tree-gtrightchild x)13

程式 5-16 搜尋二元搜尋樹的資料(非遞迴呼叫)在二元搜尋樹十分龐大時遞迴呼叫的效率不會太好可利用迴圈來完成搜尋的動作以提高執行效率請看程式 5-16

14 struct BSTreeNode Search BST_iterative15 (struct BSTreeNode node int x)16 while (node = NULL)17 if (x == node-gtdata) return node18 if (x lt node-gtdata)19 node = node-gtleftchild20 else21 node = node-gtrightchild22 23 return NULL24

592  新增資料於二元搜尋樹中欲新增資料進入一二元搜尋樹中得先決定該資料在二元搜尋樹中應出現的位置這可透過上節提到的搜尋動作來解決

在二元搜尋樹定義的性質 (1) 中我們限定了元素必須相異所以搜尋新資料的結果必是 NULL

必須將搜尋的結果改為新增資料所應在位置的節點指標

程式 5-17  在二元搜尋樹中新增資料25 int InsertBST(int x)26 struct BSTreeNode p q27 p = root q = NULL28 while (p = NULL)29 q = p30 if (x == p-gtdata) return 0

若不考慮重複則可省略之31 if (x lt p-gtdata)32 p = p-gtleftchild33 else34 p = p-gtrightchild35

3636 p = (struct BSTreeNode ) p = (struct BSTreeNode ) malloc (sizeof(BSTreeNode))malloc (sizeof(BSTreeNode))

3737 p-gtdata = x p-gtdata = x3838 p-gtleftchild = p-gtrightchild = p-gtleftchild = p-gtrightchild = NULLNULL3939 if (root == NULL) root = p if (root == NULL) root = p4040 else if (x lt q-gtdata) else if (x lt q-gtdata)4141 q-gtleftchild = p q-gtleftchild = p4242 else else4343 q-gtrightchild = p q-gtrightchild = p4444 return 1 return 145 45

程式 程式 5-175-17  在二元搜尋樹中新增資料  在二元搜尋樹中新增資料 (( 續續 ))

範例 5-23 圖 (b) 為圖 (a) 新增整數 6 之後的結果圖 (c) (d) (e) (f) 則分別是逐步再加入 28 18

10 21 的結果

bull若目前的二元搜尋樹高度為 h 則新增資料得花O(h) 的時間找到其所應在的位置遂新增資料至二元搜尋樹中的時間複雜之度為 O(h)

593 自二元搜尋樹中刪除資料欲自二元搜尋樹中刪除資料可能會面臨兩種情況(1) 刪除節點 p 位於樹葉節點上如下圖所示 這是比較容易處理的情況

(2) 刪除的節點 p 不是樹葉刪除節點 p 後原 p 所指的節點可由 q ( q 中包含了 p 左子樹中的最大節點值)或 r ( r 中包含了 p 右子樹中的最小節點值)來取代以維持二元搜尋樹中資料的大小關係其結果分別如圖 (b) 和 (c) 所示而圖 (d) 則標明了指標會改變的節點位置即 p 會消失 father 的左或右子樹指標會異動 k(可能是 q 或 r )會移到原來 p 的位置其父節點 f 的左或右子樹指標會異動其中 father 為 p 父節點 f 為 k 的父節點

程式 5-18 自二元搜尋樹中刪除資料46 int DeleteBST (int x)47 Struct BSTreeNode p father k f48 p = root father = NULL49 while (p = NULL)50 if (x == p-gtdata) 找到 x 所在的節點 p 了51 if ((p-gtleftchild == NULL) ampamp 52 (p-gtrightchild == NULL))53 k = NULL p為樹葉節點54 else if (p-gtleftchild = NULL )x位於非子節

點55 f = p 找出 p左子樹的最右樹葉56 k = p-gtleftchild57 while (k-gtrightchild =NULL)58 f = k59 k = k-gtrightchild60 k將挪至原 p處61 if (p == f)62 f-gtleftchild = k-gtleftchild 63 else64 f-gtrightchild = k-gtleftchild 65

66 else p無左子樹67 f = p 找出 p右子樹的最左樹葉68 k = p-gtrightchild69 while (k-gtleftchild = NULL)70 f = k71 k = k -gtleftchild72 73 if (p == f)74 f-gtrightchild = k-gtrightchild 75 else76 f-gtleftchild = k-gtrightchild77 78 if (k = NULL) k挪至原 p處繼承 p的左右指標79 k-gtleftchild = p-gtleftchild80 k-gtrightchild = p-gtrightchild81 82 if (father == NULL) root = k83 else if (x lt father-gtdata) 決定 k是 father的左

或右兒子84 father-gtleftchild = k85 else86 father-gtrightchild = k87 free(p)88 return 1 成功地刪除 x於此傳回 1返回呼叫處89 51~89行為找到 x時應有的處理步驟

90 else 尚未找到 x繼續往下一階層找

91 father = p92 if (x lt p-gtdata)93 p = p-gtleftchild94 else95 p = p-gtrightchild96 97 98 return 0 未找到 x傳回 099

510  樹林 (forest)

bull 定義樹林(森林)是由 n 棵沒有交集的樹所構成的集合 n 0

下圖為一包含三棵樹的樹林

5101 轉換樹林為二元樹 之前我們以二元樹來表示樹因為二元樹有較多

好用的性質在此我們也將樹林轉換成二元樹看待

在 522 節中我們嘗試了用左子右兄弟的方式來表示樹這個方法事實上已將樹轉換成二元樹

即用左子右兄弟的結構重新組織樹林中的每一棵樹這些樹的樹根將視為兄弟而最左邊的樹則視為樹林二元樹的樹根如此一來樹林即可轉換成二元樹

範例 5-25右下圖為右上圖三棵樹所對應的三棵左子右兄弟二元樹

下圖則為上圖樹林所對應的一棵樹林二元樹

演算法 5-1  轉換樹林成二元樹輸入樹林 F = T1 T2 Tn

輸出 F 的二元樹表示 BT=(T1 T2 Tn)1 if (n == 0) 輸出空二元樹 2 else

3 root(T1 T2 Tn) = T1

4 leftsubtree(T1 T2 Tn) =

BT(T11 T12 T1m)5 BT(T11 T12 T1m)為 T1的子樹6 rightsubtree(T1 T2 Tn) =

BT(T2 T3 Tn)

樹林轉換成二元樹的過程樹林轉換成二元樹的過程

  • Slide 1
  • Slide 2
  • Slide 3
  • Slide 4
  • Slide 5
  • Slide 6
  • Slide 7
  • Slide 8
  • Slide 9
  • Slide 10
  • Slide 11
  • Slide 12
  • Slide 13
  • Slide 14
  • Slide 15
  • Slide 16
  • Slide 17
  • Slide 18
  • Slide 19
  • Slide 20
  • Slide 21
  • Slide 22
  • Slide 23
  • Slide 24
  • Slide 25
  • Slide 26
  • Slide 27
  • Slide 28
  • Slide 29
  • Slide 30
  • Slide 31
  • Slide 32
  • Slide 33
  • Slide 34
  • Slide 35
  • Slide 36
  • Slide 37
  • Slide 38
  • Slide 39
  • Slide 40
  • Slide 41
  • Slide 42
  • Slide 43
  • Slide 44
  • Slide 45
  • Slide 46
  • Slide 47
  • Slide 48
  • Slide 49
  • Slide 50
  • Slide 51
  • Slide 52
  • Slide 53
  • Slide 54
  • Slide 55
  • Slide 56
  • Slide 57
  • Slide 58
  • Slide 59
  • Slide 60
  • Slide 61
  • Slide 62
  • Slide 63
  • Slide 64
  • Slide 65
  • Slide 66
  • Slide 67
  • Slide 68
  • Slide 69
  • Slide 70
  • Slide 71
  • Slide 72
  • Slide 73
  • Slide 74
  • Slide 75
  • Slide 76
  • Slide 77
  • Slide 78
  • Slide 79
  • Slide 80
  • Slide 81
  • Slide 82
  • Slide 83
  • Slide 84
  • Slide 85
  • Slide 86
  • Slide 87
  • Slide 88
  • Slide 89
  • Slide 90
  • Slide 91
  • Slide 92
  • Slide 93
  • Slide 94
  • Slide 95
  • Slide 96
  • Slide 97
  • Slide 98
  • Slide 99
  • Slide 100
  • Slide 101
  • Slide 102
  • Slide 103
Page 53: 第五章 樹

A

prefix BHJinfix HBJ

prefix CDFGEinfix FDGCE

prefix ABHJCDFGEinfix HBJAFDGCE

A

B C

H J D E

F G

prefix ABHJCDFGE

infix HBJAFDGCEk-1

struct BTreeNode BTree_InfixPrefix (AnsiString infix AnsiString prefix)

int k struct BTreeNode node if (infixLength==0) return NULLnode = (struct BTreeNode ) malloc (sizeof(struct BTreeNode))node-gtdata = prefix[1]k = find(prefix[1] infix)node-gtleftchild = BTree_InfixPrefix

(infix[1k-1] prefix[2k])node-gtrightchild = BTree_InfixPrefix(infix[k+1infixLength]prefix[k+1prefixLength])

VLR

LVRk1

k2 k+1~

k+1~

1

struct BTreeNode BTree_InfixPostfix (AnsiString infix AnsiString prefix)

int k struct BTreeNode node if (infixLength==0) return NULLnode = (struct BTreeNode ) malloc (sizeof(struct BTreeNode))node-gtdata = postfix[postfixLength]k = find(postfix[postfixLength] infix)node-gtleftchild = BTree_InfixPrefix

(infix[1k-1] postfix[1k-1])node-gtrightchild = BTree_InfixPrefix(infix[k+1infixLength]postfix[k+1postfixLength-1])

56  二元樹的複製與相等測試程式 5-8 二元樹的複製1 struct BTreeNode2 struct BTreeNode leftchild3 char data4 struct BTreeNode rightchild5 6 struct BTreeNode root7 struct BTreeNode Copy(struct BTreeNode TreeRoot)8 struct BTreeNode CopyRoot9 if (TreeRoot == NULL)10 return NULL11 CopyRoot =(struct BTreeNode ) malloc (sizeof(BTreeNode))12 CopyRoot-gtdata = TreeRoot-gtdata13 CopyRoot-gtleftchild = Copy(TreeRoot-gtleftchild)14 CopyRoot-gtrightchild = Copy(TreeRoot-

gtrightchild)15 return CopyRoot16

程式 5-9 檢測兩棵二元樹是否相等1 struct BTreeNode2 struct BTreeNode leftchild3 char data4 struct BTreeNode rightchild5 6 struct BtreeNode rightchild7 int Equal(struct BTreeNode ustruct BTreeNode v)8 if ((u==NULL) ampamp (v==NULL)) return 19 if ((u=NULL) ampamp (v=NULL)10 ampamp (u-gtdata == v-gtdata )11 ampamp Equal(u-gtleftchildv-gtleftchild)12 ampamp Equal(u-gtrightchildv-gtrightchild))

13 return 114 return 015

Counting Nodes in a Binary Tree

int CountingNodes(struct BTreeNodes node)

if (node == NULL) return 0 int leftchildren = CountingNodes(node-gtleftchild)

int rightchildren= CountingNodes(node-gtrightchild)

return leftchildren+rightchildren

Counting Leaves in a Binary Tree

int CountingLeaves(struct BTreeNodes node)

if (node == NULL) return 0 int leftleaves = CountingLeaves(node-gtleftchild)

int rightchildren= CountingLeaves(node-gtrightchild)

if (leftleaves==0 ampamp leftleaves==0) return 1

return leftleaves + leftleaves

57  引線二元樹bull 在二元樹的鍵結表示中樹葉節點的兩個子樹指標皆指向 NULL 由定理 5-1 得知 n = E+1 其中 n 為節點個數 E 為分支數

bull 而 n 個鍵結節點有 2n 個指標空間每個分支恰佔用一個指標空間共有 E (=n-1) 個指標有用到(不是空的)而共有 n+1 個指標空間是 NULL 比非空的節點還多

bull 有學者提出對這些「放空指標的空間」加以利用的概念mdash與其放空指標不如放指向其它節點的指標稱之為引線 (thread) 使得某些運算(如走訪hellip等)可以加快

bull 這個概念發展出了引線二元樹 (threaded binary tree) 這種資料結構

範例 5-17下圖為加了引線的二元樹

加入的引線將使二元樹的中序走訪更加便利沿著上右圖箭頭所指示的順序走訪即可完成此二元樹的中序走訪GDHBAIECF 為此二元樹的中序表示

bull 範例 5-17 中所加的引線目的在方便二元樹的中序走訪

bull 對樹葉節點 p 而言令其資料為 P 則引線指標的規則如下

(1) p 的左子樹指標指向中序表示中 P 的前一個 元素(前接元素 (predecessor) )

(2) p 的右子樹指標指向中序表示中 P 的後一個 元素(後繼元素 (successor) )

為了區別節點指標放的是一般指標(內部節點)抑或是引線指標(樹葉節點)我們另以 2 個欄位分別區別左和右子樹指標空間存放的對象其節點的記憶體配置有如下圖所示

其中 leftthread (rightthread) 為 0 時表示 leftchild (rightchild) 存放的是一般節點指標而 leftthread (rightthread) 為 1 時表示 leftchild (rightchild) 存放的是引線指標1 struct TBTreeNode2 int leftthread3 struct TBTreeNode leftchild4 char data5 struct TBTreeNode rightchild6 int rightthread7

由右側的引線二元樹可知有最左和最右兩條引線尚無妥善的安排我們另設計一空的引線節點做為樹根如下圖所示

571  引線二元樹的中序走訪決定節點 p 在中序表示中的後繼節點 q (簡稱中序後繼點)

程式 5-10決定節點 node的中序後繼點1 struct TBTreeNode2 int leftthread3 struct TBTreeNode leftchild4 char data5 struct TBTreeNode rightchild6 int rightthread7 8 struct TBTreeNode root9 struct TBTreeNode Next(struct TBTreeNode node)

10 struct TBTreeNode temp11 temp = node-gtrightchild12 if (node-gtrightthead) return temp13 while (temp-gtleftthead) temp=temp-gtleftchild

14 return temp15

程式 5-11引線二元樹的中序表示(接程式 5-10)1 void InorderTBTree()2 struct TBTreeNode node3 for(node=Next(root)node=rootnode=Next(node))

4 cout ltlt node-gtdata5

bull只要 node 是引線二元樹上的任一節點透過呼叫 Next(node) 即可找到其在中序表示中的後繼節點bull(試想若程式 5-10 中的程序 Next 所傳入的參數指標正是 root Next(root) 是否可正常運作請思考)bull於是只須從中序表示中的第一節點起讓每一節點 node 都執行 Next(node) 程序即可得引線二元樹的中序表示

572  在引線二元樹中加入節點在引線二元樹中樹葉節點節點的右子樹指標所指向或內部節點右子樹的最左樹葉為其中序後繼節點而且任一節點若無左子樹則其左子樹指標乃指向其中序前接節點hellip這些性質必須在節點新增進入引線二元樹時也要保持接下來討論節點 new 插入成為引線二元樹的節點 p 的右子節點的情形至於插入成為左子節點的情形則讓各位模擬推敲

572572  在引線二元樹中加入節點 在引線二元樹中加入節點 (( 續續 ))

newnew 會成為會成為 pp 的右子節點的情況共有以下兩種的右子節點的情況共有以下兩種(1) (1) 若若 pp 沒有右子節點 沒有右子節點 ((p-gtrightthread p-gtrightthread 為為1) 1) 如圖如圖 ((a) a) 則 則 new new 插入後應形成如圖插入後應形成如圖 ((b) b) 所示所示

(2)(2) 若若 pp 有右子節點 有右子節點 ((p-gtrightthread p-gtrightthread 為為0) 0) 如圖如圖 ((c) c) 則 則 new new 插入後應形成如插入後應形成如圖圖 ((d) d) 所示所示

程式 5-12  插入節點 new 成為引線二元樹中節點 p的右子節點(上接程式 5-10 5-11 )

1 void InsertRight(struct TBTreeNode p 2 struct TBTreeNode new)3 struct TBTreeNode q4 new-gtrightchild = p-gtrightchild5 new-gtrightthead = p-gtrightthread6 new-gtleftchild = p7 new-gtleftthread = 18 p-gtrightchild = new9 p-gtrightthead = 010 if (new-gtrightthread)11 q = Next(new)12 q-gtleftchild = new13 14

58  堆積bull 「堆積」 (heap) 是一個特殊的完備二元樹節點的資料與其子節點的資料之間有事先定好的大小關係存在bull 定義ndash最大堆疊為一完備二元樹任一節點的資料內容不小於其子節點的資料內容ndash最小堆疊為一完備二元樹任一節點的資料內容不大於其子節點的資料內容

581  插入節點至最大堆積中最大堆積是一完備二元樹所以如圖 (a) 之最大堆積於加入新節點後樹的形狀必須如圖 (b) 所示灰色節點即為新節點若加入的節點資料分別為 1 9 12 則其最大堆積將分別變成如圖 (c) (d) (e) 所示

程式 5-13 新增節點至最大堆積中最大堆積本身是一棵完備二元樹可以用陣列表示之 1 define maxsize 1002 int heap[maxsize]3 int n i4 void InsertHeap(int x)5 if (n == maxsize) HeapFull()6 else7 n++8 i = n9 while ((i gt 1) ampamp (x gt heap[i2]))10 heap[i] = heap[i2]11 i = 212 13 heap[i] = x14 16

程序 InsertHeap 在插入第 n 個資料時需要的時間複雜度為 O(logn)

1 100

2 93

3 84

4 77

5 65

6 81

7 79

8 66

9 70

10 50

100

84

77

93

8165

66 70

79

50

1

2 3

4 5 6 7

8 9 10

1 define maxsize 1002 int heap[maxsize]3 int n i4 void InsertHeap(int x)5 if (n == maxsize) HeapFull()6 else7 n++8 i = n9 while ((i gt 1) ampamp (x gt heap[i2]))10 heap[i] = heap[i2]11 i = 212 13 heap[i] = x14 16

582 刪除最大堆積中的最大資料圖 (a) 中的最大堆積在刪除最大資料後其樹形應變成如圖 (b) 所示將最未一個樹葉節點的資料 6 暫挪至樹根節點內如圖 (c) 再將圖 (c) 中非最大堆積的結構調整成為最大堆積 10 和 8 中選大者與 6對調即成圖 (d)

程式 5-14 刪除最大堆積中的最大資料(上接程式 5-13 )1 int DeleteHeap()2 int x i j3 if (n == 0) HeapEmpty()4 else5 x = heap[1]6 heap[1] = heap[n]7 n--8 i = 19 while (i lt= n2)10 if (heap[2i] gt heap[2i+1])11 j = 2i12 else13 j = 2i+114 if (heap[i]gtheap[j]) break15 swap(heap[i] heap[j])16 i = j17 18 return x19 20

其時間複雜度與其堆積高度相同是為O(logn) 其中 n 為堆積中的元素個數

1 100

2 93

3 84

4 77

5 65

6 81

7 79

8 66

9 70

10 50

100

84

77

93

8165

66 70

79

50

1

2 3

4 5 6 7

8 9 10

1 int DeletHeap()2 int x i j3 if(n == 0) HeapEmpty()4 else5 x = heap[1]6 heap[1] = heap[n]7 n--8 i = 19 while (i lt= n2)10 if ([heap[2i]] gt heap[2i+1])11 j = 2i12 else13 j = 2i+114 if (heap[i]gtheap[j]) break15 swap(heap[i] heap[j])16 i = j17 18 return x19 20

583  優先佇列bull 由於最大堆積(或最小堆積)的結構可提供最大元素(或最小元素)而其維護堆積的成本為 O(logn) n 為總元素個數

bull 堆積常被當成「優先佇列」 (priority queue) 使用mdash此時佇列被組織成完備二元樹若新加入佇列的資料有大小關係優先順序的需求時此優先佇列即以 O(logn) 的代價將新資料放入優先佇列(可能是最大或最小堆積依實際大小關係優先順序的需求而定)中mdash提供依元素的優先順序做為處理順序的服務

bull 一般的佇列為先進先出先到的先行處理優先佇列則是依據其優先順序優先順序高者先行處理與來到的先後無關

範例 5-21bull 在「分時」 (time sharing) 的作業系統中我們不希望使用中央處理器 (Central Processing Unit CPU) 時間短的「處理程序」 (process) 等候過久於是有「最短時間工作優先」 (shortest-job-first) 的排程策略此時等候的處理程序即可組織成之一最小堆積而 CPU 每次即取出存於樹根的處理程序予以處理任何新來的處理程序則以 O(logn) 的時間加入此優先佇列(最小堆積)中

bull 注意之前提過的線性佇列新來的直接加入成為新的尾端元素花O(1) 的時間然線性佇列本身沒有大小的關係每次要取出最大(小)的資料得花O(n) 的代價(在所有元素中挑出最大(小)者) n 為佇列內的元素個數而優先佇列是在新元素加入時要花O(logn) 的時間至於取出最大(小)則只須 O(1) 的時間

59  二元搜尋樹bull 二元搜尋樹是一棵二元樹可能是空二元樹若不為空二元樹則滿足下列性質ndash所有節點內的資料內容是相異的 ndash左子節點的資內容 ( 如果有 ) 比父節點的資料內容小 ndash右子節點的資料內容 ( 如果有 ) 比父節點的資料內容大 ndash以左和右子節點為樹根的左子樹和右子樹也是二元搜尋樹

在二元搜尋樹中搜尋資料二元搜尋樹對資料大小的掌握十分明確若恰與節點 p 資料相等則搜尋成功若欲搜尋的資料 x 比樹節點 p 中的資料小 則只須搜尋節點 p 的左子樹若比節點 p 中的資料大 則只須搜尋 p 的右子樹這樣的判斷可以遞迴地應用在各子樹節點 中

程式 5-15 在二元搜尋樹中搜尋資料(遞迴呼叫)1 struct BSTreeNode2 struct BSTreeNode leftchild3 int data4 struct BSTreeNode rightchild5 6 struct BSTreeNode root7 struct BSTreeNode SearchBST(

struct BSTreeNode tree int x)8 if (tree == NULL) return NULL9 if (x == tree-gtdata) return tree10 if (x lt tree-gtdata)11 return SearchBST(tree-gtleftchild x)

12 return SearchBST(tree-gtrightchild x)13

程式 5-16 搜尋二元搜尋樹的資料(非遞迴呼叫)在二元搜尋樹十分龐大時遞迴呼叫的效率不會太好可利用迴圈來完成搜尋的動作以提高執行效率請看程式 5-16

14 struct BSTreeNode Search BST_iterative15 (struct BSTreeNode node int x)16 while (node = NULL)17 if (x == node-gtdata) return node18 if (x lt node-gtdata)19 node = node-gtleftchild20 else21 node = node-gtrightchild22 23 return NULL24

592  新增資料於二元搜尋樹中欲新增資料進入一二元搜尋樹中得先決定該資料在二元搜尋樹中應出現的位置這可透過上節提到的搜尋動作來解決

在二元搜尋樹定義的性質 (1) 中我們限定了元素必須相異所以搜尋新資料的結果必是 NULL

必須將搜尋的結果改為新增資料所應在位置的節點指標

程式 5-17  在二元搜尋樹中新增資料25 int InsertBST(int x)26 struct BSTreeNode p q27 p = root q = NULL28 while (p = NULL)29 q = p30 if (x == p-gtdata) return 0

若不考慮重複則可省略之31 if (x lt p-gtdata)32 p = p-gtleftchild33 else34 p = p-gtrightchild35

3636 p = (struct BSTreeNode ) p = (struct BSTreeNode ) malloc (sizeof(BSTreeNode))malloc (sizeof(BSTreeNode))

3737 p-gtdata = x p-gtdata = x3838 p-gtleftchild = p-gtrightchild = p-gtleftchild = p-gtrightchild = NULLNULL3939 if (root == NULL) root = p if (root == NULL) root = p4040 else if (x lt q-gtdata) else if (x lt q-gtdata)4141 q-gtleftchild = p q-gtleftchild = p4242 else else4343 q-gtrightchild = p q-gtrightchild = p4444 return 1 return 145 45

程式 程式 5-175-17  在二元搜尋樹中新增資料  在二元搜尋樹中新增資料 (( 續續 ))

範例 5-23 圖 (b) 為圖 (a) 新增整數 6 之後的結果圖 (c) (d) (e) (f) 則分別是逐步再加入 28 18

10 21 的結果

bull若目前的二元搜尋樹高度為 h 則新增資料得花O(h) 的時間找到其所應在的位置遂新增資料至二元搜尋樹中的時間複雜之度為 O(h)

593 自二元搜尋樹中刪除資料欲自二元搜尋樹中刪除資料可能會面臨兩種情況(1) 刪除節點 p 位於樹葉節點上如下圖所示 這是比較容易處理的情況

(2) 刪除的節點 p 不是樹葉刪除節點 p 後原 p 所指的節點可由 q ( q 中包含了 p 左子樹中的最大節點值)或 r ( r 中包含了 p 右子樹中的最小節點值)來取代以維持二元搜尋樹中資料的大小關係其結果分別如圖 (b) 和 (c) 所示而圖 (d) 則標明了指標會改變的節點位置即 p 會消失 father 的左或右子樹指標會異動 k(可能是 q 或 r )會移到原來 p 的位置其父節點 f 的左或右子樹指標會異動其中 father 為 p 父節點 f 為 k 的父節點

程式 5-18 自二元搜尋樹中刪除資料46 int DeleteBST (int x)47 Struct BSTreeNode p father k f48 p = root father = NULL49 while (p = NULL)50 if (x == p-gtdata) 找到 x 所在的節點 p 了51 if ((p-gtleftchild == NULL) ampamp 52 (p-gtrightchild == NULL))53 k = NULL p為樹葉節點54 else if (p-gtleftchild = NULL )x位於非子節

點55 f = p 找出 p左子樹的最右樹葉56 k = p-gtleftchild57 while (k-gtrightchild =NULL)58 f = k59 k = k-gtrightchild60 k將挪至原 p處61 if (p == f)62 f-gtleftchild = k-gtleftchild 63 else64 f-gtrightchild = k-gtleftchild 65

66 else p無左子樹67 f = p 找出 p右子樹的最左樹葉68 k = p-gtrightchild69 while (k-gtleftchild = NULL)70 f = k71 k = k -gtleftchild72 73 if (p == f)74 f-gtrightchild = k-gtrightchild 75 else76 f-gtleftchild = k-gtrightchild77 78 if (k = NULL) k挪至原 p處繼承 p的左右指標79 k-gtleftchild = p-gtleftchild80 k-gtrightchild = p-gtrightchild81 82 if (father == NULL) root = k83 else if (x lt father-gtdata) 決定 k是 father的左

或右兒子84 father-gtleftchild = k85 else86 father-gtrightchild = k87 free(p)88 return 1 成功地刪除 x於此傳回 1返回呼叫處89 51~89行為找到 x時應有的處理步驟

90 else 尚未找到 x繼續往下一階層找

91 father = p92 if (x lt p-gtdata)93 p = p-gtleftchild94 else95 p = p-gtrightchild96 97 98 return 0 未找到 x傳回 099

510  樹林 (forest)

bull 定義樹林(森林)是由 n 棵沒有交集的樹所構成的集合 n 0

下圖為一包含三棵樹的樹林

5101 轉換樹林為二元樹 之前我們以二元樹來表示樹因為二元樹有較多

好用的性質在此我們也將樹林轉換成二元樹看待

在 522 節中我們嘗試了用左子右兄弟的方式來表示樹這個方法事實上已將樹轉換成二元樹

即用左子右兄弟的結構重新組織樹林中的每一棵樹這些樹的樹根將視為兄弟而最左邊的樹則視為樹林二元樹的樹根如此一來樹林即可轉換成二元樹

範例 5-25右下圖為右上圖三棵樹所對應的三棵左子右兄弟二元樹

下圖則為上圖樹林所對應的一棵樹林二元樹

演算法 5-1  轉換樹林成二元樹輸入樹林 F = T1 T2 Tn

輸出 F 的二元樹表示 BT=(T1 T2 Tn)1 if (n == 0) 輸出空二元樹 2 else

3 root(T1 T2 Tn) = T1

4 leftsubtree(T1 T2 Tn) =

BT(T11 T12 T1m)5 BT(T11 T12 T1m)為 T1的子樹6 rightsubtree(T1 T2 Tn) =

BT(T2 T3 Tn)

樹林轉換成二元樹的過程樹林轉換成二元樹的過程

  • Slide 1
  • Slide 2
  • Slide 3
  • Slide 4
  • Slide 5
  • Slide 6
  • Slide 7
  • Slide 8
  • Slide 9
  • Slide 10
  • Slide 11
  • Slide 12
  • Slide 13
  • Slide 14
  • Slide 15
  • Slide 16
  • Slide 17
  • Slide 18
  • Slide 19
  • Slide 20
  • Slide 21
  • Slide 22
  • Slide 23
  • Slide 24
  • Slide 25
  • Slide 26
  • Slide 27
  • Slide 28
  • Slide 29
  • Slide 30
  • Slide 31
  • Slide 32
  • Slide 33
  • Slide 34
  • Slide 35
  • Slide 36
  • Slide 37
  • Slide 38
  • Slide 39
  • Slide 40
  • Slide 41
  • Slide 42
  • Slide 43
  • Slide 44
  • Slide 45
  • Slide 46
  • Slide 47
  • Slide 48
  • Slide 49
  • Slide 50
  • Slide 51
  • Slide 52
  • Slide 53
  • Slide 54
  • Slide 55
  • Slide 56
  • Slide 57
  • Slide 58
  • Slide 59
  • Slide 60
  • Slide 61
  • Slide 62
  • Slide 63
  • Slide 64
  • Slide 65
  • Slide 66
  • Slide 67
  • Slide 68
  • Slide 69
  • Slide 70
  • Slide 71
  • Slide 72
  • Slide 73
  • Slide 74
  • Slide 75
  • Slide 76
  • Slide 77
  • Slide 78
  • Slide 79
  • Slide 80
  • Slide 81
  • Slide 82
  • Slide 83
  • Slide 84
  • Slide 85
  • Slide 86
  • Slide 87
  • Slide 88
  • Slide 89
  • Slide 90
  • Slide 91
  • Slide 92
  • Slide 93
  • Slide 94
  • Slide 95
  • Slide 96
  • Slide 97
  • Slide 98
  • Slide 99
  • Slide 100
  • Slide 101
  • Slide 102
  • Slide 103
Page 54: 第五章 樹

prefix ABHJCDFGE

infix HBJAFDGCEk-1

struct BTreeNode BTree_InfixPrefix (AnsiString infix AnsiString prefix)

int k struct BTreeNode node if (infixLength==0) return NULLnode = (struct BTreeNode ) malloc (sizeof(struct BTreeNode))node-gtdata = prefix[1]k = find(prefix[1] infix)node-gtleftchild = BTree_InfixPrefix

(infix[1k-1] prefix[2k])node-gtrightchild = BTree_InfixPrefix(infix[k+1infixLength]prefix[k+1prefixLength])

VLR

LVRk1

k2 k+1~

k+1~

1

struct BTreeNode BTree_InfixPostfix (AnsiString infix AnsiString prefix)

int k struct BTreeNode node if (infixLength==0) return NULLnode = (struct BTreeNode ) malloc (sizeof(struct BTreeNode))node-gtdata = postfix[postfixLength]k = find(postfix[postfixLength] infix)node-gtleftchild = BTree_InfixPrefix

(infix[1k-1] postfix[1k-1])node-gtrightchild = BTree_InfixPrefix(infix[k+1infixLength]postfix[k+1postfixLength-1])

56  二元樹的複製與相等測試程式 5-8 二元樹的複製1 struct BTreeNode2 struct BTreeNode leftchild3 char data4 struct BTreeNode rightchild5 6 struct BTreeNode root7 struct BTreeNode Copy(struct BTreeNode TreeRoot)8 struct BTreeNode CopyRoot9 if (TreeRoot == NULL)10 return NULL11 CopyRoot =(struct BTreeNode ) malloc (sizeof(BTreeNode))12 CopyRoot-gtdata = TreeRoot-gtdata13 CopyRoot-gtleftchild = Copy(TreeRoot-gtleftchild)14 CopyRoot-gtrightchild = Copy(TreeRoot-

gtrightchild)15 return CopyRoot16

程式 5-9 檢測兩棵二元樹是否相等1 struct BTreeNode2 struct BTreeNode leftchild3 char data4 struct BTreeNode rightchild5 6 struct BtreeNode rightchild7 int Equal(struct BTreeNode ustruct BTreeNode v)8 if ((u==NULL) ampamp (v==NULL)) return 19 if ((u=NULL) ampamp (v=NULL)10 ampamp (u-gtdata == v-gtdata )11 ampamp Equal(u-gtleftchildv-gtleftchild)12 ampamp Equal(u-gtrightchildv-gtrightchild))

13 return 114 return 015

Counting Nodes in a Binary Tree

int CountingNodes(struct BTreeNodes node)

if (node == NULL) return 0 int leftchildren = CountingNodes(node-gtleftchild)

int rightchildren= CountingNodes(node-gtrightchild)

return leftchildren+rightchildren

Counting Leaves in a Binary Tree

int CountingLeaves(struct BTreeNodes node)

if (node == NULL) return 0 int leftleaves = CountingLeaves(node-gtleftchild)

int rightchildren= CountingLeaves(node-gtrightchild)

if (leftleaves==0 ampamp leftleaves==0) return 1

return leftleaves + leftleaves

57  引線二元樹bull 在二元樹的鍵結表示中樹葉節點的兩個子樹指標皆指向 NULL 由定理 5-1 得知 n = E+1 其中 n 為節點個數 E 為分支數

bull 而 n 個鍵結節點有 2n 個指標空間每個分支恰佔用一個指標空間共有 E (=n-1) 個指標有用到(不是空的)而共有 n+1 個指標空間是 NULL 比非空的節點還多

bull 有學者提出對這些「放空指標的空間」加以利用的概念mdash與其放空指標不如放指向其它節點的指標稱之為引線 (thread) 使得某些運算(如走訪hellip等)可以加快

bull 這個概念發展出了引線二元樹 (threaded binary tree) 這種資料結構

範例 5-17下圖為加了引線的二元樹

加入的引線將使二元樹的中序走訪更加便利沿著上右圖箭頭所指示的順序走訪即可完成此二元樹的中序走訪GDHBAIECF 為此二元樹的中序表示

bull 範例 5-17 中所加的引線目的在方便二元樹的中序走訪

bull 對樹葉節點 p 而言令其資料為 P 則引線指標的規則如下

(1) p 的左子樹指標指向中序表示中 P 的前一個 元素(前接元素 (predecessor) )

(2) p 的右子樹指標指向中序表示中 P 的後一個 元素(後繼元素 (successor) )

為了區別節點指標放的是一般指標(內部節點)抑或是引線指標(樹葉節點)我們另以 2 個欄位分別區別左和右子樹指標空間存放的對象其節點的記憶體配置有如下圖所示

其中 leftthread (rightthread) 為 0 時表示 leftchild (rightchild) 存放的是一般節點指標而 leftthread (rightthread) 為 1 時表示 leftchild (rightchild) 存放的是引線指標1 struct TBTreeNode2 int leftthread3 struct TBTreeNode leftchild4 char data5 struct TBTreeNode rightchild6 int rightthread7

由右側的引線二元樹可知有最左和最右兩條引線尚無妥善的安排我們另設計一空的引線節點做為樹根如下圖所示

571  引線二元樹的中序走訪決定節點 p 在中序表示中的後繼節點 q (簡稱中序後繼點)

程式 5-10決定節點 node的中序後繼點1 struct TBTreeNode2 int leftthread3 struct TBTreeNode leftchild4 char data5 struct TBTreeNode rightchild6 int rightthread7 8 struct TBTreeNode root9 struct TBTreeNode Next(struct TBTreeNode node)

10 struct TBTreeNode temp11 temp = node-gtrightchild12 if (node-gtrightthead) return temp13 while (temp-gtleftthead) temp=temp-gtleftchild

14 return temp15

程式 5-11引線二元樹的中序表示(接程式 5-10)1 void InorderTBTree()2 struct TBTreeNode node3 for(node=Next(root)node=rootnode=Next(node))

4 cout ltlt node-gtdata5

bull只要 node 是引線二元樹上的任一節點透過呼叫 Next(node) 即可找到其在中序表示中的後繼節點bull(試想若程式 5-10 中的程序 Next 所傳入的參數指標正是 root Next(root) 是否可正常運作請思考)bull於是只須從中序表示中的第一節點起讓每一節點 node 都執行 Next(node) 程序即可得引線二元樹的中序表示

572  在引線二元樹中加入節點在引線二元樹中樹葉節點節點的右子樹指標所指向或內部節點右子樹的最左樹葉為其中序後繼節點而且任一節點若無左子樹則其左子樹指標乃指向其中序前接節點hellip這些性質必須在節點新增進入引線二元樹時也要保持接下來討論節點 new 插入成為引線二元樹的節點 p 的右子節點的情形至於插入成為左子節點的情形則讓各位模擬推敲

572572  在引線二元樹中加入節點 在引線二元樹中加入節點 (( 續續 ))

newnew 會成為會成為 pp 的右子節點的情況共有以下兩種的右子節點的情況共有以下兩種(1) (1) 若若 pp 沒有右子節點 沒有右子節點 ((p-gtrightthread p-gtrightthread 為為1) 1) 如圖如圖 ((a) a) 則 則 new new 插入後應形成如圖插入後應形成如圖 ((b) b) 所示所示

(2)(2) 若若 pp 有右子節點 有右子節點 ((p-gtrightthread p-gtrightthread 為為0) 0) 如圖如圖 ((c) c) 則 則 new new 插入後應形成如插入後應形成如圖圖 ((d) d) 所示所示

程式 5-12  插入節點 new 成為引線二元樹中節點 p的右子節點(上接程式 5-10 5-11 )

1 void InsertRight(struct TBTreeNode p 2 struct TBTreeNode new)3 struct TBTreeNode q4 new-gtrightchild = p-gtrightchild5 new-gtrightthead = p-gtrightthread6 new-gtleftchild = p7 new-gtleftthread = 18 p-gtrightchild = new9 p-gtrightthead = 010 if (new-gtrightthread)11 q = Next(new)12 q-gtleftchild = new13 14

58  堆積bull 「堆積」 (heap) 是一個特殊的完備二元樹節點的資料與其子節點的資料之間有事先定好的大小關係存在bull 定義ndash最大堆疊為一完備二元樹任一節點的資料內容不小於其子節點的資料內容ndash最小堆疊為一完備二元樹任一節點的資料內容不大於其子節點的資料內容

581  插入節點至最大堆積中最大堆積是一完備二元樹所以如圖 (a) 之最大堆積於加入新節點後樹的形狀必須如圖 (b) 所示灰色節點即為新節點若加入的節點資料分別為 1 9 12 則其最大堆積將分別變成如圖 (c) (d) (e) 所示

程式 5-13 新增節點至最大堆積中最大堆積本身是一棵完備二元樹可以用陣列表示之 1 define maxsize 1002 int heap[maxsize]3 int n i4 void InsertHeap(int x)5 if (n == maxsize) HeapFull()6 else7 n++8 i = n9 while ((i gt 1) ampamp (x gt heap[i2]))10 heap[i] = heap[i2]11 i = 212 13 heap[i] = x14 16

程序 InsertHeap 在插入第 n 個資料時需要的時間複雜度為 O(logn)

1 100

2 93

3 84

4 77

5 65

6 81

7 79

8 66

9 70

10 50

100

84

77

93

8165

66 70

79

50

1

2 3

4 5 6 7

8 9 10

1 define maxsize 1002 int heap[maxsize]3 int n i4 void InsertHeap(int x)5 if (n == maxsize) HeapFull()6 else7 n++8 i = n9 while ((i gt 1) ampamp (x gt heap[i2]))10 heap[i] = heap[i2]11 i = 212 13 heap[i] = x14 16

582 刪除最大堆積中的最大資料圖 (a) 中的最大堆積在刪除最大資料後其樹形應變成如圖 (b) 所示將最未一個樹葉節點的資料 6 暫挪至樹根節點內如圖 (c) 再將圖 (c) 中非最大堆積的結構調整成為最大堆積 10 和 8 中選大者與 6對調即成圖 (d)

程式 5-14 刪除最大堆積中的最大資料(上接程式 5-13 )1 int DeleteHeap()2 int x i j3 if (n == 0) HeapEmpty()4 else5 x = heap[1]6 heap[1] = heap[n]7 n--8 i = 19 while (i lt= n2)10 if (heap[2i] gt heap[2i+1])11 j = 2i12 else13 j = 2i+114 if (heap[i]gtheap[j]) break15 swap(heap[i] heap[j])16 i = j17 18 return x19 20

其時間複雜度與其堆積高度相同是為O(logn) 其中 n 為堆積中的元素個數

1 100

2 93

3 84

4 77

5 65

6 81

7 79

8 66

9 70

10 50

100

84

77

93

8165

66 70

79

50

1

2 3

4 5 6 7

8 9 10

1 int DeletHeap()2 int x i j3 if(n == 0) HeapEmpty()4 else5 x = heap[1]6 heap[1] = heap[n]7 n--8 i = 19 while (i lt= n2)10 if ([heap[2i]] gt heap[2i+1])11 j = 2i12 else13 j = 2i+114 if (heap[i]gtheap[j]) break15 swap(heap[i] heap[j])16 i = j17 18 return x19 20

583  優先佇列bull 由於最大堆積(或最小堆積)的結構可提供最大元素(或最小元素)而其維護堆積的成本為 O(logn) n 為總元素個數

bull 堆積常被當成「優先佇列」 (priority queue) 使用mdash此時佇列被組織成完備二元樹若新加入佇列的資料有大小關係優先順序的需求時此優先佇列即以 O(logn) 的代價將新資料放入優先佇列(可能是最大或最小堆積依實際大小關係優先順序的需求而定)中mdash提供依元素的優先順序做為處理順序的服務

bull 一般的佇列為先進先出先到的先行處理優先佇列則是依據其優先順序優先順序高者先行處理與來到的先後無關

範例 5-21bull 在「分時」 (time sharing) 的作業系統中我們不希望使用中央處理器 (Central Processing Unit CPU) 時間短的「處理程序」 (process) 等候過久於是有「最短時間工作優先」 (shortest-job-first) 的排程策略此時等候的處理程序即可組織成之一最小堆積而 CPU 每次即取出存於樹根的處理程序予以處理任何新來的處理程序則以 O(logn) 的時間加入此優先佇列(最小堆積)中

bull 注意之前提過的線性佇列新來的直接加入成為新的尾端元素花O(1) 的時間然線性佇列本身沒有大小的關係每次要取出最大(小)的資料得花O(n) 的代價(在所有元素中挑出最大(小)者) n 為佇列內的元素個數而優先佇列是在新元素加入時要花O(logn) 的時間至於取出最大(小)則只須 O(1) 的時間

59  二元搜尋樹bull 二元搜尋樹是一棵二元樹可能是空二元樹若不為空二元樹則滿足下列性質ndash所有節點內的資料內容是相異的 ndash左子節點的資內容 ( 如果有 ) 比父節點的資料內容小 ndash右子節點的資料內容 ( 如果有 ) 比父節點的資料內容大 ndash以左和右子節點為樹根的左子樹和右子樹也是二元搜尋樹

在二元搜尋樹中搜尋資料二元搜尋樹對資料大小的掌握十分明確若恰與節點 p 資料相等則搜尋成功若欲搜尋的資料 x 比樹節點 p 中的資料小 則只須搜尋節點 p 的左子樹若比節點 p 中的資料大 則只須搜尋 p 的右子樹這樣的判斷可以遞迴地應用在各子樹節點 中

程式 5-15 在二元搜尋樹中搜尋資料(遞迴呼叫)1 struct BSTreeNode2 struct BSTreeNode leftchild3 int data4 struct BSTreeNode rightchild5 6 struct BSTreeNode root7 struct BSTreeNode SearchBST(

struct BSTreeNode tree int x)8 if (tree == NULL) return NULL9 if (x == tree-gtdata) return tree10 if (x lt tree-gtdata)11 return SearchBST(tree-gtleftchild x)

12 return SearchBST(tree-gtrightchild x)13

程式 5-16 搜尋二元搜尋樹的資料(非遞迴呼叫)在二元搜尋樹十分龐大時遞迴呼叫的效率不會太好可利用迴圈來完成搜尋的動作以提高執行效率請看程式 5-16

14 struct BSTreeNode Search BST_iterative15 (struct BSTreeNode node int x)16 while (node = NULL)17 if (x == node-gtdata) return node18 if (x lt node-gtdata)19 node = node-gtleftchild20 else21 node = node-gtrightchild22 23 return NULL24

592  新增資料於二元搜尋樹中欲新增資料進入一二元搜尋樹中得先決定該資料在二元搜尋樹中應出現的位置這可透過上節提到的搜尋動作來解決

在二元搜尋樹定義的性質 (1) 中我們限定了元素必須相異所以搜尋新資料的結果必是 NULL

必須將搜尋的結果改為新增資料所應在位置的節點指標

程式 5-17  在二元搜尋樹中新增資料25 int InsertBST(int x)26 struct BSTreeNode p q27 p = root q = NULL28 while (p = NULL)29 q = p30 if (x == p-gtdata) return 0

若不考慮重複則可省略之31 if (x lt p-gtdata)32 p = p-gtleftchild33 else34 p = p-gtrightchild35

3636 p = (struct BSTreeNode ) p = (struct BSTreeNode ) malloc (sizeof(BSTreeNode))malloc (sizeof(BSTreeNode))

3737 p-gtdata = x p-gtdata = x3838 p-gtleftchild = p-gtrightchild = p-gtleftchild = p-gtrightchild = NULLNULL3939 if (root == NULL) root = p if (root == NULL) root = p4040 else if (x lt q-gtdata) else if (x lt q-gtdata)4141 q-gtleftchild = p q-gtleftchild = p4242 else else4343 q-gtrightchild = p q-gtrightchild = p4444 return 1 return 145 45

程式 程式 5-175-17  在二元搜尋樹中新增資料  在二元搜尋樹中新增資料 (( 續續 ))

範例 5-23 圖 (b) 為圖 (a) 新增整數 6 之後的結果圖 (c) (d) (e) (f) 則分別是逐步再加入 28 18

10 21 的結果

bull若目前的二元搜尋樹高度為 h 則新增資料得花O(h) 的時間找到其所應在的位置遂新增資料至二元搜尋樹中的時間複雜之度為 O(h)

593 自二元搜尋樹中刪除資料欲自二元搜尋樹中刪除資料可能會面臨兩種情況(1) 刪除節點 p 位於樹葉節點上如下圖所示 這是比較容易處理的情況

(2) 刪除的節點 p 不是樹葉刪除節點 p 後原 p 所指的節點可由 q ( q 中包含了 p 左子樹中的最大節點值)或 r ( r 中包含了 p 右子樹中的最小節點值)來取代以維持二元搜尋樹中資料的大小關係其結果分別如圖 (b) 和 (c) 所示而圖 (d) 則標明了指標會改變的節點位置即 p 會消失 father 的左或右子樹指標會異動 k(可能是 q 或 r )會移到原來 p 的位置其父節點 f 的左或右子樹指標會異動其中 father 為 p 父節點 f 為 k 的父節點

程式 5-18 自二元搜尋樹中刪除資料46 int DeleteBST (int x)47 Struct BSTreeNode p father k f48 p = root father = NULL49 while (p = NULL)50 if (x == p-gtdata) 找到 x 所在的節點 p 了51 if ((p-gtleftchild == NULL) ampamp 52 (p-gtrightchild == NULL))53 k = NULL p為樹葉節點54 else if (p-gtleftchild = NULL )x位於非子節

點55 f = p 找出 p左子樹的最右樹葉56 k = p-gtleftchild57 while (k-gtrightchild =NULL)58 f = k59 k = k-gtrightchild60 k將挪至原 p處61 if (p == f)62 f-gtleftchild = k-gtleftchild 63 else64 f-gtrightchild = k-gtleftchild 65

66 else p無左子樹67 f = p 找出 p右子樹的最左樹葉68 k = p-gtrightchild69 while (k-gtleftchild = NULL)70 f = k71 k = k -gtleftchild72 73 if (p == f)74 f-gtrightchild = k-gtrightchild 75 else76 f-gtleftchild = k-gtrightchild77 78 if (k = NULL) k挪至原 p處繼承 p的左右指標79 k-gtleftchild = p-gtleftchild80 k-gtrightchild = p-gtrightchild81 82 if (father == NULL) root = k83 else if (x lt father-gtdata) 決定 k是 father的左

或右兒子84 father-gtleftchild = k85 else86 father-gtrightchild = k87 free(p)88 return 1 成功地刪除 x於此傳回 1返回呼叫處89 51~89行為找到 x時應有的處理步驟

90 else 尚未找到 x繼續往下一階層找

91 father = p92 if (x lt p-gtdata)93 p = p-gtleftchild94 else95 p = p-gtrightchild96 97 98 return 0 未找到 x傳回 099

510  樹林 (forest)

bull 定義樹林(森林)是由 n 棵沒有交集的樹所構成的集合 n 0

下圖為一包含三棵樹的樹林

5101 轉換樹林為二元樹 之前我們以二元樹來表示樹因為二元樹有較多

好用的性質在此我們也將樹林轉換成二元樹看待

在 522 節中我們嘗試了用左子右兄弟的方式來表示樹這個方法事實上已將樹轉換成二元樹

即用左子右兄弟的結構重新組織樹林中的每一棵樹這些樹的樹根將視為兄弟而最左邊的樹則視為樹林二元樹的樹根如此一來樹林即可轉換成二元樹

範例 5-25右下圖為右上圖三棵樹所對應的三棵左子右兄弟二元樹

下圖則為上圖樹林所對應的一棵樹林二元樹

演算法 5-1  轉換樹林成二元樹輸入樹林 F = T1 T2 Tn

輸出 F 的二元樹表示 BT=(T1 T2 Tn)1 if (n == 0) 輸出空二元樹 2 else

3 root(T1 T2 Tn) = T1

4 leftsubtree(T1 T2 Tn) =

BT(T11 T12 T1m)5 BT(T11 T12 T1m)為 T1的子樹6 rightsubtree(T1 T2 Tn) =

BT(T2 T3 Tn)

樹林轉換成二元樹的過程樹林轉換成二元樹的過程

  • Slide 1
  • Slide 2
  • Slide 3
  • Slide 4
  • Slide 5
  • Slide 6
  • Slide 7
  • Slide 8
  • Slide 9
  • Slide 10
  • Slide 11
  • Slide 12
  • Slide 13
  • Slide 14
  • Slide 15
  • Slide 16
  • Slide 17
  • Slide 18
  • Slide 19
  • Slide 20
  • Slide 21
  • Slide 22
  • Slide 23
  • Slide 24
  • Slide 25
  • Slide 26
  • Slide 27
  • Slide 28
  • Slide 29
  • Slide 30
  • Slide 31
  • Slide 32
  • Slide 33
  • Slide 34
  • Slide 35
  • Slide 36
  • Slide 37
  • Slide 38
  • Slide 39
  • Slide 40
  • Slide 41
  • Slide 42
  • Slide 43
  • Slide 44
  • Slide 45
  • Slide 46
  • Slide 47
  • Slide 48
  • Slide 49
  • Slide 50
  • Slide 51
  • Slide 52
  • Slide 53
  • Slide 54
  • Slide 55
  • Slide 56
  • Slide 57
  • Slide 58
  • Slide 59
  • Slide 60
  • Slide 61
  • Slide 62
  • Slide 63
  • Slide 64
  • Slide 65
  • Slide 66
  • Slide 67
  • Slide 68
  • Slide 69
  • Slide 70
  • Slide 71
  • Slide 72
  • Slide 73
  • Slide 74
  • Slide 75
  • Slide 76
  • Slide 77
  • Slide 78
  • Slide 79
  • Slide 80
  • Slide 81
  • Slide 82
  • Slide 83
  • Slide 84
  • Slide 85
  • Slide 86
  • Slide 87
  • Slide 88
  • Slide 89
  • Slide 90
  • Slide 91
  • Slide 92
  • Slide 93
  • Slide 94
  • Slide 95
  • Slide 96
  • Slide 97
  • Slide 98
  • Slide 99
  • Slide 100
  • Slide 101
  • Slide 102
  • Slide 103
Page 55: 第五章 樹

struct BTreeNode BTree_InfixPostfix (AnsiString infix AnsiString prefix)

int k struct BTreeNode node if (infixLength==0) return NULLnode = (struct BTreeNode ) malloc (sizeof(struct BTreeNode))node-gtdata = postfix[postfixLength]k = find(postfix[postfixLength] infix)node-gtleftchild = BTree_InfixPrefix

(infix[1k-1] postfix[1k-1])node-gtrightchild = BTree_InfixPrefix(infix[k+1infixLength]postfix[k+1postfixLength-1])

56  二元樹的複製與相等測試程式 5-8 二元樹的複製1 struct BTreeNode2 struct BTreeNode leftchild3 char data4 struct BTreeNode rightchild5 6 struct BTreeNode root7 struct BTreeNode Copy(struct BTreeNode TreeRoot)8 struct BTreeNode CopyRoot9 if (TreeRoot == NULL)10 return NULL11 CopyRoot =(struct BTreeNode ) malloc (sizeof(BTreeNode))12 CopyRoot-gtdata = TreeRoot-gtdata13 CopyRoot-gtleftchild = Copy(TreeRoot-gtleftchild)14 CopyRoot-gtrightchild = Copy(TreeRoot-

gtrightchild)15 return CopyRoot16

程式 5-9 檢測兩棵二元樹是否相等1 struct BTreeNode2 struct BTreeNode leftchild3 char data4 struct BTreeNode rightchild5 6 struct BtreeNode rightchild7 int Equal(struct BTreeNode ustruct BTreeNode v)8 if ((u==NULL) ampamp (v==NULL)) return 19 if ((u=NULL) ampamp (v=NULL)10 ampamp (u-gtdata == v-gtdata )11 ampamp Equal(u-gtleftchildv-gtleftchild)12 ampamp Equal(u-gtrightchildv-gtrightchild))

13 return 114 return 015

Counting Nodes in a Binary Tree

int CountingNodes(struct BTreeNodes node)

if (node == NULL) return 0 int leftchildren = CountingNodes(node-gtleftchild)

int rightchildren= CountingNodes(node-gtrightchild)

return leftchildren+rightchildren

Counting Leaves in a Binary Tree

int CountingLeaves(struct BTreeNodes node)

if (node == NULL) return 0 int leftleaves = CountingLeaves(node-gtleftchild)

int rightchildren= CountingLeaves(node-gtrightchild)

if (leftleaves==0 ampamp leftleaves==0) return 1

return leftleaves + leftleaves

57  引線二元樹bull 在二元樹的鍵結表示中樹葉節點的兩個子樹指標皆指向 NULL 由定理 5-1 得知 n = E+1 其中 n 為節點個數 E 為分支數

bull 而 n 個鍵結節點有 2n 個指標空間每個分支恰佔用一個指標空間共有 E (=n-1) 個指標有用到(不是空的)而共有 n+1 個指標空間是 NULL 比非空的節點還多

bull 有學者提出對這些「放空指標的空間」加以利用的概念mdash與其放空指標不如放指向其它節點的指標稱之為引線 (thread) 使得某些運算(如走訪hellip等)可以加快

bull 這個概念發展出了引線二元樹 (threaded binary tree) 這種資料結構

範例 5-17下圖為加了引線的二元樹

加入的引線將使二元樹的中序走訪更加便利沿著上右圖箭頭所指示的順序走訪即可完成此二元樹的中序走訪GDHBAIECF 為此二元樹的中序表示

bull 範例 5-17 中所加的引線目的在方便二元樹的中序走訪

bull 對樹葉節點 p 而言令其資料為 P 則引線指標的規則如下

(1) p 的左子樹指標指向中序表示中 P 的前一個 元素(前接元素 (predecessor) )

(2) p 的右子樹指標指向中序表示中 P 的後一個 元素(後繼元素 (successor) )

為了區別節點指標放的是一般指標(內部節點)抑或是引線指標(樹葉節點)我們另以 2 個欄位分別區別左和右子樹指標空間存放的對象其節點的記憶體配置有如下圖所示

其中 leftthread (rightthread) 為 0 時表示 leftchild (rightchild) 存放的是一般節點指標而 leftthread (rightthread) 為 1 時表示 leftchild (rightchild) 存放的是引線指標1 struct TBTreeNode2 int leftthread3 struct TBTreeNode leftchild4 char data5 struct TBTreeNode rightchild6 int rightthread7

由右側的引線二元樹可知有最左和最右兩條引線尚無妥善的安排我們另設計一空的引線節點做為樹根如下圖所示

571  引線二元樹的中序走訪決定節點 p 在中序表示中的後繼節點 q (簡稱中序後繼點)

程式 5-10決定節點 node的中序後繼點1 struct TBTreeNode2 int leftthread3 struct TBTreeNode leftchild4 char data5 struct TBTreeNode rightchild6 int rightthread7 8 struct TBTreeNode root9 struct TBTreeNode Next(struct TBTreeNode node)

10 struct TBTreeNode temp11 temp = node-gtrightchild12 if (node-gtrightthead) return temp13 while (temp-gtleftthead) temp=temp-gtleftchild

14 return temp15

程式 5-11引線二元樹的中序表示(接程式 5-10)1 void InorderTBTree()2 struct TBTreeNode node3 for(node=Next(root)node=rootnode=Next(node))

4 cout ltlt node-gtdata5

bull只要 node 是引線二元樹上的任一節點透過呼叫 Next(node) 即可找到其在中序表示中的後繼節點bull(試想若程式 5-10 中的程序 Next 所傳入的參數指標正是 root Next(root) 是否可正常運作請思考)bull於是只須從中序表示中的第一節點起讓每一節點 node 都執行 Next(node) 程序即可得引線二元樹的中序表示

572  在引線二元樹中加入節點在引線二元樹中樹葉節點節點的右子樹指標所指向或內部節點右子樹的最左樹葉為其中序後繼節點而且任一節點若無左子樹則其左子樹指標乃指向其中序前接節點hellip這些性質必須在節點新增進入引線二元樹時也要保持接下來討論節點 new 插入成為引線二元樹的節點 p 的右子節點的情形至於插入成為左子節點的情形則讓各位模擬推敲

572572  在引線二元樹中加入節點 在引線二元樹中加入節點 (( 續續 ))

newnew 會成為會成為 pp 的右子節點的情況共有以下兩種的右子節點的情況共有以下兩種(1) (1) 若若 pp 沒有右子節點 沒有右子節點 ((p-gtrightthread p-gtrightthread 為為1) 1) 如圖如圖 ((a) a) 則 則 new new 插入後應形成如圖插入後應形成如圖 ((b) b) 所示所示

(2)(2) 若若 pp 有右子節點 有右子節點 ((p-gtrightthread p-gtrightthread 為為0) 0) 如圖如圖 ((c) c) 則 則 new new 插入後應形成如插入後應形成如圖圖 ((d) d) 所示所示

程式 5-12  插入節點 new 成為引線二元樹中節點 p的右子節點(上接程式 5-10 5-11 )

1 void InsertRight(struct TBTreeNode p 2 struct TBTreeNode new)3 struct TBTreeNode q4 new-gtrightchild = p-gtrightchild5 new-gtrightthead = p-gtrightthread6 new-gtleftchild = p7 new-gtleftthread = 18 p-gtrightchild = new9 p-gtrightthead = 010 if (new-gtrightthread)11 q = Next(new)12 q-gtleftchild = new13 14

58  堆積bull 「堆積」 (heap) 是一個特殊的完備二元樹節點的資料與其子節點的資料之間有事先定好的大小關係存在bull 定義ndash最大堆疊為一完備二元樹任一節點的資料內容不小於其子節點的資料內容ndash最小堆疊為一完備二元樹任一節點的資料內容不大於其子節點的資料內容

581  插入節點至最大堆積中最大堆積是一完備二元樹所以如圖 (a) 之最大堆積於加入新節點後樹的形狀必須如圖 (b) 所示灰色節點即為新節點若加入的節點資料分別為 1 9 12 則其最大堆積將分別變成如圖 (c) (d) (e) 所示

程式 5-13 新增節點至最大堆積中最大堆積本身是一棵完備二元樹可以用陣列表示之 1 define maxsize 1002 int heap[maxsize]3 int n i4 void InsertHeap(int x)5 if (n == maxsize) HeapFull()6 else7 n++8 i = n9 while ((i gt 1) ampamp (x gt heap[i2]))10 heap[i] = heap[i2]11 i = 212 13 heap[i] = x14 16

程序 InsertHeap 在插入第 n 個資料時需要的時間複雜度為 O(logn)

1 100

2 93

3 84

4 77

5 65

6 81

7 79

8 66

9 70

10 50

100

84

77

93

8165

66 70

79

50

1

2 3

4 5 6 7

8 9 10

1 define maxsize 1002 int heap[maxsize]3 int n i4 void InsertHeap(int x)5 if (n == maxsize) HeapFull()6 else7 n++8 i = n9 while ((i gt 1) ampamp (x gt heap[i2]))10 heap[i] = heap[i2]11 i = 212 13 heap[i] = x14 16

582 刪除最大堆積中的最大資料圖 (a) 中的最大堆積在刪除最大資料後其樹形應變成如圖 (b) 所示將最未一個樹葉節點的資料 6 暫挪至樹根節點內如圖 (c) 再將圖 (c) 中非最大堆積的結構調整成為最大堆積 10 和 8 中選大者與 6對調即成圖 (d)

程式 5-14 刪除最大堆積中的最大資料(上接程式 5-13 )1 int DeleteHeap()2 int x i j3 if (n == 0) HeapEmpty()4 else5 x = heap[1]6 heap[1] = heap[n]7 n--8 i = 19 while (i lt= n2)10 if (heap[2i] gt heap[2i+1])11 j = 2i12 else13 j = 2i+114 if (heap[i]gtheap[j]) break15 swap(heap[i] heap[j])16 i = j17 18 return x19 20

其時間複雜度與其堆積高度相同是為O(logn) 其中 n 為堆積中的元素個數

1 100

2 93

3 84

4 77

5 65

6 81

7 79

8 66

9 70

10 50

100

84

77

93

8165

66 70

79

50

1

2 3

4 5 6 7

8 9 10

1 int DeletHeap()2 int x i j3 if(n == 0) HeapEmpty()4 else5 x = heap[1]6 heap[1] = heap[n]7 n--8 i = 19 while (i lt= n2)10 if ([heap[2i]] gt heap[2i+1])11 j = 2i12 else13 j = 2i+114 if (heap[i]gtheap[j]) break15 swap(heap[i] heap[j])16 i = j17 18 return x19 20

583  優先佇列bull 由於最大堆積(或最小堆積)的結構可提供最大元素(或最小元素)而其維護堆積的成本為 O(logn) n 為總元素個數

bull 堆積常被當成「優先佇列」 (priority queue) 使用mdash此時佇列被組織成完備二元樹若新加入佇列的資料有大小關係優先順序的需求時此優先佇列即以 O(logn) 的代價將新資料放入優先佇列(可能是最大或最小堆積依實際大小關係優先順序的需求而定)中mdash提供依元素的優先順序做為處理順序的服務

bull 一般的佇列為先進先出先到的先行處理優先佇列則是依據其優先順序優先順序高者先行處理與來到的先後無關

範例 5-21bull 在「分時」 (time sharing) 的作業系統中我們不希望使用中央處理器 (Central Processing Unit CPU) 時間短的「處理程序」 (process) 等候過久於是有「最短時間工作優先」 (shortest-job-first) 的排程策略此時等候的處理程序即可組織成之一最小堆積而 CPU 每次即取出存於樹根的處理程序予以處理任何新來的處理程序則以 O(logn) 的時間加入此優先佇列(最小堆積)中

bull 注意之前提過的線性佇列新來的直接加入成為新的尾端元素花O(1) 的時間然線性佇列本身沒有大小的關係每次要取出最大(小)的資料得花O(n) 的代價(在所有元素中挑出最大(小)者) n 為佇列內的元素個數而優先佇列是在新元素加入時要花O(logn) 的時間至於取出最大(小)則只須 O(1) 的時間

59  二元搜尋樹bull 二元搜尋樹是一棵二元樹可能是空二元樹若不為空二元樹則滿足下列性質ndash所有節點內的資料內容是相異的 ndash左子節點的資內容 ( 如果有 ) 比父節點的資料內容小 ndash右子節點的資料內容 ( 如果有 ) 比父節點的資料內容大 ndash以左和右子節點為樹根的左子樹和右子樹也是二元搜尋樹

在二元搜尋樹中搜尋資料二元搜尋樹對資料大小的掌握十分明確若恰與節點 p 資料相等則搜尋成功若欲搜尋的資料 x 比樹節點 p 中的資料小 則只須搜尋節點 p 的左子樹若比節點 p 中的資料大 則只須搜尋 p 的右子樹這樣的判斷可以遞迴地應用在各子樹節點 中

程式 5-15 在二元搜尋樹中搜尋資料(遞迴呼叫)1 struct BSTreeNode2 struct BSTreeNode leftchild3 int data4 struct BSTreeNode rightchild5 6 struct BSTreeNode root7 struct BSTreeNode SearchBST(

struct BSTreeNode tree int x)8 if (tree == NULL) return NULL9 if (x == tree-gtdata) return tree10 if (x lt tree-gtdata)11 return SearchBST(tree-gtleftchild x)

12 return SearchBST(tree-gtrightchild x)13

程式 5-16 搜尋二元搜尋樹的資料(非遞迴呼叫)在二元搜尋樹十分龐大時遞迴呼叫的效率不會太好可利用迴圈來完成搜尋的動作以提高執行效率請看程式 5-16

14 struct BSTreeNode Search BST_iterative15 (struct BSTreeNode node int x)16 while (node = NULL)17 if (x == node-gtdata) return node18 if (x lt node-gtdata)19 node = node-gtleftchild20 else21 node = node-gtrightchild22 23 return NULL24

592  新增資料於二元搜尋樹中欲新增資料進入一二元搜尋樹中得先決定該資料在二元搜尋樹中應出現的位置這可透過上節提到的搜尋動作來解決

在二元搜尋樹定義的性質 (1) 中我們限定了元素必須相異所以搜尋新資料的結果必是 NULL

必須將搜尋的結果改為新增資料所應在位置的節點指標

程式 5-17  在二元搜尋樹中新增資料25 int InsertBST(int x)26 struct BSTreeNode p q27 p = root q = NULL28 while (p = NULL)29 q = p30 if (x == p-gtdata) return 0

若不考慮重複則可省略之31 if (x lt p-gtdata)32 p = p-gtleftchild33 else34 p = p-gtrightchild35

3636 p = (struct BSTreeNode ) p = (struct BSTreeNode ) malloc (sizeof(BSTreeNode))malloc (sizeof(BSTreeNode))

3737 p-gtdata = x p-gtdata = x3838 p-gtleftchild = p-gtrightchild = p-gtleftchild = p-gtrightchild = NULLNULL3939 if (root == NULL) root = p if (root == NULL) root = p4040 else if (x lt q-gtdata) else if (x lt q-gtdata)4141 q-gtleftchild = p q-gtleftchild = p4242 else else4343 q-gtrightchild = p q-gtrightchild = p4444 return 1 return 145 45

程式 程式 5-175-17  在二元搜尋樹中新增資料  在二元搜尋樹中新增資料 (( 續續 ))

範例 5-23 圖 (b) 為圖 (a) 新增整數 6 之後的結果圖 (c) (d) (e) (f) 則分別是逐步再加入 28 18

10 21 的結果

bull若目前的二元搜尋樹高度為 h 則新增資料得花O(h) 的時間找到其所應在的位置遂新增資料至二元搜尋樹中的時間複雜之度為 O(h)

593 自二元搜尋樹中刪除資料欲自二元搜尋樹中刪除資料可能會面臨兩種情況(1) 刪除節點 p 位於樹葉節點上如下圖所示 這是比較容易處理的情況

(2) 刪除的節點 p 不是樹葉刪除節點 p 後原 p 所指的節點可由 q ( q 中包含了 p 左子樹中的最大節點值)或 r ( r 中包含了 p 右子樹中的最小節點值)來取代以維持二元搜尋樹中資料的大小關係其結果分別如圖 (b) 和 (c) 所示而圖 (d) 則標明了指標會改變的節點位置即 p 會消失 father 的左或右子樹指標會異動 k(可能是 q 或 r )會移到原來 p 的位置其父節點 f 的左或右子樹指標會異動其中 father 為 p 父節點 f 為 k 的父節點

程式 5-18 自二元搜尋樹中刪除資料46 int DeleteBST (int x)47 Struct BSTreeNode p father k f48 p = root father = NULL49 while (p = NULL)50 if (x == p-gtdata) 找到 x 所在的節點 p 了51 if ((p-gtleftchild == NULL) ampamp 52 (p-gtrightchild == NULL))53 k = NULL p為樹葉節點54 else if (p-gtleftchild = NULL )x位於非子節

點55 f = p 找出 p左子樹的最右樹葉56 k = p-gtleftchild57 while (k-gtrightchild =NULL)58 f = k59 k = k-gtrightchild60 k將挪至原 p處61 if (p == f)62 f-gtleftchild = k-gtleftchild 63 else64 f-gtrightchild = k-gtleftchild 65

66 else p無左子樹67 f = p 找出 p右子樹的最左樹葉68 k = p-gtrightchild69 while (k-gtleftchild = NULL)70 f = k71 k = k -gtleftchild72 73 if (p == f)74 f-gtrightchild = k-gtrightchild 75 else76 f-gtleftchild = k-gtrightchild77 78 if (k = NULL) k挪至原 p處繼承 p的左右指標79 k-gtleftchild = p-gtleftchild80 k-gtrightchild = p-gtrightchild81 82 if (father == NULL) root = k83 else if (x lt father-gtdata) 決定 k是 father的左

或右兒子84 father-gtleftchild = k85 else86 father-gtrightchild = k87 free(p)88 return 1 成功地刪除 x於此傳回 1返回呼叫處89 51~89行為找到 x時應有的處理步驟

90 else 尚未找到 x繼續往下一階層找

91 father = p92 if (x lt p-gtdata)93 p = p-gtleftchild94 else95 p = p-gtrightchild96 97 98 return 0 未找到 x傳回 099

510  樹林 (forest)

bull 定義樹林(森林)是由 n 棵沒有交集的樹所構成的集合 n 0

下圖為一包含三棵樹的樹林

5101 轉換樹林為二元樹 之前我們以二元樹來表示樹因為二元樹有較多

好用的性質在此我們也將樹林轉換成二元樹看待

在 522 節中我們嘗試了用左子右兄弟的方式來表示樹這個方法事實上已將樹轉換成二元樹

即用左子右兄弟的結構重新組織樹林中的每一棵樹這些樹的樹根將視為兄弟而最左邊的樹則視為樹林二元樹的樹根如此一來樹林即可轉換成二元樹

範例 5-25右下圖為右上圖三棵樹所對應的三棵左子右兄弟二元樹

下圖則為上圖樹林所對應的一棵樹林二元樹

演算法 5-1  轉換樹林成二元樹輸入樹林 F = T1 T2 Tn

輸出 F 的二元樹表示 BT=(T1 T2 Tn)1 if (n == 0) 輸出空二元樹 2 else

3 root(T1 T2 Tn) = T1

4 leftsubtree(T1 T2 Tn) =

BT(T11 T12 T1m)5 BT(T11 T12 T1m)為 T1的子樹6 rightsubtree(T1 T2 Tn) =

BT(T2 T3 Tn)

樹林轉換成二元樹的過程樹林轉換成二元樹的過程

  • Slide 1
  • Slide 2
  • Slide 3
  • Slide 4
  • Slide 5
  • Slide 6
  • Slide 7
  • Slide 8
  • Slide 9
  • Slide 10
  • Slide 11
  • Slide 12
  • Slide 13
  • Slide 14
  • Slide 15
  • Slide 16
  • Slide 17
  • Slide 18
  • Slide 19
  • Slide 20
  • Slide 21
  • Slide 22
  • Slide 23
  • Slide 24
  • Slide 25
  • Slide 26
  • Slide 27
  • Slide 28
  • Slide 29
  • Slide 30
  • Slide 31
  • Slide 32
  • Slide 33
  • Slide 34
  • Slide 35
  • Slide 36
  • Slide 37
  • Slide 38
  • Slide 39
  • Slide 40
  • Slide 41
  • Slide 42
  • Slide 43
  • Slide 44
  • Slide 45
  • Slide 46
  • Slide 47
  • Slide 48
  • Slide 49
  • Slide 50
  • Slide 51
  • Slide 52
  • Slide 53
  • Slide 54
  • Slide 55
  • Slide 56
  • Slide 57
  • Slide 58
  • Slide 59
  • Slide 60
  • Slide 61
  • Slide 62
  • Slide 63
  • Slide 64
  • Slide 65
  • Slide 66
  • Slide 67
  • Slide 68
  • Slide 69
  • Slide 70
  • Slide 71
  • Slide 72
  • Slide 73
  • Slide 74
  • Slide 75
  • Slide 76
  • Slide 77
  • Slide 78
  • Slide 79
  • Slide 80
  • Slide 81
  • Slide 82
  • Slide 83
  • Slide 84
  • Slide 85
  • Slide 86
  • Slide 87
  • Slide 88
  • Slide 89
  • Slide 90
  • Slide 91
  • Slide 92
  • Slide 93
  • Slide 94
  • Slide 95
  • Slide 96
  • Slide 97
  • Slide 98
  • Slide 99
  • Slide 100
  • Slide 101
  • Slide 102
  • Slide 103
Page 56: 第五章 樹

56  二元樹的複製與相等測試程式 5-8 二元樹的複製1 struct BTreeNode2 struct BTreeNode leftchild3 char data4 struct BTreeNode rightchild5 6 struct BTreeNode root7 struct BTreeNode Copy(struct BTreeNode TreeRoot)8 struct BTreeNode CopyRoot9 if (TreeRoot == NULL)10 return NULL11 CopyRoot =(struct BTreeNode ) malloc (sizeof(BTreeNode))12 CopyRoot-gtdata = TreeRoot-gtdata13 CopyRoot-gtleftchild = Copy(TreeRoot-gtleftchild)14 CopyRoot-gtrightchild = Copy(TreeRoot-

gtrightchild)15 return CopyRoot16

程式 5-9 檢測兩棵二元樹是否相等1 struct BTreeNode2 struct BTreeNode leftchild3 char data4 struct BTreeNode rightchild5 6 struct BtreeNode rightchild7 int Equal(struct BTreeNode ustruct BTreeNode v)8 if ((u==NULL) ampamp (v==NULL)) return 19 if ((u=NULL) ampamp (v=NULL)10 ampamp (u-gtdata == v-gtdata )11 ampamp Equal(u-gtleftchildv-gtleftchild)12 ampamp Equal(u-gtrightchildv-gtrightchild))

13 return 114 return 015

Counting Nodes in a Binary Tree

int CountingNodes(struct BTreeNodes node)

if (node == NULL) return 0 int leftchildren = CountingNodes(node-gtleftchild)

int rightchildren= CountingNodes(node-gtrightchild)

return leftchildren+rightchildren

Counting Leaves in a Binary Tree

int CountingLeaves(struct BTreeNodes node)

if (node == NULL) return 0 int leftleaves = CountingLeaves(node-gtleftchild)

int rightchildren= CountingLeaves(node-gtrightchild)

if (leftleaves==0 ampamp leftleaves==0) return 1

return leftleaves + leftleaves

57  引線二元樹bull 在二元樹的鍵結表示中樹葉節點的兩個子樹指標皆指向 NULL 由定理 5-1 得知 n = E+1 其中 n 為節點個數 E 為分支數

bull 而 n 個鍵結節點有 2n 個指標空間每個分支恰佔用一個指標空間共有 E (=n-1) 個指標有用到(不是空的)而共有 n+1 個指標空間是 NULL 比非空的節點還多

bull 有學者提出對這些「放空指標的空間」加以利用的概念mdash與其放空指標不如放指向其它節點的指標稱之為引線 (thread) 使得某些運算(如走訪hellip等)可以加快

bull 這個概念發展出了引線二元樹 (threaded binary tree) 這種資料結構

範例 5-17下圖為加了引線的二元樹

加入的引線將使二元樹的中序走訪更加便利沿著上右圖箭頭所指示的順序走訪即可完成此二元樹的中序走訪GDHBAIECF 為此二元樹的中序表示

bull 範例 5-17 中所加的引線目的在方便二元樹的中序走訪

bull 對樹葉節點 p 而言令其資料為 P 則引線指標的規則如下

(1) p 的左子樹指標指向中序表示中 P 的前一個 元素(前接元素 (predecessor) )

(2) p 的右子樹指標指向中序表示中 P 的後一個 元素(後繼元素 (successor) )

為了區別節點指標放的是一般指標(內部節點)抑或是引線指標(樹葉節點)我們另以 2 個欄位分別區別左和右子樹指標空間存放的對象其節點的記憶體配置有如下圖所示

其中 leftthread (rightthread) 為 0 時表示 leftchild (rightchild) 存放的是一般節點指標而 leftthread (rightthread) 為 1 時表示 leftchild (rightchild) 存放的是引線指標1 struct TBTreeNode2 int leftthread3 struct TBTreeNode leftchild4 char data5 struct TBTreeNode rightchild6 int rightthread7

由右側的引線二元樹可知有最左和最右兩條引線尚無妥善的安排我們另設計一空的引線節點做為樹根如下圖所示

571  引線二元樹的中序走訪決定節點 p 在中序表示中的後繼節點 q (簡稱中序後繼點)

程式 5-10決定節點 node的中序後繼點1 struct TBTreeNode2 int leftthread3 struct TBTreeNode leftchild4 char data5 struct TBTreeNode rightchild6 int rightthread7 8 struct TBTreeNode root9 struct TBTreeNode Next(struct TBTreeNode node)

10 struct TBTreeNode temp11 temp = node-gtrightchild12 if (node-gtrightthead) return temp13 while (temp-gtleftthead) temp=temp-gtleftchild

14 return temp15

程式 5-11引線二元樹的中序表示(接程式 5-10)1 void InorderTBTree()2 struct TBTreeNode node3 for(node=Next(root)node=rootnode=Next(node))

4 cout ltlt node-gtdata5

bull只要 node 是引線二元樹上的任一節點透過呼叫 Next(node) 即可找到其在中序表示中的後繼節點bull(試想若程式 5-10 中的程序 Next 所傳入的參數指標正是 root Next(root) 是否可正常運作請思考)bull於是只須從中序表示中的第一節點起讓每一節點 node 都執行 Next(node) 程序即可得引線二元樹的中序表示

572  在引線二元樹中加入節點在引線二元樹中樹葉節點節點的右子樹指標所指向或內部節點右子樹的最左樹葉為其中序後繼節點而且任一節點若無左子樹則其左子樹指標乃指向其中序前接節點hellip這些性質必須在節點新增進入引線二元樹時也要保持接下來討論節點 new 插入成為引線二元樹的節點 p 的右子節點的情形至於插入成為左子節點的情形則讓各位模擬推敲

572572  在引線二元樹中加入節點 在引線二元樹中加入節點 (( 續續 ))

newnew 會成為會成為 pp 的右子節點的情況共有以下兩種的右子節點的情況共有以下兩種(1) (1) 若若 pp 沒有右子節點 沒有右子節點 ((p-gtrightthread p-gtrightthread 為為1) 1) 如圖如圖 ((a) a) 則 則 new new 插入後應形成如圖插入後應形成如圖 ((b) b) 所示所示

(2)(2) 若若 pp 有右子節點 有右子節點 ((p-gtrightthread p-gtrightthread 為為0) 0) 如圖如圖 ((c) c) 則 則 new new 插入後應形成如插入後應形成如圖圖 ((d) d) 所示所示

程式 5-12  插入節點 new 成為引線二元樹中節點 p的右子節點(上接程式 5-10 5-11 )

1 void InsertRight(struct TBTreeNode p 2 struct TBTreeNode new)3 struct TBTreeNode q4 new-gtrightchild = p-gtrightchild5 new-gtrightthead = p-gtrightthread6 new-gtleftchild = p7 new-gtleftthread = 18 p-gtrightchild = new9 p-gtrightthead = 010 if (new-gtrightthread)11 q = Next(new)12 q-gtleftchild = new13 14

58  堆積bull 「堆積」 (heap) 是一個特殊的完備二元樹節點的資料與其子節點的資料之間有事先定好的大小關係存在bull 定義ndash最大堆疊為一完備二元樹任一節點的資料內容不小於其子節點的資料內容ndash最小堆疊為一完備二元樹任一節點的資料內容不大於其子節點的資料內容

581  插入節點至最大堆積中最大堆積是一完備二元樹所以如圖 (a) 之最大堆積於加入新節點後樹的形狀必須如圖 (b) 所示灰色節點即為新節點若加入的節點資料分別為 1 9 12 則其最大堆積將分別變成如圖 (c) (d) (e) 所示

程式 5-13 新增節點至最大堆積中最大堆積本身是一棵完備二元樹可以用陣列表示之 1 define maxsize 1002 int heap[maxsize]3 int n i4 void InsertHeap(int x)5 if (n == maxsize) HeapFull()6 else7 n++8 i = n9 while ((i gt 1) ampamp (x gt heap[i2]))10 heap[i] = heap[i2]11 i = 212 13 heap[i] = x14 16

程序 InsertHeap 在插入第 n 個資料時需要的時間複雜度為 O(logn)

1 100

2 93

3 84

4 77

5 65

6 81

7 79

8 66

9 70

10 50

100

84

77

93

8165

66 70

79

50

1

2 3

4 5 6 7

8 9 10

1 define maxsize 1002 int heap[maxsize]3 int n i4 void InsertHeap(int x)5 if (n == maxsize) HeapFull()6 else7 n++8 i = n9 while ((i gt 1) ampamp (x gt heap[i2]))10 heap[i] = heap[i2]11 i = 212 13 heap[i] = x14 16

582 刪除最大堆積中的最大資料圖 (a) 中的最大堆積在刪除最大資料後其樹形應變成如圖 (b) 所示將最未一個樹葉節點的資料 6 暫挪至樹根節點內如圖 (c) 再將圖 (c) 中非最大堆積的結構調整成為最大堆積 10 和 8 中選大者與 6對調即成圖 (d)

程式 5-14 刪除最大堆積中的最大資料(上接程式 5-13 )1 int DeleteHeap()2 int x i j3 if (n == 0) HeapEmpty()4 else5 x = heap[1]6 heap[1] = heap[n]7 n--8 i = 19 while (i lt= n2)10 if (heap[2i] gt heap[2i+1])11 j = 2i12 else13 j = 2i+114 if (heap[i]gtheap[j]) break15 swap(heap[i] heap[j])16 i = j17 18 return x19 20

其時間複雜度與其堆積高度相同是為O(logn) 其中 n 為堆積中的元素個數

1 100

2 93

3 84

4 77

5 65

6 81

7 79

8 66

9 70

10 50

100

84

77

93

8165

66 70

79

50

1

2 3

4 5 6 7

8 9 10

1 int DeletHeap()2 int x i j3 if(n == 0) HeapEmpty()4 else5 x = heap[1]6 heap[1] = heap[n]7 n--8 i = 19 while (i lt= n2)10 if ([heap[2i]] gt heap[2i+1])11 j = 2i12 else13 j = 2i+114 if (heap[i]gtheap[j]) break15 swap(heap[i] heap[j])16 i = j17 18 return x19 20

583  優先佇列bull 由於最大堆積(或最小堆積)的結構可提供最大元素(或最小元素)而其維護堆積的成本為 O(logn) n 為總元素個數

bull 堆積常被當成「優先佇列」 (priority queue) 使用mdash此時佇列被組織成完備二元樹若新加入佇列的資料有大小關係優先順序的需求時此優先佇列即以 O(logn) 的代價將新資料放入優先佇列(可能是最大或最小堆積依實際大小關係優先順序的需求而定)中mdash提供依元素的優先順序做為處理順序的服務

bull 一般的佇列為先進先出先到的先行處理優先佇列則是依據其優先順序優先順序高者先行處理與來到的先後無關

範例 5-21bull 在「分時」 (time sharing) 的作業系統中我們不希望使用中央處理器 (Central Processing Unit CPU) 時間短的「處理程序」 (process) 等候過久於是有「最短時間工作優先」 (shortest-job-first) 的排程策略此時等候的處理程序即可組織成之一最小堆積而 CPU 每次即取出存於樹根的處理程序予以處理任何新來的處理程序則以 O(logn) 的時間加入此優先佇列(最小堆積)中

bull 注意之前提過的線性佇列新來的直接加入成為新的尾端元素花O(1) 的時間然線性佇列本身沒有大小的關係每次要取出最大(小)的資料得花O(n) 的代價(在所有元素中挑出最大(小)者) n 為佇列內的元素個數而優先佇列是在新元素加入時要花O(logn) 的時間至於取出最大(小)則只須 O(1) 的時間

59  二元搜尋樹bull 二元搜尋樹是一棵二元樹可能是空二元樹若不為空二元樹則滿足下列性質ndash所有節點內的資料內容是相異的 ndash左子節點的資內容 ( 如果有 ) 比父節點的資料內容小 ndash右子節點的資料內容 ( 如果有 ) 比父節點的資料內容大 ndash以左和右子節點為樹根的左子樹和右子樹也是二元搜尋樹

在二元搜尋樹中搜尋資料二元搜尋樹對資料大小的掌握十分明確若恰與節點 p 資料相等則搜尋成功若欲搜尋的資料 x 比樹節點 p 中的資料小 則只須搜尋節點 p 的左子樹若比節點 p 中的資料大 則只須搜尋 p 的右子樹這樣的判斷可以遞迴地應用在各子樹節點 中

程式 5-15 在二元搜尋樹中搜尋資料(遞迴呼叫)1 struct BSTreeNode2 struct BSTreeNode leftchild3 int data4 struct BSTreeNode rightchild5 6 struct BSTreeNode root7 struct BSTreeNode SearchBST(

struct BSTreeNode tree int x)8 if (tree == NULL) return NULL9 if (x == tree-gtdata) return tree10 if (x lt tree-gtdata)11 return SearchBST(tree-gtleftchild x)

12 return SearchBST(tree-gtrightchild x)13

程式 5-16 搜尋二元搜尋樹的資料(非遞迴呼叫)在二元搜尋樹十分龐大時遞迴呼叫的效率不會太好可利用迴圈來完成搜尋的動作以提高執行效率請看程式 5-16

14 struct BSTreeNode Search BST_iterative15 (struct BSTreeNode node int x)16 while (node = NULL)17 if (x == node-gtdata) return node18 if (x lt node-gtdata)19 node = node-gtleftchild20 else21 node = node-gtrightchild22 23 return NULL24

592  新增資料於二元搜尋樹中欲新增資料進入一二元搜尋樹中得先決定該資料在二元搜尋樹中應出現的位置這可透過上節提到的搜尋動作來解決

在二元搜尋樹定義的性質 (1) 中我們限定了元素必須相異所以搜尋新資料的結果必是 NULL

必須將搜尋的結果改為新增資料所應在位置的節點指標

程式 5-17  在二元搜尋樹中新增資料25 int InsertBST(int x)26 struct BSTreeNode p q27 p = root q = NULL28 while (p = NULL)29 q = p30 if (x == p-gtdata) return 0

若不考慮重複則可省略之31 if (x lt p-gtdata)32 p = p-gtleftchild33 else34 p = p-gtrightchild35

3636 p = (struct BSTreeNode ) p = (struct BSTreeNode ) malloc (sizeof(BSTreeNode))malloc (sizeof(BSTreeNode))

3737 p-gtdata = x p-gtdata = x3838 p-gtleftchild = p-gtrightchild = p-gtleftchild = p-gtrightchild = NULLNULL3939 if (root == NULL) root = p if (root == NULL) root = p4040 else if (x lt q-gtdata) else if (x lt q-gtdata)4141 q-gtleftchild = p q-gtleftchild = p4242 else else4343 q-gtrightchild = p q-gtrightchild = p4444 return 1 return 145 45

程式 程式 5-175-17  在二元搜尋樹中新增資料  在二元搜尋樹中新增資料 (( 續續 ))

範例 5-23 圖 (b) 為圖 (a) 新增整數 6 之後的結果圖 (c) (d) (e) (f) 則分別是逐步再加入 28 18

10 21 的結果

bull若目前的二元搜尋樹高度為 h 則新增資料得花O(h) 的時間找到其所應在的位置遂新增資料至二元搜尋樹中的時間複雜之度為 O(h)

593 自二元搜尋樹中刪除資料欲自二元搜尋樹中刪除資料可能會面臨兩種情況(1) 刪除節點 p 位於樹葉節點上如下圖所示 這是比較容易處理的情況

(2) 刪除的節點 p 不是樹葉刪除節點 p 後原 p 所指的節點可由 q ( q 中包含了 p 左子樹中的最大節點值)或 r ( r 中包含了 p 右子樹中的最小節點值)來取代以維持二元搜尋樹中資料的大小關係其結果分別如圖 (b) 和 (c) 所示而圖 (d) 則標明了指標會改變的節點位置即 p 會消失 father 的左或右子樹指標會異動 k(可能是 q 或 r )會移到原來 p 的位置其父節點 f 的左或右子樹指標會異動其中 father 為 p 父節點 f 為 k 的父節點

程式 5-18 自二元搜尋樹中刪除資料46 int DeleteBST (int x)47 Struct BSTreeNode p father k f48 p = root father = NULL49 while (p = NULL)50 if (x == p-gtdata) 找到 x 所在的節點 p 了51 if ((p-gtleftchild == NULL) ampamp 52 (p-gtrightchild == NULL))53 k = NULL p為樹葉節點54 else if (p-gtleftchild = NULL )x位於非子節

點55 f = p 找出 p左子樹的最右樹葉56 k = p-gtleftchild57 while (k-gtrightchild =NULL)58 f = k59 k = k-gtrightchild60 k將挪至原 p處61 if (p == f)62 f-gtleftchild = k-gtleftchild 63 else64 f-gtrightchild = k-gtleftchild 65

66 else p無左子樹67 f = p 找出 p右子樹的最左樹葉68 k = p-gtrightchild69 while (k-gtleftchild = NULL)70 f = k71 k = k -gtleftchild72 73 if (p == f)74 f-gtrightchild = k-gtrightchild 75 else76 f-gtleftchild = k-gtrightchild77 78 if (k = NULL) k挪至原 p處繼承 p的左右指標79 k-gtleftchild = p-gtleftchild80 k-gtrightchild = p-gtrightchild81 82 if (father == NULL) root = k83 else if (x lt father-gtdata) 決定 k是 father的左

或右兒子84 father-gtleftchild = k85 else86 father-gtrightchild = k87 free(p)88 return 1 成功地刪除 x於此傳回 1返回呼叫處89 51~89行為找到 x時應有的處理步驟

90 else 尚未找到 x繼續往下一階層找

91 father = p92 if (x lt p-gtdata)93 p = p-gtleftchild94 else95 p = p-gtrightchild96 97 98 return 0 未找到 x傳回 099

510  樹林 (forest)

bull 定義樹林(森林)是由 n 棵沒有交集的樹所構成的集合 n 0

下圖為一包含三棵樹的樹林

5101 轉換樹林為二元樹 之前我們以二元樹來表示樹因為二元樹有較多

好用的性質在此我們也將樹林轉換成二元樹看待

在 522 節中我們嘗試了用左子右兄弟的方式來表示樹這個方法事實上已將樹轉換成二元樹

即用左子右兄弟的結構重新組織樹林中的每一棵樹這些樹的樹根將視為兄弟而最左邊的樹則視為樹林二元樹的樹根如此一來樹林即可轉換成二元樹

範例 5-25右下圖為右上圖三棵樹所對應的三棵左子右兄弟二元樹

下圖則為上圖樹林所對應的一棵樹林二元樹

演算法 5-1  轉換樹林成二元樹輸入樹林 F = T1 T2 Tn

輸出 F 的二元樹表示 BT=(T1 T2 Tn)1 if (n == 0) 輸出空二元樹 2 else

3 root(T1 T2 Tn) = T1

4 leftsubtree(T1 T2 Tn) =

BT(T11 T12 T1m)5 BT(T11 T12 T1m)為 T1的子樹6 rightsubtree(T1 T2 Tn) =

BT(T2 T3 Tn)

樹林轉換成二元樹的過程樹林轉換成二元樹的過程

  • Slide 1
  • Slide 2
  • Slide 3
  • Slide 4
  • Slide 5
  • Slide 6
  • Slide 7
  • Slide 8
  • Slide 9
  • Slide 10
  • Slide 11
  • Slide 12
  • Slide 13
  • Slide 14
  • Slide 15
  • Slide 16
  • Slide 17
  • Slide 18
  • Slide 19
  • Slide 20
  • Slide 21
  • Slide 22
  • Slide 23
  • Slide 24
  • Slide 25
  • Slide 26
  • Slide 27
  • Slide 28
  • Slide 29
  • Slide 30
  • Slide 31
  • Slide 32
  • Slide 33
  • Slide 34
  • Slide 35
  • Slide 36
  • Slide 37
  • Slide 38
  • Slide 39
  • Slide 40
  • Slide 41
  • Slide 42
  • Slide 43
  • Slide 44
  • Slide 45
  • Slide 46
  • Slide 47
  • Slide 48
  • Slide 49
  • Slide 50
  • Slide 51
  • Slide 52
  • Slide 53
  • Slide 54
  • Slide 55
  • Slide 56
  • Slide 57
  • Slide 58
  • Slide 59
  • Slide 60
  • Slide 61
  • Slide 62
  • Slide 63
  • Slide 64
  • Slide 65
  • Slide 66
  • Slide 67
  • Slide 68
  • Slide 69
  • Slide 70
  • Slide 71
  • Slide 72
  • Slide 73
  • Slide 74
  • Slide 75
  • Slide 76
  • Slide 77
  • Slide 78
  • Slide 79
  • Slide 80
  • Slide 81
  • Slide 82
  • Slide 83
  • Slide 84
  • Slide 85
  • Slide 86
  • Slide 87
  • Slide 88
  • Slide 89
  • Slide 90
  • Slide 91
  • Slide 92
  • Slide 93
  • Slide 94
  • Slide 95
  • Slide 96
  • Slide 97
  • Slide 98
  • Slide 99
  • Slide 100
  • Slide 101
  • Slide 102
  • Slide 103
Page 57: 第五章 樹

程式 5-9 檢測兩棵二元樹是否相等1 struct BTreeNode2 struct BTreeNode leftchild3 char data4 struct BTreeNode rightchild5 6 struct BtreeNode rightchild7 int Equal(struct BTreeNode ustruct BTreeNode v)8 if ((u==NULL) ampamp (v==NULL)) return 19 if ((u=NULL) ampamp (v=NULL)10 ampamp (u-gtdata == v-gtdata )11 ampamp Equal(u-gtleftchildv-gtleftchild)12 ampamp Equal(u-gtrightchildv-gtrightchild))

13 return 114 return 015

Counting Nodes in a Binary Tree

int CountingNodes(struct BTreeNodes node)

if (node == NULL) return 0 int leftchildren = CountingNodes(node-gtleftchild)

int rightchildren= CountingNodes(node-gtrightchild)

return leftchildren+rightchildren

Counting Leaves in a Binary Tree

int CountingLeaves(struct BTreeNodes node)

if (node == NULL) return 0 int leftleaves = CountingLeaves(node-gtleftchild)

int rightchildren= CountingLeaves(node-gtrightchild)

if (leftleaves==0 ampamp leftleaves==0) return 1

return leftleaves + leftleaves

57  引線二元樹bull 在二元樹的鍵結表示中樹葉節點的兩個子樹指標皆指向 NULL 由定理 5-1 得知 n = E+1 其中 n 為節點個數 E 為分支數

bull 而 n 個鍵結節點有 2n 個指標空間每個分支恰佔用一個指標空間共有 E (=n-1) 個指標有用到(不是空的)而共有 n+1 個指標空間是 NULL 比非空的節點還多

bull 有學者提出對這些「放空指標的空間」加以利用的概念mdash與其放空指標不如放指向其它節點的指標稱之為引線 (thread) 使得某些運算(如走訪hellip等)可以加快

bull 這個概念發展出了引線二元樹 (threaded binary tree) 這種資料結構

範例 5-17下圖為加了引線的二元樹

加入的引線將使二元樹的中序走訪更加便利沿著上右圖箭頭所指示的順序走訪即可完成此二元樹的中序走訪GDHBAIECF 為此二元樹的中序表示

bull 範例 5-17 中所加的引線目的在方便二元樹的中序走訪

bull 對樹葉節點 p 而言令其資料為 P 則引線指標的規則如下

(1) p 的左子樹指標指向中序表示中 P 的前一個 元素(前接元素 (predecessor) )

(2) p 的右子樹指標指向中序表示中 P 的後一個 元素(後繼元素 (successor) )

為了區別節點指標放的是一般指標(內部節點)抑或是引線指標(樹葉節點)我們另以 2 個欄位分別區別左和右子樹指標空間存放的對象其節點的記憶體配置有如下圖所示

其中 leftthread (rightthread) 為 0 時表示 leftchild (rightchild) 存放的是一般節點指標而 leftthread (rightthread) 為 1 時表示 leftchild (rightchild) 存放的是引線指標1 struct TBTreeNode2 int leftthread3 struct TBTreeNode leftchild4 char data5 struct TBTreeNode rightchild6 int rightthread7

由右側的引線二元樹可知有最左和最右兩條引線尚無妥善的安排我們另設計一空的引線節點做為樹根如下圖所示

571  引線二元樹的中序走訪決定節點 p 在中序表示中的後繼節點 q (簡稱中序後繼點)

程式 5-10決定節點 node的中序後繼點1 struct TBTreeNode2 int leftthread3 struct TBTreeNode leftchild4 char data5 struct TBTreeNode rightchild6 int rightthread7 8 struct TBTreeNode root9 struct TBTreeNode Next(struct TBTreeNode node)

10 struct TBTreeNode temp11 temp = node-gtrightchild12 if (node-gtrightthead) return temp13 while (temp-gtleftthead) temp=temp-gtleftchild

14 return temp15

程式 5-11引線二元樹的中序表示(接程式 5-10)1 void InorderTBTree()2 struct TBTreeNode node3 for(node=Next(root)node=rootnode=Next(node))

4 cout ltlt node-gtdata5

bull只要 node 是引線二元樹上的任一節點透過呼叫 Next(node) 即可找到其在中序表示中的後繼節點bull(試想若程式 5-10 中的程序 Next 所傳入的參數指標正是 root Next(root) 是否可正常運作請思考)bull於是只須從中序表示中的第一節點起讓每一節點 node 都執行 Next(node) 程序即可得引線二元樹的中序表示

572  在引線二元樹中加入節點在引線二元樹中樹葉節點節點的右子樹指標所指向或內部節點右子樹的最左樹葉為其中序後繼節點而且任一節點若無左子樹則其左子樹指標乃指向其中序前接節點hellip這些性質必須在節點新增進入引線二元樹時也要保持接下來討論節點 new 插入成為引線二元樹的節點 p 的右子節點的情形至於插入成為左子節點的情形則讓各位模擬推敲

572572  在引線二元樹中加入節點 在引線二元樹中加入節點 (( 續續 ))

newnew 會成為會成為 pp 的右子節點的情況共有以下兩種的右子節點的情況共有以下兩種(1) (1) 若若 pp 沒有右子節點 沒有右子節點 ((p-gtrightthread p-gtrightthread 為為1) 1) 如圖如圖 ((a) a) 則 則 new new 插入後應形成如圖插入後應形成如圖 ((b) b) 所示所示

(2)(2) 若若 pp 有右子節點 有右子節點 ((p-gtrightthread p-gtrightthread 為為0) 0) 如圖如圖 ((c) c) 則 則 new new 插入後應形成如插入後應形成如圖圖 ((d) d) 所示所示

程式 5-12  插入節點 new 成為引線二元樹中節點 p的右子節點(上接程式 5-10 5-11 )

1 void InsertRight(struct TBTreeNode p 2 struct TBTreeNode new)3 struct TBTreeNode q4 new-gtrightchild = p-gtrightchild5 new-gtrightthead = p-gtrightthread6 new-gtleftchild = p7 new-gtleftthread = 18 p-gtrightchild = new9 p-gtrightthead = 010 if (new-gtrightthread)11 q = Next(new)12 q-gtleftchild = new13 14

58  堆積bull 「堆積」 (heap) 是一個特殊的完備二元樹節點的資料與其子節點的資料之間有事先定好的大小關係存在bull 定義ndash最大堆疊為一完備二元樹任一節點的資料內容不小於其子節點的資料內容ndash最小堆疊為一完備二元樹任一節點的資料內容不大於其子節點的資料內容

581  插入節點至最大堆積中最大堆積是一完備二元樹所以如圖 (a) 之最大堆積於加入新節點後樹的形狀必須如圖 (b) 所示灰色節點即為新節點若加入的節點資料分別為 1 9 12 則其最大堆積將分別變成如圖 (c) (d) (e) 所示

程式 5-13 新增節點至最大堆積中最大堆積本身是一棵完備二元樹可以用陣列表示之 1 define maxsize 1002 int heap[maxsize]3 int n i4 void InsertHeap(int x)5 if (n == maxsize) HeapFull()6 else7 n++8 i = n9 while ((i gt 1) ampamp (x gt heap[i2]))10 heap[i] = heap[i2]11 i = 212 13 heap[i] = x14 16

程序 InsertHeap 在插入第 n 個資料時需要的時間複雜度為 O(logn)

1 100

2 93

3 84

4 77

5 65

6 81

7 79

8 66

9 70

10 50

100

84

77

93

8165

66 70

79

50

1

2 3

4 5 6 7

8 9 10

1 define maxsize 1002 int heap[maxsize]3 int n i4 void InsertHeap(int x)5 if (n == maxsize) HeapFull()6 else7 n++8 i = n9 while ((i gt 1) ampamp (x gt heap[i2]))10 heap[i] = heap[i2]11 i = 212 13 heap[i] = x14 16

582 刪除最大堆積中的最大資料圖 (a) 中的最大堆積在刪除最大資料後其樹形應變成如圖 (b) 所示將最未一個樹葉節點的資料 6 暫挪至樹根節點內如圖 (c) 再將圖 (c) 中非最大堆積的結構調整成為最大堆積 10 和 8 中選大者與 6對調即成圖 (d)

程式 5-14 刪除最大堆積中的最大資料(上接程式 5-13 )1 int DeleteHeap()2 int x i j3 if (n == 0) HeapEmpty()4 else5 x = heap[1]6 heap[1] = heap[n]7 n--8 i = 19 while (i lt= n2)10 if (heap[2i] gt heap[2i+1])11 j = 2i12 else13 j = 2i+114 if (heap[i]gtheap[j]) break15 swap(heap[i] heap[j])16 i = j17 18 return x19 20

其時間複雜度與其堆積高度相同是為O(logn) 其中 n 為堆積中的元素個數

1 100

2 93

3 84

4 77

5 65

6 81

7 79

8 66

9 70

10 50

100

84

77

93

8165

66 70

79

50

1

2 3

4 5 6 7

8 9 10

1 int DeletHeap()2 int x i j3 if(n == 0) HeapEmpty()4 else5 x = heap[1]6 heap[1] = heap[n]7 n--8 i = 19 while (i lt= n2)10 if ([heap[2i]] gt heap[2i+1])11 j = 2i12 else13 j = 2i+114 if (heap[i]gtheap[j]) break15 swap(heap[i] heap[j])16 i = j17 18 return x19 20

583  優先佇列bull 由於最大堆積(或最小堆積)的結構可提供最大元素(或最小元素)而其維護堆積的成本為 O(logn) n 為總元素個數

bull 堆積常被當成「優先佇列」 (priority queue) 使用mdash此時佇列被組織成完備二元樹若新加入佇列的資料有大小關係優先順序的需求時此優先佇列即以 O(logn) 的代價將新資料放入優先佇列(可能是最大或最小堆積依實際大小關係優先順序的需求而定)中mdash提供依元素的優先順序做為處理順序的服務

bull 一般的佇列為先進先出先到的先行處理優先佇列則是依據其優先順序優先順序高者先行處理與來到的先後無關

範例 5-21bull 在「分時」 (time sharing) 的作業系統中我們不希望使用中央處理器 (Central Processing Unit CPU) 時間短的「處理程序」 (process) 等候過久於是有「最短時間工作優先」 (shortest-job-first) 的排程策略此時等候的處理程序即可組織成之一最小堆積而 CPU 每次即取出存於樹根的處理程序予以處理任何新來的處理程序則以 O(logn) 的時間加入此優先佇列(最小堆積)中

bull 注意之前提過的線性佇列新來的直接加入成為新的尾端元素花O(1) 的時間然線性佇列本身沒有大小的關係每次要取出最大(小)的資料得花O(n) 的代價(在所有元素中挑出最大(小)者) n 為佇列內的元素個數而優先佇列是在新元素加入時要花O(logn) 的時間至於取出最大(小)則只須 O(1) 的時間

59  二元搜尋樹bull 二元搜尋樹是一棵二元樹可能是空二元樹若不為空二元樹則滿足下列性質ndash所有節點內的資料內容是相異的 ndash左子節點的資內容 ( 如果有 ) 比父節點的資料內容小 ndash右子節點的資料內容 ( 如果有 ) 比父節點的資料內容大 ndash以左和右子節點為樹根的左子樹和右子樹也是二元搜尋樹

在二元搜尋樹中搜尋資料二元搜尋樹對資料大小的掌握十分明確若恰與節點 p 資料相等則搜尋成功若欲搜尋的資料 x 比樹節點 p 中的資料小 則只須搜尋節點 p 的左子樹若比節點 p 中的資料大 則只須搜尋 p 的右子樹這樣的判斷可以遞迴地應用在各子樹節點 中

程式 5-15 在二元搜尋樹中搜尋資料(遞迴呼叫)1 struct BSTreeNode2 struct BSTreeNode leftchild3 int data4 struct BSTreeNode rightchild5 6 struct BSTreeNode root7 struct BSTreeNode SearchBST(

struct BSTreeNode tree int x)8 if (tree == NULL) return NULL9 if (x == tree-gtdata) return tree10 if (x lt tree-gtdata)11 return SearchBST(tree-gtleftchild x)

12 return SearchBST(tree-gtrightchild x)13

程式 5-16 搜尋二元搜尋樹的資料(非遞迴呼叫)在二元搜尋樹十分龐大時遞迴呼叫的效率不會太好可利用迴圈來完成搜尋的動作以提高執行效率請看程式 5-16

14 struct BSTreeNode Search BST_iterative15 (struct BSTreeNode node int x)16 while (node = NULL)17 if (x == node-gtdata) return node18 if (x lt node-gtdata)19 node = node-gtleftchild20 else21 node = node-gtrightchild22 23 return NULL24

592  新增資料於二元搜尋樹中欲新增資料進入一二元搜尋樹中得先決定該資料在二元搜尋樹中應出現的位置這可透過上節提到的搜尋動作來解決

在二元搜尋樹定義的性質 (1) 中我們限定了元素必須相異所以搜尋新資料的結果必是 NULL

必須將搜尋的結果改為新增資料所應在位置的節點指標

程式 5-17  在二元搜尋樹中新增資料25 int InsertBST(int x)26 struct BSTreeNode p q27 p = root q = NULL28 while (p = NULL)29 q = p30 if (x == p-gtdata) return 0

若不考慮重複則可省略之31 if (x lt p-gtdata)32 p = p-gtleftchild33 else34 p = p-gtrightchild35

3636 p = (struct BSTreeNode ) p = (struct BSTreeNode ) malloc (sizeof(BSTreeNode))malloc (sizeof(BSTreeNode))

3737 p-gtdata = x p-gtdata = x3838 p-gtleftchild = p-gtrightchild = p-gtleftchild = p-gtrightchild = NULLNULL3939 if (root == NULL) root = p if (root == NULL) root = p4040 else if (x lt q-gtdata) else if (x lt q-gtdata)4141 q-gtleftchild = p q-gtleftchild = p4242 else else4343 q-gtrightchild = p q-gtrightchild = p4444 return 1 return 145 45

程式 程式 5-175-17  在二元搜尋樹中新增資料  在二元搜尋樹中新增資料 (( 續續 ))

範例 5-23 圖 (b) 為圖 (a) 新增整數 6 之後的結果圖 (c) (d) (e) (f) 則分別是逐步再加入 28 18

10 21 的結果

bull若目前的二元搜尋樹高度為 h 則新增資料得花O(h) 的時間找到其所應在的位置遂新增資料至二元搜尋樹中的時間複雜之度為 O(h)

593 自二元搜尋樹中刪除資料欲自二元搜尋樹中刪除資料可能會面臨兩種情況(1) 刪除節點 p 位於樹葉節點上如下圖所示 這是比較容易處理的情況

(2) 刪除的節點 p 不是樹葉刪除節點 p 後原 p 所指的節點可由 q ( q 中包含了 p 左子樹中的最大節點值)或 r ( r 中包含了 p 右子樹中的最小節點值)來取代以維持二元搜尋樹中資料的大小關係其結果分別如圖 (b) 和 (c) 所示而圖 (d) 則標明了指標會改變的節點位置即 p 會消失 father 的左或右子樹指標會異動 k(可能是 q 或 r )會移到原來 p 的位置其父節點 f 的左或右子樹指標會異動其中 father 為 p 父節點 f 為 k 的父節點

程式 5-18 自二元搜尋樹中刪除資料46 int DeleteBST (int x)47 Struct BSTreeNode p father k f48 p = root father = NULL49 while (p = NULL)50 if (x == p-gtdata) 找到 x 所在的節點 p 了51 if ((p-gtleftchild == NULL) ampamp 52 (p-gtrightchild == NULL))53 k = NULL p為樹葉節點54 else if (p-gtleftchild = NULL )x位於非子節

點55 f = p 找出 p左子樹的最右樹葉56 k = p-gtleftchild57 while (k-gtrightchild =NULL)58 f = k59 k = k-gtrightchild60 k將挪至原 p處61 if (p == f)62 f-gtleftchild = k-gtleftchild 63 else64 f-gtrightchild = k-gtleftchild 65

66 else p無左子樹67 f = p 找出 p右子樹的最左樹葉68 k = p-gtrightchild69 while (k-gtleftchild = NULL)70 f = k71 k = k -gtleftchild72 73 if (p == f)74 f-gtrightchild = k-gtrightchild 75 else76 f-gtleftchild = k-gtrightchild77 78 if (k = NULL) k挪至原 p處繼承 p的左右指標79 k-gtleftchild = p-gtleftchild80 k-gtrightchild = p-gtrightchild81 82 if (father == NULL) root = k83 else if (x lt father-gtdata) 決定 k是 father的左

或右兒子84 father-gtleftchild = k85 else86 father-gtrightchild = k87 free(p)88 return 1 成功地刪除 x於此傳回 1返回呼叫處89 51~89行為找到 x時應有的處理步驟

90 else 尚未找到 x繼續往下一階層找

91 father = p92 if (x lt p-gtdata)93 p = p-gtleftchild94 else95 p = p-gtrightchild96 97 98 return 0 未找到 x傳回 099

510  樹林 (forest)

bull 定義樹林(森林)是由 n 棵沒有交集的樹所構成的集合 n 0

下圖為一包含三棵樹的樹林

5101 轉換樹林為二元樹 之前我們以二元樹來表示樹因為二元樹有較多

好用的性質在此我們也將樹林轉換成二元樹看待

在 522 節中我們嘗試了用左子右兄弟的方式來表示樹這個方法事實上已將樹轉換成二元樹

即用左子右兄弟的結構重新組織樹林中的每一棵樹這些樹的樹根將視為兄弟而最左邊的樹則視為樹林二元樹的樹根如此一來樹林即可轉換成二元樹

範例 5-25右下圖為右上圖三棵樹所對應的三棵左子右兄弟二元樹

下圖則為上圖樹林所對應的一棵樹林二元樹

演算法 5-1  轉換樹林成二元樹輸入樹林 F = T1 T2 Tn

輸出 F 的二元樹表示 BT=(T1 T2 Tn)1 if (n == 0) 輸出空二元樹 2 else

3 root(T1 T2 Tn) = T1

4 leftsubtree(T1 T2 Tn) =

BT(T11 T12 T1m)5 BT(T11 T12 T1m)為 T1的子樹6 rightsubtree(T1 T2 Tn) =

BT(T2 T3 Tn)

樹林轉換成二元樹的過程樹林轉換成二元樹的過程

  • Slide 1
  • Slide 2
  • Slide 3
  • Slide 4
  • Slide 5
  • Slide 6
  • Slide 7
  • Slide 8
  • Slide 9
  • Slide 10
  • Slide 11
  • Slide 12
  • Slide 13
  • Slide 14
  • Slide 15
  • Slide 16
  • Slide 17
  • Slide 18
  • Slide 19
  • Slide 20
  • Slide 21
  • Slide 22
  • Slide 23
  • Slide 24
  • Slide 25
  • Slide 26
  • Slide 27
  • Slide 28
  • Slide 29
  • Slide 30
  • Slide 31
  • Slide 32
  • Slide 33
  • Slide 34
  • Slide 35
  • Slide 36
  • Slide 37
  • Slide 38
  • Slide 39
  • Slide 40
  • Slide 41
  • Slide 42
  • Slide 43
  • Slide 44
  • Slide 45
  • Slide 46
  • Slide 47
  • Slide 48
  • Slide 49
  • Slide 50
  • Slide 51
  • Slide 52
  • Slide 53
  • Slide 54
  • Slide 55
  • Slide 56
  • Slide 57
  • Slide 58
  • Slide 59
  • Slide 60
  • Slide 61
  • Slide 62
  • Slide 63
  • Slide 64
  • Slide 65
  • Slide 66
  • Slide 67
  • Slide 68
  • Slide 69
  • Slide 70
  • Slide 71
  • Slide 72
  • Slide 73
  • Slide 74
  • Slide 75
  • Slide 76
  • Slide 77
  • Slide 78
  • Slide 79
  • Slide 80
  • Slide 81
  • Slide 82
  • Slide 83
  • Slide 84
  • Slide 85
  • Slide 86
  • Slide 87
  • Slide 88
  • Slide 89
  • Slide 90
  • Slide 91
  • Slide 92
  • Slide 93
  • Slide 94
  • Slide 95
  • Slide 96
  • Slide 97
  • Slide 98
  • Slide 99
  • Slide 100
  • Slide 101
  • Slide 102
  • Slide 103
Page 58: 第五章 樹

Counting Nodes in a Binary Tree

int CountingNodes(struct BTreeNodes node)

if (node == NULL) return 0 int leftchildren = CountingNodes(node-gtleftchild)

int rightchildren= CountingNodes(node-gtrightchild)

return leftchildren+rightchildren

Counting Leaves in a Binary Tree

int CountingLeaves(struct BTreeNodes node)

if (node == NULL) return 0 int leftleaves = CountingLeaves(node-gtleftchild)

int rightchildren= CountingLeaves(node-gtrightchild)

if (leftleaves==0 ampamp leftleaves==0) return 1

return leftleaves + leftleaves

57  引線二元樹bull 在二元樹的鍵結表示中樹葉節點的兩個子樹指標皆指向 NULL 由定理 5-1 得知 n = E+1 其中 n 為節點個數 E 為分支數

bull 而 n 個鍵結節點有 2n 個指標空間每個分支恰佔用一個指標空間共有 E (=n-1) 個指標有用到(不是空的)而共有 n+1 個指標空間是 NULL 比非空的節點還多

bull 有學者提出對這些「放空指標的空間」加以利用的概念mdash與其放空指標不如放指向其它節點的指標稱之為引線 (thread) 使得某些運算(如走訪hellip等)可以加快

bull 這個概念發展出了引線二元樹 (threaded binary tree) 這種資料結構

範例 5-17下圖為加了引線的二元樹

加入的引線將使二元樹的中序走訪更加便利沿著上右圖箭頭所指示的順序走訪即可完成此二元樹的中序走訪GDHBAIECF 為此二元樹的中序表示

bull 範例 5-17 中所加的引線目的在方便二元樹的中序走訪

bull 對樹葉節點 p 而言令其資料為 P 則引線指標的規則如下

(1) p 的左子樹指標指向中序表示中 P 的前一個 元素(前接元素 (predecessor) )

(2) p 的右子樹指標指向中序表示中 P 的後一個 元素(後繼元素 (successor) )

為了區別節點指標放的是一般指標(內部節點)抑或是引線指標(樹葉節點)我們另以 2 個欄位分別區別左和右子樹指標空間存放的對象其節點的記憶體配置有如下圖所示

其中 leftthread (rightthread) 為 0 時表示 leftchild (rightchild) 存放的是一般節點指標而 leftthread (rightthread) 為 1 時表示 leftchild (rightchild) 存放的是引線指標1 struct TBTreeNode2 int leftthread3 struct TBTreeNode leftchild4 char data5 struct TBTreeNode rightchild6 int rightthread7

由右側的引線二元樹可知有最左和最右兩條引線尚無妥善的安排我們另設計一空的引線節點做為樹根如下圖所示

571  引線二元樹的中序走訪決定節點 p 在中序表示中的後繼節點 q (簡稱中序後繼點)

程式 5-10決定節點 node的中序後繼點1 struct TBTreeNode2 int leftthread3 struct TBTreeNode leftchild4 char data5 struct TBTreeNode rightchild6 int rightthread7 8 struct TBTreeNode root9 struct TBTreeNode Next(struct TBTreeNode node)

10 struct TBTreeNode temp11 temp = node-gtrightchild12 if (node-gtrightthead) return temp13 while (temp-gtleftthead) temp=temp-gtleftchild

14 return temp15

程式 5-11引線二元樹的中序表示(接程式 5-10)1 void InorderTBTree()2 struct TBTreeNode node3 for(node=Next(root)node=rootnode=Next(node))

4 cout ltlt node-gtdata5

bull只要 node 是引線二元樹上的任一節點透過呼叫 Next(node) 即可找到其在中序表示中的後繼節點bull(試想若程式 5-10 中的程序 Next 所傳入的參數指標正是 root Next(root) 是否可正常運作請思考)bull於是只須從中序表示中的第一節點起讓每一節點 node 都執行 Next(node) 程序即可得引線二元樹的中序表示

572  在引線二元樹中加入節點在引線二元樹中樹葉節點節點的右子樹指標所指向或內部節點右子樹的最左樹葉為其中序後繼節點而且任一節點若無左子樹則其左子樹指標乃指向其中序前接節點hellip這些性質必須在節點新增進入引線二元樹時也要保持接下來討論節點 new 插入成為引線二元樹的節點 p 的右子節點的情形至於插入成為左子節點的情形則讓各位模擬推敲

572572  在引線二元樹中加入節點 在引線二元樹中加入節點 (( 續續 ))

newnew 會成為會成為 pp 的右子節點的情況共有以下兩種的右子節點的情況共有以下兩種(1) (1) 若若 pp 沒有右子節點 沒有右子節點 ((p-gtrightthread p-gtrightthread 為為1) 1) 如圖如圖 ((a) a) 則 則 new new 插入後應形成如圖插入後應形成如圖 ((b) b) 所示所示

(2)(2) 若若 pp 有右子節點 有右子節點 ((p-gtrightthread p-gtrightthread 為為0) 0) 如圖如圖 ((c) c) 則 則 new new 插入後應形成如插入後應形成如圖圖 ((d) d) 所示所示

程式 5-12  插入節點 new 成為引線二元樹中節點 p的右子節點(上接程式 5-10 5-11 )

1 void InsertRight(struct TBTreeNode p 2 struct TBTreeNode new)3 struct TBTreeNode q4 new-gtrightchild = p-gtrightchild5 new-gtrightthead = p-gtrightthread6 new-gtleftchild = p7 new-gtleftthread = 18 p-gtrightchild = new9 p-gtrightthead = 010 if (new-gtrightthread)11 q = Next(new)12 q-gtleftchild = new13 14

58  堆積bull 「堆積」 (heap) 是一個特殊的完備二元樹節點的資料與其子節點的資料之間有事先定好的大小關係存在bull 定義ndash最大堆疊為一完備二元樹任一節點的資料內容不小於其子節點的資料內容ndash最小堆疊為一完備二元樹任一節點的資料內容不大於其子節點的資料內容

581  插入節點至最大堆積中最大堆積是一完備二元樹所以如圖 (a) 之最大堆積於加入新節點後樹的形狀必須如圖 (b) 所示灰色節點即為新節點若加入的節點資料分別為 1 9 12 則其最大堆積將分別變成如圖 (c) (d) (e) 所示

程式 5-13 新增節點至最大堆積中最大堆積本身是一棵完備二元樹可以用陣列表示之 1 define maxsize 1002 int heap[maxsize]3 int n i4 void InsertHeap(int x)5 if (n == maxsize) HeapFull()6 else7 n++8 i = n9 while ((i gt 1) ampamp (x gt heap[i2]))10 heap[i] = heap[i2]11 i = 212 13 heap[i] = x14 16

程序 InsertHeap 在插入第 n 個資料時需要的時間複雜度為 O(logn)

1 100

2 93

3 84

4 77

5 65

6 81

7 79

8 66

9 70

10 50

100

84

77

93

8165

66 70

79

50

1

2 3

4 5 6 7

8 9 10

1 define maxsize 1002 int heap[maxsize]3 int n i4 void InsertHeap(int x)5 if (n == maxsize) HeapFull()6 else7 n++8 i = n9 while ((i gt 1) ampamp (x gt heap[i2]))10 heap[i] = heap[i2]11 i = 212 13 heap[i] = x14 16

582 刪除最大堆積中的最大資料圖 (a) 中的最大堆積在刪除最大資料後其樹形應變成如圖 (b) 所示將最未一個樹葉節點的資料 6 暫挪至樹根節點內如圖 (c) 再將圖 (c) 中非最大堆積的結構調整成為最大堆積 10 和 8 中選大者與 6對調即成圖 (d)

程式 5-14 刪除最大堆積中的最大資料(上接程式 5-13 )1 int DeleteHeap()2 int x i j3 if (n == 0) HeapEmpty()4 else5 x = heap[1]6 heap[1] = heap[n]7 n--8 i = 19 while (i lt= n2)10 if (heap[2i] gt heap[2i+1])11 j = 2i12 else13 j = 2i+114 if (heap[i]gtheap[j]) break15 swap(heap[i] heap[j])16 i = j17 18 return x19 20

其時間複雜度與其堆積高度相同是為O(logn) 其中 n 為堆積中的元素個數

1 100

2 93

3 84

4 77

5 65

6 81

7 79

8 66

9 70

10 50

100

84

77

93

8165

66 70

79

50

1

2 3

4 5 6 7

8 9 10

1 int DeletHeap()2 int x i j3 if(n == 0) HeapEmpty()4 else5 x = heap[1]6 heap[1] = heap[n]7 n--8 i = 19 while (i lt= n2)10 if ([heap[2i]] gt heap[2i+1])11 j = 2i12 else13 j = 2i+114 if (heap[i]gtheap[j]) break15 swap(heap[i] heap[j])16 i = j17 18 return x19 20

583  優先佇列bull 由於最大堆積(或最小堆積)的結構可提供最大元素(或最小元素)而其維護堆積的成本為 O(logn) n 為總元素個數

bull 堆積常被當成「優先佇列」 (priority queue) 使用mdash此時佇列被組織成完備二元樹若新加入佇列的資料有大小關係優先順序的需求時此優先佇列即以 O(logn) 的代價將新資料放入優先佇列(可能是最大或最小堆積依實際大小關係優先順序的需求而定)中mdash提供依元素的優先順序做為處理順序的服務

bull 一般的佇列為先進先出先到的先行處理優先佇列則是依據其優先順序優先順序高者先行處理與來到的先後無關

範例 5-21bull 在「分時」 (time sharing) 的作業系統中我們不希望使用中央處理器 (Central Processing Unit CPU) 時間短的「處理程序」 (process) 等候過久於是有「最短時間工作優先」 (shortest-job-first) 的排程策略此時等候的處理程序即可組織成之一最小堆積而 CPU 每次即取出存於樹根的處理程序予以處理任何新來的處理程序則以 O(logn) 的時間加入此優先佇列(最小堆積)中

bull 注意之前提過的線性佇列新來的直接加入成為新的尾端元素花O(1) 的時間然線性佇列本身沒有大小的關係每次要取出最大(小)的資料得花O(n) 的代價(在所有元素中挑出最大(小)者) n 為佇列內的元素個數而優先佇列是在新元素加入時要花O(logn) 的時間至於取出最大(小)則只須 O(1) 的時間

59  二元搜尋樹bull 二元搜尋樹是一棵二元樹可能是空二元樹若不為空二元樹則滿足下列性質ndash所有節點內的資料內容是相異的 ndash左子節點的資內容 ( 如果有 ) 比父節點的資料內容小 ndash右子節點的資料內容 ( 如果有 ) 比父節點的資料內容大 ndash以左和右子節點為樹根的左子樹和右子樹也是二元搜尋樹

在二元搜尋樹中搜尋資料二元搜尋樹對資料大小的掌握十分明確若恰與節點 p 資料相等則搜尋成功若欲搜尋的資料 x 比樹節點 p 中的資料小 則只須搜尋節點 p 的左子樹若比節點 p 中的資料大 則只須搜尋 p 的右子樹這樣的判斷可以遞迴地應用在各子樹節點 中

程式 5-15 在二元搜尋樹中搜尋資料(遞迴呼叫)1 struct BSTreeNode2 struct BSTreeNode leftchild3 int data4 struct BSTreeNode rightchild5 6 struct BSTreeNode root7 struct BSTreeNode SearchBST(

struct BSTreeNode tree int x)8 if (tree == NULL) return NULL9 if (x == tree-gtdata) return tree10 if (x lt tree-gtdata)11 return SearchBST(tree-gtleftchild x)

12 return SearchBST(tree-gtrightchild x)13

程式 5-16 搜尋二元搜尋樹的資料(非遞迴呼叫)在二元搜尋樹十分龐大時遞迴呼叫的效率不會太好可利用迴圈來完成搜尋的動作以提高執行效率請看程式 5-16

14 struct BSTreeNode Search BST_iterative15 (struct BSTreeNode node int x)16 while (node = NULL)17 if (x == node-gtdata) return node18 if (x lt node-gtdata)19 node = node-gtleftchild20 else21 node = node-gtrightchild22 23 return NULL24

592  新增資料於二元搜尋樹中欲新增資料進入一二元搜尋樹中得先決定該資料在二元搜尋樹中應出現的位置這可透過上節提到的搜尋動作來解決

在二元搜尋樹定義的性質 (1) 中我們限定了元素必須相異所以搜尋新資料的結果必是 NULL

必須將搜尋的結果改為新增資料所應在位置的節點指標

程式 5-17  在二元搜尋樹中新增資料25 int InsertBST(int x)26 struct BSTreeNode p q27 p = root q = NULL28 while (p = NULL)29 q = p30 if (x == p-gtdata) return 0

若不考慮重複則可省略之31 if (x lt p-gtdata)32 p = p-gtleftchild33 else34 p = p-gtrightchild35

3636 p = (struct BSTreeNode ) p = (struct BSTreeNode ) malloc (sizeof(BSTreeNode))malloc (sizeof(BSTreeNode))

3737 p-gtdata = x p-gtdata = x3838 p-gtleftchild = p-gtrightchild = p-gtleftchild = p-gtrightchild = NULLNULL3939 if (root == NULL) root = p if (root == NULL) root = p4040 else if (x lt q-gtdata) else if (x lt q-gtdata)4141 q-gtleftchild = p q-gtleftchild = p4242 else else4343 q-gtrightchild = p q-gtrightchild = p4444 return 1 return 145 45

程式 程式 5-175-17  在二元搜尋樹中新增資料  在二元搜尋樹中新增資料 (( 續續 ))

範例 5-23 圖 (b) 為圖 (a) 新增整數 6 之後的結果圖 (c) (d) (e) (f) 則分別是逐步再加入 28 18

10 21 的結果

bull若目前的二元搜尋樹高度為 h 則新增資料得花O(h) 的時間找到其所應在的位置遂新增資料至二元搜尋樹中的時間複雜之度為 O(h)

593 自二元搜尋樹中刪除資料欲自二元搜尋樹中刪除資料可能會面臨兩種情況(1) 刪除節點 p 位於樹葉節點上如下圖所示 這是比較容易處理的情況

(2) 刪除的節點 p 不是樹葉刪除節點 p 後原 p 所指的節點可由 q ( q 中包含了 p 左子樹中的最大節點值)或 r ( r 中包含了 p 右子樹中的最小節點值)來取代以維持二元搜尋樹中資料的大小關係其結果分別如圖 (b) 和 (c) 所示而圖 (d) 則標明了指標會改變的節點位置即 p 會消失 father 的左或右子樹指標會異動 k(可能是 q 或 r )會移到原來 p 的位置其父節點 f 的左或右子樹指標會異動其中 father 為 p 父節點 f 為 k 的父節點

程式 5-18 自二元搜尋樹中刪除資料46 int DeleteBST (int x)47 Struct BSTreeNode p father k f48 p = root father = NULL49 while (p = NULL)50 if (x == p-gtdata) 找到 x 所在的節點 p 了51 if ((p-gtleftchild == NULL) ampamp 52 (p-gtrightchild == NULL))53 k = NULL p為樹葉節點54 else if (p-gtleftchild = NULL )x位於非子節

點55 f = p 找出 p左子樹的最右樹葉56 k = p-gtleftchild57 while (k-gtrightchild =NULL)58 f = k59 k = k-gtrightchild60 k將挪至原 p處61 if (p == f)62 f-gtleftchild = k-gtleftchild 63 else64 f-gtrightchild = k-gtleftchild 65

66 else p無左子樹67 f = p 找出 p右子樹的最左樹葉68 k = p-gtrightchild69 while (k-gtleftchild = NULL)70 f = k71 k = k -gtleftchild72 73 if (p == f)74 f-gtrightchild = k-gtrightchild 75 else76 f-gtleftchild = k-gtrightchild77 78 if (k = NULL) k挪至原 p處繼承 p的左右指標79 k-gtleftchild = p-gtleftchild80 k-gtrightchild = p-gtrightchild81 82 if (father == NULL) root = k83 else if (x lt father-gtdata) 決定 k是 father的左

或右兒子84 father-gtleftchild = k85 else86 father-gtrightchild = k87 free(p)88 return 1 成功地刪除 x於此傳回 1返回呼叫處89 51~89行為找到 x時應有的處理步驟

90 else 尚未找到 x繼續往下一階層找

91 father = p92 if (x lt p-gtdata)93 p = p-gtleftchild94 else95 p = p-gtrightchild96 97 98 return 0 未找到 x傳回 099

510  樹林 (forest)

bull 定義樹林(森林)是由 n 棵沒有交集的樹所構成的集合 n 0

下圖為一包含三棵樹的樹林

5101 轉換樹林為二元樹 之前我們以二元樹來表示樹因為二元樹有較多

好用的性質在此我們也將樹林轉換成二元樹看待

在 522 節中我們嘗試了用左子右兄弟的方式來表示樹這個方法事實上已將樹轉換成二元樹

即用左子右兄弟的結構重新組織樹林中的每一棵樹這些樹的樹根將視為兄弟而最左邊的樹則視為樹林二元樹的樹根如此一來樹林即可轉換成二元樹

範例 5-25右下圖為右上圖三棵樹所對應的三棵左子右兄弟二元樹

下圖則為上圖樹林所對應的一棵樹林二元樹

演算法 5-1  轉換樹林成二元樹輸入樹林 F = T1 T2 Tn

輸出 F 的二元樹表示 BT=(T1 T2 Tn)1 if (n == 0) 輸出空二元樹 2 else

3 root(T1 T2 Tn) = T1

4 leftsubtree(T1 T2 Tn) =

BT(T11 T12 T1m)5 BT(T11 T12 T1m)為 T1的子樹6 rightsubtree(T1 T2 Tn) =

BT(T2 T3 Tn)

樹林轉換成二元樹的過程樹林轉換成二元樹的過程

  • Slide 1
  • Slide 2
  • Slide 3
  • Slide 4
  • Slide 5
  • Slide 6
  • Slide 7
  • Slide 8
  • Slide 9
  • Slide 10
  • Slide 11
  • Slide 12
  • Slide 13
  • Slide 14
  • Slide 15
  • Slide 16
  • Slide 17
  • Slide 18
  • Slide 19
  • Slide 20
  • Slide 21
  • Slide 22
  • Slide 23
  • Slide 24
  • Slide 25
  • Slide 26
  • Slide 27
  • Slide 28
  • Slide 29
  • Slide 30
  • Slide 31
  • Slide 32
  • Slide 33
  • Slide 34
  • Slide 35
  • Slide 36
  • Slide 37
  • Slide 38
  • Slide 39
  • Slide 40
  • Slide 41
  • Slide 42
  • Slide 43
  • Slide 44
  • Slide 45
  • Slide 46
  • Slide 47
  • Slide 48
  • Slide 49
  • Slide 50
  • Slide 51
  • Slide 52
  • Slide 53
  • Slide 54
  • Slide 55
  • Slide 56
  • Slide 57
  • Slide 58
  • Slide 59
  • Slide 60
  • Slide 61
  • Slide 62
  • Slide 63
  • Slide 64
  • Slide 65
  • Slide 66
  • Slide 67
  • Slide 68
  • Slide 69
  • Slide 70
  • Slide 71
  • Slide 72
  • Slide 73
  • Slide 74
  • Slide 75
  • Slide 76
  • Slide 77
  • Slide 78
  • Slide 79
  • Slide 80
  • Slide 81
  • Slide 82
  • Slide 83
  • Slide 84
  • Slide 85
  • Slide 86
  • Slide 87
  • Slide 88
  • Slide 89
  • Slide 90
  • Slide 91
  • Slide 92
  • Slide 93
  • Slide 94
  • Slide 95
  • Slide 96
  • Slide 97
  • Slide 98
  • Slide 99
  • Slide 100
  • Slide 101
  • Slide 102
  • Slide 103
Page 59: 第五章 樹

Counting Leaves in a Binary Tree

int CountingLeaves(struct BTreeNodes node)

if (node == NULL) return 0 int leftleaves = CountingLeaves(node-gtleftchild)

int rightchildren= CountingLeaves(node-gtrightchild)

if (leftleaves==0 ampamp leftleaves==0) return 1

return leftleaves + leftleaves

57  引線二元樹bull 在二元樹的鍵結表示中樹葉節點的兩個子樹指標皆指向 NULL 由定理 5-1 得知 n = E+1 其中 n 為節點個數 E 為分支數

bull 而 n 個鍵結節點有 2n 個指標空間每個分支恰佔用一個指標空間共有 E (=n-1) 個指標有用到(不是空的)而共有 n+1 個指標空間是 NULL 比非空的節點還多

bull 有學者提出對這些「放空指標的空間」加以利用的概念mdash與其放空指標不如放指向其它節點的指標稱之為引線 (thread) 使得某些運算(如走訪hellip等)可以加快

bull 這個概念發展出了引線二元樹 (threaded binary tree) 這種資料結構

範例 5-17下圖為加了引線的二元樹

加入的引線將使二元樹的中序走訪更加便利沿著上右圖箭頭所指示的順序走訪即可完成此二元樹的中序走訪GDHBAIECF 為此二元樹的中序表示

bull 範例 5-17 中所加的引線目的在方便二元樹的中序走訪

bull 對樹葉節點 p 而言令其資料為 P 則引線指標的規則如下

(1) p 的左子樹指標指向中序表示中 P 的前一個 元素(前接元素 (predecessor) )

(2) p 的右子樹指標指向中序表示中 P 的後一個 元素(後繼元素 (successor) )

為了區別節點指標放的是一般指標(內部節點)抑或是引線指標(樹葉節點)我們另以 2 個欄位分別區別左和右子樹指標空間存放的對象其節點的記憶體配置有如下圖所示

其中 leftthread (rightthread) 為 0 時表示 leftchild (rightchild) 存放的是一般節點指標而 leftthread (rightthread) 為 1 時表示 leftchild (rightchild) 存放的是引線指標1 struct TBTreeNode2 int leftthread3 struct TBTreeNode leftchild4 char data5 struct TBTreeNode rightchild6 int rightthread7

由右側的引線二元樹可知有最左和最右兩條引線尚無妥善的安排我們另設計一空的引線節點做為樹根如下圖所示

571  引線二元樹的中序走訪決定節點 p 在中序表示中的後繼節點 q (簡稱中序後繼點)

程式 5-10決定節點 node的中序後繼點1 struct TBTreeNode2 int leftthread3 struct TBTreeNode leftchild4 char data5 struct TBTreeNode rightchild6 int rightthread7 8 struct TBTreeNode root9 struct TBTreeNode Next(struct TBTreeNode node)

10 struct TBTreeNode temp11 temp = node-gtrightchild12 if (node-gtrightthead) return temp13 while (temp-gtleftthead) temp=temp-gtleftchild

14 return temp15

程式 5-11引線二元樹的中序表示(接程式 5-10)1 void InorderTBTree()2 struct TBTreeNode node3 for(node=Next(root)node=rootnode=Next(node))

4 cout ltlt node-gtdata5

bull只要 node 是引線二元樹上的任一節點透過呼叫 Next(node) 即可找到其在中序表示中的後繼節點bull(試想若程式 5-10 中的程序 Next 所傳入的參數指標正是 root Next(root) 是否可正常運作請思考)bull於是只須從中序表示中的第一節點起讓每一節點 node 都執行 Next(node) 程序即可得引線二元樹的中序表示

572  在引線二元樹中加入節點在引線二元樹中樹葉節點節點的右子樹指標所指向或內部節點右子樹的最左樹葉為其中序後繼節點而且任一節點若無左子樹則其左子樹指標乃指向其中序前接節點hellip這些性質必須在節點新增進入引線二元樹時也要保持接下來討論節點 new 插入成為引線二元樹的節點 p 的右子節點的情形至於插入成為左子節點的情形則讓各位模擬推敲

572572  在引線二元樹中加入節點 在引線二元樹中加入節點 (( 續續 ))

newnew 會成為會成為 pp 的右子節點的情況共有以下兩種的右子節點的情況共有以下兩種(1) (1) 若若 pp 沒有右子節點 沒有右子節點 ((p-gtrightthread p-gtrightthread 為為1) 1) 如圖如圖 ((a) a) 則 則 new new 插入後應形成如圖插入後應形成如圖 ((b) b) 所示所示

(2)(2) 若若 pp 有右子節點 有右子節點 ((p-gtrightthread p-gtrightthread 為為0) 0) 如圖如圖 ((c) c) 則 則 new new 插入後應形成如插入後應形成如圖圖 ((d) d) 所示所示

程式 5-12  插入節點 new 成為引線二元樹中節點 p的右子節點(上接程式 5-10 5-11 )

1 void InsertRight(struct TBTreeNode p 2 struct TBTreeNode new)3 struct TBTreeNode q4 new-gtrightchild = p-gtrightchild5 new-gtrightthead = p-gtrightthread6 new-gtleftchild = p7 new-gtleftthread = 18 p-gtrightchild = new9 p-gtrightthead = 010 if (new-gtrightthread)11 q = Next(new)12 q-gtleftchild = new13 14

58  堆積bull 「堆積」 (heap) 是一個特殊的完備二元樹節點的資料與其子節點的資料之間有事先定好的大小關係存在bull 定義ndash最大堆疊為一完備二元樹任一節點的資料內容不小於其子節點的資料內容ndash最小堆疊為一完備二元樹任一節點的資料內容不大於其子節點的資料內容

581  插入節點至最大堆積中最大堆積是一完備二元樹所以如圖 (a) 之最大堆積於加入新節點後樹的形狀必須如圖 (b) 所示灰色節點即為新節點若加入的節點資料分別為 1 9 12 則其最大堆積將分別變成如圖 (c) (d) (e) 所示

程式 5-13 新增節點至最大堆積中最大堆積本身是一棵完備二元樹可以用陣列表示之 1 define maxsize 1002 int heap[maxsize]3 int n i4 void InsertHeap(int x)5 if (n == maxsize) HeapFull()6 else7 n++8 i = n9 while ((i gt 1) ampamp (x gt heap[i2]))10 heap[i] = heap[i2]11 i = 212 13 heap[i] = x14 16

程序 InsertHeap 在插入第 n 個資料時需要的時間複雜度為 O(logn)

1 100

2 93

3 84

4 77

5 65

6 81

7 79

8 66

9 70

10 50

100

84

77

93

8165

66 70

79

50

1

2 3

4 5 6 7

8 9 10

1 define maxsize 1002 int heap[maxsize]3 int n i4 void InsertHeap(int x)5 if (n == maxsize) HeapFull()6 else7 n++8 i = n9 while ((i gt 1) ampamp (x gt heap[i2]))10 heap[i] = heap[i2]11 i = 212 13 heap[i] = x14 16

582 刪除最大堆積中的最大資料圖 (a) 中的最大堆積在刪除最大資料後其樹形應變成如圖 (b) 所示將最未一個樹葉節點的資料 6 暫挪至樹根節點內如圖 (c) 再將圖 (c) 中非最大堆積的結構調整成為最大堆積 10 和 8 中選大者與 6對調即成圖 (d)

程式 5-14 刪除最大堆積中的最大資料(上接程式 5-13 )1 int DeleteHeap()2 int x i j3 if (n == 0) HeapEmpty()4 else5 x = heap[1]6 heap[1] = heap[n]7 n--8 i = 19 while (i lt= n2)10 if (heap[2i] gt heap[2i+1])11 j = 2i12 else13 j = 2i+114 if (heap[i]gtheap[j]) break15 swap(heap[i] heap[j])16 i = j17 18 return x19 20

其時間複雜度與其堆積高度相同是為O(logn) 其中 n 為堆積中的元素個數

1 100

2 93

3 84

4 77

5 65

6 81

7 79

8 66

9 70

10 50

100

84

77

93

8165

66 70

79

50

1

2 3

4 5 6 7

8 9 10

1 int DeletHeap()2 int x i j3 if(n == 0) HeapEmpty()4 else5 x = heap[1]6 heap[1] = heap[n]7 n--8 i = 19 while (i lt= n2)10 if ([heap[2i]] gt heap[2i+1])11 j = 2i12 else13 j = 2i+114 if (heap[i]gtheap[j]) break15 swap(heap[i] heap[j])16 i = j17 18 return x19 20

583  優先佇列bull 由於最大堆積(或最小堆積)的結構可提供最大元素(或最小元素)而其維護堆積的成本為 O(logn) n 為總元素個數

bull 堆積常被當成「優先佇列」 (priority queue) 使用mdash此時佇列被組織成完備二元樹若新加入佇列的資料有大小關係優先順序的需求時此優先佇列即以 O(logn) 的代價將新資料放入優先佇列(可能是最大或最小堆積依實際大小關係優先順序的需求而定)中mdash提供依元素的優先順序做為處理順序的服務

bull 一般的佇列為先進先出先到的先行處理優先佇列則是依據其優先順序優先順序高者先行處理與來到的先後無關

範例 5-21bull 在「分時」 (time sharing) 的作業系統中我們不希望使用中央處理器 (Central Processing Unit CPU) 時間短的「處理程序」 (process) 等候過久於是有「最短時間工作優先」 (shortest-job-first) 的排程策略此時等候的處理程序即可組織成之一最小堆積而 CPU 每次即取出存於樹根的處理程序予以處理任何新來的處理程序則以 O(logn) 的時間加入此優先佇列(最小堆積)中

bull 注意之前提過的線性佇列新來的直接加入成為新的尾端元素花O(1) 的時間然線性佇列本身沒有大小的關係每次要取出最大(小)的資料得花O(n) 的代價(在所有元素中挑出最大(小)者) n 為佇列內的元素個數而優先佇列是在新元素加入時要花O(logn) 的時間至於取出最大(小)則只須 O(1) 的時間

59  二元搜尋樹bull 二元搜尋樹是一棵二元樹可能是空二元樹若不為空二元樹則滿足下列性質ndash所有節點內的資料內容是相異的 ndash左子節點的資內容 ( 如果有 ) 比父節點的資料內容小 ndash右子節點的資料內容 ( 如果有 ) 比父節點的資料內容大 ndash以左和右子節點為樹根的左子樹和右子樹也是二元搜尋樹

在二元搜尋樹中搜尋資料二元搜尋樹對資料大小的掌握十分明確若恰與節點 p 資料相等則搜尋成功若欲搜尋的資料 x 比樹節點 p 中的資料小 則只須搜尋節點 p 的左子樹若比節點 p 中的資料大 則只須搜尋 p 的右子樹這樣的判斷可以遞迴地應用在各子樹節點 中

程式 5-15 在二元搜尋樹中搜尋資料(遞迴呼叫)1 struct BSTreeNode2 struct BSTreeNode leftchild3 int data4 struct BSTreeNode rightchild5 6 struct BSTreeNode root7 struct BSTreeNode SearchBST(

struct BSTreeNode tree int x)8 if (tree == NULL) return NULL9 if (x == tree-gtdata) return tree10 if (x lt tree-gtdata)11 return SearchBST(tree-gtleftchild x)

12 return SearchBST(tree-gtrightchild x)13

程式 5-16 搜尋二元搜尋樹的資料(非遞迴呼叫)在二元搜尋樹十分龐大時遞迴呼叫的效率不會太好可利用迴圈來完成搜尋的動作以提高執行效率請看程式 5-16

14 struct BSTreeNode Search BST_iterative15 (struct BSTreeNode node int x)16 while (node = NULL)17 if (x == node-gtdata) return node18 if (x lt node-gtdata)19 node = node-gtleftchild20 else21 node = node-gtrightchild22 23 return NULL24

592  新增資料於二元搜尋樹中欲新增資料進入一二元搜尋樹中得先決定該資料在二元搜尋樹中應出現的位置這可透過上節提到的搜尋動作來解決

在二元搜尋樹定義的性質 (1) 中我們限定了元素必須相異所以搜尋新資料的結果必是 NULL

必須將搜尋的結果改為新增資料所應在位置的節點指標

程式 5-17  在二元搜尋樹中新增資料25 int InsertBST(int x)26 struct BSTreeNode p q27 p = root q = NULL28 while (p = NULL)29 q = p30 if (x == p-gtdata) return 0

若不考慮重複則可省略之31 if (x lt p-gtdata)32 p = p-gtleftchild33 else34 p = p-gtrightchild35

3636 p = (struct BSTreeNode ) p = (struct BSTreeNode ) malloc (sizeof(BSTreeNode))malloc (sizeof(BSTreeNode))

3737 p-gtdata = x p-gtdata = x3838 p-gtleftchild = p-gtrightchild = p-gtleftchild = p-gtrightchild = NULLNULL3939 if (root == NULL) root = p if (root == NULL) root = p4040 else if (x lt q-gtdata) else if (x lt q-gtdata)4141 q-gtleftchild = p q-gtleftchild = p4242 else else4343 q-gtrightchild = p q-gtrightchild = p4444 return 1 return 145 45

程式 程式 5-175-17  在二元搜尋樹中新增資料  在二元搜尋樹中新增資料 (( 續續 ))

範例 5-23 圖 (b) 為圖 (a) 新增整數 6 之後的結果圖 (c) (d) (e) (f) 則分別是逐步再加入 28 18

10 21 的結果

bull若目前的二元搜尋樹高度為 h 則新增資料得花O(h) 的時間找到其所應在的位置遂新增資料至二元搜尋樹中的時間複雜之度為 O(h)

593 自二元搜尋樹中刪除資料欲自二元搜尋樹中刪除資料可能會面臨兩種情況(1) 刪除節點 p 位於樹葉節點上如下圖所示 這是比較容易處理的情況

(2) 刪除的節點 p 不是樹葉刪除節點 p 後原 p 所指的節點可由 q ( q 中包含了 p 左子樹中的最大節點值)或 r ( r 中包含了 p 右子樹中的最小節點值)來取代以維持二元搜尋樹中資料的大小關係其結果分別如圖 (b) 和 (c) 所示而圖 (d) 則標明了指標會改變的節點位置即 p 會消失 father 的左或右子樹指標會異動 k(可能是 q 或 r )會移到原來 p 的位置其父節點 f 的左或右子樹指標會異動其中 father 為 p 父節點 f 為 k 的父節點

程式 5-18 自二元搜尋樹中刪除資料46 int DeleteBST (int x)47 Struct BSTreeNode p father k f48 p = root father = NULL49 while (p = NULL)50 if (x == p-gtdata) 找到 x 所在的節點 p 了51 if ((p-gtleftchild == NULL) ampamp 52 (p-gtrightchild == NULL))53 k = NULL p為樹葉節點54 else if (p-gtleftchild = NULL )x位於非子節

點55 f = p 找出 p左子樹的最右樹葉56 k = p-gtleftchild57 while (k-gtrightchild =NULL)58 f = k59 k = k-gtrightchild60 k將挪至原 p處61 if (p == f)62 f-gtleftchild = k-gtleftchild 63 else64 f-gtrightchild = k-gtleftchild 65

66 else p無左子樹67 f = p 找出 p右子樹的最左樹葉68 k = p-gtrightchild69 while (k-gtleftchild = NULL)70 f = k71 k = k -gtleftchild72 73 if (p == f)74 f-gtrightchild = k-gtrightchild 75 else76 f-gtleftchild = k-gtrightchild77 78 if (k = NULL) k挪至原 p處繼承 p的左右指標79 k-gtleftchild = p-gtleftchild80 k-gtrightchild = p-gtrightchild81 82 if (father == NULL) root = k83 else if (x lt father-gtdata) 決定 k是 father的左

或右兒子84 father-gtleftchild = k85 else86 father-gtrightchild = k87 free(p)88 return 1 成功地刪除 x於此傳回 1返回呼叫處89 51~89行為找到 x時應有的處理步驟

90 else 尚未找到 x繼續往下一階層找

91 father = p92 if (x lt p-gtdata)93 p = p-gtleftchild94 else95 p = p-gtrightchild96 97 98 return 0 未找到 x傳回 099

510  樹林 (forest)

bull 定義樹林(森林)是由 n 棵沒有交集的樹所構成的集合 n 0

下圖為一包含三棵樹的樹林

5101 轉換樹林為二元樹 之前我們以二元樹來表示樹因為二元樹有較多

好用的性質在此我們也將樹林轉換成二元樹看待

在 522 節中我們嘗試了用左子右兄弟的方式來表示樹這個方法事實上已將樹轉換成二元樹

即用左子右兄弟的結構重新組織樹林中的每一棵樹這些樹的樹根將視為兄弟而最左邊的樹則視為樹林二元樹的樹根如此一來樹林即可轉換成二元樹

範例 5-25右下圖為右上圖三棵樹所對應的三棵左子右兄弟二元樹

下圖則為上圖樹林所對應的一棵樹林二元樹

演算法 5-1  轉換樹林成二元樹輸入樹林 F = T1 T2 Tn

輸出 F 的二元樹表示 BT=(T1 T2 Tn)1 if (n == 0) 輸出空二元樹 2 else

3 root(T1 T2 Tn) = T1

4 leftsubtree(T1 T2 Tn) =

BT(T11 T12 T1m)5 BT(T11 T12 T1m)為 T1的子樹6 rightsubtree(T1 T2 Tn) =

BT(T2 T3 Tn)

樹林轉換成二元樹的過程樹林轉換成二元樹的過程

  • Slide 1
  • Slide 2
  • Slide 3
  • Slide 4
  • Slide 5
  • Slide 6
  • Slide 7
  • Slide 8
  • Slide 9
  • Slide 10
  • Slide 11
  • Slide 12
  • Slide 13
  • Slide 14
  • Slide 15
  • Slide 16
  • Slide 17
  • Slide 18
  • Slide 19
  • Slide 20
  • Slide 21
  • Slide 22
  • Slide 23
  • Slide 24
  • Slide 25
  • Slide 26
  • Slide 27
  • Slide 28
  • Slide 29
  • Slide 30
  • Slide 31
  • Slide 32
  • Slide 33
  • Slide 34
  • Slide 35
  • Slide 36
  • Slide 37
  • Slide 38
  • Slide 39
  • Slide 40
  • Slide 41
  • Slide 42
  • Slide 43
  • Slide 44
  • Slide 45
  • Slide 46
  • Slide 47
  • Slide 48
  • Slide 49
  • Slide 50
  • Slide 51
  • Slide 52
  • Slide 53
  • Slide 54
  • Slide 55
  • Slide 56
  • Slide 57
  • Slide 58
  • Slide 59
  • Slide 60
  • Slide 61
  • Slide 62
  • Slide 63
  • Slide 64
  • Slide 65
  • Slide 66
  • Slide 67
  • Slide 68
  • Slide 69
  • Slide 70
  • Slide 71
  • Slide 72
  • Slide 73
  • Slide 74
  • Slide 75
  • Slide 76
  • Slide 77
  • Slide 78
  • Slide 79
  • Slide 80
  • Slide 81
  • Slide 82
  • Slide 83
  • Slide 84
  • Slide 85
  • Slide 86
  • Slide 87
  • Slide 88
  • Slide 89
  • Slide 90
  • Slide 91
  • Slide 92
  • Slide 93
  • Slide 94
  • Slide 95
  • Slide 96
  • Slide 97
  • Slide 98
  • Slide 99
  • Slide 100
  • Slide 101
  • Slide 102
  • Slide 103
Page 60: 第五章 樹

57  引線二元樹bull 在二元樹的鍵結表示中樹葉節點的兩個子樹指標皆指向 NULL 由定理 5-1 得知 n = E+1 其中 n 為節點個數 E 為分支數

bull 而 n 個鍵結節點有 2n 個指標空間每個分支恰佔用一個指標空間共有 E (=n-1) 個指標有用到(不是空的)而共有 n+1 個指標空間是 NULL 比非空的節點還多

bull 有學者提出對這些「放空指標的空間」加以利用的概念mdash與其放空指標不如放指向其它節點的指標稱之為引線 (thread) 使得某些運算(如走訪hellip等)可以加快

bull 這個概念發展出了引線二元樹 (threaded binary tree) 這種資料結構

範例 5-17下圖為加了引線的二元樹

加入的引線將使二元樹的中序走訪更加便利沿著上右圖箭頭所指示的順序走訪即可完成此二元樹的中序走訪GDHBAIECF 為此二元樹的中序表示

bull 範例 5-17 中所加的引線目的在方便二元樹的中序走訪

bull 對樹葉節點 p 而言令其資料為 P 則引線指標的規則如下

(1) p 的左子樹指標指向中序表示中 P 的前一個 元素(前接元素 (predecessor) )

(2) p 的右子樹指標指向中序表示中 P 的後一個 元素(後繼元素 (successor) )

為了區別節點指標放的是一般指標(內部節點)抑或是引線指標(樹葉節點)我們另以 2 個欄位分別區別左和右子樹指標空間存放的對象其節點的記憶體配置有如下圖所示

其中 leftthread (rightthread) 為 0 時表示 leftchild (rightchild) 存放的是一般節點指標而 leftthread (rightthread) 為 1 時表示 leftchild (rightchild) 存放的是引線指標1 struct TBTreeNode2 int leftthread3 struct TBTreeNode leftchild4 char data5 struct TBTreeNode rightchild6 int rightthread7

由右側的引線二元樹可知有最左和最右兩條引線尚無妥善的安排我們另設計一空的引線節點做為樹根如下圖所示

571  引線二元樹的中序走訪決定節點 p 在中序表示中的後繼節點 q (簡稱中序後繼點)

程式 5-10決定節點 node的中序後繼點1 struct TBTreeNode2 int leftthread3 struct TBTreeNode leftchild4 char data5 struct TBTreeNode rightchild6 int rightthread7 8 struct TBTreeNode root9 struct TBTreeNode Next(struct TBTreeNode node)

10 struct TBTreeNode temp11 temp = node-gtrightchild12 if (node-gtrightthead) return temp13 while (temp-gtleftthead) temp=temp-gtleftchild

14 return temp15

程式 5-11引線二元樹的中序表示(接程式 5-10)1 void InorderTBTree()2 struct TBTreeNode node3 for(node=Next(root)node=rootnode=Next(node))

4 cout ltlt node-gtdata5

bull只要 node 是引線二元樹上的任一節點透過呼叫 Next(node) 即可找到其在中序表示中的後繼節點bull(試想若程式 5-10 中的程序 Next 所傳入的參數指標正是 root Next(root) 是否可正常運作請思考)bull於是只須從中序表示中的第一節點起讓每一節點 node 都執行 Next(node) 程序即可得引線二元樹的中序表示

572  在引線二元樹中加入節點在引線二元樹中樹葉節點節點的右子樹指標所指向或內部節點右子樹的最左樹葉為其中序後繼節點而且任一節點若無左子樹則其左子樹指標乃指向其中序前接節點hellip這些性質必須在節點新增進入引線二元樹時也要保持接下來討論節點 new 插入成為引線二元樹的節點 p 的右子節點的情形至於插入成為左子節點的情形則讓各位模擬推敲

572572  在引線二元樹中加入節點 在引線二元樹中加入節點 (( 續續 ))

newnew 會成為會成為 pp 的右子節點的情況共有以下兩種的右子節點的情況共有以下兩種(1) (1) 若若 pp 沒有右子節點 沒有右子節點 ((p-gtrightthread p-gtrightthread 為為1) 1) 如圖如圖 ((a) a) 則 則 new new 插入後應形成如圖插入後應形成如圖 ((b) b) 所示所示

(2)(2) 若若 pp 有右子節點 有右子節點 ((p-gtrightthread p-gtrightthread 為為0) 0) 如圖如圖 ((c) c) 則 則 new new 插入後應形成如插入後應形成如圖圖 ((d) d) 所示所示

程式 5-12  插入節點 new 成為引線二元樹中節點 p的右子節點(上接程式 5-10 5-11 )

1 void InsertRight(struct TBTreeNode p 2 struct TBTreeNode new)3 struct TBTreeNode q4 new-gtrightchild = p-gtrightchild5 new-gtrightthead = p-gtrightthread6 new-gtleftchild = p7 new-gtleftthread = 18 p-gtrightchild = new9 p-gtrightthead = 010 if (new-gtrightthread)11 q = Next(new)12 q-gtleftchild = new13 14

58  堆積bull 「堆積」 (heap) 是一個特殊的完備二元樹節點的資料與其子節點的資料之間有事先定好的大小關係存在bull 定義ndash最大堆疊為一完備二元樹任一節點的資料內容不小於其子節點的資料內容ndash最小堆疊為一完備二元樹任一節點的資料內容不大於其子節點的資料內容

581  插入節點至最大堆積中最大堆積是一完備二元樹所以如圖 (a) 之最大堆積於加入新節點後樹的形狀必須如圖 (b) 所示灰色節點即為新節點若加入的節點資料分別為 1 9 12 則其最大堆積將分別變成如圖 (c) (d) (e) 所示

程式 5-13 新增節點至最大堆積中最大堆積本身是一棵完備二元樹可以用陣列表示之 1 define maxsize 1002 int heap[maxsize]3 int n i4 void InsertHeap(int x)5 if (n == maxsize) HeapFull()6 else7 n++8 i = n9 while ((i gt 1) ampamp (x gt heap[i2]))10 heap[i] = heap[i2]11 i = 212 13 heap[i] = x14 16

程序 InsertHeap 在插入第 n 個資料時需要的時間複雜度為 O(logn)

1 100

2 93

3 84

4 77

5 65

6 81

7 79

8 66

9 70

10 50

100

84

77

93

8165

66 70

79

50

1

2 3

4 5 6 7

8 9 10

1 define maxsize 1002 int heap[maxsize]3 int n i4 void InsertHeap(int x)5 if (n == maxsize) HeapFull()6 else7 n++8 i = n9 while ((i gt 1) ampamp (x gt heap[i2]))10 heap[i] = heap[i2]11 i = 212 13 heap[i] = x14 16

582 刪除最大堆積中的最大資料圖 (a) 中的最大堆積在刪除最大資料後其樹形應變成如圖 (b) 所示將最未一個樹葉節點的資料 6 暫挪至樹根節點內如圖 (c) 再將圖 (c) 中非最大堆積的結構調整成為最大堆積 10 和 8 中選大者與 6對調即成圖 (d)

程式 5-14 刪除最大堆積中的最大資料(上接程式 5-13 )1 int DeleteHeap()2 int x i j3 if (n == 0) HeapEmpty()4 else5 x = heap[1]6 heap[1] = heap[n]7 n--8 i = 19 while (i lt= n2)10 if (heap[2i] gt heap[2i+1])11 j = 2i12 else13 j = 2i+114 if (heap[i]gtheap[j]) break15 swap(heap[i] heap[j])16 i = j17 18 return x19 20

其時間複雜度與其堆積高度相同是為O(logn) 其中 n 為堆積中的元素個數

1 100

2 93

3 84

4 77

5 65

6 81

7 79

8 66

9 70

10 50

100

84

77

93

8165

66 70

79

50

1

2 3

4 5 6 7

8 9 10

1 int DeletHeap()2 int x i j3 if(n == 0) HeapEmpty()4 else5 x = heap[1]6 heap[1] = heap[n]7 n--8 i = 19 while (i lt= n2)10 if ([heap[2i]] gt heap[2i+1])11 j = 2i12 else13 j = 2i+114 if (heap[i]gtheap[j]) break15 swap(heap[i] heap[j])16 i = j17 18 return x19 20

583  優先佇列bull 由於最大堆積(或最小堆積)的結構可提供最大元素(或最小元素)而其維護堆積的成本為 O(logn) n 為總元素個數

bull 堆積常被當成「優先佇列」 (priority queue) 使用mdash此時佇列被組織成完備二元樹若新加入佇列的資料有大小關係優先順序的需求時此優先佇列即以 O(logn) 的代價將新資料放入優先佇列(可能是最大或最小堆積依實際大小關係優先順序的需求而定)中mdash提供依元素的優先順序做為處理順序的服務

bull 一般的佇列為先進先出先到的先行處理優先佇列則是依據其優先順序優先順序高者先行處理與來到的先後無關

範例 5-21bull 在「分時」 (time sharing) 的作業系統中我們不希望使用中央處理器 (Central Processing Unit CPU) 時間短的「處理程序」 (process) 等候過久於是有「最短時間工作優先」 (shortest-job-first) 的排程策略此時等候的處理程序即可組織成之一最小堆積而 CPU 每次即取出存於樹根的處理程序予以處理任何新來的處理程序則以 O(logn) 的時間加入此優先佇列(最小堆積)中

bull 注意之前提過的線性佇列新來的直接加入成為新的尾端元素花O(1) 的時間然線性佇列本身沒有大小的關係每次要取出最大(小)的資料得花O(n) 的代價(在所有元素中挑出最大(小)者) n 為佇列內的元素個數而優先佇列是在新元素加入時要花O(logn) 的時間至於取出最大(小)則只須 O(1) 的時間

59  二元搜尋樹bull 二元搜尋樹是一棵二元樹可能是空二元樹若不為空二元樹則滿足下列性質ndash所有節點內的資料內容是相異的 ndash左子節點的資內容 ( 如果有 ) 比父節點的資料內容小 ndash右子節點的資料內容 ( 如果有 ) 比父節點的資料內容大 ndash以左和右子節點為樹根的左子樹和右子樹也是二元搜尋樹

在二元搜尋樹中搜尋資料二元搜尋樹對資料大小的掌握十分明確若恰與節點 p 資料相等則搜尋成功若欲搜尋的資料 x 比樹節點 p 中的資料小 則只須搜尋節點 p 的左子樹若比節點 p 中的資料大 則只須搜尋 p 的右子樹這樣的判斷可以遞迴地應用在各子樹節點 中

程式 5-15 在二元搜尋樹中搜尋資料(遞迴呼叫)1 struct BSTreeNode2 struct BSTreeNode leftchild3 int data4 struct BSTreeNode rightchild5 6 struct BSTreeNode root7 struct BSTreeNode SearchBST(

struct BSTreeNode tree int x)8 if (tree == NULL) return NULL9 if (x == tree-gtdata) return tree10 if (x lt tree-gtdata)11 return SearchBST(tree-gtleftchild x)

12 return SearchBST(tree-gtrightchild x)13

程式 5-16 搜尋二元搜尋樹的資料(非遞迴呼叫)在二元搜尋樹十分龐大時遞迴呼叫的效率不會太好可利用迴圈來完成搜尋的動作以提高執行效率請看程式 5-16

14 struct BSTreeNode Search BST_iterative15 (struct BSTreeNode node int x)16 while (node = NULL)17 if (x == node-gtdata) return node18 if (x lt node-gtdata)19 node = node-gtleftchild20 else21 node = node-gtrightchild22 23 return NULL24

592  新增資料於二元搜尋樹中欲新增資料進入一二元搜尋樹中得先決定該資料在二元搜尋樹中應出現的位置這可透過上節提到的搜尋動作來解決

在二元搜尋樹定義的性質 (1) 中我們限定了元素必須相異所以搜尋新資料的結果必是 NULL

必須將搜尋的結果改為新增資料所應在位置的節點指標

程式 5-17  在二元搜尋樹中新增資料25 int InsertBST(int x)26 struct BSTreeNode p q27 p = root q = NULL28 while (p = NULL)29 q = p30 if (x == p-gtdata) return 0

若不考慮重複則可省略之31 if (x lt p-gtdata)32 p = p-gtleftchild33 else34 p = p-gtrightchild35

3636 p = (struct BSTreeNode ) p = (struct BSTreeNode ) malloc (sizeof(BSTreeNode))malloc (sizeof(BSTreeNode))

3737 p-gtdata = x p-gtdata = x3838 p-gtleftchild = p-gtrightchild = p-gtleftchild = p-gtrightchild = NULLNULL3939 if (root == NULL) root = p if (root == NULL) root = p4040 else if (x lt q-gtdata) else if (x lt q-gtdata)4141 q-gtleftchild = p q-gtleftchild = p4242 else else4343 q-gtrightchild = p q-gtrightchild = p4444 return 1 return 145 45

程式 程式 5-175-17  在二元搜尋樹中新增資料  在二元搜尋樹中新增資料 (( 續續 ))

範例 5-23 圖 (b) 為圖 (a) 新增整數 6 之後的結果圖 (c) (d) (e) (f) 則分別是逐步再加入 28 18

10 21 的結果

bull若目前的二元搜尋樹高度為 h 則新增資料得花O(h) 的時間找到其所應在的位置遂新增資料至二元搜尋樹中的時間複雜之度為 O(h)

593 自二元搜尋樹中刪除資料欲自二元搜尋樹中刪除資料可能會面臨兩種情況(1) 刪除節點 p 位於樹葉節點上如下圖所示 這是比較容易處理的情況

(2) 刪除的節點 p 不是樹葉刪除節點 p 後原 p 所指的節點可由 q ( q 中包含了 p 左子樹中的最大節點值)或 r ( r 中包含了 p 右子樹中的最小節點值)來取代以維持二元搜尋樹中資料的大小關係其結果分別如圖 (b) 和 (c) 所示而圖 (d) 則標明了指標會改變的節點位置即 p 會消失 father 的左或右子樹指標會異動 k(可能是 q 或 r )會移到原來 p 的位置其父節點 f 的左或右子樹指標會異動其中 father 為 p 父節點 f 為 k 的父節點

程式 5-18 自二元搜尋樹中刪除資料46 int DeleteBST (int x)47 Struct BSTreeNode p father k f48 p = root father = NULL49 while (p = NULL)50 if (x == p-gtdata) 找到 x 所在的節點 p 了51 if ((p-gtleftchild == NULL) ampamp 52 (p-gtrightchild == NULL))53 k = NULL p為樹葉節點54 else if (p-gtleftchild = NULL )x位於非子節

點55 f = p 找出 p左子樹的最右樹葉56 k = p-gtleftchild57 while (k-gtrightchild =NULL)58 f = k59 k = k-gtrightchild60 k將挪至原 p處61 if (p == f)62 f-gtleftchild = k-gtleftchild 63 else64 f-gtrightchild = k-gtleftchild 65

66 else p無左子樹67 f = p 找出 p右子樹的最左樹葉68 k = p-gtrightchild69 while (k-gtleftchild = NULL)70 f = k71 k = k -gtleftchild72 73 if (p == f)74 f-gtrightchild = k-gtrightchild 75 else76 f-gtleftchild = k-gtrightchild77 78 if (k = NULL) k挪至原 p處繼承 p的左右指標79 k-gtleftchild = p-gtleftchild80 k-gtrightchild = p-gtrightchild81 82 if (father == NULL) root = k83 else if (x lt father-gtdata) 決定 k是 father的左

或右兒子84 father-gtleftchild = k85 else86 father-gtrightchild = k87 free(p)88 return 1 成功地刪除 x於此傳回 1返回呼叫處89 51~89行為找到 x時應有的處理步驟

90 else 尚未找到 x繼續往下一階層找

91 father = p92 if (x lt p-gtdata)93 p = p-gtleftchild94 else95 p = p-gtrightchild96 97 98 return 0 未找到 x傳回 099

510  樹林 (forest)

bull 定義樹林(森林)是由 n 棵沒有交集的樹所構成的集合 n 0

下圖為一包含三棵樹的樹林

5101 轉換樹林為二元樹 之前我們以二元樹來表示樹因為二元樹有較多

好用的性質在此我們也將樹林轉換成二元樹看待

在 522 節中我們嘗試了用左子右兄弟的方式來表示樹這個方法事實上已將樹轉換成二元樹

即用左子右兄弟的結構重新組織樹林中的每一棵樹這些樹的樹根將視為兄弟而最左邊的樹則視為樹林二元樹的樹根如此一來樹林即可轉換成二元樹

範例 5-25右下圖為右上圖三棵樹所對應的三棵左子右兄弟二元樹

下圖則為上圖樹林所對應的一棵樹林二元樹

演算法 5-1  轉換樹林成二元樹輸入樹林 F = T1 T2 Tn

輸出 F 的二元樹表示 BT=(T1 T2 Tn)1 if (n == 0) 輸出空二元樹 2 else

3 root(T1 T2 Tn) = T1

4 leftsubtree(T1 T2 Tn) =

BT(T11 T12 T1m)5 BT(T11 T12 T1m)為 T1的子樹6 rightsubtree(T1 T2 Tn) =

BT(T2 T3 Tn)

樹林轉換成二元樹的過程樹林轉換成二元樹的過程

  • Slide 1
  • Slide 2
  • Slide 3
  • Slide 4
  • Slide 5
  • Slide 6
  • Slide 7
  • Slide 8
  • Slide 9
  • Slide 10
  • Slide 11
  • Slide 12
  • Slide 13
  • Slide 14
  • Slide 15
  • Slide 16
  • Slide 17
  • Slide 18
  • Slide 19
  • Slide 20
  • Slide 21
  • Slide 22
  • Slide 23
  • Slide 24
  • Slide 25
  • Slide 26
  • Slide 27
  • Slide 28
  • Slide 29
  • Slide 30
  • Slide 31
  • Slide 32
  • Slide 33
  • Slide 34
  • Slide 35
  • Slide 36
  • Slide 37
  • Slide 38
  • Slide 39
  • Slide 40
  • Slide 41
  • Slide 42
  • Slide 43
  • Slide 44
  • Slide 45
  • Slide 46
  • Slide 47
  • Slide 48
  • Slide 49
  • Slide 50
  • Slide 51
  • Slide 52
  • Slide 53
  • Slide 54
  • Slide 55
  • Slide 56
  • Slide 57
  • Slide 58
  • Slide 59
  • Slide 60
  • Slide 61
  • Slide 62
  • Slide 63
  • Slide 64
  • Slide 65
  • Slide 66
  • Slide 67
  • Slide 68
  • Slide 69
  • Slide 70
  • Slide 71
  • Slide 72
  • Slide 73
  • Slide 74
  • Slide 75
  • Slide 76
  • Slide 77
  • Slide 78
  • Slide 79
  • Slide 80
  • Slide 81
  • Slide 82
  • Slide 83
  • Slide 84
  • Slide 85
  • Slide 86
  • Slide 87
  • Slide 88
  • Slide 89
  • Slide 90
  • Slide 91
  • Slide 92
  • Slide 93
  • Slide 94
  • Slide 95
  • Slide 96
  • Slide 97
  • Slide 98
  • Slide 99
  • Slide 100
  • Slide 101
  • Slide 102
  • Slide 103
Page 61: 第五章 樹

範例 5-17下圖為加了引線的二元樹

加入的引線將使二元樹的中序走訪更加便利沿著上右圖箭頭所指示的順序走訪即可完成此二元樹的中序走訪GDHBAIECF 為此二元樹的中序表示

bull 範例 5-17 中所加的引線目的在方便二元樹的中序走訪

bull 對樹葉節點 p 而言令其資料為 P 則引線指標的規則如下

(1) p 的左子樹指標指向中序表示中 P 的前一個 元素(前接元素 (predecessor) )

(2) p 的右子樹指標指向中序表示中 P 的後一個 元素(後繼元素 (successor) )

為了區別節點指標放的是一般指標(內部節點)抑或是引線指標(樹葉節點)我們另以 2 個欄位分別區別左和右子樹指標空間存放的對象其節點的記憶體配置有如下圖所示

其中 leftthread (rightthread) 為 0 時表示 leftchild (rightchild) 存放的是一般節點指標而 leftthread (rightthread) 為 1 時表示 leftchild (rightchild) 存放的是引線指標1 struct TBTreeNode2 int leftthread3 struct TBTreeNode leftchild4 char data5 struct TBTreeNode rightchild6 int rightthread7

由右側的引線二元樹可知有最左和最右兩條引線尚無妥善的安排我們另設計一空的引線節點做為樹根如下圖所示

571  引線二元樹的中序走訪決定節點 p 在中序表示中的後繼節點 q (簡稱中序後繼點)

程式 5-10決定節點 node的中序後繼點1 struct TBTreeNode2 int leftthread3 struct TBTreeNode leftchild4 char data5 struct TBTreeNode rightchild6 int rightthread7 8 struct TBTreeNode root9 struct TBTreeNode Next(struct TBTreeNode node)

10 struct TBTreeNode temp11 temp = node-gtrightchild12 if (node-gtrightthead) return temp13 while (temp-gtleftthead) temp=temp-gtleftchild

14 return temp15

程式 5-11引線二元樹的中序表示(接程式 5-10)1 void InorderTBTree()2 struct TBTreeNode node3 for(node=Next(root)node=rootnode=Next(node))

4 cout ltlt node-gtdata5

bull只要 node 是引線二元樹上的任一節點透過呼叫 Next(node) 即可找到其在中序表示中的後繼節點bull(試想若程式 5-10 中的程序 Next 所傳入的參數指標正是 root Next(root) 是否可正常運作請思考)bull於是只須從中序表示中的第一節點起讓每一節點 node 都執行 Next(node) 程序即可得引線二元樹的中序表示

572  在引線二元樹中加入節點在引線二元樹中樹葉節點節點的右子樹指標所指向或內部節點右子樹的最左樹葉為其中序後繼節點而且任一節點若無左子樹則其左子樹指標乃指向其中序前接節點hellip這些性質必須在節點新增進入引線二元樹時也要保持接下來討論節點 new 插入成為引線二元樹的節點 p 的右子節點的情形至於插入成為左子節點的情形則讓各位模擬推敲

572572  在引線二元樹中加入節點 在引線二元樹中加入節點 (( 續續 ))

newnew 會成為會成為 pp 的右子節點的情況共有以下兩種的右子節點的情況共有以下兩種(1) (1) 若若 pp 沒有右子節點 沒有右子節點 ((p-gtrightthread p-gtrightthread 為為1) 1) 如圖如圖 ((a) a) 則 則 new new 插入後應形成如圖插入後應形成如圖 ((b) b) 所示所示

(2)(2) 若若 pp 有右子節點 有右子節點 ((p-gtrightthread p-gtrightthread 為為0) 0) 如圖如圖 ((c) c) 則 則 new new 插入後應形成如插入後應形成如圖圖 ((d) d) 所示所示

程式 5-12  插入節點 new 成為引線二元樹中節點 p的右子節點(上接程式 5-10 5-11 )

1 void InsertRight(struct TBTreeNode p 2 struct TBTreeNode new)3 struct TBTreeNode q4 new-gtrightchild = p-gtrightchild5 new-gtrightthead = p-gtrightthread6 new-gtleftchild = p7 new-gtleftthread = 18 p-gtrightchild = new9 p-gtrightthead = 010 if (new-gtrightthread)11 q = Next(new)12 q-gtleftchild = new13 14

58  堆積bull 「堆積」 (heap) 是一個特殊的完備二元樹節點的資料與其子節點的資料之間有事先定好的大小關係存在bull 定義ndash最大堆疊為一完備二元樹任一節點的資料內容不小於其子節點的資料內容ndash最小堆疊為一完備二元樹任一節點的資料內容不大於其子節點的資料內容

581  插入節點至最大堆積中最大堆積是一完備二元樹所以如圖 (a) 之最大堆積於加入新節點後樹的形狀必須如圖 (b) 所示灰色節點即為新節點若加入的節點資料分別為 1 9 12 則其最大堆積將分別變成如圖 (c) (d) (e) 所示

程式 5-13 新增節點至最大堆積中最大堆積本身是一棵完備二元樹可以用陣列表示之 1 define maxsize 1002 int heap[maxsize]3 int n i4 void InsertHeap(int x)5 if (n == maxsize) HeapFull()6 else7 n++8 i = n9 while ((i gt 1) ampamp (x gt heap[i2]))10 heap[i] = heap[i2]11 i = 212 13 heap[i] = x14 16

程序 InsertHeap 在插入第 n 個資料時需要的時間複雜度為 O(logn)

1 100

2 93

3 84

4 77

5 65

6 81

7 79

8 66

9 70

10 50

100

84

77

93

8165

66 70

79

50

1

2 3

4 5 6 7

8 9 10

1 define maxsize 1002 int heap[maxsize]3 int n i4 void InsertHeap(int x)5 if (n == maxsize) HeapFull()6 else7 n++8 i = n9 while ((i gt 1) ampamp (x gt heap[i2]))10 heap[i] = heap[i2]11 i = 212 13 heap[i] = x14 16

582 刪除最大堆積中的最大資料圖 (a) 中的最大堆積在刪除最大資料後其樹形應變成如圖 (b) 所示將最未一個樹葉節點的資料 6 暫挪至樹根節點內如圖 (c) 再將圖 (c) 中非最大堆積的結構調整成為最大堆積 10 和 8 中選大者與 6對調即成圖 (d)

程式 5-14 刪除最大堆積中的最大資料(上接程式 5-13 )1 int DeleteHeap()2 int x i j3 if (n == 0) HeapEmpty()4 else5 x = heap[1]6 heap[1] = heap[n]7 n--8 i = 19 while (i lt= n2)10 if (heap[2i] gt heap[2i+1])11 j = 2i12 else13 j = 2i+114 if (heap[i]gtheap[j]) break15 swap(heap[i] heap[j])16 i = j17 18 return x19 20

其時間複雜度與其堆積高度相同是為O(logn) 其中 n 為堆積中的元素個數

1 100

2 93

3 84

4 77

5 65

6 81

7 79

8 66

9 70

10 50

100

84

77

93

8165

66 70

79

50

1

2 3

4 5 6 7

8 9 10

1 int DeletHeap()2 int x i j3 if(n == 0) HeapEmpty()4 else5 x = heap[1]6 heap[1] = heap[n]7 n--8 i = 19 while (i lt= n2)10 if ([heap[2i]] gt heap[2i+1])11 j = 2i12 else13 j = 2i+114 if (heap[i]gtheap[j]) break15 swap(heap[i] heap[j])16 i = j17 18 return x19 20

583  優先佇列bull 由於最大堆積(或最小堆積)的結構可提供最大元素(或最小元素)而其維護堆積的成本為 O(logn) n 為總元素個數

bull 堆積常被當成「優先佇列」 (priority queue) 使用mdash此時佇列被組織成完備二元樹若新加入佇列的資料有大小關係優先順序的需求時此優先佇列即以 O(logn) 的代價將新資料放入優先佇列(可能是最大或最小堆積依實際大小關係優先順序的需求而定)中mdash提供依元素的優先順序做為處理順序的服務

bull 一般的佇列為先進先出先到的先行處理優先佇列則是依據其優先順序優先順序高者先行處理與來到的先後無關

範例 5-21bull 在「分時」 (time sharing) 的作業系統中我們不希望使用中央處理器 (Central Processing Unit CPU) 時間短的「處理程序」 (process) 等候過久於是有「最短時間工作優先」 (shortest-job-first) 的排程策略此時等候的處理程序即可組織成之一最小堆積而 CPU 每次即取出存於樹根的處理程序予以處理任何新來的處理程序則以 O(logn) 的時間加入此優先佇列(最小堆積)中

bull 注意之前提過的線性佇列新來的直接加入成為新的尾端元素花O(1) 的時間然線性佇列本身沒有大小的關係每次要取出最大(小)的資料得花O(n) 的代價(在所有元素中挑出最大(小)者) n 為佇列內的元素個數而優先佇列是在新元素加入時要花O(logn) 的時間至於取出最大(小)則只須 O(1) 的時間

59  二元搜尋樹bull 二元搜尋樹是一棵二元樹可能是空二元樹若不為空二元樹則滿足下列性質ndash所有節點內的資料內容是相異的 ndash左子節點的資內容 ( 如果有 ) 比父節點的資料內容小 ndash右子節點的資料內容 ( 如果有 ) 比父節點的資料內容大 ndash以左和右子節點為樹根的左子樹和右子樹也是二元搜尋樹

在二元搜尋樹中搜尋資料二元搜尋樹對資料大小的掌握十分明確若恰與節點 p 資料相等則搜尋成功若欲搜尋的資料 x 比樹節點 p 中的資料小 則只須搜尋節點 p 的左子樹若比節點 p 中的資料大 則只須搜尋 p 的右子樹這樣的判斷可以遞迴地應用在各子樹節點 中

程式 5-15 在二元搜尋樹中搜尋資料(遞迴呼叫)1 struct BSTreeNode2 struct BSTreeNode leftchild3 int data4 struct BSTreeNode rightchild5 6 struct BSTreeNode root7 struct BSTreeNode SearchBST(

struct BSTreeNode tree int x)8 if (tree == NULL) return NULL9 if (x == tree-gtdata) return tree10 if (x lt tree-gtdata)11 return SearchBST(tree-gtleftchild x)

12 return SearchBST(tree-gtrightchild x)13

程式 5-16 搜尋二元搜尋樹的資料(非遞迴呼叫)在二元搜尋樹十分龐大時遞迴呼叫的效率不會太好可利用迴圈來完成搜尋的動作以提高執行效率請看程式 5-16

14 struct BSTreeNode Search BST_iterative15 (struct BSTreeNode node int x)16 while (node = NULL)17 if (x == node-gtdata) return node18 if (x lt node-gtdata)19 node = node-gtleftchild20 else21 node = node-gtrightchild22 23 return NULL24

592  新增資料於二元搜尋樹中欲新增資料進入一二元搜尋樹中得先決定該資料在二元搜尋樹中應出現的位置這可透過上節提到的搜尋動作來解決

在二元搜尋樹定義的性質 (1) 中我們限定了元素必須相異所以搜尋新資料的結果必是 NULL

必須將搜尋的結果改為新增資料所應在位置的節點指標

程式 5-17  在二元搜尋樹中新增資料25 int InsertBST(int x)26 struct BSTreeNode p q27 p = root q = NULL28 while (p = NULL)29 q = p30 if (x == p-gtdata) return 0

若不考慮重複則可省略之31 if (x lt p-gtdata)32 p = p-gtleftchild33 else34 p = p-gtrightchild35

3636 p = (struct BSTreeNode ) p = (struct BSTreeNode ) malloc (sizeof(BSTreeNode))malloc (sizeof(BSTreeNode))

3737 p-gtdata = x p-gtdata = x3838 p-gtleftchild = p-gtrightchild = p-gtleftchild = p-gtrightchild = NULLNULL3939 if (root == NULL) root = p if (root == NULL) root = p4040 else if (x lt q-gtdata) else if (x lt q-gtdata)4141 q-gtleftchild = p q-gtleftchild = p4242 else else4343 q-gtrightchild = p q-gtrightchild = p4444 return 1 return 145 45

程式 程式 5-175-17  在二元搜尋樹中新增資料  在二元搜尋樹中新增資料 (( 續續 ))

範例 5-23 圖 (b) 為圖 (a) 新增整數 6 之後的結果圖 (c) (d) (e) (f) 則分別是逐步再加入 28 18

10 21 的結果

bull若目前的二元搜尋樹高度為 h 則新增資料得花O(h) 的時間找到其所應在的位置遂新增資料至二元搜尋樹中的時間複雜之度為 O(h)

593 自二元搜尋樹中刪除資料欲自二元搜尋樹中刪除資料可能會面臨兩種情況(1) 刪除節點 p 位於樹葉節點上如下圖所示 這是比較容易處理的情況

(2) 刪除的節點 p 不是樹葉刪除節點 p 後原 p 所指的節點可由 q ( q 中包含了 p 左子樹中的最大節點值)或 r ( r 中包含了 p 右子樹中的最小節點值)來取代以維持二元搜尋樹中資料的大小關係其結果分別如圖 (b) 和 (c) 所示而圖 (d) 則標明了指標會改變的節點位置即 p 會消失 father 的左或右子樹指標會異動 k(可能是 q 或 r )會移到原來 p 的位置其父節點 f 的左或右子樹指標會異動其中 father 為 p 父節點 f 為 k 的父節點

程式 5-18 自二元搜尋樹中刪除資料46 int DeleteBST (int x)47 Struct BSTreeNode p father k f48 p = root father = NULL49 while (p = NULL)50 if (x == p-gtdata) 找到 x 所在的節點 p 了51 if ((p-gtleftchild == NULL) ampamp 52 (p-gtrightchild == NULL))53 k = NULL p為樹葉節點54 else if (p-gtleftchild = NULL )x位於非子節

點55 f = p 找出 p左子樹的最右樹葉56 k = p-gtleftchild57 while (k-gtrightchild =NULL)58 f = k59 k = k-gtrightchild60 k將挪至原 p處61 if (p == f)62 f-gtleftchild = k-gtleftchild 63 else64 f-gtrightchild = k-gtleftchild 65

66 else p無左子樹67 f = p 找出 p右子樹的最左樹葉68 k = p-gtrightchild69 while (k-gtleftchild = NULL)70 f = k71 k = k -gtleftchild72 73 if (p == f)74 f-gtrightchild = k-gtrightchild 75 else76 f-gtleftchild = k-gtrightchild77 78 if (k = NULL) k挪至原 p處繼承 p的左右指標79 k-gtleftchild = p-gtleftchild80 k-gtrightchild = p-gtrightchild81 82 if (father == NULL) root = k83 else if (x lt father-gtdata) 決定 k是 father的左

或右兒子84 father-gtleftchild = k85 else86 father-gtrightchild = k87 free(p)88 return 1 成功地刪除 x於此傳回 1返回呼叫處89 51~89行為找到 x時應有的處理步驟

90 else 尚未找到 x繼續往下一階層找

91 father = p92 if (x lt p-gtdata)93 p = p-gtleftchild94 else95 p = p-gtrightchild96 97 98 return 0 未找到 x傳回 099

510  樹林 (forest)

bull 定義樹林(森林)是由 n 棵沒有交集的樹所構成的集合 n 0

下圖為一包含三棵樹的樹林

5101 轉換樹林為二元樹 之前我們以二元樹來表示樹因為二元樹有較多

好用的性質在此我們也將樹林轉換成二元樹看待

在 522 節中我們嘗試了用左子右兄弟的方式來表示樹這個方法事實上已將樹轉換成二元樹

即用左子右兄弟的結構重新組織樹林中的每一棵樹這些樹的樹根將視為兄弟而最左邊的樹則視為樹林二元樹的樹根如此一來樹林即可轉換成二元樹

範例 5-25右下圖為右上圖三棵樹所對應的三棵左子右兄弟二元樹

下圖則為上圖樹林所對應的一棵樹林二元樹

演算法 5-1  轉換樹林成二元樹輸入樹林 F = T1 T2 Tn

輸出 F 的二元樹表示 BT=(T1 T2 Tn)1 if (n == 0) 輸出空二元樹 2 else

3 root(T1 T2 Tn) = T1

4 leftsubtree(T1 T2 Tn) =

BT(T11 T12 T1m)5 BT(T11 T12 T1m)為 T1的子樹6 rightsubtree(T1 T2 Tn) =

BT(T2 T3 Tn)

樹林轉換成二元樹的過程樹林轉換成二元樹的過程

  • Slide 1
  • Slide 2
  • Slide 3
  • Slide 4
  • Slide 5
  • Slide 6
  • Slide 7
  • Slide 8
  • Slide 9
  • Slide 10
  • Slide 11
  • Slide 12
  • Slide 13
  • Slide 14
  • Slide 15
  • Slide 16
  • Slide 17
  • Slide 18
  • Slide 19
  • Slide 20
  • Slide 21
  • Slide 22
  • Slide 23
  • Slide 24
  • Slide 25
  • Slide 26
  • Slide 27
  • Slide 28
  • Slide 29
  • Slide 30
  • Slide 31
  • Slide 32
  • Slide 33
  • Slide 34
  • Slide 35
  • Slide 36
  • Slide 37
  • Slide 38
  • Slide 39
  • Slide 40
  • Slide 41
  • Slide 42
  • Slide 43
  • Slide 44
  • Slide 45
  • Slide 46
  • Slide 47
  • Slide 48
  • Slide 49
  • Slide 50
  • Slide 51
  • Slide 52
  • Slide 53
  • Slide 54
  • Slide 55
  • Slide 56
  • Slide 57
  • Slide 58
  • Slide 59
  • Slide 60
  • Slide 61
  • Slide 62
  • Slide 63
  • Slide 64
  • Slide 65
  • Slide 66
  • Slide 67
  • Slide 68
  • Slide 69
  • Slide 70
  • Slide 71
  • Slide 72
  • Slide 73
  • Slide 74
  • Slide 75
  • Slide 76
  • Slide 77
  • Slide 78
  • Slide 79
  • Slide 80
  • Slide 81
  • Slide 82
  • Slide 83
  • Slide 84
  • Slide 85
  • Slide 86
  • Slide 87
  • Slide 88
  • Slide 89
  • Slide 90
  • Slide 91
  • Slide 92
  • Slide 93
  • Slide 94
  • Slide 95
  • Slide 96
  • Slide 97
  • Slide 98
  • Slide 99
  • Slide 100
  • Slide 101
  • Slide 102
  • Slide 103
Page 62: 第五章 樹

bull 範例 5-17 中所加的引線目的在方便二元樹的中序走訪

bull 對樹葉節點 p 而言令其資料為 P 則引線指標的規則如下

(1) p 的左子樹指標指向中序表示中 P 的前一個 元素(前接元素 (predecessor) )

(2) p 的右子樹指標指向中序表示中 P 的後一個 元素(後繼元素 (successor) )

為了區別節點指標放的是一般指標(內部節點)抑或是引線指標(樹葉節點)我們另以 2 個欄位分別區別左和右子樹指標空間存放的對象其節點的記憶體配置有如下圖所示

其中 leftthread (rightthread) 為 0 時表示 leftchild (rightchild) 存放的是一般節點指標而 leftthread (rightthread) 為 1 時表示 leftchild (rightchild) 存放的是引線指標1 struct TBTreeNode2 int leftthread3 struct TBTreeNode leftchild4 char data5 struct TBTreeNode rightchild6 int rightthread7

由右側的引線二元樹可知有最左和最右兩條引線尚無妥善的安排我們另設計一空的引線節點做為樹根如下圖所示

571  引線二元樹的中序走訪決定節點 p 在中序表示中的後繼節點 q (簡稱中序後繼點)

程式 5-10決定節點 node的中序後繼點1 struct TBTreeNode2 int leftthread3 struct TBTreeNode leftchild4 char data5 struct TBTreeNode rightchild6 int rightthread7 8 struct TBTreeNode root9 struct TBTreeNode Next(struct TBTreeNode node)

10 struct TBTreeNode temp11 temp = node-gtrightchild12 if (node-gtrightthead) return temp13 while (temp-gtleftthead) temp=temp-gtleftchild

14 return temp15

程式 5-11引線二元樹的中序表示(接程式 5-10)1 void InorderTBTree()2 struct TBTreeNode node3 for(node=Next(root)node=rootnode=Next(node))

4 cout ltlt node-gtdata5

bull只要 node 是引線二元樹上的任一節點透過呼叫 Next(node) 即可找到其在中序表示中的後繼節點bull(試想若程式 5-10 中的程序 Next 所傳入的參數指標正是 root Next(root) 是否可正常運作請思考)bull於是只須從中序表示中的第一節點起讓每一節點 node 都執行 Next(node) 程序即可得引線二元樹的中序表示

572  在引線二元樹中加入節點在引線二元樹中樹葉節點節點的右子樹指標所指向或內部節點右子樹的最左樹葉為其中序後繼節點而且任一節點若無左子樹則其左子樹指標乃指向其中序前接節點hellip這些性質必須在節點新增進入引線二元樹時也要保持接下來討論節點 new 插入成為引線二元樹的節點 p 的右子節點的情形至於插入成為左子節點的情形則讓各位模擬推敲

572572  在引線二元樹中加入節點 在引線二元樹中加入節點 (( 續續 ))

newnew 會成為會成為 pp 的右子節點的情況共有以下兩種的右子節點的情況共有以下兩種(1) (1) 若若 pp 沒有右子節點 沒有右子節點 ((p-gtrightthread p-gtrightthread 為為1) 1) 如圖如圖 ((a) a) 則 則 new new 插入後應形成如圖插入後應形成如圖 ((b) b) 所示所示

(2)(2) 若若 pp 有右子節點 有右子節點 ((p-gtrightthread p-gtrightthread 為為0) 0) 如圖如圖 ((c) c) 則 則 new new 插入後應形成如插入後應形成如圖圖 ((d) d) 所示所示

程式 5-12  插入節點 new 成為引線二元樹中節點 p的右子節點(上接程式 5-10 5-11 )

1 void InsertRight(struct TBTreeNode p 2 struct TBTreeNode new)3 struct TBTreeNode q4 new-gtrightchild = p-gtrightchild5 new-gtrightthead = p-gtrightthread6 new-gtleftchild = p7 new-gtleftthread = 18 p-gtrightchild = new9 p-gtrightthead = 010 if (new-gtrightthread)11 q = Next(new)12 q-gtleftchild = new13 14

58  堆積bull 「堆積」 (heap) 是一個特殊的完備二元樹節點的資料與其子節點的資料之間有事先定好的大小關係存在bull 定義ndash最大堆疊為一完備二元樹任一節點的資料內容不小於其子節點的資料內容ndash最小堆疊為一完備二元樹任一節點的資料內容不大於其子節點的資料內容

581  插入節點至最大堆積中最大堆積是一完備二元樹所以如圖 (a) 之最大堆積於加入新節點後樹的形狀必須如圖 (b) 所示灰色節點即為新節點若加入的節點資料分別為 1 9 12 則其最大堆積將分別變成如圖 (c) (d) (e) 所示

程式 5-13 新增節點至最大堆積中最大堆積本身是一棵完備二元樹可以用陣列表示之 1 define maxsize 1002 int heap[maxsize]3 int n i4 void InsertHeap(int x)5 if (n == maxsize) HeapFull()6 else7 n++8 i = n9 while ((i gt 1) ampamp (x gt heap[i2]))10 heap[i] = heap[i2]11 i = 212 13 heap[i] = x14 16

程序 InsertHeap 在插入第 n 個資料時需要的時間複雜度為 O(logn)

1 100

2 93

3 84

4 77

5 65

6 81

7 79

8 66

9 70

10 50

100

84

77

93

8165

66 70

79

50

1

2 3

4 5 6 7

8 9 10

1 define maxsize 1002 int heap[maxsize]3 int n i4 void InsertHeap(int x)5 if (n == maxsize) HeapFull()6 else7 n++8 i = n9 while ((i gt 1) ampamp (x gt heap[i2]))10 heap[i] = heap[i2]11 i = 212 13 heap[i] = x14 16

582 刪除最大堆積中的最大資料圖 (a) 中的最大堆積在刪除最大資料後其樹形應變成如圖 (b) 所示將最未一個樹葉節點的資料 6 暫挪至樹根節點內如圖 (c) 再將圖 (c) 中非最大堆積的結構調整成為最大堆積 10 和 8 中選大者與 6對調即成圖 (d)

程式 5-14 刪除最大堆積中的最大資料(上接程式 5-13 )1 int DeleteHeap()2 int x i j3 if (n == 0) HeapEmpty()4 else5 x = heap[1]6 heap[1] = heap[n]7 n--8 i = 19 while (i lt= n2)10 if (heap[2i] gt heap[2i+1])11 j = 2i12 else13 j = 2i+114 if (heap[i]gtheap[j]) break15 swap(heap[i] heap[j])16 i = j17 18 return x19 20

其時間複雜度與其堆積高度相同是為O(logn) 其中 n 為堆積中的元素個數

1 100

2 93

3 84

4 77

5 65

6 81

7 79

8 66

9 70

10 50

100

84

77

93

8165

66 70

79

50

1

2 3

4 5 6 7

8 9 10

1 int DeletHeap()2 int x i j3 if(n == 0) HeapEmpty()4 else5 x = heap[1]6 heap[1] = heap[n]7 n--8 i = 19 while (i lt= n2)10 if ([heap[2i]] gt heap[2i+1])11 j = 2i12 else13 j = 2i+114 if (heap[i]gtheap[j]) break15 swap(heap[i] heap[j])16 i = j17 18 return x19 20

583  優先佇列bull 由於最大堆積(或最小堆積)的結構可提供最大元素(或最小元素)而其維護堆積的成本為 O(logn) n 為總元素個數

bull 堆積常被當成「優先佇列」 (priority queue) 使用mdash此時佇列被組織成完備二元樹若新加入佇列的資料有大小關係優先順序的需求時此優先佇列即以 O(logn) 的代價將新資料放入優先佇列(可能是最大或最小堆積依實際大小關係優先順序的需求而定)中mdash提供依元素的優先順序做為處理順序的服務

bull 一般的佇列為先進先出先到的先行處理優先佇列則是依據其優先順序優先順序高者先行處理與來到的先後無關

範例 5-21bull 在「分時」 (time sharing) 的作業系統中我們不希望使用中央處理器 (Central Processing Unit CPU) 時間短的「處理程序」 (process) 等候過久於是有「最短時間工作優先」 (shortest-job-first) 的排程策略此時等候的處理程序即可組織成之一最小堆積而 CPU 每次即取出存於樹根的處理程序予以處理任何新來的處理程序則以 O(logn) 的時間加入此優先佇列(最小堆積)中

bull 注意之前提過的線性佇列新來的直接加入成為新的尾端元素花O(1) 的時間然線性佇列本身沒有大小的關係每次要取出最大(小)的資料得花O(n) 的代價(在所有元素中挑出最大(小)者) n 為佇列內的元素個數而優先佇列是在新元素加入時要花O(logn) 的時間至於取出最大(小)則只須 O(1) 的時間

59  二元搜尋樹bull 二元搜尋樹是一棵二元樹可能是空二元樹若不為空二元樹則滿足下列性質ndash所有節點內的資料內容是相異的 ndash左子節點的資內容 ( 如果有 ) 比父節點的資料內容小 ndash右子節點的資料內容 ( 如果有 ) 比父節點的資料內容大 ndash以左和右子節點為樹根的左子樹和右子樹也是二元搜尋樹

在二元搜尋樹中搜尋資料二元搜尋樹對資料大小的掌握十分明確若恰與節點 p 資料相等則搜尋成功若欲搜尋的資料 x 比樹節點 p 中的資料小 則只須搜尋節點 p 的左子樹若比節點 p 中的資料大 則只須搜尋 p 的右子樹這樣的判斷可以遞迴地應用在各子樹節點 中

程式 5-15 在二元搜尋樹中搜尋資料(遞迴呼叫)1 struct BSTreeNode2 struct BSTreeNode leftchild3 int data4 struct BSTreeNode rightchild5 6 struct BSTreeNode root7 struct BSTreeNode SearchBST(

struct BSTreeNode tree int x)8 if (tree == NULL) return NULL9 if (x == tree-gtdata) return tree10 if (x lt tree-gtdata)11 return SearchBST(tree-gtleftchild x)

12 return SearchBST(tree-gtrightchild x)13

程式 5-16 搜尋二元搜尋樹的資料(非遞迴呼叫)在二元搜尋樹十分龐大時遞迴呼叫的效率不會太好可利用迴圈來完成搜尋的動作以提高執行效率請看程式 5-16

14 struct BSTreeNode Search BST_iterative15 (struct BSTreeNode node int x)16 while (node = NULL)17 if (x == node-gtdata) return node18 if (x lt node-gtdata)19 node = node-gtleftchild20 else21 node = node-gtrightchild22 23 return NULL24

592  新增資料於二元搜尋樹中欲新增資料進入一二元搜尋樹中得先決定該資料在二元搜尋樹中應出現的位置這可透過上節提到的搜尋動作來解決

在二元搜尋樹定義的性質 (1) 中我們限定了元素必須相異所以搜尋新資料的結果必是 NULL

必須將搜尋的結果改為新增資料所應在位置的節點指標

程式 5-17  在二元搜尋樹中新增資料25 int InsertBST(int x)26 struct BSTreeNode p q27 p = root q = NULL28 while (p = NULL)29 q = p30 if (x == p-gtdata) return 0

若不考慮重複則可省略之31 if (x lt p-gtdata)32 p = p-gtleftchild33 else34 p = p-gtrightchild35

3636 p = (struct BSTreeNode ) p = (struct BSTreeNode ) malloc (sizeof(BSTreeNode))malloc (sizeof(BSTreeNode))

3737 p-gtdata = x p-gtdata = x3838 p-gtleftchild = p-gtrightchild = p-gtleftchild = p-gtrightchild = NULLNULL3939 if (root == NULL) root = p if (root == NULL) root = p4040 else if (x lt q-gtdata) else if (x lt q-gtdata)4141 q-gtleftchild = p q-gtleftchild = p4242 else else4343 q-gtrightchild = p q-gtrightchild = p4444 return 1 return 145 45

程式 程式 5-175-17  在二元搜尋樹中新增資料  在二元搜尋樹中新增資料 (( 續續 ))

範例 5-23 圖 (b) 為圖 (a) 新增整數 6 之後的結果圖 (c) (d) (e) (f) 則分別是逐步再加入 28 18

10 21 的結果

bull若目前的二元搜尋樹高度為 h 則新增資料得花O(h) 的時間找到其所應在的位置遂新增資料至二元搜尋樹中的時間複雜之度為 O(h)

593 自二元搜尋樹中刪除資料欲自二元搜尋樹中刪除資料可能會面臨兩種情況(1) 刪除節點 p 位於樹葉節點上如下圖所示 這是比較容易處理的情況

(2) 刪除的節點 p 不是樹葉刪除節點 p 後原 p 所指的節點可由 q ( q 中包含了 p 左子樹中的最大節點值)或 r ( r 中包含了 p 右子樹中的最小節點值)來取代以維持二元搜尋樹中資料的大小關係其結果分別如圖 (b) 和 (c) 所示而圖 (d) 則標明了指標會改變的節點位置即 p 會消失 father 的左或右子樹指標會異動 k(可能是 q 或 r )會移到原來 p 的位置其父節點 f 的左或右子樹指標會異動其中 father 為 p 父節點 f 為 k 的父節點

程式 5-18 自二元搜尋樹中刪除資料46 int DeleteBST (int x)47 Struct BSTreeNode p father k f48 p = root father = NULL49 while (p = NULL)50 if (x == p-gtdata) 找到 x 所在的節點 p 了51 if ((p-gtleftchild == NULL) ampamp 52 (p-gtrightchild == NULL))53 k = NULL p為樹葉節點54 else if (p-gtleftchild = NULL )x位於非子節

點55 f = p 找出 p左子樹的最右樹葉56 k = p-gtleftchild57 while (k-gtrightchild =NULL)58 f = k59 k = k-gtrightchild60 k將挪至原 p處61 if (p == f)62 f-gtleftchild = k-gtleftchild 63 else64 f-gtrightchild = k-gtleftchild 65

66 else p無左子樹67 f = p 找出 p右子樹的最左樹葉68 k = p-gtrightchild69 while (k-gtleftchild = NULL)70 f = k71 k = k -gtleftchild72 73 if (p == f)74 f-gtrightchild = k-gtrightchild 75 else76 f-gtleftchild = k-gtrightchild77 78 if (k = NULL) k挪至原 p處繼承 p的左右指標79 k-gtleftchild = p-gtleftchild80 k-gtrightchild = p-gtrightchild81 82 if (father == NULL) root = k83 else if (x lt father-gtdata) 決定 k是 father的左

或右兒子84 father-gtleftchild = k85 else86 father-gtrightchild = k87 free(p)88 return 1 成功地刪除 x於此傳回 1返回呼叫處89 51~89行為找到 x時應有的處理步驟

90 else 尚未找到 x繼續往下一階層找

91 father = p92 if (x lt p-gtdata)93 p = p-gtleftchild94 else95 p = p-gtrightchild96 97 98 return 0 未找到 x傳回 099

510  樹林 (forest)

bull 定義樹林(森林)是由 n 棵沒有交集的樹所構成的集合 n 0

下圖為一包含三棵樹的樹林

5101 轉換樹林為二元樹 之前我們以二元樹來表示樹因為二元樹有較多

好用的性質在此我們也將樹林轉換成二元樹看待

在 522 節中我們嘗試了用左子右兄弟的方式來表示樹這個方法事實上已將樹轉換成二元樹

即用左子右兄弟的結構重新組織樹林中的每一棵樹這些樹的樹根將視為兄弟而最左邊的樹則視為樹林二元樹的樹根如此一來樹林即可轉換成二元樹

範例 5-25右下圖為右上圖三棵樹所對應的三棵左子右兄弟二元樹

下圖則為上圖樹林所對應的一棵樹林二元樹

演算法 5-1  轉換樹林成二元樹輸入樹林 F = T1 T2 Tn

輸出 F 的二元樹表示 BT=(T1 T2 Tn)1 if (n == 0) 輸出空二元樹 2 else

3 root(T1 T2 Tn) = T1

4 leftsubtree(T1 T2 Tn) =

BT(T11 T12 T1m)5 BT(T11 T12 T1m)為 T1的子樹6 rightsubtree(T1 T2 Tn) =

BT(T2 T3 Tn)

樹林轉換成二元樹的過程樹林轉換成二元樹的過程

  • Slide 1
  • Slide 2
  • Slide 3
  • Slide 4
  • Slide 5
  • Slide 6
  • Slide 7
  • Slide 8
  • Slide 9
  • Slide 10
  • Slide 11
  • Slide 12
  • Slide 13
  • Slide 14
  • Slide 15
  • Slide 16
  • Slide 17
  • Slide 18
  • Slide 19
  • Slide 20
  • Slide 21
  • Slide 22
  • Slide 23
  • Slide 24
  • Slide 25
  • Slide 26
  • Slide 27
  • Slide 28
  • Slide 29
  • Slide 30
  • Slide 31
  • Slide 32
  • Slide 33
  • Slide 34
  • Slide 35
  • Slide 36
  • Slide 37
  • Slide 38
  • Slide 39
  • Slide 40
  • Slide 41
  • Slide 42
  • Slide 43
  • Slide 44
  • Slide 45
  • Slide 46
  • Slide 47
  • Slide 48
  • Slide 49
  • Slide 50
  • Slide 51
  • Slide 52
  • Slide 53
  • Slide 54
  • Slide 55
  • Slide 56
  • Slide 57
  • Slide 58
  • Slide 59
  • Slide 60
  • Slide 61
  • Slide 62
  • Slide 63
  • Slide 64
  • Slide 65
  • Slide 66
  • Slide 67
  • Slide 68
  • Slide 69
  • Slide 70
  • Slide 71
  • Slide 72
  • Slide 73
  • Slide 74
  • Slide 75
  • Slide 76
  • Slide 77
  • Slide 78
  • Slide 79
  • Slide 80
  • Slide 81
  • Slide 82
  • Slide 83
  • Slide 84
  • Slide 85
  • Slide 86
  • Slide 87
  • Slide 88
  • Slide 89
  • Slide 90
  • Slide 91
  • Slide 92
  • Slide 93
  • Slide 94
  • Slide 95
  • Slide 96
  • Slide 97
  • Slide 98
  • Slide 99
  • Slide 100
  • Slide 101
  • Slide 102
  • Slide 103
Page 63: 第五章 樹

為了區別節點指標放的是一般指標(內部節點)抑或是引線指標(樹葉節點)我們另以 2 個欄位分別區別左和右子樹指標空間存放的對象其節點的記憶體配置有如下圖所示

其中 leftthread (rightthread) 為 0 時表示 leftchild (rightchild) 存放的是一般節點指標而 leftthread (rightthread) 為 1 時表示 leftchild (rightchild) 存放的是引線指標1 struct TBTreeNode2 int leftthread3 struct TBTreeNode leftchild4 char data5 struct TBTreeNode rightchild6 int rightthread7

由右側的引線二元樹可知有最左和最右兩條引線尚無妥善的安排我們另設計一空的引線節點做為樹根如下圖所示

571  引線二元樹的中序走訪決定節點 p 在中序表示中的後繼節點 q (簡稱中序後繼點)

程式 5-10決定節點 node的中序後繼點1 struct TBTreeNode2 int leftthread3 struct TBTreeNode leftchild4 char data5 struct TBTreeNode rightchild6 int rightthread7 8 struct TBTreeNode root9 struct TBTreeNode Next(struct TBTreeNode node)

10 struct TBTreeNode temp11 temp = node-gtrightchild12 if (node-gtrightthead) return temp13 while (temp-gtleftthead) temp=temp-gtleftchild

14 return temp15

程式 5-11引線二元樹的中序表示(接程式 5-10)1 void InorderTBTree()2 struct TBTreeNode node3 for(node=Next(root)node=rootnode=Next(node))

4 cout ltlt node-gtdata5

bull只要 node 是引線二元樹上的任一節點透過呼叫 Next(node) 即可找到其在中序表示中的後繼節點bull(試想若程式 5-10 中的程序 Next 所傳入的參數指標正是 root Next(root) 是否可正常運作請思考)bull於是只須從中序表示中的第一節點起讓每一節點 node 都執行 Next(node) 程序即可得引線二元樹的中序表示

572  在引線二元樹中加入節點在引線二元樹中樹葉節點節點的右子樹指標所指向或內部節點右子樹的最左樹葉為其中序後繼節點而且任一節點若無左子樹則其左子樹指標乃指向其中序前接節點hellip這些性質必須在節點新增進入引線二元樹時也要保持接下來討論節點 new 插入成為引線二元樹的節點 p 的右子節點的情形至於插入成為左子節點的情形則讓各位模擬推敲

572572  在引線二元樹中加入節點 在引線二元樹中加入節點 (( 續續 ))

newnew 會成為會成為 pp 的右子節點的情況共有以下兩種的右子節點的情況共有以下兩種(1) (1) 若若 pp 沒有右子節點 沒有右子節點 ((p-gtrightthread p-gtrightthread 為為1) 1) 如圖如圖 ((a) a) 則 則 new new 插入後應形成如圖插入後應形成如圖 ((b) b) 所示所示

(2)(2) 若若 pp 有右子節點 有右子節點 ((p-gtrightthread p-gtrightthread 為為0) 0) 如圖如圖 ((c) c) 則 則 new new 插入後應形成如插入後應形成如圖圖 ((d) d) 所示所示

程式 5-12  插入節點 new 成為引線二元樹中節點 p的右子節點(上接程式 5-10 5-11 )

1 void InsertRight(struct TBTreeNode p 2 struct TBTreeNode new)3 struct TBTreeNode q4 new-gtrightchild = p-gtrightchild5 new-gtrightthead = p-gtrightthread6 new-gtleftchild = p7 new-gtleftthread = 18 p-gtrightchild = new9 p-gtrightthead = 010 if (new-gtrightthread)11 q = Next(new)12 q-gtleftchild = new13 14

58  堆積bull 「堆積」 (heap) 是一個特殊的完備二元樹節點的資料與其子節點的資料之間有事先定好的大小關係存在bull 定義ndash最大堆疊為一完備二元樹任一節點的資料內容不小於其子節點的資料內容ndash最小堆疊為一完備二元樹任一節點的資料內容不大於其子節點的資料內容

581  插入節點至最大堆積中最大堆積是一完備二元樹所以如圖 (a) 之最大堆積於加入新節點後樹的形狀必須如圖 (b) 所示灰色節點即為新節點若加入的節點資料分別為 1 9 12 則其最大堆積將分別變成如圖 (c) (d) (e) 所示

程式 5-13 新增節點至最大堆積中最大堆積本身是一棵完備二元樹可以用陣列表示之 1 define maxsize 1002 int heap[maxsize]3 int n i4 void InsertHeap(int x)5 if (n == maxsize) HeapFull()6 else7 n++8 i = n9 while ((i gt 1) ampamp (x gt heap[i2]))10 heap[i] = heap[i2]11 i = 212 13 heap[i] = x14 16

程序 InsertHeap 在插入第 n 個資料時需要的時間複雜度為 O(logn)

1 100

2 93

3 84

4 77

5 65

6 81

7 79

8 66

9 70

10 50

100

84

77

93

8165

66 70

79

50

1

2 3

4 5 6 7

8 9 10

1 define maxsize 1002 int heap[maxsize]3 int n i4 void InsertHeap(int x)5 if (n == maxsize) HeapFull()6 else7 n++8 i = n9 while ((i gt 1) ampamp (x gt heap[i2]))10 heap[i] = heap[i2]11 i = 212 13 heap[i] = x14 16

582 刪除最大堆積中的最大資料圖 (a) 中的最大堆積在刪除最大資料後其樹形應變成如圖 (b) 所示將最未一個樹葉節點的資料 6 暫挪至樹根節點內如圖 (c) 再將圖 (c) 中非最大堆積的結構調整成為最大堆積 10 和 8 中選大者與 6對調即成圖 (d)

程式 5-14 刪除最大堆積中的最大資料(上接程式 5-13 )1 int DeleteHeap()2 int x i j3 if (n == 0) HeapEmpty()4 else5 x = heap[1]6 heap[1] = heap[n]7 n--8 i = 19 while (i lt= n2)10 if (heap[2i] gt heap[2i+1])11 j = 2i12 else13 j = 2i+114 if (heap[i]gtheap[j]) break15 swap(heap[i] heap[j])16 i = j17 18 return x19 20

其時間複雜度與其堆積高度相同是為O(logn) 其中 n 為堆積中的元素個數

1 100

2 93

3 84

4 77

5 65

6 81

7 79

8 66

9 70

10 50

100

84

77

93

8165

66 70

79

50

1

2 3

4 5 6 7

8 9 10

1 int DeletHeap()2 int x i j3 if(n == 0) HeapEmpty()4 else5 x = heap[1]6 heap[1] = heap[n]7 n--8 i = 19 while (i lt= n2)10 if ([heap[2i]] gt heap[2i+1])11 j = 2i12 else13 j = 2i+114 if (heap[i]gtheap[j]) break15 swap(heap[i] heap[j])16 i = j17 18 return x19 20

583  優先佇列bull 由於最大堆積(或最小堆積)的結構可提供最大元素(或最小元素)而其維護堆積的成本為 O(logn) n 為總元素個數

bull 堆積常被當成「優先佇列」 (priority queue) 使用mdash此時佇列被組織成完備二元樹若新加入佇列的資料有大小關係優先順序的需求時此優先佇列即以 O(logn) 的代價將新資料放入優先佇列(可能是最大或最小堆積依實際大小關係優先順序的需求而定)中mdash提供依元素的優先順序做為處理順序的服務

bull 一般的佇列為先進先出先到的先行處理優先佇列則是依據其優先順序優先順序高者先行處理與來到的先後無關

範例 5-21bull 在「分時」 (time sharing) 的作業系統中我們不希望使用中央處理器 (Central Processing Unit CPU) 時間短的「處理程序」 (process) 等候過久於是有「最短時間工作優先」 (shortest-job-first) 的排程策略此時等候的處理程序即可組織成之一最小堆積而 CPU 每次即取出存於樹根的處理程序予以處理任何新來的處理程序則以 O(logn) 的時間加入此優先佇列(最小堆積)中

bull 注意之前提過的線性佇列新來的直接加入成為新的尾端元素花O(1) 的時間然線性佇列本身沒有大小的關係每次要取出最大(小)的資料得花O(n) 的代價(在所有元素中挑出最大(小)者) n 為佇列內的元素個數而優先佇列是在新元素加入時要花O(logn) 的時間至於取出最大(小)則只須 O(1) 的時間

59  二元搜尋樹bull 二元搜尋樹是一棵二元樹可能是空二元樹若不為空二元樹則滿足下列性質ndash所有節點內的資料內容是相異的 ndash左子節點的資內容 ( 如果有 ) 比父節點的資料內容小 ndash右子節點的資料內容 ( 如果有 ) 比父節點的資料內容大 ndash以左和右子節點為樹根的左子樹和右子樹也是二元搜尋樹

在二元搜尋樹中搜尋資料二元搜尋樹對資料大小的掌握十分明確若恰與節點 p 資料相等則搜尋成功若欲搜尋的資料 x 比樹節點 p 中的資料小 則只須搜尋節點 p 的左子樹若比節點 p 中的資料大 則只須搜尋 p 的右子樹這樣的判斷可以遞迴地應用在各子樹節點 中

程式 5-15 在二元搜尋樹中搜尋資料(遞迴呼叫)1 struct BSTreeNode2 struct BSTreeNode leftchild3 int data4 struct BSTreeNode rightchild5 6 struct BSTreeNode root7 struct BSTreeNode SearchBST(

struct BSTreeNode tree int x)8 if (tree == NULL) return NULL9 if (x == tree-gtdata) return tree10 if (x lt tree-gtdata)11 return SearchBST(tree-gtleftchild x)

12 return SearchBST(tree-gtrightchild x)13

程式 5-16 搜尋二元搜尋樹的資料(非遞迴呼叫)在二元搜尋樹十分龐大時遞迴呼叫的效率不會太好可利用迴圈來完成搜尋的動作以提高執行效率請看程式 5-16

14 struct BSTreeNode Search BST_iterative15 (struct BSTreeNode node int x)16 while (node = NULL)17 if (x == node-gtdata) return node18 if (x lt node-gtdata)19 node = node-gtleftchild20 else21 node = node-gtrightchild22 23 return NULL24

592  新增資料於二元搜尋樹中欲新增資料進入一二元搜尋樹中得先決定該資料在二元搜尋樹中應出現的位置這可透過上節提到的搜尋動作來解決

在二元搜尋樹定義的性質 (1) 中我們限定了元素必須相異所以搜尋新資料的結果必是 NULL

必須將搜尋的結果改為新增資料所應在位置的節點指標

程式 5-17  在二元搜尋樹中新增資料25 int InsertBST(int x)26 struct BSTreeNode p q27 p = root q = NULL28 while (p = NULL)29 q = p30 if (x == p-gtdata) return 0

若不考慮重複則可省略之31 if (x lt p-gtdata)32 p = p-gtleftchild33 else34 p = p-gtrightchild35

3636 p = (struct BSTreeNode ) p = (struct BSTreeNode ) malloc (sizeof(BSTreeNode))malloc (sizeof(BSTreeNode))

3737 p-gtdata = x p-gtdata = x3838 p-gtleftchild = p-gtrightchild = p-gtleftchild = p-gtrightchild = NULLNULL3939 if (root == NULL) root = p if (root == NULL) root = p4040 else if (x lt q-gtdata) else if (x lt q-gtdata)4141 q-gtleftchild = p q-gtleftchild = p4242 else else4343 q-gtrightchild = p q-gtrightchild = p4444 return 1 return 145 45

程式 程式 5-175-17  在二元搜尋樹中新增資料  在二元搜尋樹中新增資料 (( 續續 ))

範例 5-23 圖 (b) 為圖 (a) 新增整數 6 之後的結果圖 (c) (d) (e) (f) 則分別是逐步再加入 28 18

10 21 的結果

bull若目前的二元搜尋樹高度為 h 則新增資料得花O(h) 的時間找到其所應在的位置遂新增資料至二元搜尋樹中的時間複雜之度為 O(h)

593 自二元搜尋樹中刪除資料欲自二元搜尋樹中刪除資料可能會面臨兩種情況(1) 刪除節點 p 位於樹葉節點上如下圖所示 這是比較容易處理的情況

(2) 刪除的節點 p 不是樹葉刪除節點 p 後原 p 所指的節點可由 q ( q 中包含了 p 左子樹中的最大節點值)或 r ( r 中包含了 p 右子樹中的最小節點值)來取代以維持二元搜尋樹中資料的大小關係其結果分別如圖 (b) 和 (c) 所示而圖 (d) 則標明了指標會改變的節點位置即 p 會消失 father 的左或右子樹指標會異動 k(可能是 q 或 r )會移到原來 p 的位置其父節點 f 的左或右子樹指標會異動其中 father 為 p 父節點 f 為 k 的父節點

程式 5-18 自二元搜尋樹中刪除資料46 int DeleteBST (int x)47 Struct BSTreeNode p father k f48 p = root father = NULL49 while (p = NULL)50 if (x == p-gtdata) 找到 x 所在的節點 p 了51 if ((p-gtleftchild == NULL) ampamp 52 (p-gtrightchild == NULL))53 k = NULL p為樹葉節點54 else if (p-gtleftchild = NULL )x位於非子節

點55 f = p 找出 p左子樹的最右樹葉56 k = p-gtleftchild57 while (k-gtrightchild =NULL)58 f = k59 k = k-gtrightchild60 k將挪至原 p處61 if (p == f)62 f-gtleftchild = k-gtleftchild 63 else64 f-gtrightchild = k-gtleftchild 65

66 else p無左子樹67 f = p 找出 p右子樹的最左樹葉68 k = p-gtrightchild69 while (k-gtleftchild = NULL)70 f = k71 k = k -gtleftchild72 73 if (p == f)74 f-gtrightchild = k-gtrightchild 75 else76 f-gtleftchild = k-gtrightchild77 78 if (k = NULL) k挪至原 p處繼承 p的左右指標79 k-gtleftchild = p-gtleftchild80 k-gtrightchild = p-gtrightchild81 82 if (father == NULL) root = k83 else if (x lt father-gtdata) 決定 k是 father的左

或右兒子84 father-gtleftchild = k85 else86 father-gtrightchild = k87 free(p)88 return 1 成功地刪除 x於此傳回 1返回呼叫處89 51~89行為找到 x時應有的處理步驟

90 else 尚未找到 x繼續往下一階層找

91 father = p92 if (x lt p-gtdata)93 p = p-gtleftchild94 else95 p = p-gtrightchild96 97 98 return 0 未找到 x傳回 099

510  樹林 (forest)

bull 定義樹林(森林)是由 n 棵沒有交集的樹所構成的集合 n 0

下圖為一包含三棵樹的樹林

5101 轉換樹林為二元樹 之前我們以二元樹來表示樹因為二元樹有較多

好用的性質在此我們也將樹林轉換成二元樹看待

在 522 節中我們嘗試了用左子右兄弟的方式來表示樹這個方法事實上已將樹轉換成二元樹

即用左子右兄弟的結構重新組織樹林中的每一棵樹這些樹的樹根將視為兄弟而最左邊的樹則視為樹林二元樹的樹根如此一來樹林即可轉換成二元樹

範例 5-25右下圖為右上圖三棵樹所對應的三棵左子右兄弟二元樹

下圖則為上圖樹林所對應的一棵樹林二元樹

演算法 5-1  轉換樹林成二元樹輸入樹林 F = T1 T2 Tn

輸出 F 的二元樹表示 BT=(T1 T2 Tn)1 if (n == 0) 輸出空二元樹 2 else

3 root(T1 T2 Tn) = T1

4 leftsubtree(T1 T2 Tn) =

BT(T11 T12 T1m)5 BT(T11 T12 T1m)為 T1的子樹6 rightsubtree(T1 T2 Tn) =

BT(T2 T3 Tn)

樹林轉換成二元樹的過程樹林轉換成二元樹的過程

  • Slide 1
  • Slide 2
  • Slide 3
  • Slide 4
  • Slide 5
  • Slide 6
  • Slide 7
  • Slide 8
  • Slide 9
  • Slide 10
  • Slide 11
  • Slide 12
  • Slide 13
  • Slide 14
  • Slide 15
  • Slide 16
  • Slide 17
  • Slide 18
  • Slide 19
  • Slide 20
  • Slide 21
  • Slide 22
  • Slide 23
  • Slide 24
  • Slide 25
  • Slide 26
  • Slide 27
  • Slide 28
  • Slide 29
  • Slide 30
  • Slide 31
  • Slide 32
  • Slide 33
  • Slide 34
  • Slide 35
  • Slide 36
  • Slide 37
  • Slide 38
  • Slide 39
  • Slide 40
  • Slide 41
  • Slide 42
  • Slide 43
  • Slide 44
  • Slide 45
  • Slide 46
  • Slide 47
  • Slide 48
  • Slide 49
  • Slide 50
  • Slide 51
  • Slide 52
  • Slide 53
  • Slide 54
  • Slide 55
  • Slide 56
  • Slide 57
  • Slide 58
  • Slide 59
  • Slide 60
  • Slide 61
  • Slide 62
  • Slide 63
  • Slide 64
  • Slide 65
  • Slide 66
  • Slide 67
  • Slide 68
  • Slide 69
  • Slide 70
  • Slide 71
  • Slide 72
  • Slide 73
  • Slide 74
  • Slide 75
  • Slide 76
  • Slide 77
  • Slide 78
  • Slide 79
  • Slide 80
  • Slide 81
  • Slide 82
  • Slide 83
  • Slide 84
  • Slide 85
  • Slide 86
  • Slide 87
  • Slide 88
  • Slide 89
  • Slide 90
  • Slide 91
  • Slide 92
  • Slide 93
  • Slide 94
  • Slide 95
  • Slide 96
  • Slide 97
  • Slide 98
  • Slide 99
  • Slide 100
  • Slide 101
  • Slide 102
  • Slide 103
Page 64: 第五章 樹

由右側的引線二元樹可知有最左和最右兩條引線尚無妥善的安排我們另設計一空的引線節點做為樹根如下圖所示

571  引線二元樹的中序走訪決定節點 p 在中序表示中的後繼節點 q (簡稱中序後繼點)

程式 5-10決定節點 node的中序後繼點1 struct TBTreeNode2 int leftthread3 struct TBTreeNode leftchild4 char data5 struct TBTreeNode rightchild6 int rightthread7 8 struct TBTreeNode root9 struct TBTreeNode Next(struct TBTreeNode node)

10 struct TBTreeNode temp11 temp = node-gtrightchild12 if (node-gtrightthead) return temp13 while (temp-gtleftthead) temp=temp-gtleftchild

14 return temp15

程式 5-11引線二元樹的中序表示(接程式 5-10)1 void InorderTBTree()2 struct TBTreeNode node3 for(node=Next(root)node=rootnode=Next(node))

4 cout ltlt node-gtdata5

bull只要 node 是引線二元樹上的任一節點透過呼叫 Next(node) 即可找到其在中序表示中的後繼節點bull(試想若程式 5-10 中的程序 Next 所傳入的參數指標正是 root Next(root) 是否可正常運作請思考)bull於是只須從中序表示中的第一節點起讓每一節點 node 都執行 Next(node) 程序即可得引線二元樹的中序表示

572  在引線二元樹中加入節點在引線二元樹中樹葉節點節點的右子樹指標所指向或內部節點右子樹的最左樹葉為其中序後繼節點而且任一節點若無左子樹則其左子樹指標乃指向其中序前接節點hellip這些性質必須在節點新增進入引線二元樹時也要保持接下來討論節點 new 插入成為引線二元樹的節點 p 的右子節點的情形至於插入成為左子節點的情形則讓各位模擬推敲

572572  在引線二元樹中加入節點 在引線二元樹中加入節點 (( 續續 ))

newnew 會成為會成為 pp 的右子節點的情況共有以下兩種的右子節點的情況共有以下兩種(1) (1) 若若 pp 沒有右子節點 沒有右子節點 ((p-gtrightthread p-gtrightthread 為為1) 1) 如圖如圖 ((a) a) 則 則 new new 插入後應形成如圖插入後應形成如圖 ((b) b) 所示所示

(2)(2) 若若 pp 有右子節點 有右子節點 ((p-gtrightthread p-gtrightthread 為為0) 0) 如圖如圖 ((c) c) 則 則 new new 插入後應形成如插入後應形成如圖圖 ((d) d) 所示所示

程式 5-12  插入節點 new 成為引線二元樹中節點 p的右子節點(上接程式 5-10 5-11 )

1 void InsertRight(struct TBTreeNode p 2 struct TBTreeNode new)3 struct TBTreeNode q4 new-gtrightchild = p-gtrightchild5 new-gtrightthead = p-gtrightthread6 new-gtleftchild = p7 new-gtleftthread = 18 p-gtrightchild = new9 p-gtrightthead = 010 if (new-gtrightthread)11 q = Next(new)12 q-gtleftchild = new13 14

58  堆積bull 「堆積」 (heap) 是一個特殊的完備二元樹節點的資料與其子節點的資料之間有事先定好的大小關係存在bull 定義ndash最大堆疊為一完備二元樹任一節點的資料內容不小於其子節點的資料內容ndash最小堆疊為一完備二元樹任一節點的資料內容不大於其子節點的資料內容

581  插入節點至最大堆積中最大堆積是一完備二元樹所以如圖 (a) 之最大堆積於加入新節點後樹的形狀必須如圖 (b) 所示灰色節點即為新節點若加入的節點資料分別為 1 9 12 則其最大堆積將分別變成如圖 (c) (d) (e) 所示

程式 5-13 新增節點至最大堆積中最大堆積本身是一棵完備二元樹可以用陣列表示之 1 define maxsize 1002 int heap[maxsize]3 int n i4 void InsertHeap(int x)5 if (n == maxsize) HeapFull()6 else7 n++8 i = n9 while ((i gt 1) ampamp (x gt heap[i2]))10 heap[i] = heap[i2]11 i = 212 13 heap[i] = x14 16

程序 InsertHeap 在插入第 n 個資料時需要的時間複雜度為 O(logn)

1 100

2 93

3 84

4 77

5 65

6 81

7 79

8 66

9 70

10 50

100

84

77

93

8165

66 70

79

50

1

2 3

4 5 6 7

8 9 10

1 define maxsize 1002 int heap[maxsize]3 int n i4 void InsertHeap(int x)5 if (n == maxsize) HeapFull()6 else7 n++8 i = n9 while ((i gt 1) ampamp (x gt heap[i2]))10 heap[i] = heap[i2]11 i = 212 13 heap[i] = x14 16

582 刪除最大堆積中的最大資料圖 (a) 中的最大堆積在刪除最大資料後其樹形應變成如圖 (b) 所示將最未一個樹葉節點的資料 6 暫挪至樹根節點內如圖 (c) 再將圖 (c) 中非最大堆積的結構調整成為最大堆積 10 和 8 中選大者與 6對調即成圖 (d)

程式 5-14 刪除最大堆積中的最大資料(上接程式 5-13 )1 int DeleteHeap()2 int x i j3 if (n == 0) HeapEmpty()4 else5 x = heap[1]6 heap[1] = heap[n]7 n--8 i = 19 while (i lt= n2)10 if (heap[2i] gt heap[2i+1])11 j = 2i12 else13 j = 2i+114 if (heap[i]gtheap[j]) break15 swap(heap[i] heap[j])16 i = j17 18 return x19 20

其時間複雜度與其堆積高度相同是為O(logn) 其中 n 為堆積中的元素個數

1 100

2 93

3 84

4 77

5 65

6 81

7 79

8 66

9 70

10 50

100

84

77

93

8165

66 70

79

50

1

2 3

4 5 6 7

8 9 10

1 int DeletHeap()2 int x i j3 if(n == 0) HeapEmpty()4 else5 x = heap[1]6 heap[1] = heap[n]7 n--8 i = 19 while (i lt= n2)10 if ([heap[2i]] gt heap[2i+1])11 j = 2i12 else13 j = 2i+114 if (heap[i]gtheap[j]) break15 swap(heap[i] heap[j])16 i = j17 18 return x19 20

583  優先佇列bull 由於最大堆積(或最小堆積)的結構可提供最大元素(或最小元素)而其維護堆積的成本為 O(logn) n 為總元素個數

bull 堆積常被當成「優先佇列」 (priority queue) 使用mdash此時佇列被組織成完備二元樹若新加入佇列的資料有大小關係優先順序的需求時此優先佇列即以 O(logn) 的代價將新資料放入優先佇列(可能是最大或最小堆積依實際大小關係優先順序的需求而定)中mdash提供依元素的優先順序做為處理順序的服務

bull 一般的佇列為先進先出先到的先行處理優先佇列則是依據其優先順序優先順序高者先行處理與來到的先後無關

範例 5-21bull 在「分時」 (time sharing) 的作業系統中我們不希望使用中央處理器 (Central Processing Unit CPU) 時間短的「處理程序」 (process) 等候過久於是有「最短時間工作優先」 (shortest-job-first) 的排程策略此時等候的處理程序即可組織成之一最小堆積而 CPU 每次即取出存於樹根的處理程序予以處理任何新來的處理程序則以 O(logn) 的時間加入此優先佇列(最小堆積)中

bull 注意之前提過的線性佇列新來的直接加入成為新的尾端元素花O(1) 的時間然線性佇列本身沒有大小的關係每次要取出最大(小)的資料得花O(n) 的代價(在所有元素中挑出最大(小)者) n 為佇列內的元素個數而優先佇列是在新元素加入時要花O(logn) 的時間至於取出最大(小)則只須 O(1) 的時間

59  二元搜尋樹bull 二元搜尋樹是一棵二元樹可能是空二元樹若不為空二元樹則滿足下列性質ndash所有節點內的資料內容是相異的 ndash左子節點的資內容 ( 如果有 ) 比父節點的資料內容小 ndash右子節點的資料內容 ( 如果有 ) 比父節點的資料內容大 ndash以左和右子節點為樹根的左子樹和右子樹也是二元搜尋樹

在二元搜尋樹中搜尋資料二元搜尋樹對資料大小的掌握十分明確若恰與節點 p 資料相等則搜尋成功若欲搜尋的資料 x 比樹節點 p 中的資料小 則只須搜尋節點 p 的左子樹若比節點 p 中的資料大 則只須搜尋 p 的右子樹這樣的判斷可以遞迴地應用在各子樹節點 中

程式 5-15 在二元搜尋樹中搜尋資料(遞迴呼叫)1 struct BSTreeNode2 struct BSTreeNode leftchild3 int data4 struct BSTreeNode rightchild5 6 struct BSTreeNode root7 struct BSTreeNode SearchBST(

struct BSTreeNode tree int x)8 if (tree == NULL) return NULL9 if (x == tree-gtdata) return tree10 if (x lt tree-gtdata)11 return SearchBST(tree-gtleftchild x)

12 return SearchBST(tree-gtrightchild x)13

程式 5-16 搜尋二元搜尋樹的資料(非遞迴呼叫)在二元搜尋樹十分龐大時遞迴呼叫的效率不會太好可利用迴圈來完成搜尋的動作以提高執行效率請看程式 5-16

14 struct BSTreeNode Search BST_iterative15 (struct BSTreeNode node int x)16 while (node = NULL)17 if (x == node-gtdata) return node18 if (x lt node-gtdata)19 node = node-gtleftchild20 else21 node = node-gtrightchild22 23 return NULL24

592  新增資料於二元搜尋樹中欲新增資料進入一二元搜尋樹中得先決定該資料在二元搜尋樹中應出現的位置這可透過上節提到的搜尋動作來解決

在二元搜尋樹定義的性質 (1) 中我們限定了元素必須相異所以搜尋新資料的結果必是 NULL

必須將搜尋的結果改為新增資料所應在位置的節點指標

程式 5-17  在二元搜尋樹中新增資料25 int InsertBST(int x)26 struct BSTreeNode p q27 p = root q = NULL28 while (p = NULL)29 q = p30 if (x == p-gtdata) return 0

若不考慮重複則可省略之31 if (x lt p-gtdata)32 p = p-gtleftchild33 else34 p = p-gtrightchild35

3636 p = (struct BSTreeNode ) p = (struct BSTreeNode ) malloc (sizeof(BSTreeNode))malloc (sizeof(BSTreeNode))

3737 p-gtdata = x p-gtdata = x3838 p-gtleftchild = p-gtrightchild = p-gtleftchild = p-gtrightchild = NULLNULL3939 if (root == NULL) root = p if (root == NULL) root = p4040 else if (x lt q-gtdata) else if (x lt q-gtdata)4141 q-gtleftchild = p q-gtleftchild = p4242 else else4343 q-gtrightchild = p q-gtrightchild = p4444 return 1 return 145 45

程式 程式 5-175-17  在二元搜尋樹中新增資料  在二元搜尋樹中新增資料 (( 續續 ))

範例 5-23 圖 (b) 為圖 (a) 新增整數 6 之後的結果圖 (c) (d) (e) (f) 則分別是逐步再加入 28 18

10 21 的結果

bull若目前的二元搜尋樹高度為 h 則新增資料得花O(h) 的時間找到其所應在的位置遂新增資料至二元搜尋樹中的時間複雜之度為 O(h)

593 自二元搜尋樹中刪除資料欲自二元搜尋樹中刪除資料可能會面臨兩種情況(1) 刪除節點 p 位於樹葉節點上如下圖所示 這是比較容易處理的情況

(2) 刪除的節點 p 不是樹葉刪除節點 p 後原 p 所指的節點可由 q ( q 中包含了 p 左子樹中的最大節點值)或 r ( r 中包含了 p 右子樹中的最小節點值)來取代以維持二元搜尋樹中資料的大小關係其結果分別如圖 (b) 和 (c) 所示而圖 (d) 則標明了指標會改變的節點位置即 p 會消失 father 的左或右子樹指標會異動 k(可能是 q 或 r )會移到原來 p 的位置其父節點 f 的左或右子樹指標會異動其中 father 為 p 父節點 f 為 k 的父節點

程式 5-18 自二元搜尋樹中刪除資料46 int DeleteBST (int x)47 Struct BSTreeNode p father k f48 p = root father = NULL49 while (p = NULL)50 if (x == p-gtdata) 找到 x 所在的節點 p 了51 if ((p-gtleftchild == NULL) ampamp 52 (p-gtrightchild == NULL))53 k = NULL p為樹葉節點54 else if (p-gtleftchild = NULL )x位於非子節

點55 f = p 找出 p左子樹的最右樹葉56 k = p-gtleftchild57 while (k-gtrightchild =NULL)58 f = k59 k = k-gtrightchild60 k將挪至原 p處61 if (p == f)62 f-gtleftchild = k-gtleftchild 63 else64 f-gtrightchild = k-gtleftchild 65

66 else p無左子樹67 f = p 找出 p右子樹的最左樹葉68 k = p-gtrightchild69 while (k-gtleftchild = NULL)70 f = k71 k = k -gtleftchild72 73 if (p == f)74 f-gtrightchild = k-gtrightchild 75 else76 f-gtleftchild = k-gtrightchild77 78 if (k = NULL) k挪至原 p處繼承 p的左右指標79 k-gtleftchild = p-gtleftchild80 k-gtrightchild = p-gtrightchild81 82 if (father == NULL) root = k83 else if (x lt father-gtdata) 決定 k是 father的左

或右兒子84 father-gtleftchild = k85 else86 father-gtrightchild = k87 free(p)88 return 1 成功地刪除 x於此傳回 1返回呼叫處89 51~89行為找到 x時應有的處理步驟

90 else 尚未找到 x繼續往下一階層找

91 father = p92 if (x lt p-gtdata)93 p = p-gtleftchild94 else95 p = p-gtrightchild96 97 98 return 0 未找到 x傳回 099

510  樹林 (forest)

bull 定義樹林(森林)是由 n 棵沒有交集的樹所構成的集合 n 0

下圖為一包含三棵樹的樹林

5101 轉換樹林為二元樹 之前我們以二元樹來表示樹因為二元樹有較多

好用的性質在此我們也將樹林轉換成二元樹看待

在 522 節中我們嘗試了用左子右兄弟的方式來表示樹這個方法事實上已將樹轉換成二元樹

即用左子右兄弟的結構重新組織樹林中的每一棵樹這些樹的樹根將視為兄弟而最左邊的樹則視為樹林二元樹的樹根如此一來樹林即可轉換成二元樹

範例 5-25右下圖為右上圖三棵樹所對應的三棵左子右兄弟二元樹

下圖則為上圖樹林所對應的一棵樹林二元樹

演算法 5-1  轉換樹林成二元樹輸入樹林 F = T1 T2 Tn

輸出 F 的二元樹表示 BT=(T1 T2 Tn)1 if (n == 0) 輸出空二元樹 2 else

3 root(T1 T2 Tn) = T1

4 leftsubtree(T1 T2 Tn) =

BT(T11 T12 T1m)5 BT(T11 T12 T1m)為 T1的子樹6 rightsubtree(T1 T2 Tn) =

BT(T2 T3 Tn)

樹林轉換成二元樹的過程樹林轉換成二元樹的過程

  • Slide 1
  • Slide 2
  • Slide 3
  • Slide 4
  • Slide 5
  • Slide 6
  • Slide 7
  • Slide 8
  • Slide 9
  • Slide 10
  • Slide 11
  • Slide 12
  • Slide 13
  • Slide 14
  • Slide 15
  • Slide 16
  • Slide 17
  • Slide 18
  • Slide 19
  • Slide 20
  • Slide 21
  • Slide 22
  • Slide 23
  • Slide 24
  • Slide 25
  • Slide 26
  • Slide 27
  • Slide 28
  • Slide 29
  • Slide 30
  • Slide 31
  • Slide 32
  • Slide 33
  • Slide 34
  • Slide 35
  • Slide 36
  • Slide 37
  • Slide 38
  • Slide 39
  • Slide 40
  • Slide 41
  • Slide 42
  • Slide 43
  • Slide 44
  • Slide 45
  • Slide 46
  • Slide 47
  • Slide 48
  • Slide 49
  • Slide 50
  • Slide 51
  • Slide 52
  • Slide 53
  • Slide 54
  • Slide 55
  • Slide 56
  • Slide 57
  • Slide 58
  • Slide 59
  • Slide 60
  • Slide 61
  • Slide 62
  • Slide 63
  • Slide 64
  • Slide 65
  • Slide 66
  • Slide 67
  • Slide 68
  • Slide 69
  • Slide 70
  • Slide 71
  • Slide 72
  • Slide 73
  • Slide 74
  • Slide 75
  • Slide 76
  • Slide 77
  • Slide 78
  • Slide 79
  • Slide 80
  • Slide 81
  • Slide 82
  • Slide 83
  • Slide 84
  • Slide 85
  • Slide 86
  • Slide 87
  • Slide 88
  • Slide 89
  • Slide 90
  • Slide 91
  • Slide 92
  • Slide 93
  • Slide 94
  • Slide 95
  • Slide 96
  • Slide 97
  • Slide 98
  • Slide 99
  • Slide 100
  • Slide 101
  • Slide 102
  • Slide 103
Page 65: 第五章 樹

571  引線二元樹的中序走訪決定節點 p 在中序表示中的後繼節點 q (簡稱中序後繼點)

程式 5-10決定節點 node的中序後繼點1 struct TBTreeNode2 int leftthread3 struct TBTreeNode leftchild4 char data5 struct TBTreeNode rightchild6 int rightthread7 8 struct TBTreeNode root9 struct TBTreeNode Next(struct TBTreeNode node)

10 struct TBTreeNode temp11 temp = node-gtrightchild12 if (node-gtrightthead) return temp13 while (temp-gtleftthead) temp=temp-gtleftchild

14 return temp15

程式 5-11引線二元樹的中序表示(接程式 5-10)1 void InorderTBTree()2 struct TBTreeNode node3 for(node=Next(root)node=rootnode=Next(node))

4 cout ltlt node-gtdata5

bull只要 node 是引線二元樹上的任一節點透過呼叫 Next(node) 即可找到其在中序表示中的後繼節點bull(試想若程式 5-10 中的程序 Next 所傳入的參數指標正是 root Next(root) 是否可正常運作請思考)bull於是只須從中序表示中的第一節點起讓每一節點 node 都執行 Next(node) 程序即可得引線二元樹的中序表示

572  在引線二元樹中加入節點在引線二元樹中樹葉節點節點的右子樹指標所指向或內部節點右子樹的最左樹葉為其中序後繼節點而且任一節點若無左子樹則其左子樹指標乃指向其中序前接節點hellip這些性質必須在節點新增進入引線二元樹時也要保持接下來討論節點 new 插入成為引線二元樹的節點 p 的右子節點的情形至於插入成為左子節點的情形則讓各位模擬推敲

572572  在引線二元樹中加入節點 在引線二元樹中加入節點 (( 續續 ))

newnew 會成為會成為 pp 的右子節點的情況共有以下兩種的右子節點的情況共有以下兩種(1) (1) 若若 pp 沒有右子節點 沒有右子節點 ((p-gtrightthread p-gtrightthread 為為1) 1) 如圖如圖 ((a) a) 則 則 new new 插入後應形成如圖插入後應形成如圖 ((b) b) 所示所示

(2)(2) 若若 pp 有右子節點 有右子節點 ((p-gtrightthread p-gtrightthread 為為0) 0) 如圖如圖 ((c) c) 則 則 new new 插入後應形成如插入後應形成如圖圖 ((d) d) 所示所示

程式 5-12  插入節點 new 成為引線二元樹中節點 p的右子節點(上接程式 5-10 5-11 )

1 void InsertRight(struct TBTreeNode p 2 struct TBTreeNode new)3 struct TBTreeNode q4 new-gtrightchild = p-gtrightchild5 new-gtrightthead = p-gtrightthread6 new-gtleftchild = p7 new-gtleftthread = 18 p-gtrightchild = new9 p-gtrightthead = 010 if (new-gtrightthread)11 q = Next(new)12 q-gtleftchild = new13 14

58  堆積bull 「堆積」 (heap) 是一個特殊的完備二元樹節點的資料與其子節點的資料之間有事先定好的大小關係存在bull 定義ndash最大堆疊為一完備二元樹任一節點的資料內容不小於其子節點的資料內容ndash最小堆疊為一完備二元樹任一節點的資料內容不大於其子節點的資料內容

581  插入節點至最大堆積中最大堆積是一完備二元樹所以如圖 (a) 之最大堆積於加入新節點後樹的形狀必須如圖 (b) 所示灰色節點即為新節點若加入的節點資料分別為 1 9 12 則其最大堆積將分別變成如圖 (c) (d) (e) 所示

程式 5-13 新增節點至最大堆積中最大堆積本身是一棵完備二元樹可以用陣列表示之 1 define maxsize 1002 int heap[maxsize]3 int n i4 void InsertHeap(int x)5 if (n == maxsize) HeapFull()6 else7 n++8 i = n9 while ((i gt 1) ampamp (x gt heap[i2]))10 heap[i] = heap[i2]11 i = 212 13 heap[i] = x14 16

程序 InsertHeap 在插入第 n 個資料時需要的時間複雜度為 O(logn)

1 100

2 93

3 84

4 77

5 65

6 81

7 79

8 66

9 70

10 50

100

84

77

93

8165

66 70

79

50

1

2 3

4 5 6 7

8 9 10

1 define maxsize 1002 int heap[maxsize]3 int n i4 void InsertHeap(int x)5 if (n == maxsize) HeapFull()6 else7 n++8 i = n9 while ((i gt 1) ampamp (x gt heap[i2]))10 heap[i] = heap[i2]11 i = 212 13 heap[i] = x14 16

582 刪除最大堆積中的最大資料圖 (a) 中的最大堆積在刪除最大資料後其樹形應變成如圖 (b) 所示將最未一個樹葉節點的資料 6 暫挪至樹根節點內如圖 (c) 再將圖 (c) 中非最大堆積的結構調整成為最大堆積 10 和 8 中選大者與 6對調即成圖 (d)

程式 5-14 刪除最大堆積中的最大資料(上接程式 5-13 )1 int DeleteHeap()2 int x i j3 if (n == 0) HeapEmpty()4 else5 x = heap[1]6 heap[1] = heap[n]7 n--8 i = 19 while (i lt= n2)10 if (heap[2i] gt heap[2i+1])11 j = 2i12 else13 j = 2i+114 if (heap[i]gtheap[j]) break15 swap(heap[i] heap[j])16 i = j17 18 return x19 20

其時間複雜度與其堆積高度相同是為O(logn) 其中 n 為堆積中的元素個數

1 100

2 93

3 84

4 77

5 65

6 81

7 79

8 66

9 70

10 50

100

84

77

93

8165

66 70

79

50

1

2 3

4 5 6 7

8 9 10

1 int DeletHeap()2 int x i j3 if(n == 0) HeapEmpty()4 else5 x = heap[1]6 heap[1] = heap[n]7 n--8 i = 19 while (i lt= n2)10 if ([heap[2i]] gt heap[2i+1])11 j = 2i12 else13 j = 2i+114 if (heap[i]gtheap[j]) break15 swap(heap[i] heap[j])16 i = j17 18 return x19 20

583  優先佇列bull 由於最大堆積(或最小堆積)的結構可提供最大元素(或最小元素)而其維護堆積的成本為 O(logn) n 為總元素個數

bull 堆積常被當成「優先佇列」 (priority queue) 使用mdash此時佇列被組織成完備二元樹若新加入佇列的資料有大小關係優先順序的需求時此優先佇列即以 O(logn) 的代價將新資料放入優先佇列(可能是最大或最小堆積依實際大小關係優先順序的需求而定)中mdash提供依元素的優先順序做為處理順序的服務

bull 一般的佇列為先進先出先到的先行處理優先佇列則是依據其優先順序優先順序高者先行處理與來到的先後無關

範例 5-21bull 在「分時」 (time sharing) 的作業系統中我們不希望使用中央處理器 (Central Processing Unit CPU) 時間短的「處理程序」 (process) 等候過久於是有「最短時間工作優先」 (shortest-job-first) 的排程策略此時等候的處理程序即可組織成之一最小堆積而 CPU 每次即取出存於樹根的處理程序予以處理任何新來的處理程序則以 O(logn) 的時間加入此優先佇列(最小堆積)中

bull 注意之前提過的線性佇列新來的直接加入成為新的尾端元素花O(1) 的時間然線性佇列本身沒有大小的關係每次要取出最大(小)的資料得花O(n) 的代價(在所有元素中挑出最大(小)者) n 為佇列內的元素個數而優先佇列是在新元素加入時要花O(logn) 的時間至於取出最大(小)則只須 O(1) 的時間

59  二元搜尋樹bull 二元搜尋樹是一棵二元樹可能是空二元樹若不為空二元樹則滿足下列性質ndash所有節點內的資料內容是相異的 ndash左子節點的資內容 ( 如果有 ) 比父節點的資料內容小 ndash右子節點的資料內容 ( 如果有 ) 比父節點的資料內容大 ndash以左和右子節點為樹根的左子樹和右子樹也是二元搜尋樹

在二元搜尋樹中搜尋資料二元搜尋樹對資料大小的掌握十分明確若恰與節點 p 資料相等則搜尋成功若欲搜尋的資料 x 比樹節點 p 中的資料小 則只須搜尋節點 p 的左子樹若比節點 p 中的資料大 則只須搜尋 p 的右子樹這樣的判斷可以遞迴地應用在各子樹節點 中

程式 5-15 在二元搜尋樹中搜尋資料(遞迴呼叫)1 struct BSTreeNode2 struct BSTreeNode leftchild3 int data4 struct BSTreeNode rightchild5 6 struct BSTreeNode root7 struct BSTreeNode SearchBST(

struct BSTreeNode tree int x)8 if (tree == NULL) return NULL9 if (x == tree-gtdata) return tree10 if (x lt tree-gtdata)11 return SearchBST(tree-gtleftchild x)

12 return SearchBST(tree-gtrightchild x)13

程式 5-16 搜尋二元搜尋樹的資料(非遞迴呼叫)在二元搜尋樹十分龐大時遞迴呼叫的效率不會太好可利用迴圈來完成搜尋的動作以提高執行效率請看程式 5-16

14 struct BSTreeNode Search BST_iterative15 (struct BSTreeNode node int x)16 while (node = NULL)17 if (x == node-gtdata) return node18 if (x lt node-gtdata)19 node = node-gtleftchild20 else21 node = node-gtrightchild22 23 return NULL24

592  新增資料於二元搜尋樹中欲新增資料進入一二元搜尋樹中得先決定該資料在二元搜尋樹中應出現的位置這可透過上節提到的搜尋動作來解決

在二元搜尋樹定義的性質 (1) 中我們限定了元素必須相異所以搜尋新資料的結果必是 NULL

必須將搜尋的結果改為新增資料所應在位置的節點指標

程式 5-17  在二元搜尋樹中新增資料25 int InsertBST(int x)26 struct BSTreeNode p q27 p = root q = NULL28 while (p = NULL)29 q = p30 if (x == p-gtdata) return 0

若不考慮重複則可省略之31 if (x lt p-gtdata)32 p = p-gtleftchild33 else34 p = p-gtrightchild35

3636 p = (struct BSTreeNode ) p = (struct BSTreeNode ) malloc (sizeof(BSTreeNode))malloc (sizeof(BSTreeNode))

3737 p-gtdata = x p-gtdata = x3838 p-gtleftchild = p-gtrightchild = p-gtleftchild = p-gtrightchild = NULLNULL3939 if (root == NULL) root = p if (root == NULL) root = p4040 else if (x lt q-gtdata) else if (x lt q-gtdata)4141 q-gtleftchild = p q-gtleftchild = p4242 else else4343 q-gtrightchild = p q-gtrightchild = p4444 return 1 return 145 45

程式 程式 5-175-17  在二元搜尋樹中新增資料  在二元搜尋樹中新增資料 (( 續續 ))

範例 5-23 圖 (b) 為圖 (a) 新增整數 6 之後的結果圖 (c) (d) (e) (f) 則分別是逐步再加入 28 18

10 21 的結果

bull若目前的二元搜尋樹高度為 h 則新增資料得花O(h) 的時間找到其所應在的位置遂新增資料至二元搜尋樹中的時間複雜之度為 O(h)

593 自二元搜尋樹中刪除資料欲自二元搜尋樹中刪除資料可能會面臨兩種情況(1) 刪除節點 p 位於樹葉節點上如下圖所示 這是比較容易處理的情況

(2) 刪除的節點 p 不是樹葉刪除節點 p 後原 p 所指的節點可由 q ( q 中包含了 p 左子樹中的最大節點值)或 r ( r 中包含了 p 右子樹中的最小節點值)來取代以維持二元搜尋樹中資料的大小關係其結果分別如圖 (b) 和 (c) 所示而圖 (d) 則標明了指標會改變的節點位置即 p 會消失 father 的左或右子樹指標會異動 k(可能是 q 或 r )會移到原來 p 的位置其父節點 f 的左或右子樹指標會異動其中 father 為 p 父節點 f 為 k 的父節點

程式 5-18 自二元搜尋樹中刪除資料46 int DeleteBST (int x)47 Struct BSTreeNode p father k f48 p = root father = NULL49 while (p = NULL)50 if (x == p-gtdata) 找到 x 所在的節點 p 了51 if ((p-gtleftchild == NULL) ampamp 52 (p-gtrightchild == NULL))53 k = NULL p為樹葉節點54 else if (p-gtleftchild = NULL )x位於非子節

點55 f = p 找出 p左子樹的最右樹葉56 k = p-gtleftchild57 while (k-gtrightchild =NULL)58 f = k59 k = k-gtrightchild60 k將挪至原 p處61 if (p == f)62 f-gtleftchild = k-gtleftchild 63 else64 f-gtrightchild = k-gtleftchild 65

66 else p無左子樹67 f = p 找出 p右子樹的最左樹葉68 k = p-gtrightchild69 while (k-gtleftchild = NULL)70 f = k71 k = k -gtleftchild72 73 if (p == f)74 f-gtrightchild = k-gtrightchild 75 else76 f-gtleftchild = k-gtrightchild77 78 if (k = NULL) k挪至原 p處繼承 p的左右指標79 k-gtleftchild = p-gtleftchild80 k-gtrightchild = p-gtrightchild81 82 if (father == NULL) root = k83 else if (x lt father-gtdata) 決定 k是 father的左

或右兒子84 father-gtleftchild = k85 else86 father-gtrightchild = k87 free(p)88 return 1 成功地刪除 x於此傳回 1返回呼叫處89 51~89行為找到 x時應有的處理步驟

90 else 尚未找到 x繼續往下一階層找

91 father = p92 if (x lt p-gtdata)93 p = p-gtleftchild94 else95 p = p-gtrightchild96 97 98 return 0 未找到 x傳回 099

510  樹林 (forest)

bull 定義樹林(森林)是由 n 棵沒有交集的樹所構成的集合 n 0

下圖為一包含三棵樹的樹林

5101 轉換樹林為二元樹 之前我們以二元樹來表示樹因為二元樹有較多

好用的性質在此我們也將樹林轉換成二元樹看待

在 522 節中我們嘗試了用左子右兄弟的方式來表示樹這個方法事實上已將樹轉換成二元樹

即用左子右兄弟的結構重新組織樹林中的每一棵樹這些樹的樹根將視為兄弟而最左邊的樹則視為樹林二元樹的樹根如此一來樹林即可轉換成二元樹

範例 5-25右下圖為右上圖三棵樹所對應的三棵左子右兄弟二元樹

下圖則為上圖樹林所對應的一棵樹林二元樹

演算法 5-1  轉換樹林成二元樹輸入樹林 F = T1 T2 Tn

輸出 F 的二元樹表示 BT=(T1 T2 Tn)1 if (n == 0) 輸出空二元樹 2 else

3 root(T1 T2 Tn) = T1

4 leftsubtree(T1 T2 Tn) =

BT(T11 T12 T1m)5 BT(T11 T12 T1m)為 T1的子樹6 rightsubtree(T1 T2 Tn) =

BT(T2 T3 Tn)

樹林轉換成二元樹的過程樹林轉換成二元樹的過程

  • Slide 1
  • Slide 2
  • Slide 3
  • Slide 4
  • Slide 5
  • Slide 6
  • Slide 7
  • Slide 8
  • Slide 9
  • Slide 10
  • Slide 11
  • Slide 12
  • Slide 13
  • Slide 14
  • Slide 15
  • Slide 16
  • Slide 17
  • Slide 18
  • Slide 19
  • Slide 20
  • Slide 21
  • Slide 22
  • Slide 23
  • Slide 24
  • Slide 25
  • Slide 26
  • Slide 27
  • Slide 28
  • Slide 29
  • Slide 30
  • Slide 31
  • Slide 32
  • Slide 33
  • Slide 34
  • Slide 35
  • Slide 36
  • Slide 37
  • Slide 38
  • Slide 39
  • Slide 40
  • Slide 41
  • Slide 42
  • Slide 43
  • Slide 44
  • Slide 45
  • Slide 46
  • Slide 47
  • Slide 48
  • Slide 49
  • Slide 50
  • Slide 51
  • Slide 52
  • Slide 53
  • Slide 54
  • Slide 55
  • Slide 56
  • Slide 57
  • Slide 58
  • Slide 59
  • Slide 60
  • Slide 61
  • Slide 62
  • Slide 63
  • Slide 64
  • Slide 65
  • Slide 66
  • Slide 67
  • Slide 68
  • Slide 69
  • Slide 70
  • Slide 71
  • Slide 72
  • Slide 73
  • Slide 74
  • Slide 75
  • Slide 76
  • Slide 77
  • Slide 78
  • Slide 79
  • Slide 80
  • Slide 81
  • Slide 82
  • Slide 83
  • Slide 84
  • Slide 85
  • Slide 86
  • Slide 87
  • Slide 88
  • Slide 89
  • Slide 90
  • Slide 91
  • Slide 92
  • Slide 93
  • Slide 94
  • Slide 95
  • Slide 96
  • Slide 97
  • Slide 98
  • Slide 99
  • Slide 100
  • Slide 101
  • Slide 102
  • Slide 103
Page 66: 第五章 樹

程式 5-10決定節點 node的中序後繼點1 struct TBTreeNode2 int leftthread3 struct TBTreeNode leftchild4 char data5 struct TBTreeNode rightchild6 int rightthread7 8 struct TBTreeNode root9 struct TBTreeNode Next(struct TBTreeNode node)

10 struct TBTreeNode temp11 temp = node-gtrightchild12 if (node-gtrightthead) return temp13 while (temp-gtleftthead) temp=temp-gtleftchild

14 return temp15

程式 5-11引線二元樹的中序表示(接程式 5-10)1 void InorderTBTree()2 struct TBTreeNode node3 for(node=Next(root)node=rootnode=Next(node))

4 cout ltlt node-gtdata5

bull只要 node 是引線二元樹上的任一節點透過呼叫 Next(node) 即可找到其在中序表示中的後繼節點bull(試想若程式 5-10 中的程序 Next 所傳入的參數指標正是 root Next(root) 是否可正常運作請思考)bull於是只須從中序表示中的第一節點起讓每一節點 node 都執行 Next(node) 程序即可得引線二元樹的中序表示

572  在引線二元樹中加入節點在引線二元樹中樹葉節點節點的右子樹指標所指向或內部節點右子樹的最左樹葉為其中序後繼節點而且任一節點若無左子樹則其左子樹指標乃指向其中序前接節點hellip這些性質必須在節點新增進入引線二元樹時也要保持接下來討論節點 new 插入成為引線二元樹的節點 p 的右子節點的情形至於插入成為左子節點的情形則讓各位模擬推敲

572572  在引線二元樹中加入節點 在引線二元樹中加入節點 (( 續續 ))

newnew 會成為會成為 pp 的右子節點的情況共有以下兩種的右子節點的情況共有以下兩種(1) (1) 若若 pp 沒有右子節點 沒有右子節點 ((p-gtrightthread p-gtrightthread 為為1) 1) 如圖如圖 ((a) a) 則 則 new new 插入後應形成如圖插入後應形成如圖 ((b) b) 所示所示

(2)(2) 若若 pp 有右子節點 有右子節點 ((p-gtrightthread p-gtrightthread 為為0) 0) 如圖如圖 ((c) c) 則 則 new new 插入後應形成如插入後應形成如圖圖 ((d) d) 所示所示

程式 5-12  插入節點 new 成為引線二元樹中節點 p的右子節點(上接程式 5-10 5-11 )

1 void InsertRight(struct TBTreeNode p 2 struct TBTreeNode new)3 struct TBTreeNode q4 new-gtrightchild = p-gtrightchild5 new-gtrightthead = p-gtrightthread6 new-gtleftchild = p7 new-gtleftthread = 18 p-gtrightchild = new9 p-gtrightthead = 010 if (new-gtrightthread)11 q = Next(new)12 q-gtleftchild = new13 14

58  堆積bull 「堆積」 (heap) 是一個特殊的完備二元樹節點的資料與其子節點的資料之間有事先定好的大小關係存在bull 定義ndash最大堆疊為一完備二元樹任一節點的資料內容不小於其子節點的資料內容ndash最小堆疊為一完備二元樹任一節點的資料內容不大於其子節點的資料內容

581  插入節點至最大堆積中最大堆積是一完備二元樹所以如圖 (a) 之最大堆積於加入新節點後樹的形狀必須如圖 (b) 所示灰色節點即為新節點若加入的節點資料分別為 1 9 12 則其最大堆積將分別變成如圖 (c) (d) (e) 所示

程式 5-13 新增節點至最大堆積中最大堆積本身是一棵完備二元樹可以用陣列表示之 1 define maxsize 1002 int heap[maxsize]3 int n i4 void InsertHeap(int x)5 if (n == maxsize) HeapFull()6 else7 n++8 i = n9 while ((i gt 1) ampamp (x gt heap[i2]))10 heap[i] = heap[i2]11 i = 212 13 heap[i] = x14 16

程序 InsertHeap 在插入第 n 個資料時需要的時間複雜度為 O(logn)

1 100

2 93

3 84

4 77

5 65

6 81

7 79

8 66

9 70

10 50

100

84

77

93

8165

66 70

79

50

1

2 3

4 5 6 7

8 9 10

1 define maxsize 1002 int heap[maxsize]3 int n i4 void InsertHeap(int x)5 if (n == maxsize) HeapFull()6 else7 n++8 i = n9 while ((i gt 1) ampamp (x gt heap[i2]))10 heap[i] = heap[i2]11 i = 212 13 heap[i] = x14 16

582 刪除最大堆積中的最大資料圖 (a) 中的最大堆積在刪除最大資料後其樹形應變成如圖 (b) 所示將最未一個樹葉節點的資料 6 暫挪至樹根節點內如圖 (c) 再將圖 (c) 中非最大堆積的結構調整成為最大堆積 10 和 8 中選大者與 6對調即成圖 (d)

程式 5-14 刪除最大堆積中的最大資料(上接程式 5-13 )1 int DeleteHeap()2 int x i j3 if (n == 0) HeapEmpty()4 else5 x = heap[1]6 heap[1] = heap[n]7 n--8 i = 19 while (i lt= n2)10 if (heap[2i] gt heap[2i+1])11 j = 2i12 else13 j = 2i+114 if (heap[i]gtheap[j]) break15 swap(heap[i] heap[j])16 i = j17 18 return x19 20

其時間複雜度與其堆積高度相同是為O(logn) 其中 n 為堆積中的元素個數

1 100

2 93

3 84

4 77

5 65

6 81

7 79

8 66

9 70

10 50

100

84

77

93

8165

66 70

79

50

1

2 3

4 5 6 7

8 9 10

1 int DeletHeap()2 int x i j3 if(n == 0) HeapEmpty()4 else5 x = heap[1]6 heap[1] = heap[n]7 n--8 i = 19 while (i lt= n2)10 if ([heap[2i]] gt heap[2i+1])11 j = 2i12 else13 j = 2i+114 if (heap[i]gtheap[j]) break15 swap(heap[i] heap[j])16 i = j17 18 return x19 20

583  優先佇列bull 由於最大堆積(或最小堆積)的結構可提供最大元素(或最小元素)而其維護堆積的成本為 O(logn) n 為總元素個數

bull 堆積常被當成「優先佇列」 (priority queue) 使用mdash此時佇列被組織成完備二元樹若新加入佇列的資料有大小關係優先順序的需求時此優先佇列即以 O(logn) 的代價將新資料放入優先佇列(可能是最大或最小堆積依實際大小關係優先順序的需求而定)中mdash提供依元素的優先順序做為處理順序的服務

bull 一般的佇列為先進先出先到的先行處理優先佇列則是依據其優先順序優先順序高者先行處理與來到的先後無關

範例 5-21bull 在「分時」 (time sharing) 的作業系統中我們不希望使用中央處理器 (Central Processing Unit CPU) 時間短的「處理程序」 (process) 等候過久於是有「最短時間工作優先」 (shortest-job-first) 的排程策略此時等候的處理程序即可組織成之一最小堆積而 CPU 每次即取出存於樹根的處理程序予以處理任何新來的處理程序則以 O(logn) 的時間加入此優先佇列(最小堆積)中

bull 注意之前提過的線性佇列新來的直接加入成為新的尾端元素花O(1) 的時間然線性佇列本身沒有大小的關係每次要取出最大(小)的資料得花O(n) 的代價(在所有元素中挑出最大(小)者) n 為佇列內的元素個數而優先佇列是在新元素加入時要花O(logn) 的時間至於取出最大(小)則只須 O(1) 的時間

59  二元搜尋樹bull 二元搜尋樹是一棵二元樹可能是空二元樹若不為空二元樹則滿足下列性質ndash所有節點內的資料內容是相異的 ndash左子節點的資內容 ( 如果有 ) 比父節點的資料內容小 ndash右子節點的資料內容 ( 如果有 ) 比父節點的資料內容大 ndash以左和右子節點為樹根的左子樹和右子樹也是二元搜尋樹

在二元搜尋樹中搜尋資料二元搜尋樹對資料大小的掌握十分明確若恰與節點 p 資料相等則搜尋成功若欲搜尋的資料 x 比樹節點 p 中的資料小 則只須搜尋節點 p 的左子樹若比節點 p 中的資料大 則只須搜尋 p 的右子樹這樣的判斷可以遞迴地應用在各子樹節點 中

程式 5-15 在二元搜尋樹中搜尋資料(遞迴呼叫)1 struct BSTreeNode2 struct BSTreeNode leftchild3 int data4 struct BSTreeNode rightchild5 6 struct BSTreeNode root7 struct BSTreeNode SearchBST(

struct BSTreeNode tree int x)8 if (tree == NULL) return NULL9 if (x == tree-gtdata) return tree10 if (x lt tree-gtdata)11 return SearchBST(tree-gtleftchild x)

12 return SearchBST(tree-gtrightchild x)13

程式 5-16 搜尋二元搜尋樹的資料(非遞迴呼叫)在二元搜尋樹十分龐大時遞迴呼叫的效率不會太好可利用迴圈來完成搜尋的動作以提高執行效率請看程式 5-16

14 struct BSTreeNode Search BST_iterative15 (struct BSTreeNode node int x)16 while (node = NULL)17 if (x == node-gtdata) return node18 if (x lt node-gtdata)19 node = node-gtleftchild20 else21 node = node-gtrightchild22 23 return NULL24

592  新增資料於二元搜尋樹中欲新增資料進入一二元搜尋樹中得先決定該資料在二元搜尋樹中應出現的位置這可透過上節提到的搜尋動作來解決

在二元搜尋樹定義的性質 (1) 中我們限定了元素必須相異所以搜尋新資料的結果必是 NULL

必須將搜尋的結果改為新增資料所應在位置的節點指標

程式 5-17  在二元搜尋樹中新增資料25 int InsertBST(int x)26 struct BSTreeNode p q27 p = root q = NULL28 while (p = NULL)29 q = p30 if (x == p-gtdata) return 0

若不考慮重複則可省略之31 if (x lt p-gtdata)32 p = p-gtleftchild33 else34 p = p-gtrightchild35

3636 p = (struct BSTreeNode ) p = (struct BSTreeNode ) malloc (sizeof(BSTreeNode))malloc (sizeof(BSTreeNode))

3737 p-gtdata = x p-gtdata = x3838 p-gtleftchild = p-gtrightchild = p-gtleftchild = p-gtrightchild = NULLNULL3939 if (root == NULL) root = p if (root == NULL) root = p4040 else if (x lt q-gtdata) else if (x lt q-gtdata)4141 q-gtleftchild = p q-gtleftchild = p4242 else else4343 q-gtrightchild = p q-gtrightchild = p4444 return 1 return 145 45

程式 程式 5-175-17  在二元搜尋樹中新增資料  在二元搜尋樹中新增資料 (( 續續 ))

範例 5-23 圖 (b) 為圖 (a) 新增整數 6 之後的結果圖 (c) (d) (e) (f) 則分別是逐步再加入 28 18

10 21 的結果

bull若目前的二元搜尋樹高度為 h 則新增資料得花O(h) 的時間找到其所應在的位置遂新增資料至二元搜尋樹中的時間複雜之度為 O(h)

593 自二元搜尋樹中刪除資料欲自二元搜尋樹中刪除資料可能會面臨兩種情況(1) 刪除節點 p 位於樹葉節點上如下圖所示 這是比較容易處理的情況

(2) 刪除的節點 p 不是樹葉刪除節點 p 後原 p 所指的節點可由 q ( q 中包含了 p 左子樹中的最大節點值)或 r ( r 中包含了 p 右子樹中的最小節點值)來取代以維持二元搜尋樹中資料的大小關係其結果分別如圖 (b) 和 (c) 所示而圖 (d) 則標明了指標會改變的節點位置即 p 會消失 father 的左或右子樹指標會異動 k(可能是 q 或 r )會移到原來 p 的位置其父節點 f 的左或右子樹指標會異動其中 father 為 p 父節點 f 為 k 的父節點

程式 5-18 自二元搜尋樹中刪除資料46 int DeleteBST (int x)47 Struct BSTreeNode p father k f48 p = root father = NULL49 while (p = NULL)50 if (x == p-gtdata) 找到 x 所在的節點 p 了51 if ((p-gtleftchild == NULL) ampamp 52 (p-gtrightchild == NULL))53 k = NULL p為樹葉節點54 else if (p-gtleftchild = NULL )x位於非子節

點55 f = p 找出 p左子樹的最右樹葉56 k = p-gtleftchild57 while (k-gtrightchild =NULL)58 f = k59 k = k-gtrightchild60 k將挪至原 p處61 if (p == f)62 f-gtleftchild = k-gtleftchild 63 else64 f-gtrightchild = k-gtleftchild 65

66 else p無左子樹67 f = p 找出 p右子樹的最左樹葉68 k = p-gtrightchild69 while (k-gtleftchild = NULL)70 f = k71 k = k -gtleftchild72 73 if (p == f)74 f-gtrightchild = k-gtrightchild 75 else76 f-gtleftchild = k-gtrightchild77 78 if (k = NULL) k挪至原 p處繼承 p的左右指標79 k-gtleftchild = p-gtleftchild80 k-gtrightchild = p-gtrightchild81 82 if (father == NULL) root = k83 else if (x lt father-gtdata) 決定 k是 father的左

或右兒子84 father-gtleftchild = k85 else86 father-gtrightchild = k87 free(p)88 return 1 成功地刪除 x於此傳回 1返回呼叫處89 51~89行為找到 x時應有的處理步驟

90 else 尚未找到 x繼續往下一階層找

91 father = p92 if (x lt p-gtdata)93 p = p-gtleftchild94 else95 p = p-gtrightchild96 97 98 return 0 未找到 x傳回 099

510  樹林 (forest)

bull 定義樹林(森林)是由 n 棵沒有交集的樹所構成的集合 n 0

下圖為一包含三棵樹的樹林

5101 轉換樹林為二元樹 之前我們以二元樹來表示樹因為二元樹有較多

好用的性質在此我們也將樹林轉換成二元樹看待

在 522 節中我們嘗試了用左子右兄弟的方式來表示樹這個方法事實上已將樹轉換成二元樹

即用左子右兄弟的結構重新組織樹林中的每一棵樹這些樹的樹根將視為兄弟而最左邊的樹則視為樹林二元樹的樹根如此一來樹林即可轉換成二元樹

範例 5-25右下圖為右上圖三棵樹所對應的三棵左子右兄弟二元樹

下圖則為上圖樹林所對應的一棵樹林二元樹

演算法 5-1  轉換樹林成二元樹輸入樹林 F = T1 T2 Tn

輸出 F 的二元樹表示 BT=(T1 T2 Tn)1 if (n == 0) 輸出空二元樹 2 else

3 root(T1 T2 Tn) = T1

4 leftsubtree(T1 T2 Tn) =

BT(T11 T12 T1m)5 BT(T11 T12 T1m)為 T1的子樹6 rightsubtree(T1 T2 Tn) =

BT(T2 T3 Tn)

樹林轉換成二元樹的過程樹林轉換成二元樹的過程

  • Slide 1
  • Slide 2
  • Slide 3
  • Slide 4
  • Slide 5
  • Slide 6
  • Slide 7
  • Slide 8
  • Slide 9
  • Slide 10
  • Slide 11
  • Slide 12
  • Slide 13
  • Slide 14
  • Slide 15
  • Slide 16
  • Slide 17
  • Slide 18
  • Slide 19
  • Slide 20
  • Slide 21
  • Slide 22
  • Slide 23
  • Slide 24
  • Slide 25
  • Slide 26
  • Slide 27
  • Slide 28
  • Slide 29
  • Slide 30
  • Slide 31
  • Slide 32
  • Slide 33
  • Slide 34
  • Slide 35
  • Slide 36
  • Slide 37
  • Slide 38
  • Slide 39
  • Slide 40
  • Slide 41
  • Slide 42
  • Slide 43
  • Slide 44
  • Slide 45
  • Slide 46
  • Slide 47
  • Slide 48
  • Slide 49
  • Slide 50
  • Slide 51
  • Slide 52
  • Slide 53
  • Slide 54
  • Slide 55
  • Slide 56
  • Slide 57
  • Slide 58
  • Slide 59
  • Slide 60
  • Slide 61
  • Slide 62
  • Slide 63
  • Slide 64
  • Slide 65
  • Slide 66
  • Slide 67
  • Slide 68
  • Slide 69
  • Slide 70
  • Slide 71
  • Slide 72
  • Slide 73
  • Slide 74
  • Slide 75
  • Slide 76
  • Slide 77
  • Slide 78
  • Slide 79
  • Slide 80
  • Slide 81
  • Slide 82
  • Slide 83
  • Slide 84
  • Slide 85
  • Slide 86
  • Slide 87
  • Slide 88
  • Slide 89
  • Slide 90
  • Slide 91
  • Slide 92
  • Slide 93
  • Slide 94
  • Slide 95
  • Slide 96
  • Slide 97
  • Slide 98
  • Slide 99
  • Slide 100
  • Slide 101
  • Slide 102
  • Slide 103
Page 67: 第五章 樹

程式 5-11引線二元樹的中序表示(接程式 5-10)1 void InorderTBTree()2 struct TBTreeNode node3 for(node=Next(root)node=rootnode=Next(node))

4 cout ltlt node-gtdata5

bull只要 node 是引線二元樹上的任一節點透過呼叫 Next(node) 即可找到其在中序表示中的後繼節點bull(試想若程式 5-10 中的程序 Next 所傳入的參數指標正是 root Next(root) 是否可正常運作請思考)bull於是只須從中序表示中的第一節點起讓每一節點 node 都執行 Next(node) 程序即可得引線二元樹的中序表示

572  在引線二元樹中加入節點在引線二元樹中樹葉節點節點的右子樹指標所指向或內部節點右子樹的最左樹葉為其中序後繼節點而且任一節點若無左子樹則其左子樹指標乃指向其中序前接節點hellip這些性質必須在節點新增進入引線二元樹時也要保持接下來討論節點 new 插入成為引線二元樹的節點 p 的右子節點的情形至於插入成為左子節點的情形則讓各位模擬推敲

572572  在引線二元樹中加入節點 在引線二元樹中加入節點 (( 續續 ))

newnew 會成為會成為 pp 的右子節點的情況共有以下兩種的右子節點的情況共有以下兩種(1) (1) 若若 pp 沒有右子節點 沒有右子節點 ((p-gtrightthread p-gtrightthread 為為1) 1) 如圖如圖 ((a) a) 則 則 new new 插入後應形成如圖插入後應形成如圖 ((b) b) 所示所示

(2)(2) 若若 pp 有右子節點 有右子節點 ((p-gtrightthread p-gtrightthread 為為0) 0) 如圖如圖 ((c) c) 則 則 new new 插入後應形成如插入後應形成如圖圖 ((d) d) 所示所示

程式 5-12  插入節點 new 成為引線二元樹中節點 p的右子節點(上接程式 5-10 5-11 )

1 void InsertRight(struct TBTreeNode p 2 struct TBTreeNode new)3 struct TBTreeNode q4 new-gtrightchild = p-gtrightchild5 new-gtrightthead = p-gtrightthread6 new-gtleftchild = p7 new-gtleftthread = 18 p-gtrightchild = new9 p-gtrightthead = 010 if (new-gtrightthread)11 q = Next(new)12 q-gtleftchild = new13 14

58  堆積bull 「堆積」 (heap) 是一個特殊的完備二元樹節點的資料與其子節點的資料之間有事先定好的大小關係存在bull 定義ndash最大堆疊為一完備二元樹任一節點的資料內容不小於其子節點的資料內容ndash最小堆疊為一完備二元樹任一節點的資料內容不大於其子節點的資料內容

581  插入節點至最大堆積中最大堆積是一完備二元樹所以如圖 (a) 之最大堆積於加入新節點後樹的形狀必須如圖 (b) 所示灰色節點即為新節點若加入的節點資料分別為 1 9 12 則其最大堆積將分別變成如圖 (c) (d) (e) 所示

程式 5-13 新增節點至最大堆積中最大堆積本身是一棵完備二元樹可以用陣列表示之 1 define maxsize 1002 int heap[maxsize]3 int n i4 void InsertHeap(int x)5 if (n == maxsize) HeapFull()6 else7 n++8 i = n9 while ((i gt 1) ampamp (x gt heap[i2]))10 heap[i] = heap[i2]11 i = 212 13 heap[i] = x14 16

程序 InsertHeap 在插入第 n 個資料時需要的時間複雜度為 O(logn)

1 100

2 93

3 84

4 77

5 65

6 81

7 79

8 66

9 70

10 50

100

84

77

93

8165

66 70

79

50

1

2 3

4 5 6 7

8 9 10

1 define maxsize 1002 int heap[maxsize]3 int n i4 void InsertHeap(int x)5 if (n == maxsize) HeapFull()6 else7 n++8 i = n9 while ((i gt 1) ampamp (x gt heap[i2]))10 heap[i] = heap[i2]11 i = 212 13 heap[i] = x14 16

582 刪除最大堆積中的最大資料圖 (a) 中的最大堆積在刪除最大資料後其樹形應變成如圖 (b) 所示將最未一個樹葉節點的資料 6 暫挪至樹根節點內如圖 (c) 再將圖 (c) 中非最大堆積的結構調整成為最大堆積 10 和 8 中選大者與 6對調即成圖 (d)

程式 5-14 刪除最大堆積中的最大資料(上接程式 5-13 )1 int DeleteHeap()2 int x i j3 if (n == 0) HeapEmpty()4 else5 x = heap[1]6 heap[1] = heap[n]7 n--8 i = 19 while (i lt= n2)10 if (heap[2i] gt heap[2i+1])11 j = 2i12 else13 j = 2i+114 if (heap[i]gtheap[j]) break15 swap(heap[i] heap[j])16 i = j17 18 return x19 20

其時間複雜度與其堆積高度相同是為O(logn) 其中 n 為堆積中的元素個數

1 100

2 93

3 84

4 77

5 65

6 81

7 79

8 66

9 70

10 50

100

84

77

93

8165

66 70

79

50

1

2 3

4 5 6 7

8 9 10

1 int DeletHeap()2 int x i j3 if(n == 0) HeapEmpty()4 else5 x = heap[1]6 heap[1] = heap[n]7 n--8 i = 19 while (i lt= n2)10 if ([heap[2i]] gt heap[2i+1])11 j = 2i12 else13 j = 2i+114 if (heap[i]gtheap[j]) break15 swap(heap[i] heap[j])16 i = j17 18 return x19 20

583  優先佇列bull 由於最大堆積(或最小堆積)的結構可提供最大元素(或最小元素)而其維護堆積的成本為 O(logn) n 為總元素個數

bull 堆積常被當成「優先佇列」 (priority queue) 使用mdash此時佇列被組織成完備二元樹若新加入佇列的資料有大小關係優先順序的需求時此優先佇列即以 O(logn) 的代價將新資料放入優先佇列(可能是最大或最小堆積依實際大小關係優先順序的需求而定)中mdash提供依元素的優先順序做為處理順序的服務

bull 一般的佇列為先進先出先到的先行處理優先佇列則是依據其優先順序優先順序高者先行處理與來到的先後無關

範例 5-21bull 在「分時」 (time sharing) 的作業系統中我們不希望使用中央處理器 (Central Processing Unit CPU) 時間短的「處理程序」 (process) 等候過久於是有「最短時間工作優先」 (shortest-job-first) 的排程策略此時等候的處理程序即可組織成之一最小堆積而 CPU 每次即取出存於樹根的處理程序予以處理任何新來的處理程序則以 O(logn) 的時間加入此優先佇列(最小堆積)中

bull 注意之前提過的線性佇列新來的直接加入成為新的尾端元素花O(1) 的時間然線性佇列本身沒有大小的關係每次要取出最大(小)的資料得花O(n) 的代價(在所有元素中挑出最大(小)者) n 為佇列內的元素個數而優先佇列是在新元素加入時要花O(logn) 的時間至於取出最大(小)則只須 O(1) 的時間

59  二元搜尋樹bull 二元搜尋樹是一棵二元樹可能是空二元樹若不為空二元樹則滿足下列性質ndash所有節點內的資料內容是相異的 ndash左子節點的資內容 ( 如果有 ) 比父節點的資料內容小 ndash右子節點的資料內容 ( 如果有 ) 比父節點的資料內容大 ndash以左和右子節點為樹根的左子樹和右子樹也是二元搜尋樹

在二元搜尋樹中搜尋資料二元搜尋樹對資料大小的掌握十分明確若恰與節點 p 資料相等則搜尋成功若欲搜尋的資料 x 比樹節點 p 中的資料小 則只須搜尋節點 p 的左子樹若比節點 p 中的資料大 則只須搜尋 p 的右子樹這樣的判斷可以遞迴地應用在各子樹節點 中

程式 5-15 在二元搜尋樹中搜尋資料(遞迴呼叫)1 struct BSTreeNode2 struct BSTreeNode leftchild3 int data4 struct BSTreeNode rightchild5 6 struct BSTreeNode root7 struct BSTreeNode SearchBST(

struct BSTreeNode tree int x)8 if (tree == NULL) return NULL9 if (x == tree-gtdata) return tree10 if (x lt tree-gtdata)11 return SearchBST(tree-gtleftchild x)

12 return SearchBST(tree-gtrightchild x)13

程式 5-16 搜尋二元搜尋樹的資料(非遞迴呼叫)在二元搜尋樹十分龐大時遞迴呼叫的效率不會太好可利用迴圈來完成搜尋的動作以提高執行效率請看程式 5-16

14 struct BSTreeNode Search BST_iterative15 (struct BSTreeNode node int x)16 while (node = NULL)17 if (x == node-gtdata) return node18 if (x lt node-gtdata)19 node = node-gtleftchild20 else21 node = node-gtrightchild22 23 return NULL24

592  新增資料於二元搜尋樹中欲新增資料進入一二元搜尋樹中得先決定該資料在二元搜尋樹中應出現的位置這可透過上節提到的搜尋動作來解決

在二元搜尋樹定義的性質 (1) 中我們限定了元素必須相異所以搜尋新資料的結果必是 NULL

必須將搜尋的結果改為新增資料所應在位置的節點指標

程式 5-17  在二元搜尋樹中新增資料25 int InsertBST(int x)26 struct BSTreeNode p q27 p = root q = NULL28 while (p = NULL)29 q = p30 if (x == p-gtdata) return 0

若不考慮重複則可省略之31 if (x lt p-gtdata)32 p = p-gtleftchild33 else34 p = p-gtrightchild35

3636 p = (struct BSTreeNode ) p = (struct BSTreeNode ) malloc (sizeof(BSTreeNode))malloc (sizeof(BSTreeNode))

3737 p-gtdata = x p-gtdata = x3838 p-gtleftchild = p-gtrightchild = p-gtleftchild = p-gtrightchild = NULLNULL3939 if (root == NULL) root = p if (root == NULL) root = p4040 else if (x lt q-gtdata) else if (x lt q-gtdata)4141 q-gtleftchild = p q-gtleftchild = p4242 else else4343 q-gtrightchild = p q-gtrightchild = p4444 return 1 return 145 45

程式 程式 5-175-17  在二元搜尋樹中新增資料  在二元搜尋樹中新增資料 (( 續續 ))

範例 5-23 圖 (b) 為圖 (a) 新增整數 6 之後的結果圖 (c) (d) (e) (f) 則分別是逐步再加入 28 18

10 21 的結果

bull若目前的二元搜尋樹高度為 h 則新增資料得花O(h) 的時間找到其所應在的位置遂新增資料至二元搜尋樹中的時間複雜之度為 O(h)

593 自二元搜尋樹中刪除資料欲自二元搜尋樹中刪除資料可能會面臨兩種情況(1) 刪除節點 p 位於樹葉節點上如下圖所示 這是比較容易處理的情況

(2) 刪除的節點 p 不是樹葉刪除節點 p 後原 p 所指的節點可由 q ( q 中包含了 p 左子樹中的最大節點值)或 r ( r 中包含了 p 右子樹中的最小節點值)來取代以維持二元搜尋樹中資料的大小關係其結果分別如圖 (b) 和 (c) 所示而圖 (d) 則標明了指標會改變的節點位置即 p 會消失 father 的左或右子樹指標會異動 k(可能是 q 或 r )會移到原來 p 的位置其父節點 f 的左或右子樹指標會異動其中 father 為 p 父節點 f 為 k 的父節點

程式 5-18 自二元搜尋樹中刪除資料46 int DeleteBST (int x)47 Struct BSTreeNode p father k f48 p = root father = NULL49 while (p = NULL)50 if (x == p-gtdata) 找到 x 所在的節點 p 了51 if ((p-gtleftchild == NULL) ampamp 52 (p-gtrightchild == NULL))53 k = NULL p為樹葉節點54 else if (p-gtleftchild = NULL )x位於非子節

點55 f = p 找出 p左子樹的最右樹葉56 k = p-gtleftchild57 while (k-gtrightchild =NULL)58 f = k59 k = k-gtrightchild60 k將挪至原 p處61 if (p == f)62 f-gtleftchild = k-gtleftchild 63 else64 f-gtrightchild = k-gtleftchild 65

66 else p無左子樹67 f = p 找出 p右子樹的最左樹葉68 k = p-gtrightchild69 while (k-gtleftchild = NULL)70 f = k71 k = k -gtleftchild72 73 if (p == f)74 f-gtrightchild = k-gtrightchild 75 else76 f-gtleftchild = k-gtrightchild77 78 if (k = NULL) k挪至原 p處繼承 p的左右指標79 k-gtleftchild = p-gtleftchild80 k-gtrightchild = p-gtrightchild81 82 if (father == NULL) root = k83 else if (x lt father-gtdata) 決定 k是 father的左

或右兒子84 father-gtleftchild = k85 else86 father-gtrightchild = k87 free(p)88 return 1 成功地刪除 x於此傳回 1返回呼叫處89 51~89行為找到 x時應有的處理步驟

90 else 尚未找到 x繼續往下一階層找

91 father = p92 if (x lt p-gtdata)93 p = p-gtleftchild94 else95 p = p-gtrightchild96 97 98 return 0 未找到 x傳回 099

510  樹林 (forest)

bull 定義樹林(森林)是由 n 棵沒有交集的樹所構成的集合 n 0

下圖為一包含三棵樹的樹林

5101 轉換樹林為二元樹 之前我們以二元樹來表示樹因為二元樹有較多

好用的性質在此我們也將樹林轉換成二元樹看待

在 522 節中我們嘗試了用左子右兄弟的方式來表示樹這個方法事實上已將樹轉換成二元樹

即用左子右兄弟的結構重新組織樹林中的每一棵樹這些樹的樹根將視為兄弟而最左邊的樹則視為樹林二元樹的樹根如此一來樹林即可轉換成二元樹

範例 5-25右下圖為右上圖三棵樹所對應的三棵左子右兄弟二元樹

下圖則為上圖樹林所對應的一棵樹林二元樹

演算法 5-1  轉換樹林成二元樹輸入樹林 F = T1 T2 Tn

輸出 F 的二元樹表示 BT=(T1 T2 Tn)1 if (n == 0) 輸出空二元樹 2 else

3 root(T1 T2 Tn) = T1

4 leftsubtree(T1 T2 Tn) =

BT(T11 T12 T1m)5 BT(T11 T12 T1m)為 T1的子樹6 rightsubtree(T1 T2 Tn) =

BT(T2 T3 Tn)

樹林轉換成二元樹的過程樹林轉換成二元樹的過程

  • Slide 1
  • Slide 2
  • Slide 3
  • Slide 4
  • Slide 5
  • Slide 6
  • Slide 7
  • Slide 8
  • Slide 9
  • Slide 10
  • Slide 11
  • Slide 12
  • Slide 13
  • Slide 14
  • Slide 15
  • Slide 16
  • Slide 17
  • Slide 18
  • Slide 19
  • Slide 20
  • Slide 21
  • Slide 22
  • Slide 23
  • Slide 24
  • Slide 25
  • Slide 26
  • Slide 27
  • Slide 28
  • Slide 29
  • Slide 30
  • Slide 31
  • Slide 32
  • Slide 33
  • Slide 34
  • Slide 35
  • Slide 36
  • Slide 37
  • Slide 38
  • Slide 39
  • Slide 40
  • Slide 41
  • Slide 42
  • Slide 43
  • Slide 44
  • Slide 45
  • Slide 46
  • Slide 47
  • Slide 48
  • Slide 49
  • Slide 50
  • Slide 51
  • Slide 52
  • Slide 53
  • Slide 54
  • Slide 55
  • Slide 56
  • Slide 57
  • Slide 58
  • Slide 59
  • Slide 60
  • Slide 61
  • Slide 62
  • Slide 63
  • Slide 64
  • Slide 65
  • Slide 66
  • Slide 67
  • Slide 68
  • Slide 69
  • Slide 70
  • Slide 71
  • Slide 72
  • Slide 73
  • Slide 74
  • Slide 75
  • Slide 76
  • Slide 77
  • Slide 78
  • Slide 79
  • Slide 80
  • Slide 81
  • Slide 82
  • Slide 83
  • Slide 84
  • Slide 85
  • Slide 86
  • Slide 87
  • Slide 88
  • Slide 89
  • Slide 90
  • Slide 91
  • Slide 92
  • Slide 93
  • Slide 94
  • Slide 95
  • Slide 96
  • Slide 97
  • Slide 98
  • Slide 99
  • Slide 100
  • Slide 101
  • Slide 102
  • Slide 103
Page 68: 第五章 樹

572  在引線二元樹中加入節點在引線二元樹中樹葉節點節點的右子樹指標所指向或內部節點右子樹的最左樹葉為其中序後繼節點而且任一節點若無左子樹則其左子樹指標乃指向其中序前接節點hellip這些性質必須在節點新增進入引線二元樹時也要保持接下來討論節點 new 插入成為引線二元樹的節點 p 的右子節點的情形至於插入成為左子節點的情形則讓各位模擬推敲

572572  在引線二元樹中加入節點 在引線二元樹中加入節點 (( 續續 ))

newnew 會成為會成為 pp 的右子節點的情況共有以下兩種的右子節點的情況共有以下兩種(1) (1) 若若 pp 沒有右子節點 沒有右子節點 ((p-gtrightthread p-gtrightthread 為為1) 1) 如圖如圖 ((a) a) 則 則 new new 插入後應形成如圖插入後應形成如圖 ((b) b) 所示所示

(2)(2) 若若 pp 有右子節點 有右子節點 ((p-gtrightthread p-gtrightthread 為為0) 0) 如圖如圖 ((c) c) 則 則 new new 插入後應形成如插入後應形成如圖圖 ((d) d) 所示所示

程式 5-12  插入節點 new 成為引線二元樹中節點 p的右子節點(上接程式 5-10 5-11 )

1 void InsertRight(struct TBTreeNode p 2 struct TBTreeNode new)3 struct TBTreeNode q4 new-gtrightchild = p-gtrightchild5 new-gtrightthead = p-gtrightthread6 new-gtleftchild = p7 new-gtleftthread = 18 p-gtrightchild = new9 p-gtrightthead = 010 if (new-gtrightthread)11 q = Next(new)12 q-gtleftchild = new13 14

58  堆積bull 「堆積」 (heap) 是一個特殊的完備二元樹節點的資料與其子節點的資料之間有事先定好的大小關係存在bull 定義ndash最大堆疊為一完備二元樹任一節點的資料內容不小於其子節點的資料內容ndash最小堆疊為一完備二元樹任一節點的資料內容不大於其子節點的資料內容

581  插入節點至最大堆積中最大堆積是一完備二元樹所以如圖 (a) 之最大堆積於加入新節點後樹的形狀必須如圖 (b) 所示灰色節點即為新節點若加入的節點資料分別為 1 9 12 則其最大堆積將分別變成如圖 (c) (d) (e) 所示

程式 5-13 新增節點至最大堆積中最大堆積本身是一棵完備二元樹可以用陣列表示之 1 define maxsize 1002 int heap[maxsize]3 int n i4 void InsertHeap(int x)5 if (n == maxsize) HeapFull()6 else7 n++8 i = n9 while ((i gt 1) ampamp (x gt heap[i2]))10 heap[i] = heap[i2]11 i = 212 13 heap[i] = x14 16

程序 InsertHeap 在插入第 n 個資料時需要的時間複雜度為 O(logn)

1 100

2 93

3 84

4 77

5 65

6 81

7 79

8 66

9 70

10 50

100

84

77

93

8165

66 70

79

50

1

2 3

4 5 6 7

8 9 10

1 define maxsize 1002 int heap[maxsize]3 int n i4 void InsertHeap(int x)5 if (n == maxsize) HeapFull()6 else7 n++8 i = n9 while ((i gt 1) ampamp (x gt heap[i2]))10 heap[i] = heap[i2]11 i = 212 13 heap[i] = x14 16

582 刪除最大堆積中的最大資料圖 (a) 中的最大堆積在刪除最大資料後其樹形應變成如圖 (b) 所示將最未一個樹葉節點的資料 6 暫挪至樹根節點內如圖 (c) 再將圖 (c) 中非最大堆積的結構調整成為最大堆積 10 和 8 中選大者與 6對調即成圖 (d)

程式 5-14 刪除最大堆積中的最大資料(上接程式 5-13 )1 int DeleteHeap()2 int x i j3 if (n == 0) HeapEmpty()4 else5 x = heap[1]6 heap[1] = heap[n]7 n--8 i = 19 while (i lt= n2)10 if (heap[2i] gt heap[2i+1])11 j = 2i12 else13 j = 2i+114 if (heap[i]gtheap[j]) break15 swap(heap[i] heap[j])16 i = j17 18 return x19 20

其時間複雜度與其堆積高度相同是為O(logn) 其中 n 為堆積中的元素個數

1 100

2 93

3 84

4 77

5 65

6 81

7 79

8 66

9 70

10 50

100

84

77

93

8165

66 70

79

50

1

2 3

4 5 6 7

8 9 10

1 int DeletHeap()2 int x i j3 if(n == 0) HeapEmpty()4 else5 x = heap[1]6 heap[1] = heap[n]7 n--8 i = 19 while (i lt= n2)10 if ([heap[2i]] gt heap[2i+1])11 j = 2i12 else13 j = 2i+114 if (heap[i]gtheap[j]) break15 swap(heap[i] heap[j])16 i = j17 18 return x19 20

583  優先佇列bull 由於最大堆積(或最小堆積)的結構可提供最大元素(或最小元素)而其維護堆積的成本為 O(logn) n 為總元素個數

bull 堆積常被當成「優先佇列」 (priority queue) 使用mdash此時佇列被組織成完備二元樹若新加入佇列的資料有大小關係優先順序的需求時此優先佇列即以 O(logn) 的代價將新資料放入優先佇列(可能是最大或最小堆積依實際大小關係優先順序的需求而定)中mdash提供依元素的優先順序做為處理順序的服務

bull 一般的佇列為先進先出先到的先行處理優先佇列則是依據其優先順序優先順序高者先行處理與來到的先後無關

範例 5-21bull 在「分時」 (time sharing) 的作業系統中我們不希望使用中央處理器 (Central Processing Unit CPU) 時間短的「處理程序」 (process) 等候過久於是有「最短時間工作優先」 (shortest-job-first) 的排程策略此時等候的處理程序即可組織成之一最小堆積而 CPU 每次即取出存於樹根的處理程序予以處理任何新來的處理程序則以 O(logn) 的時間加入此優先佇列(最小堆積)中

bull 注意之前提過的線性佇列新來的直接加入成為新的尾端元素花O(1) 的時間然線性佇列本身沒有大小的關係每次要取出最大(小)的資料得花O(n) 的代價(在所有元素中挑出最大(小)者) n 為佇列內的元素個數而優先佇列是在新元素加入時要花O(logn) 的時間至於取出最大(小)則只須 O(1) 的時間

59  二元搜尋樹bull 二元搜尋樹是一棵二元樹可能是空二元樹若不為空二元樹則滿足下列性質ndash所有節點內的資料內容是相異的 ndash左子節點的資內容 ( 如果有 ) 比父節點的資料內容小 ndash右子節點的資料內容 ( 如果有 ) 比父節點的資料內容大 ndash以左和右子節點為樹根的左子樹和右子樹也是二元搜尋樹

在二元搜尋樹中搜尋資料二元搜尋樹對資料大小的掌握十分明確若恰與節點 p 資料相等則搜尋成功若欲搜尋的資料 x 比樹節點 p 中的資料小 則只須搜尋節點 p 的左子樹若比節點 p 中的資料大 則只須搜尋 p 的右子樹這樣的判斷可以遞迴地應用在各子樹節點 中

程式 5-15 在二元搜尋樹中搜尋資料(遞迴呼叫)1 struct BSTreeNode2 struct BSTreeNode leftchild3 int data4 struct BSTreeNode rightchild5 6 struct BSTreeNode root7 struct BSTreeNode SearchBST(

struct BSTreeNode tree int x)8 if (tree == NULL) return NULL9 if (x == tree-gtdata) return tree10 if (x lt tree-gtdata)11 return SearchBST(tree-gtleftchild x)

12 return SearchBST(tree-gtrightchild x)13

程式 5-16 搜尋二元搜尋樹的資料(非遞迴呼叫)在二元搜尋樹十分龐大時遞迴呼叫的效率不會太好可利用迴圈來完成搜尋的動作以提高執行效率請看程式 5-16

14 struct BSTreeNode Search BST_iterative15 (struct BSTreeNode node int x)16 while (node = NULL)17 if (x == node-gtdata) return node18 if (x lt node-gtdata)19 node = node-gtleftchild20 else21 node = node-gtrightchild22 23 return NULL24

592  新增資料於二元搜尋樹中欲新增資料進入一二元搜尋樹中得先決定該資料在二元搜尋樹中應出現的位置這可透過上節提到的搜尋動作來解決

在二元搜尋樹定義的性質 (1) 中我們限定了元素必須相異所以搜尋新資料的結果必是 NULL

必須將搜尋的結果改為新增資料所應在位置的節點指標

程式 5-17  在二元搜尋樹中新增資料25 int InsertBST(int x)26 struct BSTreeNode p q27 p = root q = NULL28 while (p = NULL)29 q = p30 if (x == p-gtdata) return 0

若不考慮重複則可省略之31 if (x lt p-gtdata)32 p = p-gtleftchild33 else34 p = p-gtrightchild35

3636 p = (struct BSTreeNode ) p = (struct BSTreeNode ) malloc (sizeof(BSTreeNode))malloc (sizeof(BSTreeNode))

3737 p-gtdata = x p-gtdata = x3838 p-gtleftchild = p-gtrightchild = p-gtleftchild = p-gtrightchild = NULLNULL3939 if (root == NULL) root = p if (root == NULL) root = p4040 else if (x lt q-gtdata) else if (x lt q-gtdata)4141 q-gtleftchild = p q-gtleftchild = p4242 else else4343 q-gtrightchild = p q-gtrightchild = p4444 return 1 return 145 45

程式 程式 5-175-17  在二元搜尋樹中新增資料  在二元搜尋樹中新增資料 (( 續續 ))

範例 5-23 圖 (b) 為圖 (a) 新增整數 6 之後的結果圖 (c) (d) (e) (f) 則分別是逐步再加入 28 18

10 21 的結果

bull若目前的二元搜尋樹高度為 h 則新增資料得花O(h) 的時間找到其所應在的位置遂新增資料至二元搜尋樹中的時間複雜之度為 O(h)

593 自二元搜尋樹中刪除資料欲自二元搜尋樹中刪除資料可能會面臨兩種情況(1) 刪除節點 p 位於樹葉節點上如下圖所示 這是比較容易處理的情況

(2) 刪除的節點 p 不是樹葉刪除節點 p 後原 p 所指的節點可由 q ( q 中包含了 p 左子樹中的最大節點值)或 r ( r 中包含了 p 右子樹中的最小節點值)來取代以維持二元搜尋樹中資料的大小關係其結果分別如圖 (b) 和 (c) 所示而圖 (d) 則標明了指標會改變的節點位置即 p 會消失 father 的左或右子樹指標會異動 k(可能是 q 或 r )會移到原來 p 的位置其父節點 f 的左或右子樹指標會異動其中 father 為 p 父節點 f 為 k 的父節點

程式 5-18 自二元搜尋樹中刪除資料46 int DeleteBST (int x)47 Struct BSTreeNode p father k f48 p = root father = NULL49 while (p = NULL)50 if (x == p-gtdata) 找到 x 所在的節點 p 了51 if ((p-gtleftchild == NULL) ampamp 52 (p-gtrightchild == NULL))53 k = NULL p為樹葉節點54 else if (p-gtleftchild = NULL )x位於非子節

點55 f = p 找出 p左子樹的最右樹葉56 k = p-gtleftchild57 while (k-gtrightchild =NULL)58 f = k59 k = k-gtrightchild60 k將挪至原 p處61 if (p == f)62 f-gtleftchild = k-gtleftchild 63 else64 f-gtrightchild = k-gtleftchild 65

66 else p無左子樹67 f = p 找出 p右子樹的最左樹葉68 k = p-gtrightchild69 while (k-gtleftchild = NULL)70 f = k71 k = k -gtleftchild72 73 if (p == f)74 f-gtrightchild = k-gtrightchild 75 else76 f-gtleftchild = k-gtrightchild77 78 if (k = NULL) k挪至原 p處繼承 p的左右指標79 k-gtleftchild = p-gtleftchild80 k-gtrightchild = p-gtrightchild81 82 if (father == NULL) root = k83 else if (x lt father-gtdata) 決定 k是 father的左

或右兒子84 father-gtleftchild = k85 else86 father-gtrightchild = k87 free(p)88 return 1 成功地刪除 x於此傳回 1返回呼叫處89 51~89行為找到 x時應有的處理步驟

90 else 尚未找到 x繼續往下一階層找

91 father = p92 if (x lt p-gtdata)93 p = p-gtleftchild94 else95 p = p-gtrightchild96 97 98 return 0 未找到 x傳回 099

510  樹林 (forest)

bull 定義樹林(森林)是由 n 棵沒有交集的樹所構成的集合 n 0

下圖為一包含三棵樹的樹林

5101 轉換樹林為二元樹 之前我們以二元樹來表示樹因為二元樹有較多

好用的性質在此我們也將樹林轉換成二元樹看待

在 522 節中我們嘗試了用左子右兄弟的方式來表示樹這個方法事實上已將樹轉換成二元樹

即用左子右兄弟的結構重新組織樹林中的每一棵樹這些樹的樹根將視為兄弟而最左邊的樹則視為樹林二元樹的樹根如此一來樹林即可轉換成二元樹

範例 5-25右下圖為右上圖三棵樹所對應的三棵左子右兄弟二元樹

下圖則為上圖樹林所對應的一棵樹林二元樹

演算法 5-1  轉換樹林成二元樹輸入樹林 F = T1 T2 Tn

輸出 F 的二元樹表示 BT=(T1 T2 Tn)1 if (n == 0) 輸出空二元樹 2 else

3 root(T1 T2 Tn) = T1

4 leftsubtree(T1 T2 Tn) =

BT(T11 T12 T1m)5 BT(T11 T12 T1m)為 T1的子樹6 rightsubtree(T1 T2 Tn) =

BT(T2 T3 Tn)

樹林轉換成二元樹的過程樹林轉換成二元樹的過程

  • Slide 1
  • Slide 2
  • Slide 3
  • Slide 4
  • Slide 5
  • Slide 6
  • Slide 7
  • Slide 8
  • Slide 9
  • Slide 10
  • Slide 11
  • Slide 12
  • Slide 13
  • Slide 14
  • Slide 15
  • Slide 16
  • Slide 17
  • Slide 18
  • Slide 19
  • Slide 20
  • Slide 21
  • Slide 22
  • Slide 23
  • Slide 24
  • Slide 25
  • Slide 26
  • Slide 27
  • Slide 28
  • Slide 29
  • Slide 30
  • Slide 31
  • Slide 32
  • Slide 33
  • Slide 34
  • Slide 35
  • Slide 36
  • Slide 37
  • Slide 38
  • Slide 39
  • Slide 40
  • Slide 41
  • Slide 42
  • Slide 43
  • Slide 44
  • Slide 45
  • Slide 46
  • Slide 47
  • Slide 48
  • Slide 49
  • Slide 50
  • Slide 51
  • Slide 52
  • Slide 53
  • Slide 54
  • Slide 55
  • Slide 56
  • Slide 57
  • Slide 58
  • Slide 59
  • Slide 60
  • Slide 61
  • Slide 62
  • Slide 63
  • Slide 64
  • Slide 65
  • Slide 66
  • Slide 67
  • Slide 68
  • Slide 69
  • Slide 70
  • Slide 71
  • Slide 72
  • Slide 73
  • Slide 74
  • Slide 75
  • Slide 76
  • Slide 77
  • Slide 78
  • Slide 79
  • Slide 80
  • Slide 81
  • Slide 82
  • Slide 83
  • Slide 84
  • Slide 85
  • Slide 86
  • Slide 87
  • Slide 88
  • Slide 89
  • Slide 90
  • Slide 91
  • Slide 92
  • Slide 93
  • Slide 94
  • Slide 95
  • Slide 96
  • Slide 97
  • Slide 98
  • Slide 99
  • Slide 100
  • Slide 101
  • Slide 102
  • Slide 103
Page 69: 第五章 樹

572572  在引線二元樹中加入節點 在引線二元樹中加入節點 (( 續續 ))

newnew 會成為會成為 pp 的右子節點的情況共有以下兩種的右子節點的情況共有以下兩種(1) (1) 若若 pp 沒有右子節點 沒有右子節點 ((p-gtrightthread p-gtrightthread 為為1) 1) 如圖如圖 ((a) a) 則 則 new new 插入後應形成如圖插入後應形成如圖 ((b) b) 所示所示

(2)(2) 若若 pp 有右子節點 有右子節點 ((p-gtrightthread p-gtrightthread 為為0) 0) 如圖如圖 ((c) c) 則 則 new new 插入後應形成如插入後應形成如圖圖 ((d) d) 所示所示

程式 5-12  插入節點 new 成為引線二元樹中節點 p的右子節點(上接程式 5-10 5-11 )

1 void InsertRight(struct TBTreeNode p 2 struct TBTreeNode new)3 struct TBTreeNode q4 new-gtrightchild = p-gtrightchild5 new-gtrightthead = p-gtrightthread6 new-gtleftchild = p7 new-gtleftthread = 18 p-gtrightchild = new9 p-gtrightthead = 010 if (new-gtrightthread)11 q = Next(new)12 q-gtleftchild = new13 14

58  堆積bull 「堆積」 (heap) 是一個特殊的完備二元樹節點的資料與其子節點的資料之間有事先定好的大小關係存在bull 定義ndash最大堆疊為一完備二元樹任一節點的資料內容不小於其子節點的資料內容ndash最小堆疊為一完備二元樹任一節點的資料內容不大於其子節點的資料內容

581  插入節點至最大堆積中最大堆積是一完備二元樹所以如圖 (a) 之最大堆積於加入新節點後樹的形狀必須如圖 (b) 所示灰色節點即為新節點若加入的節點資料分別為 1 9 12 則其最大堆積將分別變成如圖 (c) (d) (e) 所示

程式 5-13 新增節點至最大堆積中最大堆積本身是一棵完備二元樹可以用陣列表示之 1 define maxsize 1002 int heap[maxsize]3 int n i4 void InsertHeap(int x)5 if (n == maxsize) HeapFull()6 else7 n++8 i = n9 while ((i gt 1) ampamp (x gt heap[i2]))10 heap[i] = heap[i2]11 i = 212 13 heap[i] = x14 16

程序 InsertHeap 在插入第 n 個資料時需要的時間複雜度為 O(logn)

1 100

2 93

3 84

4 77

5 65

6 81

7 79

8 66

9 70

10 50

100

84

77

93

8165

66 70

79

50

1

2 3

4 5 6 7

8 9 10

1 define maxsize 1002 int heap[maxsize]3 int n i4 void InsertHeap(int x)5 if (n == maxsize) HeapFull()6 else7 n++8 i = n9 while ((i gt 1) ampamp (x gt heap[i2]))10 heap[i] = heap[i2]11 i = 212 13 heap[i] = x14 16

582 刪除最大堆積中的最大資料圖 (a) 中的最大堆積在刪除最大資料後其樹形應變成如圖 (b) 所示將最未一個樹葉節點的資料 6 暫挪至樹根節點內如圖 (c) 再將圖 (c) 中非最大堆積的結構調整成為最大堆積 10 和 8 中選大者與 6對調即成圖 (d)

程式 5-14 刪除最大堆積中的最大資料(上接程式 5-13 )1 int DeleteHeap()2 int x i j3 if (n == 0) HeapEmpty()4 else5 x = heap[1]6 heap[1] = heap[n]7 n--8 i = 19 while (i lt= n2)10 if (heap[2i] gt heap[2i+1])11 j = 2i12 else13 j = 2i+114 if (heap[i]gtheap[j]) break15 swap(heap[i] heap[j])16 i = j17 18 return x19 20

其時間複雜度與其堆積高度相同是為O(logn) 其中 n 為堆積中的元素個數

1 100

2 93

3 84

4 77

5 65

6 81

7 79

8 66

9 70

10 50

100

84

77

93

8165

66 70

79

50

1

2 3

4 5 6 7

8 9 10

1 int DeletHeap()2 int x i j3 if(n == 0) HeapEmpty()4 else5 x = heap[1]6 heap[1] = heap[n]7 n--8 i = 19 while (i lt= n2)10 if ([heap[2i]] gt heap[2i+1])11 j = 2i12 else13 j = 2i+114 if (heap[i]gtheap[j]) break15 swap(heap[i] heap[j])16 i = j17 18 return x19 20

583  優先佇列bull 由於最大堆積(或最小堆積)的結構可提供最大元素(或最小元素)而其維護堆積的成本為 O(logn) n 為總元素個數

bull 堆積常被當成「優先佇列」 (priority queue) 使用mdash此時佇列被組織成完備二元樹若新加入佇列的資料有大小關係優先順序的需求時此優先佇列即以 O(logn) 的代價將新資料放入優先佇列(可能是最大或最小堆積依實際大小關係優先順序的需求而定)中mdash提供依元素的優先順序做為處理順序的服務

bull 一般的佇列為先進先出先到的先行處理優先佇列則是依據其優先順序優先順序高者先行處理與來到的先後無關

範例 5-21bull 在「分時」 (time sharing) 的作業系統中我們不希望使用中央處理器 (Central Processing Unit CPU) 時間短的「處理程序」 (process) 等候過久於是有「最短時間工作優先」 (shortest-job-first) 的排程策略此時等候的處理程序即可組織成之一最小堆積而 CPU 每次即取出存於樹根的處理程序予以處理任何新來的處理程序則以 O(logn) 的時間加入此優先佇列(最小堆積)中

bull 注意之前提過的線性佇列新來的直接加入成為新的尾端元素花O(1) 的時間然線性佇列本身沒有大小的關係每次要取出最大(小)的資料得花O(n) 的代價(在所有元素中挑出最大(小)者) n 為佇列內的元素個數而優先佇列是在新元素加入時要花O(logn) 的時間至於取出最大(小)則只須 O(1) 的時間

59  二元搜尋樹bull 二元搜尋樹是一棵二元樹可能是空二元樹若不為空二元樹則滿足下列性質ndash所有節點內的資料內容是相異的 ndash左子節點的資內容 ( 如果有 ) 比父節點的資料內容小 ndash右子節點的資料內容 ( 如果有 ) 比父節點的資料內容大 ndash以左和右子節點為樹根的左子樹和右子樹也是二元搜尋樹

在二元搜尋樹中搜尋資料二元搜尋樹對資料大小的掌握十分明確若恰與節點 p 資料相等則搜尋成功若欲搜尋的資料 x 比樹節點 p 中的資料小 則只須搜尋節點 p 的左子樹若比節點 p 中的資料大 則只須搜尋 p 的右子樹這樣的判斷可以遞迴地應用在各子樹節點 中

程式 5-15 在二元搜尋樹中搜尋資料(遞迴呼叫)1 struct BSTreeNode2 struct BSTreeNode leftchild3 int data4 struct BSTreeNode rightchild5 6 struct BSTreeNode root7 struct BSTreeNode SearchBST(

struct BSTreeNode tree int x)8 if (tree == NULL) return NULL9 if (x == tree-gtdata) return tree10 if (x lt tree-gtdata)11 return SearchBST(tree-gtleftchild x)

12 return SearchBST(tree-gtrightchild x)13

程式 5-16 搜尋二元搜尋樹的資料(非遞迴呼叫)在二元搜尋樹十分龐大時遞迴呼叫的效率不會太好可利用迴圈來完成搜尋的動作以提高執行效率請看程式 5-16

14 struct BSTreeNode Search BST_iterative15 (struct BSTreeNode node int x)16 while (node = NULL)17 if (x == node-gtdata) return node18 if (x lt node-gtdata)19 node = node-gtleftchild20 else21 node = node-gtrightchild22 23 return NULL24

592  新增資料於二元搜尋樹中欲新增資料進入一二元搜尋樹中得先決定該資料在二元搜尋樹中應出現的位置這可透過上節提到的搜尋動作來解決

在二元搜尋樹定義的性質 (1) 中我們限定了元素必須相異所以搜尋新資料的結果必是 NULL

必須將搜尋的結果改為新增資料所應在位置的節點指標

程式 5-17  在二元搜尋樹中新增資料25 int InsertBST(int x)26 struct BSTreeNode p q27 p = root q = NULL28 while (p = NULL)29 q = p30 if (x == p-gtdata) return 0

若不考慮重複則可省略之31 if (x lt p-gtdata)32 p = p-gtleftchild33 else34 p = p-gtrightchild35

3636 p = (struct BSTreeNode ) p = (struct BSTreeNode ) malloc (sizeof(BSTreeNode))malloc (sizeof(BSTreeNode))

3737 p-gtdata = x p-gtdata = x3838 p-gtleftchild = p-gtrightchild = p-gtleftchild = p-gtrightchild = NULLNULL3939 if (root == NULL) root = p if (root == NULL) root = p4040 else if (x lt q-gtdata) else if (x lt q-gtdata)4141 q-gtleftchild = p q-gtleftchild = p4242 else else4343 q-gtrightchild = p q-gtrightchild = p4444 return 1 return 145 45

程式 程式 5-175-17  在二元搜尋樹中新增資料  在二元搜尋樹中新增資料 (( 續續 ))

範例 5-23 圖 (b) 為圖 (a) 新增整數 6 之後的結果圖 (c) (d) (e) (f) 則分別是逐步再加入 28 18

10 21 的結果

bull若目前的二元搜尋樹高度為 h 則新增資料得花O(h) 的時間找到其所應在的位置遂新增資料至二元搜尋樹中的時間複雜之度為 O(h)

593 自二元搜尋樹中刪除資料欲自二元搜尋樹中刪除資料可能會面臨兩種情況(1) 刪除節點 p 位於樹葉節點上如下圖所示 這是比較容易處理的情況

(2) 刪除的節點 p 不是樹葉刪除節點 p 後原 p 所指的節點可由 q ( q 中包含了 p 左子樹中的最大節點值)或 r ( r 中包含了 p 右子樹中的最小節點值)來取代以維持二元搜尋樹中資料的大小關係其結果分別如圖 (b) 和 (c) 所示而圖 (d) 則標明了指標會改變的節點位置即 p 會消失 father 的左或右子樹指標會異動 k(可能是 q 或 r )會移到原來 p 的位置其父節點 f 的左或右子樹指標會異動其中 father 為 p 父節點 f 為 k 的父節點

程式 5-18 自二元搜尋樹中刪除資料46 int DeleteBST (int x)47 Struct BSTreeNode p father k f48 p = root father = NULL49 while (p = NULL)50 if (x == p-gtdata) 找到 x 所在的節點 p 了51 if ((p-gtleftchild == NULL) ampamp 52 (p-gtrightchild == NULL))53 k = NULL p為樹葉節點54 else if (p-gtleftchild = NULL )x位於非子節

點55 f = p 找出 p左子樹的最右樹葉56 k = p-gtleftchild57 while (k-gtrightchild =NULL)58 f = k59 k = k-gtrightchild60 k將挪至原 p處61 if (p == f)62 f-gtleftchild = k-gtleftchild 63 else64 f-gtrightchild = k-gtleftchild 65

66 else p無左子樹67 f = p 找出 p右子樹的最左樹葉68 k = p-gtrightchild69 while (k-gtleftchild = NULL)70 f = k71 k = k -gtleftchild72 73 if (p == f)74 f-gtrightchild = k-gtrightchild 75 else76 f-gtleftchild = k-gtrightchild77 78 if (k = NULL) k挪至原 p處繼承 p的左右指標79 k-gtleftchild = p-gtleftchild80 k-gtrightchild = p-gtrightchild81 82 if (father == NULL) root = k83 else if (x lt father-gtdata) 決定 k是 father的左

或右兒子84 father-gtleftchild = k85 else86 father-gtrightchild = k87 free(p)88 return 1 成功地刪除 x於此傳回 1返回呼叫處89 51~89行為找到 x時應有的處理步驟

90 else 尚未找到 x繼續往下一階層找

91 father = p92 if (x lt p-gtdata)93 p = p-gtleftchild94 else95 p = p-gtrightchild96 97 98 return 0 未找到 x傳回 099

510  樹林 (forest)

bull 定義樹林(森林)是由 n 棵沒有交集的樹所構成的集合 n 0

下圖為一包含三棵樹的樹林

5101 轉換樹林為二元樹 之前我們以二元樹來表示樹因為二元樹有較多

好用的性質在此我們也將樹林轉換成二元樹看待

在 522 節中我們嘗試了用左子右兄弟的方式來表示樹這個方法事實上已將樹轉換成二元樹

即用左子右兄弟的結構重新組織樹林中的每一棵樹這些樹的樹根將視為兄弟而最左邊的樹則視為樹林二元樹的樹根如此一來樹林即可轉換成二元樹

範例 5-25右下圖為右上圖三棵樹所對應的三棵左子右兄弟二元樹

下圖則為上圖樹林所對應的一棵樹林二元樹

演算法 5-1  轉換樹林成二元樹輸入樹林 F = T1 T2 Tn

輸出 F 的二元樹表示 BT=(T1 T2 Tn)1 if (n == 0) 輸出空二元樹 2 else

3 root(T1 T2 Tn) = T1

4 leftsubtree(T1 T2 Tn) =

BT(T11 T12 T1m)5 BT(T11 T12 T1m)為 T1的子樹6 rightsubtree(T1 T2 Tn) =

BT(T2 T3 Tn)

樹林轉換成二元樹的過程樹林轉換成二元樹的過程

  • Slide 1
  • Slide 2
  • Slide 3
  • Slide 4
  • Slide 5
  • Slide 6
  • Slide 7
  • Slide 8
  • Slide 9
  • Slide 10
  • Slide 11
  • Slide 12
  • Slide 13
  • Slide 14
  • Slide 15
  • Slide 16
  • Slide 17
  • Slide 18
  • Slide 19
  • Slide 20
  • Slide 21
  • Slide 22
  • Slide 23
  • Slide 24
  • Slide 25
  • Slide 26
  • Slide 27
  • Slide 28
  • Slide 29
  • Slide 30
  • Slide 31
  • Slide 32
  • Slide 33
  • Slide 34
  • Slide 35
  • Slide 36
  • Slide 37
  • Slide 38
  • Slide 39
  • Slide 40
  • Slide 41
  • Slide 42
  • Slide 43
  • Slide 44
  • Slide 45
  • Slide 46
  • Slide 47
  • Slide 48
  • Slide 49
  • Slide 50
  • Slide 51
  • Slide 52
  • Slide 53
  • Slide 54
  • Slide 55
  • Slide 56
  • Slide 57
  • Slide 58
  • Slide 59
  • Slide 60
  • Slide 61
  • Slide 62
  • Slide 63
  • Slide 64
  • Slide 65
  • Slide 66
  • Slide 67
  • Slide 68
  • Slide 69
  • Slide 70
  • Slide 71
  • Slide 72
  • Slide 73
  • Slide 74
  • Slide 75
  • Slide 76
  • Slide 77
  • Slide 78
  • Slide 79
  • Slide 80
  • Slide 81
  • Slide 82
  • Slide 83
  • Slide 84
  • Slide 85
  • Slide 86
  • Slide 87
  • Slide 88
  • Slide 89
  • Slide 90
  • Slide 91
  • Slide 92
  • Slide 93
  • Slide 94
  • Slide 95
  • Slide 96
  • Slide 97
  • Slide 98
  • Slide 99
  • Slide 100
  • Slide 101
  • Slide 102
  • Slide 103
Page 70: 第五章 樹

(2)(2) 若若 pp 有右子節點 有右子節點 ((p-gtrightthread p-gtrightthread 為為0) 0) 如圖如圖 ((c) c) 則 則 new new 插入後應形成如插入後應形成如圖圖 ((d) d) 所示所示

程式 5-12  插入節點 new 成為引線二元樹中節點 p的右子節點(上接程式 5-10 5-11 )

1 void InsertRight(struct TBTreeNode p 2 struct TBTreeNode new)3 struct TBTreeNode q4 new-gtrightchild = p-gtrightchild5 new-gtrightthead = p-gtrightthread6 new-gtleftchild = p7 new-gtleftthread = 18 p-gtrightchild = new9 p-gtrightthead = 010 if (new-gtrightthread)11 q = Next(new)12 q-gtleftchild = new13 14

58  堆積bull 「堆積」 (heap) 是一個特殊的完備二元樹節點的資料與其子節點的資料之間有事先定好的大小關係存在bull 定義ndash最大堆疊為一完備二元樹任一節點的資料內容不小於其子節點的資料內容ndash最小堆疊為一完備二元樹任一節點的資料內容不大於其子節點的資料內容

581  插入節點至最大堆積中最大堆積是一完備二元樹所以如圖 (a) 之最大堆積於加入新節點後樹的形狀必須如圖 (b) 所示灰色節點即為新節點若加入的節點資料分別為 1 9 12 則其最大堆積將分別變成如圖 (c) (d) (e) 所示

程式 5-13 新增節點至最大堆積中最大堆積本身是一棵完備二元樹可以用陣列表示之 1 define maxsize 1002 int heap[maxsize]3 int n i4 void InsertHeap(int x)5 if (n == maxsize) HeapFull()6 else7 n++8 i = n9 while ((i gt 1) ampamp (x gt heap[i2]))10 heap[i] = heap[i2]11 i = 212 13 heap[i] = x14 16

程序 InsertHeap 在插入第 n 個資料時需要的時間複雜度為 O(logn)

1 100

2 93

3 84

4 77

5 65

6 81

7 79

8 66

9 70

10 50

100

84

77

93

8165

66 70

79

50

1

2 3

4 5 6 7

8 9 10

1 define maxsize 1002 int heap[maxsize]3 int n i4 void InsertHeap(int x)5 if (n == maxsize) HeapFull()6 else7 n++8 i = n9 while ((i gt 1) ampamp (x gt heap[i2]))10 heap[i] = heap[i2]11 i = 212 13 heap[i] = x14 16

582 刪除最大堆積中的最大資料圖 (a) 中的最大堆積在刪除最大資料後其樹形應變成如圖 (b) 所示將最未一個樹葉節點的資料 6 暫挪至樹根節點內如圖 (c) 再將圖 (c) 中非最大堆積的結構調整成為最大堆積 10 和 8 中選大者與 6對調即成圖 (d)

程式 5-14 刪除最大堆積中的最大資料(上接程式 5-13 )1 int DeleteHeap()2 int x i j3 if (n == 0) HeapEmpty()4 else5 x = heap[1]6 heap[1] = heap[n]7 n--8 i = 19 while (i lt= n2)10 if (heap[2i] gt heap[2i+1])11 j = 2i12 else13 j = 2i+114 if (heap[i]gtheap[j]) break15 swap(heap[i] heap[j])16 i = j17 18 return x19 20

其時間複雜度與其堆積高度相同是為O(logn) 其中 n 為堆積中的元素個數

1 100

2 93

3 84

4 77

5 65

6 81

7 79

8 66

9 70

10 50

100

84

77

93

8165

66 70

79

50

1

2 3

4 5 6 7

8 9 10

1 int DeletHeap()2 int x i j3 if(n == 0) HeapEmpty()4 else5 x = heap[1]6 heap[1] = heap[n]7 n--8 i = 19 while (i lt= n2)10 if ([heap[2i]] gt heap[2i+1])11 j = 2i12 else13 j = 2i+114 if (heap[i]gtheap[j]) break15 swap(heap[i] heap[j])16 i = j17 18 return x19 20

583  優先佇列bull 由於最大堆積(或最小堆積)的結構可提供最大元素(或最小元素)而其維護堆積的成本為 O(logn) n 為總元素個數

bull 堆積常被當成「優先佇列」 (priority queue) 使用mdash此時佇列被組織成完備二元樹若新加入佇列的資料有大小關係優先順序的需求時此優先佇列即以 O(logn) 的代價將新資料放入優先佇列(可能是最大或最小堆積依實際大小關係優先順序的需求而定)中mdash提供依元素的優先順序做為處理順序的服務

bull 一般的佇列為先進先出先到的先行處理優先佇列則是依據其優先順序優先順序高者先行處理與來到的先後無關

範例 5-21bull 在「分時」 (time sharing) 的作業系統中我們不希望使用中央處理器 (Central Processing Unit CPU) 時間短的「處理程序」 (process) 等候過久於是有「最短時間工作優先」 (shortest-job-first) 的排程策略此時等候的處理程序即可組織成之一最小堆積而 CPU 每次即取出存於樹根的處理程序予以處理任何新來的處理程序則以 O(logn) 的時間加入此優先佇列(最小堆積)中

bull 注意之前提過的線性佇列新來的直接加入成為新的尾端元素花O(1) 的時間然線性佇列本身沒有大小的關係每次要取出最大(小)的資料得花O(n) 的代價(在所有元素中挑出最大(小)者) n 為佇列內的元素個數而優先佇列是在新元素加入時要花O(logn) 的時間至於取出最大(小)則只須 O(1) 的時間

59  二元搜尋樹bull 二元搜尋樹是一棵二元樹可能是空二元樹若不為空二元樹則滿足下列性質ndash所有節點內的資料內容是相異的 ndash左子節點的資內容 ( 如果有 ) 比父節點的資料內容小 ndash右子節點的資料內容 ( 如果有 ) 比父節點的資料內容大 ndash以左和右子節點為樹根的左子樹和右子樹也是二元搜尋樹

在二元搜尋樹中搜尋資料二元搜尋樹對資料大小的掌握十分明確若恰與節點 p 資料相等則搜尋成功若欲搜尋的資料 x 比樹節點 p 中的資料小 則只須搜尋節點 p 的左子樹若比節點 p 中的資料大 則只須搜尋 p 的右子樹這樣的判斷可以遞迴地應用在各子樹節點 中

程式 5-15 在二元搜尋樹中搜尋資料(遞迴呼叫)1 struct BSTreeNode2 struct BSTreeNode leftchild3 int data4 struct BSTreeNode rightchild5 6 struct BSTreeNode root7 struct BSTreeNode SearchBST(

struct BSTreeNode tree int x)8 if (tree == NULL) return NULL9 if (x == tree-gtdata) return tree10 if (x lt tree-gtdata)11 return SearchBST(tree-gtleftchild x)

12 return SearchBST(tree-gtrightchild x)13

程式 5-16 搜尋二元搜尋樹的資料(非遞迴呼叫)在二元搜尋樹十分龐大時遞迴呼叫的效率不會太好可利用迴圈來完成搜尋的動作以提高執行效率請看程式 5-16

14 struct BSTreeNode Search BST_iterative15 (struct BSTreeNode node int x)16 while (node = NULL)17 if (x == node-gtdata) return node18 if (x lt node-gtdata)19 node = node-gtleftchild20 else21 node = node-gtrightchild22 23 return NULL24

592  新增資料於二元搜尋樹中欲新增資料進入一二元搜尋樹中得先決定該資料在二元搜尋樹中應出現的位置這可透過上節提到的搜尋動作來解決

在二元搜尋樹定義的性質 (1) 中我們限定了元素必須相異所以搜尋新資料的結果必是 NULL

必須將搜尋的結果改為新增資料所應在位置的節點指標

程式 5-17  在二元搜尋樹中新增資料25 int InsertBST(int x)26 struct BSTreeNode p q27 p = root q = NULL28 while (p = NULL)29 q = p30 if (x == p-gtdata) return 0

若不考慮重複則可省略之31 if (x lt p-gtdata)32 p = p-gtleftchild33 else34 p = p-gtrightchild35

3636 p = (struct BSTreeNode ) p = (struct BSTreeNode ) malloc (sizeof(BSTreeNode))malloc (sizeof(BSTreeNode))

3737 p-gtdata = x p-gtdata = x3838 p-gtleftchild = p-gtrightchild = p-gtleftchild = p-gtrightchild = NULLNULL3939 if (root == NULL) root = p if (root == NULL) root = p4040 else if (x lt q-gtdata) else if (x lt q-gtdata)4141 q-gtleftchild = p q-gtleftchild = p4242 else else4343 q-gtrightchild = p q-gtrightchild = p4444 return 1 return 145 45

程式 程式 5-175-17  在二元搜尋樹中新增資料  在二元搜尋樹中新增資料 (( 續續 ))

範例 5-23 圖 (b) 為圖 (a) 新增整數 6 之後的結果圖 (c) (d) (e) (f) 則分別是逐步再加入 28 18

10 21 的結果

bull若目前的二元搜尋樹高度為 h 則新增資料得花O(h) 的時間找到其所應在的位置遂新增資料至二元搜尋樹中的時間複雜之度為 O(h)

593 自二元搜尋樹中刪除資料欲自二元搜尋樹中刪除資料可能會面臨兩種情況(1) 刪除節點 p 位於樹葉節點上如下圖所示 這是比較容易處理的情況

(2) 刪除的節點 p 不是樹葉刪除節點 p 後原 p 所指的節點可由 q ( q 中包含了 p 左子樹中的最大節點值)或 r ( r 中包含了 p 右子樹中的最小節點值)來取代以維持二元搜尋樹中資料的大小關係其結果分別如圖 (b) 和 (c) 所示而圖 (d) 則標明了指標會改變的節點位置即 p 會消失 father 的左或右子樹指標會異動 k(可能是 q 或 r )會移到原來 p 的位置其父節點 f 的左或右子樹指標會異動其中 father 為 p 父節點 f 為 k 的父節點

程式 5-18 自二元搜尋樹中刪除資料46 int DeleteBST (int x)47 Struct BSTreeNode p father k f48 p = root father = NULL49 while (p = NULL)50 if (x == p-gtdata) 找到 x 所在的節點 p 了51 if ((p-gtleftchild == NULL) ampamp 52 (p-gtrightchild == NULL))53 k = NULL p為樹葉節點54 else if (p-gtleftchild = NULL )x位於非子節

點55 f = p 找出 p左子樹的最右樹葉56 k = p-gtleftchild57 while (k-gtrightchild =NULL)58 f = k59 k = k-gtrightchild60 k將挪至原 p處61 if (p == f)62 f-gtleftchild = k-gtleftchild 63 else64 f-gtrightchild = k-gtleftchild 65

66 else p無左子樹67 f = p 找出 p右子樹的最左樹葉68 k = p-gtrightchild69 while (k-gtleftchild = NULL)70 f = k71 k = k -gtleftchild72 73 if (p == f)74 f-gtrightchild = k-gtrightchild 75 else76 f-gtleftchild = k-gtrightchild77 78 if (k = NULL) k挪至原 p處繼承 p的左右指標79 k-gtleftchild = p-gtleftchild80 k-gtrightchild = p-gtrightchild81 82 if (father == NULL) root = k83 else if (x lt father-gtdata) 決定 k是 father的左

或右兒子84 father-gtleftchild = k85 else86 father-gtrightchild = k87 free(p)88 return 1 成功地刪除 x於此傳回 1返回呼叫處89 51~89行為找到 x時應有的處理步驟

90 else 尚未找到 x繼續往下一階層找

91 father = p92 if (x lt p-gtdata)93 p = p-gtleftchild94 else95 p = p-gtrightchild96 97 98 return 0 未找到 x傳回 099

510  樹林 (forest)

bull 定義樹林(森林)是由 n 棵沒有交集的樹所構成的集合 n 0

下圖為一包含三棵樹的樹林

5101 轉換樹林為二元樹 之前我們以二元樹來表示樹因為二元樹有較多

好用的性質在此我們也將樹林轉換成二元樹看待

在 522 節中我們嘗試了用左子右兄弟的方式來表示樹這個方法事實上已將樹轉換成二元樹

即用左子右兄弟的結構重新組織樹林中的每一棵樹這些樹的樹根將視為兄弟而最左邊的樹則視為樹林二元樹的樹根如此一來樹林即可轉換成二元樹

範例 5-25右下圖為右上圖三棵樹所對應的三棵左子右兄弟二元樹

下圖則為上圖樹林所對應的一棵樹林二元樹

演算法 5-1  轉換樹林成二元樹輸入樹林 F = T1 T2 Tn

輸出 F 的二元樹表示 BT=(T1 T2 Tn)1 if (n == 0) 輸出空二元樹 2 else

3 root(T1 T2 Tn) = T1

4 leftsubtree(T1 T2 Tn) =

BT(T11 T12 T1m)5 BT(T11 T12 T1m)為 T1的子樹6 rightsubtree(T1 T2 Tn) =

BT(T2 T3 Tn)

樹林轉換成二元樹的過程樹林轉換成二元樹的過程

  • Slide 1
  • Slide 2
  • Slide 3
  • Slide 4
  • Slide 5
  • Slide 6
  • Slide 7
  • Slide 8
  • Slide 9
  • Slide 10
  • Slide 11
  • Slide 12
  • Slide 13
  • Slide 14
  • Slide 15
  • Slide 16
  • Slide 17
  • Slide 18
  • Slide 19
  • Slide 20
  • Slide 21
  • Slide 22
  • Slide 23
  • Slide 24
  • Slide 25
  • Slide 26
  • Slide 27
  • Slide 28
  • Slide 29
  • Slide 30
  • Slide 31
  • Slide 32
  • Slide 33
  • Slide 34
  • Slide 35
  • Slide 36
  • Slide 37
  • Slide 38
  • Slide 39
  • Slide 40
  • Slide 41
  • Slide 42
  • Slide 43
  • Slide 44
  • Slide 45
  • Slide 46
  • Slide 47
  • Slide 48
  • Slide 49
  • Slide 50
  • Slide 51
  • Slide 52
  • Slide 53
  • Slide 54
  • Slide 55
  • Slide 56
  • Slide 57
  • Slide 58
  • Slide 59
  • Slide 60
  • Slide 61
  • Slide 62
  • Slide 63
  • Slide 64
  • Slide 65
  • Slide 66
  • Slide 67
  • Slide 68
  • Slide 69
  • Slide 70
  • Slide 71
  • Slide 72
  • Slide 73
  • Slide 74
  • Slide 75
  • Slide 76
  • Slide 77
  • Slide 78
  • Slide 79
  • Slide 80
  • Slide 81
  • Slide 82
  • Slide 83
  • Slide 84
  • Slide 85
  • Slide 86
  • Slide 87
  • Slide 88
  • Slide 89
  • Slide 90
  • Slide 91
  • Slide 92
  • Slide 93
  • Slide 94
  • Slide 95
  • Slide 96
  • Slide 97
  • Slide 98
  • Slide 99
  • Slide 100
  • Slide 101
  • Slide 102
  • Slide 103
Page 71: 第五章 樹

程式 5-12  插入節點 new 成為引線二元樹中節點 p的右子節點(上接程式 5-10 5-11 )

1 void InsertRight(struct TBTreeNode p 2 struct TBTreeNode new)3 struct TBTreeNode q4 new-gtrightchild = p-gtrightchild5 new-gtrightthead = p-gtrightthread6 new-gtleftchild = p7 new-gtleftthread = 18 p-gtrightchild = new9 p-gtrightthead = 010 if (new-gtrightthread)11 q = Next(new)12 q-gtleftchild = new13 14

58  堆積bull 「堆積」 (heap) 是一個特殊的完備二元樹節點的資料與其子節點的資料之間有事先定好的大小關係存在bull 定義ndash最大堆疊為一完備二元樹任一節點的資料內容不小於其子節點的資料內容ndash最小堆疊為一完備二元樹任一節點的資料內容不大於其子節點的資料內容

581  插入節點至最大堆積中最大堆積是一完備二元樹所以如圖 (a) 之最大堆積於加入新節點後樹的形狀必須如圖 (b) 所示灰色節點即為新節點若加入的節點資料分別為 1 9 12 則其最大堆積將分別變成如圖 (c) (d) (e) 所示

程式 5-13 新增節點至最大堆積中最大堆積本身是一棵完備二元樹可以用陣列表示之 1 define maxsize 1002 int heap[maxsize]3 int n i4 void InsertHeap(int x)5 if (n == maxsize) HeapFull()6 else7 n++8 i = n9 while ((i gt 1) ampamp (x gt heap[i2]))10 heap[i] = heap[i2]11 i = 212 13 heap[i] = x14 16

程序 InsertHeap 在插入第 n 個資料時需要的時間複雜度為 O(logn)

1 100

2 93

3 84

4 77

5 65

6 81

7 79

8 66

9 70

10 50

100

84

77

93

8165

66 70

79

50

1

2 3

4 5 6 7

8 9 10

1 define maxsize 1002 int heap[maxsize]3 int n i4 void InsertHeap(int x)5 if (n == maxsize) HeapFull()6 else7 n++8 i = n9 while ((i gt 1) ampamp (x gt heap[i2]))10 heap[i] = heap[i2]11 i = 212 13 heap[i] = x14 16

582 刪除最大堆積中的最大資料圖 (a) 中的最大堆積在刪除最大資料後其樹形應變成如圖 (b) 所示將最未一個樹葉節點的資料 6 暫挪至樹根節點內如圖 (c) 再將圖 (c) 中非最大堆積的結構調整成為最大堆積 10 和 8 中選大者與 6對調即成圖 (d)

程式 5-14 刪除最大堆積中的最大資料(上接程式 5-13 )1 int DeleteHeap()2 int x i j3 if (n == 0) HeapEmpty()4 else5 x = heap[1]6 heap[1] = heap[n]7 n--8 i = 19 while (i lt= n2)10 if (heap[2i] gt heap[2i+1])11 j = 2i12 else13 j = 2i+114 if (heap[i]gtheap[j]) break15 swap(heap[i] heap[j])16 i = j17 18 return x19 20

其時間複雜度與其堆積高度相同是為O(logn) 其中 n 為堆積中的元素個數

1 100

2 93

3 84

4 77

5 65

6 81

7 79

8 66

9 70

10 50

100

84

77

93

8165

66 70

79

50

1

2 3

4 5 6 7

8 9 10

1 int DeletHeap()2 int x i j3 if(n == 0) HeapEmpty()4 else5 x = heap[1]6 heap[1] = heap[n]7 n--8 i = 19 while (i lt= n2)10 if ([heap[2i]] gt heap[2i+1])11 j = 2i12 else13 j = 2i+114 if (heap[i]gtheap[j]) break15 swap(heap[i] heap[j])16 i = j17 18 return x19 20

583  優先佇列bull 由於最大堆積(或最小堆積)的結構可提供最大元素(或最小元素)而其維護堆積的成本為 O(logn) n 為總元素個數

bull 堆積常被當成「優先佇列」 (priority queue) 使用mdash此時佇列被組織成完備二元樹若新加入佇列的資料有大小關係優先順序的需求時此優先佇列即以 O(logn) 的代價將新資料放入優先佇列(可能是最大或最小堆積依實際大小關係優先順序的需求而定)中mdash提供依元素的優先順序做為處理順序的服務

bull 一般的佇列為先進先出先到的先行處理優先佇列則是依據其優先順序優先順序高者先行處理與來到的先後無關

範例 5-21bull 在「分時」 (time sharing) 的作業系統中我們不希望使用中央處理器 (Central Processing Unit CPU) 時間短的「處理程序」 (process) 等候過久於是有「最短時間工作優先」 (shortest-job-first) 的排程策略此時等候的處理程序即可組織成之一最小堆積而 CPU 每次即取出存於樹根的處理程序予以處理任何新來的處理程序則以 O(logn) 的時間加入此優先佇列(最小堆積)中

bull 注意之前提過的線性佇列新來的直接加入成為新的尾端元素花O(1) 的時間然線性佇列本身沒有大小的關係每次要取出最大(小)的資料得花O(n) 的代價(在所有元素中挑出最大(小)者) n 為佇列內的元素個數而優先佇列是在新元素加入時要花O(logn) 的時間至於取出最大(小)則只須 O(1) 的時間

59  二元搜尋樹bull 二元搜尋樹是一棵二元樹可能是空二元樹若不為空二元樹則滿足下列性質ndash所有節點內的資料內容是相異的 ndash左子節點的資內容 ( 如果有 ) 比父節點的資料內容小 ndash右子節點的資料內容 ( 如果有 ) 比父節點的資料內容大 ndash以左和右子節點為樹根的左子樹和右子樹也是二元搜尋樹

在二元搜尋樹中搜尋資料二元搜尋樹對資料大小的掌握十分明確若恰與節點 p 資料相等則搜尋成功若欲搜尋的資料 x 比樹節點 p 中的資料小 則只須搜尋節點 p 的左子樹若比節點 p 中的資料大 則只須搜尋 p 的右子樹這樣的判斷可以遞迴地應用在各子樹節點 中

程式 5-15 在二元搜尋樹中搜尋資料(遞迴呼叫)1 struct BSTreeNode2 struct BSTreeNode leftchild3 int data4 struct BSTreeNode rightchild5 6 struct BSTreeNode root7 struct BSTreeNode SearchBST(

struct BSTreeNode tree int x)8 if (tree == NULL) return NULL9 if (x == tree-gtdata) return tree10 if (x lt tree-gtdata)11 return SearchBST(tree-gtleftchild x)

12 return SearchBST(tree-gtrightchild x)13

程式 5-16 搜尋二元搜尋樹的資料(非遞迴呼叫)在二元搜尋樹十分龐大時遞迴呼叫的效率不會太好可利用迴圈來完成搜尋的動作以提高執行效率請看程式 5-16

14 struct BSTreeNode Search BST_iterative15 (struct BSTreeNode node int x)16 while (node = NULL)17 if (x == node-gtdata) return node18 if (x lt node-gtdata)19 node = node-gtleftchild20 else21 node = node-gtrightchild22 23 return NULL24

592  新增資料於二元搜尋樹中欲新增資料進入一二元搜尋樹中得先決定該資料在二元搜尋樹中應出現的位置這可透過上節提到的搜尋動作來解決

在二元搜尋樹定義的性質 (1) 中我們限定了元素必須相異所以搜尋新資料的結果必是 NULL

必須將搜尋的結果改為新增資料所應在位置的節點指標

程式 5-17  在二元搜尋樹中新增資料25 int InsertBST(int x)26 struct BSTreeNode p q27 p = root q = NULL28 while (p = NULL)29 q = p30 if (x == p-gtdata) return 0

若不考慮重複則可省略之31 if (x lt p-gtdata)32 p = p-gtleftchild33 else34 p = p-gtrightchild35

3636 p = (struct BSTreeNode ) p = (struct BSTreeNode ) malloc (sizeof(BSTreeNode))malloc (sizeof(BSTreeNode))

3737 p-gtdata = x p-gtdata = x3838 p-gtleftchild = p-gtrightchild = p-gtleftchild = p-gtrightchild = NULLNULL3939 if (root == NULL) root = p if (root == NULL) root = p4040 else if (x lt q-gtdata) else if (x lt q-gtdata)4141 q-gtleftchild = p q-gtleftchild = p4242 else else4343 q-gtrightchild = p q-gtrightchild = p4444 return 1 return 145 45

程式 程式 5-175-17  在二元搜尋樹中新增資料  在二元搜尋樹中新增資料 (( 續續 ))

範例 5-23 圖 (b) 為圖 (a) 新增整數 6 之後的結果圖 (c) (d) (e) (f) 則分別是逐步再加入 28 18

10 21 的結果

bull若目前的二元搜尋樹高度為 h 則新增資料得花O(h) 的時間找到其所應在的位置遂新增資料至二元搜尋樹中的時間複雜之度為 O(h)

593 自二元搜尋樹中刪除資料欲自二元搜尋樹中刪除資料可能會面臨兩種情況(1) 刪除節點 p 位於樹葉節點上如下圖所示 這是比較容易處理的情況

(2) 刪除的節點 p 不是樹葉刪除節點 p 後原 p 所指的節點可由 q ( q 中包含了 p 左子樹中的最大節點值)或 r ( r 中包含了 p 右子樹中的最小節點值)來取代以維持二元搜尋樹中資料的大小關係其結果分別如圖 (b) 和 (c) 所示而圖 (d) 則標明了指標會改變的節點位置即 p 會消失 father 的左或右子樹指標會異動 k(可能是 q 或 r )會移到原來 p 的位置其父節點 f 的左或右子樹指標會異動其中 father 為 p 父節點 f 為 k 的父節點

程式 5-18 自二元搜尋樹中刪除資料46 int DeleteBST (int x)47 Struct BSTreeNode p father k f48 p = root father = NULL49 while (p = NULL)50 if (x == p-gtdata) 找到 x 所在的節點 p 了51 if ((p-gtleftchild == NULL) ampamp 52 (p-gtrightchild == NULL))53 k = NULL p為樹葉節點54 else if (p-gtleftchild = NULL )x位於非子節

點55 f = p 找出 p左子樹的最右樹葉56 k = p-gtleftchild57 while (k-gtrightchild =NULL)58 f = k59 k = k-gtrightchild60 k將挪至原 p處61 if (p == f)62 f-gtleftchild = k-gtleftchild 63 else64 f-gtrightchild = k-gtleftchild 65

66 else p無左子樹67 f = p 找出 p右子樹的最左樹葉68 k = p-gtrightchild69 while (k-gtleftchild = NULL)70 f = k71 k = k -gtleftchild72 73 if (p == f)74 f-gtrightchild = k-gtrightchild 75 else76 f-gtleftchild = k-gtrightchild77 78 if (k = NULL) k挪至原 p處繼承 p的左右指標79 k-gtleftchild = p-gtleftchild80 k-gtrightchild = p-gtrightchild81 82 if (father == NULL) root = k83 else if (x lt father-gtdata) 決定 k是 father的左

或右兒子84 father-gtleftchild = k85 else86 father-gtrightchild = k87 free(p)88 return 1 成功地刪除 x於此傳回 1返回呼叫處89 51~89行為找到 x時應有的處理步驟

90 else 尚未找到 x繼續往下一階層找

91 father = p92 if (x lt p-gtdata)93 p = p-gtleftchild94 else95 p = p-gtrightchild96 97 98 return 0 未找到 x傳回 099

510  樹林 (forest)

bull 定義樹林(森林)是由 n 棵沒有交集的樹所構成的集合 n 0

下圖為一包含三棵樹的樹林

5101 轉換樹林為二元樹 之前我們以二元樹來表示樹因為二元樹有較多

好用的性質在此我們也將樹林轉換成二元樹看待

在 522 節中我們嘗試了用左子右兄弟的方式來表示樹這個方法事實上已將樹轉換成二元樹

即用左子右兄弟的結構重新組織樹林中的每一棵樹這些樹的樹根將視為兄弟而最左邊的樹則視為樹林二元樹的樹根如此一來樹林即可轉換成二元樹

範例 5-25右下圖為右上圖三棵樹所對應的三棵左子右兄弟二元樹

下圖則為上圖樹林所對應的一棵樹林二元樹

演算法 5-1  轉換樹林成二元樹輸入樹林 F = T1 T2 Tn

輸出 F 的二元樹表示 BT=(T1 T2 Tn)1 if (n == 0) 輸出空二元樹 2 else

3 root(T1 T2 Tn) = T1

4 leftsubtree(T1 T2 Tn) =

BT(T11 T12 T1m)5 BT(T11 T12 T1m)為 T1的子樹6 rightsubtree(T1 T2 Tn) =

BT(T2 T3 Tn)

樹林轉換成二元樹的過程樹林轉換成二元樹的過程

  • Slide 1
  • Slide 2
  • Slide 3
  • Slide 4
  • Slide 5
  • Slide 6
  • Slide 7
  • Slide 8
  • Slide 9
  • Slide 10
  • Slide 11
  • Slide 12
  • Slide 13
  • Slide 14
  • Slide 15
  • Slide 16
  • Slide 17
  • Slide 18
  • Slide 19
  • Slide 20
  • Slide 21
  • Slide 22
  • Slide 23
  • Slide 24
  • Slide 25
  • Slide 26
  • Slide 27
  • Slide 28
  • Slide 29
  • Slide 30
  • Slide 31
  • Slide 32
  • Slide 33
  • Slide 34
  • Slide 35
  • Slide 36
  • Slide 37
  • Slide 38
  • Slide 39
  • Slide 40
  • Slide 41
  • Slide 42
  • Slide 43
  • Slide 44
  • Slide 45
  • Slide 46
  • Slide 47
  • Slide 48
  • Slide 49
  • Slide 50
  • Slide 51
  • Slide 52
  • Slide 53
  • Slide 54
  • Slide 55
  • Slide 56
  • Slide 57
  • Slide 58
  • Slide 59
  • Slide 60
  • Slide 61
  • Slide 62
  • Slide 63
  • Slide 64
  • Slide 65
  • Slide 66
  • Slide 67
  • Slide 68
  • Slide 69
  • Slide 70
  • Slide 71
  • Slide 72
  • Slide 73
  • Slide 74
  • Slide 75
  • Slide 76
  • Slide 77
  • Slide 78
  • Slide 79
  • Slide 80
  • Slide 81
  • Slide 82
  • Slide 83
  • Slide 84
  • Slide 85
  • Slide 86
  • Slide 87
  • Slide 88
  • Slide 89
  • Slide 90
  • Slide 91
  • Slide 92
  • Slide 93
  • Slide 94
  • Slide 95
  • Slide 96
  • Slide 97
  • Slide 98
  • Slide 99
  • Slide 100
  • Slide 101
  • Slide 102
  • Slide 103
Page 72: 第五章 樹

58  堆積bull 「堆積」 (heap) 是一個特殊的完備二元樹節點的資料與其子節點的資料之間有事先定好的大小關係存在bull 定義ndash最大堆疊為一完備二元樹任一節點的資料內容不小於其子節點的資料內容ndash最小堆疊為一完備二元樹任一節點的資料內容不大於其子節點的資料內容

581  插入節點至最大堆積中最大堆積是一完備二元樹所以如圖 (a) 之最大堆積於加入新節點後樹的形狀必須如圖 (b) 所示灰色節點即為新節點若加入的節點資料分別為 1 9 12 則其最大堆積將分別變成如圖 (c) (d) (e) 所示

程式 5-13 新增節點至最大堆積中最大堆積本身是一棵完備二元樹可以用陣列表示之 1 define maxsize 1002 int heap[maxsize]3 int n i4 void InsertHeap(int x)5 if (n == maxsize) HeapFull()6 else7 n++8 i = n9 while ((i gt 1) ampamp (x gt heap[i2]))10 heap[i] = heap[i2]11 i = 212 13 heap[i] = x14 16

程序 InsertHeap 在插入第 n 個資料時需要的時間複雜度為 O(logn)

1 100

2 93

3 84

4 77

5 65

6 81

7 79

8 66

9 70

10 50

100

84

77

93

8165

66 70

79

50

1

2 3

4 5 6 7

8 9 10

1 define maxsize 1002 int heap[maxsize]3 int n i4 void InsertHeap(int x)5 if (n == maxsize) HeapFull()6 else7 n++8 i = n9 while ((i gt 1) ampamp (x gt heap[i2]))10 heap[i] = heap[i2]11 i = 212 13 heap[i] = x14 16

582 刪除最大堆積中的最大資料圖 (a) 中的最大堆積在刪除最大資料後其樹形應變成如圖 (b) 所示將最未一個樹葉節點的資料 6 暫挪至樹根節點內如圖 (c) 再將圖 (c) 中非最大堆積的結構調整成為最大堆積 10 和 8 中選大者與 6對調即成圖 (d)

程式 5-14 刪除最大堆積中的最大資料(上接程式 5-13 )1 int DeleteHeap()2 int x i j3 if (n == 0) HeapEmpty()4 else5 x = heap[1]6 heap[1] = heap[n]7 n--8 i = 19 while (i lt= n2)10 if (heap[2i] gt heap[2i+1])11 j = 2i12 else13 j = 2i+114 if (heap[i]gtheap[j]) break15 swap(heap[i] heap[j])16 i = j17 18 return x19 20

其時間複雜度與其堆積高度相同是為O(logn) 其中 n 為堆積中的元素個數

1 100

2 93

3 84

4 77

5 65

6 81

7 79

8 66

9 70

10 50

100

84

77

93

8165

66 70

79

50

1

2 3

4 5 6 7

8 9 10

1 int DeletHeap()2 int x i j3 if(n == 0) HeapEmpty()4 else5 x = heap[1]6 heap[1] = heap[n]7 n--8 i = 19 while (i lt= n2)10 if ([heap[2i]] gt heap[2i+1])11 j = 2i12 else13 j = 2i+114 if (heap[i]gtheap[j]) break15 swap(heap[i] heap[j])16 i = j17 18 return x19 20

583  優先佇列bull 由於最大堆積(或最小堆積)的結構可提供最大元素(或最小元素)而其維護堆積的成本為 O(logn) n 為總元素個數

bull 堆積常被當成「優先佇列」 (priority queue) 使用mdash此時佇列被組織成完備二元樹若新加入佇列的資料有大小關係優先順序的需求時此優先佇列即以 O(logn) 的代價將新資料放入優先佇列(可能是最大或最小堆積依實際大小關係優先順序的需求而定)中mdash提供依元素的優先順序做為處理順序的服務

bull 一般的佇列為先進先出先到的先行處理優先佇列則是依據其優先順序優先順序高者先行處理與來到的先後無關

範例 5-21bull 在「分時」 (time sharing) 的作業系統中我們不希望使用中央處理器 (Central Processing Unit CPU) 時間短的「處理程序」 (process) 等候過久於是有「最短時間工作優先」 (shortest-job-first) 的排程策略此時等候的處理程序即可組織成之一最小堆積而 CPU 每次即取出存於樹根的處理程序予以處理任何新來的處理程序則以 O(logn) 的時間加入此優先佇列(最小堆積)中

bull 注意之前提過的線性佇列新來的直接加入成為新的尾端元素花O(1) 的時間然線性佇列本身沒有大小的關係每次要取出最大(小)的資料得花O(n) 的代價(在所有元素中挑出最大(小)者) n 為佇列內的元素個數而優先佇列是在新元素加入時要花O(logn) 的時間至於取出最大(小)則只須 O(1) 的時間

59  二元搜尋樹bull 二元搜尋樹是一棵二元樹可能是空二元樹若不為空二元樹則滿足下列性質ndash所有節點內的資料內容是相異的 ndash左子節點的資內容 ( 如果有 ) 比父節點的資料內容小 ndash右子節點的資料內容 ( 如果有 ) 比父節點的資料內容大 ndash以左和右子節點為樹根的左子樹和右子樹也是二元搜尋樹

在二元搜尋樹中搜尋資料二元搜尋樹對資料大小的掌握十分明確若恰與節點 p 資料相等則搜尋成功若欲搜尋的資料 x 比樹節點 p 中的資料小 則只須搜尋節點 p 的左子樹若比節點 p 中的資料大 則只須搜尋 p 的右子樹這樣的判斷可以遞迴地應用在各子樹節點 中

程式 5-15 在二元搜尋樹中搜尋資料(遞迴呼叫)1 struct BSTreeNode2 struct BSTreeNode leftchild3 int data4 struct BSTreeNode rightchild5 6 struct BSTreeNode root7 struct BSTreeNode SearchBST(

struct BSTreeNode tree int x)8 if (tree == NULL) return NULL9 if (x == tree-gtdata) return tree10 if (x lt tree-gtdata)11 return SearchBST(tree-gtleftchild x)

12 return SearchBST(tree-gtrightchild x)13

程式 5-16 搜尋二元搜尋樹的資料(非遞迴呼叫)在二元搜尋樹十分龐大時遞迴呼叫的效率不會太好可利用迴圈來完成搜尋的動作以提高執行效率請看程式 5-16

14 struct BSTreeNode Search BST_iterative15 (struct BSTreeNode node int x)16 while (node = NULL)17 if (x == node-gtdata) return node18 if (x lt node-gtdata)19 node = node-gtleftchild20 else21 node = node-gtrightchild22 23 return NULL24

592  新增資料於二元搜尋樹中欲新增資料進入一二元搜尋樹中得先決定該資料在二元搜尋樹中應出現的位置這可透過上節提到的搜尋動作來解決

在二元搜尋樹定義的性質 (1) 中我們限定了元素必須相異所以搜尋新資料的結果必是 NULL

必須將搜尋的結果改為新增資料所應在位置的節點指標

程式 5-17  在二元搜尋樹中新增資料25 int InsertBST(int x)26 struct BSTreeNode p q27 p = root q = NULL28 while (p = NULL)29 q = p30 if (x == p-gtdata) return 0

若不考慮重複則可省略之31 if (x lt p-gtdata)32 p = p-gtleftchild33 else34 p = p-gtrightchild35

3636 p = (struct BSTreeNode ) p = (struct BSTreeNode ) malloc (sizeof(BSTreeNode))malloc (sizeof(BSTreeNode))

3737 p-gtdata = x p-gtdata = x3838 p-gtleftchild = p-gtrightchild = p-gtleftchild = p-gtrightchild = NULLNULL3939 if (root == NULL) root = p if (root == NULL) root = p4040 else if (x lt q-gtdata) else if (x lt q-gtdata)4141 q-gtleftchild = p q-gtleftchild = p4242 else else4343 q-gtrightchild = p q-gtrightchild = p4444 return 1 return 145 45

程式 程式 5-175-17  在二元搜尋樹中新增資料  在二元搜尋樹中新增資料 (( 續續 ))

範例 5-23 圖 (b) 為圖 (a) 新增整數 6 之後的結果圖 (c) (d) (e) (f) 則分別是逐步再加入 28 18

10 21 的結果

bull若目前的二元搜尋樹高度為 h 則新增資料得花O(h) 的時間找到其所應在的位置遂新增資料至二元搜尋樹中的時間複雜之度為 O(h)

593 自二元搜尋樹中刪除資料欲自二元搜尋樹中刪除資料可能會面臨兩種情況(1) 刪除節點 p 位於樹葉節點上如下圖所示 這是比較容易處理的情況

(2) 刪除的節點 p 不是樹葉刪除節點 p 後原 p 所指的節點可由 q ( q 中包含了 p 左子樹中的最大節點值)或 r ( r 中包含了 p 右子樹中的最小節點值)來取代以維持二元搜尋樹中資料的大小關係其結果分別如圖 (b) 和 (c) 所示而圖 (d) 則標明了指標會改變的節點位置即 p 會消失 father 的左或右子樹指標會異動 k(可能是 q 或 r )會移到原來 p 的位置其父節點 f 的左或右子樹指標會異動其中 father 為 p 父節點 f 為 k 的父節點

程式 5-18 自二元搜尋樹中刪除資料46 int DeleteBST (int x)47 Struct BSTreeNode p father k f48 p = root father = NULL49 while (p = NULL)50 if (x == p-gtdata) 找到 x 所在的節點 p 了51 if ((p-gtleftchild == NULL) ampamp 52 (p-gtrightchild == NULL))53 k = NULL p為樹葉節點54 else if (p-gtleftchild = NULL )x位於非子節

點55 f = p 找出 p左子樹的最右樹葉56 k = p-gtleftchild57 while (k-gtrightchild =NULL)58 f = k59 k = k-gtrightchild60 k將挪至原 p處61 if (p == f)62 f-gtleftchild = k-gtleftchild 63 else64 f-gtrightchild = k-gtleftchild 65

66 else p無左子樹67 f = p 找出 p右子樹的最左樹葉68 k = p-gtrightchild69 while (k-gtleftchild = NULL)70 f = k71 k = k -gtleftchild72 73 if (p == f)74 f-gtrightchild = k-gtrightchild 75 else76 f-gtleftchild = k-gtrightchild77 78 if (k = NULL) k挪至原 p處繼承 p的左右指標79 k-gtleftchild = p-gtleftchild80 k-gtrightchild = p-gtrightchild81 82 if (father == NULL) root = k83 else if (x lt father-gtdata) 決定 k是 father的左

或右兒子84 father-gtleftchild = k85 else86 father-gtrightchild = k87 free(p)88 return 1 成功地刪除 x於此傳回 1返回呼叫處89 51~89行為找到 x時應有的處理步驟

90 else 尚未找到 x繼續往下一階層找

91 father = p92 if (x lt p-gtdata)93 p = p-gtleftchild94 else95 p = p-gtrightchild96 97 98 return 0 未找到 x傳回 099

510  樹林 (forest)

bull 定義樹林(森林)是由 n 棵沒有交集的樹所構成的集合 n 0

下圖為一包含三棵樹的樹林

5101 轉換樹林為二元樹 之前我們以二元樹來表示樹因為二元樹有較多

好用的性質在此我們也將樹林轉換成二元樹看待

在 522 節中我們嘗試了用左子右兄弟的方式來表示樹這個方法事實上已將樹轉換成二元樹

即用左子右兄弟的結構重新組織樹林中的每一棵樹這些樹的樹根將視為兄弟而最左邊的樹則視為樹林二元樹的樹根如此一來樹林即可轉換成二元樹

範例 5-25右下圖為右上圖三棵樹所對應的三棵左子右兄弟二元樹

下圖則為上圖樹林所對應的一棵樹林二元樹

演算法 5-1  轉換樹林成二元樹輸入樹林 F = T1 T2 Tn

輸出 F 的二元樹表示 BT=(T1 T2 Tn)1 if (n == 0) 輸出空二元樹 2 else

3 root(T1 T2 Tn) = T1

4 leftsubtree(T1 T2 Tn) =

BT(T11 T12 T1m)5 BT(T11 T12 T1m)為 T1的子樹6 rightsubtree(T1 T2 Tn) =

BT(T2 T3 Tn)

樹林轉換成二元樹的過程樹林轉換成二元樹的過程

  • Slide 1
  • Slide 2
  • Slide 3
  • Slide 4
  • Slide 5
  • Slide 6
  • Slide 7
  • Slide 8
  • Slide 9
  • Slide 10
  • Slide 11
  • Slide 12
  • Slide 13
  • Slide 14
  • Slide 15
  • Slide 16
  • Slide 17
  • Slide 18
  • Slide 19
  • Slide 20
  • Slide 21
  • Slide 22
  • Slide 23
  • Slide 24
  • Slide 25
  • Slide 26
  • Slide 27
  • Slide 28
  • Slide 29
  • Slide 30
  • Slide 31
  • Slide 32
  • Slide 33
  • Slide 34
  • Slide 35
  • Slide 36
  • Slide 37
  • Slide 38
  • Slide 39
  • Slide 40
  • Slide 41
  • Slide 42
  • Slide 43
  • Slide 44
  • Slide 45
  • Slide 46
  • Slide 47
  • Slide 48
  • Slide 49
  • Slide 50
  • Slide 51
  • Slide 52
  • Slide 53
  • Slide 54
  • Slide 55
  • Slide 56
  • Slide 57
  • Slide 58
  • Slide 59
  • Slide 60
  • Slide 61
  • Slide 62
  • Slide 63
  • Slide 64
  • Slide 65
  • Slide 66
  • Slide 67
  • Slide 68
  • Slide 69
  • Slide 70
  • Slide 71
  • Slide 72
  • Slide 73
  • Slide 74
  • Slide 75
  • Slide 76
  • Slide 77
  • Slide 78
  • Slide 79
  • Slide 80
  • Slide 81
  • Slide 82
  • Slide 83
  • Slide 84
  • Slide 85
  • Slide 86
  • Slide 87
  • Slide 88
  • Slide 89
  • Slide 90
  • Slide 91
  • Slide 92
  • Slide 93
  • Slide 94
  • Slide 95
  • Slide 96
  • Slide 97
  • Slide 98
  • Slide 99
  • Slide 100
  • Slide 101
  • Slide 102
  • Slide 103
Page 73: 第五章 樹

581  插入節點至最大堆積中最大堆積是一完備二元樹所以如圖 (a) 之最大堆積於加入新節點後樹的形狀必須如圖 (b) 所示灰色節點即為新節點若加入的節點資料分別為 1 9 12 則其最大堆積將分別變成如圖 (c) (d) (e) 所示

程式 5-13 新增節點至最大堆積中最大堆積本身是一棵完備二元樹可以用陣列表示之 1 define maxsize 1002 int heap[maxsize]3 int n i4 void InsertHeap(int x)5 if (n == maxsize) HeapFull()6 else7 n++8 i = n9 while ((i gt 1) ampamp (x gt heap[i2]))10 heap[i] = heap[i2]11 i = 212 13 heap[i] = x14 16

程序 InsertHeap 在插入第 n 個資料時需要的時間複雜度為 O(logn)

1 100

2 93

3 84

4 77

5 65

6 81

7 79

8 66

9 70

10 50

100

84

77

93

8165

66 70

79

50

1

2 3

4 5 6 7

8 9 10

1 define maxsize 1002 int heap[maxsize]3 int n i4 void InsertHeap(int x)5 if (n == maxsize) HeapFull()6 else7 n++8 i = n9 while ((i gt 1) ampamp (x gt heap[i2]))10 heap[i] = heap[i2]11 i = 212 13 heap[i] = x14 16

582 刪除最大堆積中的最大資料圖 (a) 中的最大堆積在刪除最大資料後其樹形應變成如圖 (b) 所示將最未一個樹葉節點的資料 6 暫挪至樹根節點內如圖 (c) 再將圖 (c) 中非最大堆積的結構調整成為最大堆積 10 和 8 中選大者與 6對調即成圖 (d)

程式 5-14 刪除最大堆積中的最大資料(上接程式 5-13 )1 int DeleteHeap()2 int x i j3 if (n == 0) HeapEmpty()4 else5 x = heap[1]6 heap[1] = heap[n]7 n--8 i = 19 while (i lt= n2)10 if (heap[2i] gt heap[2i+1])11 j = 2i12 else13 j = 2i+114 if (heap[i]gtheap[j]) break15 swap(heap[i] heap[j])16 i = j17 18 return x19 20

其時間複雜度與其堆積高度相同是為O(logn) 其中 n 為堆積中的元素個數

1 100

2 93

3 84

4 77

5 65

6 81

7 79

8 66

9 70

10 50

100

84

77

93

8165

66 70

79

50

1

2 3

4 5 6 7

8 9 10

1 int DeletHeap()2 int x i j3 if(n == 0) HeapEmpty()4 else5 x = heap[1]6 heap[1] = heap[n]7 n--8 i = 19 while (i lt= n2)10 if ([heap[2i]] gt heap[2i+1])11 j = 2i12 else13 j = 2i+114 if (heap[i]gtheap[j]) break15 swap(heap[i] heap[j])16 i = j17 18 return x19 20

583  優先佇列bull 由於最大堆積(或最小堆積)的結構可提供最大元素(或最小元素)而其維護堆積的成本為 O(logn) n 為總元素個數

bull 堆積常被當成「優先佇列」 (priority queue) 使用mdash此時佇列被組織成完備二元樹若新加入佇列的資料有大小關係優先順序的需求時此優先佇列即以 O(logn) 的代價將新資料放入優先佇列(可能是最大或最小堆積依實際大小關係優先順序的需求而定)中mdash提供依元素的優先順序做為處理順序的服務

bull 一般的佇列為先進先出先到的先行處理優先佇列則是依據其優先順序優先順序高者先行處理與來到的先後無關

範例 5-21bull 在「分時」 (time sharing) 的作業系統中我們不希望使用中央處理器 (Central Processing Unit CPU) 時間短的「處理程序」 (process) 等候過久於是有「最短時間工作優先」 (shortest-job-first) 的排程策略此時等候的處理程序即可組織成之一最小堆積而 CPU 每次即取出存於樹根的處理程序予以處理任何新來的處理程序則以 O(logn) 的時間加入此優先佇列(最小堆積)中

bull 注意之前提過的線性佇列新來的直接加入成為新的尾端元素花O(1) 的時間然線性佇列本身沒有大小的關係每次要取出最大(小)的資料得花O(n) 的代價(在所有元素中挑出最大(小)者) n 為佇列內的元素個數而優先佇列是在新元素加入時要花O(logn) 的時間至於取出最大(小)則只須 O(1) 的時間

59  二元搜尋樹bull 二元搜尋樹是一棵二元樹可能是空二元樹若不為空二元樹則滿足下列性質ndash所有節點內的資料內容是相異的 ndash左子節點的資內容 ( 如果有 ) 比父節點的資料內容小 ndash右子節點的資料內容 ( 如果有 ) 比父節點的資料內容大 ndash以左和右子節點為樹根的左子樹和右子樹也是二元搜尋樹

在二元搜尋樹中搜尋資料二元搜尋樹對資料大小的掌握十分明確若恰與節點 p 資料相等則搜尋成功若欲搜尋的資料 x 比樹節點 p 中的資料小 則只須搜尋節點 p 的左子樹若比節點 p 中的資料大 則只須搜尋 p 的右子樹這樣的判斷可以遞迴地應用在各子樹節點 中

程式 5-15 在二元搜尋樹中搜尋資料(遞迴呼叫)1 struct BSTreeNode2 struct BSTreeNode leftchild3 int data4 struct BSTreeNode rightchild5 6 struct BSTreeNode root7 struct BSTreeNode SearchBST(

struct BSTreeNode tree int x)8 if (tree == NULL) return NULL9 if (x == tree-gtdata) return tree10 if (x lt tree-gtdata)11 return SearchBST(tree-gtleftchild x)

12 return SearchBST(tree-gtrightchild x)13

程式 5-16 搜尋二元搜尋樹的資料(非遞迴呼叫)在二元搜尋樹十分龐大時遞迴呼叫的效率不會太好可利用迴圈來完成搜尋的動作以提高執行效率請看程式 5-16

14 struct BSTreeNode Search BST_iterative15 (struct BSTreeNode node int x)16 while (node = NULL)17 if (x == node-gtdata) return node18 if (x lt node-gtdata)19 node = node-gtleftchild20 else21 node = node-gtrightchild22 23 return NULL24

592  新增資料於二元搜尋樹中欲新增資料進入一二元搜尋樹中得先決定該資料在二元搜尋樹中應出現的位置這可透過上節提到的搜尋動作來解決

在二元搜尋樹定義的性質 (1) 中我們限定了元素必須相異所以搜尋新資料的結果必是 NULL

必須將搜尋的結果改為新增資料所應在位置的節點指標

程式 5-17  在二元搜尋樹中新增資料25 int InsertBST(int x)26 struct BSTreeNode p q27 p = root q = NULL28 while (p = NULL)29 q = p30 if (x == p-gtdata) return 0

若不考慮重複則可省略之31 if (x lt p-gtdata)32 p = p-gtleftchild33 else34 p = p-gtrightchild35

3636 p = (struct BSTreeNode ) p = (struct BSTreeNode ) malloc (sizeof(BSTreeNode))malloc (sizeof(BSTreeNode))

3737 p-gtdata = x p-gtdata = x3838 p-gtleftchild = p-gtrightchild = p-gtleftchild = p-gtrightchild = NULLNULL3939 if (root == NULL) root = p if (root == NULL) root = p4040 else if (x lt q-gtdata) else if (x lt q-gtdata)4141 q-gtleftchild = p q-gtleftchild = p4242 else else4343 q-gtrightchild = p q-gtrightchild = p4444 return 1 return 145 45

程式 程式 5-175-17  在二元搜尋樹中新增資料  在二元搜尋樹中新增資料 (( 續續 ))

範例 5-23 圖 (b) 為圖 (a) 新增整數 6 之後的結果圖 (c) (d) (e) (f) 則分別是逐步再加入 28 18

10 21 的結果

bull若目前的二元搜尋樹高度為 h 則新增資料得花O(h) 的時間找到其所應在的位置遂新增資料至二元搜尋樹中的時間複雜之度為 O(h)

593 自二元搜尋樹中刪除資料欲自二元搜尋樹中刪除資料可能會面臨兩種情況(1) 刪除節點 p 位於樹葉節點上如下圖所示 這是比較容易處理的情況

(2) 刪除的節點 p 不是樹葉刪除節點 p 後原 p 所指的節點可由 q ( q 中包含了 p 左子樹中的最大節點值)或 r ( r 中包含了 p 右子樹中的最小節點值)來取代以維持二元搜尋樹中資料的大小關係其結果分別如圖 (b) 和 (c) 所示而圖 (d) 則標明了指標會改變的節點位置即 p 會消失 father 的左或右子樹指標會異動 k(可能是 q 或 r )會移到原來 p 的位置其父節點 f 的左或右子樹指標會異動其中 father 為 p 父節點 f 為 k 的父節點

程式 5-18 自二元搜尋樹中刪除資料46 int DeleteBST (int x)47 Struct BSTreeNode p father k f48 p = root father = NULL49 while (p = NULL)50 if (x == p-gtdata) 找到 x 所在的節點 p 了51 if ((p-gtleftchild == NULL) ampamp 52 (p-gtrightchild == NULL))53 k = NULL p為樹葉節點54 else if (p-gtleftchild = NULL )x位於非子節

點55 f = p 找出 p左子樹的最右樹葉56 k = p-gtleftchild57 while (k-gtrightchild =NULL)58 f = k59 k = k-gtrightchild60 k將挪至原 p處61 if (p == f)62 f-gtleftchild = k-gtleftchild 63 else64 f-gtrightchild = k-gtleftchild 65

66 else p無左子樹67 f = p 找出 p右子樹的最左樹葉68 k = p-gtrightchild69 while (k-gtleftchild = NULL)70 f = k71 k = k -gtleftchild72 73 if (p == f)74 f-gtrightchild = k-gtrightchild 75 else76 f-gtleftchild = k-gtrightchild77 78 if (k = NULL) k挪至原 p處繼承 p的左右指標79 k-gtleftchild = p-gtleftchild80 k-gtrightchild = p-gtrightchild81 82 if (father == NULL) root = k83 else if (x lt father-gtdata) 決定 k是 father的左

或右兒子84 father-gtleftchild = k85 else86 father-gtrightchild = k87 free(p)88 return 1 成功地刪除 x於此傳回 1返回呼叫處89 51~89行為找到 x時應有的處理步驟

90 else 尚未找到 x繼續往下一階層找

91 father = p92 if (x lt p-gtdata)93 p = p-gtleftchild94 else95 p = p-gtrightchild96 97 98 return 0 未找到 x傳回 099

510  樹林 (forest)

bull 定義樹林(森林)是由 n 棵沒有交集的樹所構成的集合 n 0

下圖為一包含三棵樹的樹林

5101 轉換樹林為二元樹 之前我們以二元樹來表示樹因為二元樹有較多

好用的性質在此我們也將樹林轉換成二元樹看待

在 522 節中我們嘗試了用左子右兄弟的方式來表示樹這個方法事實上已將樹轉換成二元樹

即用左子右兄弟的結構重新組織樹林中的每一棵樹這些樹的樹根將視為兄弟而最左邊的樹則視為樹林二元樹的樹根如此一來樹林即可轉換成二元樹

範例 5-25右下圖為右上圖三棵樹所對應的三棵左子右兄弟二元樹

下圖則為上圖樹林所對應的一棵樹林二元樹

演算法 5-1  轉換樹林成二元樹輸入樹林 F = T1 T2 Tn

輸出 F 的二元樹表示 BT=(T1 T2 Tn)1 if (n == 0) 輸出空二元樹 2 else

3 root(T1 T2 Tn) = T1

4 leftsubtree(T1 T2 Tn) =

BT(T11 T12 T1m)5 BT(T11 T12 T1m)為 T1的子樹6 rightsubtree(T1 T2 Tn) =

BT(T2 T3 Tn)

樹林轉換成二元樹的過程樹林轉換成二元樹的過程

  • Slide 1
  • Slide 2
  • Slide 3
  • Slide 4
  • Slide 5
  • Slide 6
  • Slide 7
  • Slide 8
  • Slide 9
  • Slide 10
  • Slide 11
  • Slide 12
  • Slide 13
  • Slide 14
  • Slide 15
  • Slide 16
  • Slide 17
  • Slide 18
  • Slide 19
  • Slide 20
  • Slide 21
  • Slide 22
  • Slide 23
  • Slide 24
  • Slide 25
  • Slide 26
  • Slide 27
  • Slide 28
  • Slide 29
  • Slide 30
  • Slide 31
  • Slide 32
  • Slide 33
  • Slide 34
  • Slide 35
  • Slide 36
  • Slide 37
  • Slide 38
  • Slide 39
  • Slide 40
  • Slide 41
  • Slide 42
  • Slide 43
  • Slide 44
  • Slide 45
  • Slide 46
  • Slide 47
  • Slide 48
  • Slide 49
  • Slide 50
  • Slide 51
  • Slide 52
  • Slide 53
  • Slide 54
  • Slide 55
  • Slide 56
  • Slide 57
  • Slide 58
  • Slide 59
  • Slide 60
  • Slide 61
  • Slide 62
  • Slide 63
  • Slide 64
  • Slide 65
  • Slide 66
  • Slide 67
  • Slide 68
  • Slide 69
  • Slide 70
  • Slide 71
  • Slide 72
  • Slide 73
  • Slide 74
  • Slide 75
  • Slide 76
  • Slide 77
  • Slide 78
  • Slide 79
  • Slide 80
  • Slide 81
  • Slide 82
  • Slide 83
  • Slide 84
  • Slide 85
  • Slide 86
  • Slide 87
  • Slide 88
  • Slide 89
  • Slide 90
  • Slide 91
  • Slide 92
  • Slide 93
  • Slide 94
  • Slide 95
  • Slide 96
  • Slide 97
  • Slide 98
  • Slide 99
  • Slide 100
  • Slide 101
  • Slide 102
  • Slide 103
Page 74: 第五章 樹

程式 5-13 新增節點至最大堆積中最大堆積本身是一棵完備二元樹可以用陣列表示之 1 define maxsize 1002 int heap[maxsize]3 int n i4 void InsertHeap(int x)5 if (n == maxsize) HeapFull()6 else7 n++8 i = n9 while ((i gt 1) ampamp (x gt heap[i2]))10 heap[i] = heap[i2]11 i = 212 13 heap[i] = x14 16

程序 InsertHeap 在插入第 n 個資料時需要的時間複雜度為 O(logn)

1 100

2 93

3 84

4 77

5 65

6 81

7 79

8 66

9 70

10 50

100

84

77

93

8165

66 70

79

50

1

2 3

4 5 6 7

8 9 10

1 define maxsize 1002 int heap[maxsize]3 int n i4 void InsertHeap(int x)5 if (n == maxsize) HeapFull()6 else7 n++8 i = n9 while ((i gt 1) ampamp (x gt heap[i2]))10 heap[i] = heap[i2]11 i = 212 13 heap[i] = x14 16

582 刪除最大堆積中的最大資料圖 (a) 中的最大堆積在刪除最大資料後其樹形應變成如圖 (b) 所示將最未一個樹葉節點的資料 6 暫挪至樹根節點內如圖 (c) 再將圖 (c) 中非最大堆積的結構調整成為最大堆積 10 和 8 中選大者與 6對調即成圖 (d)

程式 5-14 刪除最大堆積中的最大資料(上接程式 5-13 )1 int DeleteHeap()2 int x i j3 if (n == 0) HeapEmpty()4 else5 x = heap[1]6 heap[1] = heap[n]7 n--8 i = 19 while (i lt= n2)10 if (heap[2i] gt heap[2i+1])11 j = 2i12 else13 j = 2i+114 if (heap[i]gtheap[j]) break15 swap(heap[i] heap[j])16 i = j17 18 return x19 20

其時間複雜度與其堆積高度相同是為O(logn) 其中 n 為堆積中的元素個數

1 100

2 93

3 84

4 77

5 65

6 81

7 79

8 66

9 70

10 50

100

84

77

93

8165

66 70

79

50

1

2 3

4 5 6 7

8 9 10

1 int DeletHeap()2 int x i j3 if(n == 0) HeapEmpty()4 else5 x = heap[1]6 heap[1] = heap[n]7 n--8 i = 19 while (i lt= n2)10 if ([heap[2i]] gt heap[2i+1])11 j = 2i12 else13 j = 2i+114 if (heap[i]gtheap[j]) break15 swap(heap[i] heap[j])16 i = j17 18 return x19 20

583  優先佇列bull 由於最大堆積(或最小堆積)的結構可提供最大元素(或最小元素)而其維護堆積的成本為 O(logn) n 為總元素個數

bull 堆積常被當成「優先佇列」 (priority queue) 使用mdash此時佇列被組織成完備二元樹若新加入佇列的資料有大小關係優先順序的需求時此優先佇列即以 O(logn) 的代價將新資料放入優先佇列(可能是最大或最小堆積依實際大小關係優先順序的需求而定)中mdash提供依元素的優先順序做為處理順序的服務

bull 一般的佇列為先進先出先到的先行處理優先佇列則是依據其優先順序優先順序高者先行處理與來到的先後無關

範例 5-21bull 在「分時」 (time sharing) 的作業系統中我們不希望使用中央處理器 (Central Processing Unit CPU) 時間短的「處理程序」 (process) 等候過久於是有「最短時間工作優先」 (shortest-job-first) 的排程策略此時等候的處理程序即可組織成之一最小堆積而 CPU 每次即取出存於樹根的處理程序予以處理任何新來的處理程序則以 O(logn) 的時間加入此優先佇列(最小堆積)中

bull 注意之前提過的線性佇列新來的直接加入成為新的尾端元素花O(1) 的時間然線性佇列本身沒有大小的關係每次要取出最大(小)的資料得花O(n) 的代價(在所有元素中挑出最大(小)者) n 為佇列內的元素個數而優先佇列是在新元素加入時要花O(logn) 的時間至於取出最大(小)則只須 O(1) 的時間

59  二元搜尋樹bull 二元搜尋樹是一棵二元樹可能是空二元樹若不為空二元樹則滿足下列性質ndash所有節點內的資料內容是相異的 ndash左子節點的資內容 ( 如果有 ) 比父節點的資料內容小 ndash右子節點的資料內容 ( 如果有 ) 比父節點的資料內容大 ndash以左和右子節點為樹根的左子樹和右子樹也是二元搜尋樹

在二元搜尋樹中搜尋資料二元搜尋樹對資料大小的掌握十分明確若恰與節點 p 資料相等則搜尋成功若欲搜尋的資料 x 比樹節點 p 中的資料小 則只須搜尋節點 p 的左子樹若比節點 p 中的資料大 則只須搜尋 p 的右子樹這樣的判斷可以遞迴地應用在各子樹節點 中

程式 5-15 在二元搜尋樹中搜尋資料(遞迴呼叫)1 struct BSTreeNode2 struct BSTreeNode leftchild3 int data4 struct BSTreeNode rightchild5 6 struct BSTreeNode root7 struct BSTreeNode SearchBST(

struct BSTreeNode tree int x)8 if (tree == NULL) return NULL9 if (x == tree-gtdata) return tree10 if (x lt tree-gtdata)11 return SearchBST(tree-gtleftchild x)

12 return SearchBST(tree-gtrightchild x)13

程式 5-16 搜尋二元搜尋樹的資料(非遞迴呼叫)在二元搜尋樹十分龐大時遞迴呼叫的效率不會太好可利用迴圈來完成搜尋的動作以提高執行效率請看程式 5-16

14 struct BSTreeNode Search BST_iterative15 (struct BSTreeNode node int x)16 while (node = NULL)17 if (x == node-gtdata) return node18 if (x lt node-gtdata)19 node = node-gtleftchild20 else21 node = node-gtrightchild22 23 return NULL24

592  新增資料於二元搜尋樹中欲新增資料進入一二元搜尋樹中得先決定該資料在二元搜尋樹中應出現的位置這可透過上節提到的搜尋動作來解決

在二元搜尋樹定義的性質 (1) 中我們限定了元素必須相異所以搜尋新資料的結果必是 NULL

必須將搜尋的結果改為新增資料所應在位置的節點指標

程式 5-17  在二元搜尋樹中新增資料25 int InsertBST(int x)26 struct BSTreeNode p q27 p = root q = NULL28 while (p = NULL)29 q = p30 if (x == p-gtdata) return 0

若不考慮重複則可省略之31 if (x lt p-gtdata)32 p = p-gtleftchild33 else34 p = p-gtrightchild35

3636 p = (struct BSTreeNode ) p = (struct BSTreeNode ) malloc (sizeof(BSTreeNode))malloc (sizeof(BSTreeNode))

3737 p-gtdata = x p-gtdata = x3838 p-gtleftchild = p-gtrightchild = p-gtleftchild = p-gtrightchild = NULLNULL3939 if (root == NULL) root = p if (root == NULL) root = p4040 else if (x lt q-gtdata) else if (x lt q-gtdata)4141 q-gtleftchild = p q-gtleftchild = p4242 else else4343 q-gtrightchild = p q-gtrightchild = p4444 return 1 return 145 45

程式 程式 5-175-17  在二元搜尋樹中新增資料  在二元搜尋樹中新增資料 (( 續續 ))

範例 5-23 圖 (b) 為圖 (a) 新增整數 6 之後的結果圖 (c) (d) (e) (f) 則分別是逐步再加入 28 18

10 21 的結果

bull若目前的二元搜尋樹高度為 h 則新增資料得花O(h) 的時間找到其所應在的位置遂新增資料至二元搜尋樹中的時間複雜之度為 O(h)

593 自二元搜尋樹中刪除資料欲自二元搜尋樹中刪除資料可能會面臨兩種情況(1) 刪除節點 p 位於樹葉節點上如下圖所示 這是比較容易處理的情況

(2) 刪除的節點 p 不是樹葉刪除節點 p 後原 p 所指的節點可由 q ( q 中包含了 p 左子樹中的最大節點值)或 r ( r 中包含了 p 右子樹中的最小節點值)來取代以維持二元搜尋樹中資料的大小關係其結果分別如圖 (b) 和 (c) 所示而圖 (d) 則標明了指標會改變的節點位置即 p 會消失 father 的左或右子樹指標會異動 k(可能是 q 或 r )會移到原來 p 的位置其父節點 f 的左或右子樹指標會異動其中 father 為 p 父節點 f 為 k 的父節點

程式 5-18 自二元搜尋樹中刪除資料46 int DeleteBST (int x)47 Struct BSTreeNode p father k f48 p = root father = NULL49 while (p = NULL)50 if (x == p-gtdata) 找到 x 所在的節點 p 了51 if ((p-gtleftchild == NULL) ampamp 52 (p-gtrightchild == NULL))53 k = NULL p為樹葉節點54 else if (p-gtleftchild = NULL )x位於非子節

點55 f = p 找出 p左子樹的最右樹葉56 k = p-gtleftchild57 while (k-gtrightchild =NULL)58 f = k59 k = k-gtrightchild60 k將挪至原 p處61 if (p == f)62 f-gtleftchild = k-gtleftchild 63 else64 f-gtrightchild = k-gtleftchild 65

66 else p無左子樹67 f = p 找出 p右子樹的最左樹葉68 k = p-gtrightchild69 while (k-gtleftchild = NULL)70 f = k71 k = k -gtleftchild72 73 if (p == f)74 f-gtrightchild = k-gtrightchild 75 else76 f-gtleftchild = k-gtrightchild77 78 if (k = NULL) k挪至原 p處繼承 p的左右指標79 k-gtleftchild = p-gtleftchild80 k-gtrightchild = p-gtrightchild81 82 if (father == NULL) root = k83 else if (x lt father-gtdata) 決定 k是 father的左

或右兒子84 father-gtleftchild = k85 else86 father-gtrightchild = k87 free(p)88 return 1 成功地刪除 x於此傳回 1返回呼叫處89 51~89行為找到 x時應有的處理步驟

90 else 尚未找到 x繼續往下一階層找

91 father = p92 if (x lt p-gtdata)93 p = p-gtleftchild94 else95 p = p-gtrightchild96 97 98 return 0 未找到 x傳回 099

510  樹林 (forest)

bull 定義樹林(森林)是由 n 棵沒有交集的樹所構成的集合 n 0

下圖為一包含三棵樹的樹林

5101 轉換樹林為二元樹 之前我們以二元樹來表示樹因為二元樹有較多

好用的性質在此我們也將樹林轉換成二元樹看待

在 522 節中我們嘗試了用左子右兄弟的方式來表示樹這個方法事實上已將樹轉換成二元樹

即用左子右兄弟的結構重新組織樹林中的每一棵樹這些樹的樹根將視為兄弟而最左邊的樹則視為樹林二元樹的樹根如此一來樹林即可轉換成二元樹

範例 5-25右下圖為右上圖三棵樹所對應的三棵左子右兄弟二元樹

下圖則為上圖樹林所對應的一棵樹林二元樹

演算法 5-1  轉換樹林成二元樹輸入樹林 F = T1 T2 Tn

輸出 F 的二元樹表示 BT=(T1 T2 Tn)1 if (n == 0) 輸出空二元樹 2 else

3 root(T1 T2 Tn) = T1

4 leftsubtree(T1 T2 Tn) =

BT(T11 T12 T1m)5 BT(T11 T12 T1m)為 T1的子樹6 rightsubtree(T1 T2 Tn) =

BT(T2 T3 Tn)

樹林轉換成二元樹的過程樹林轉換成二元樹的過程

  • Slide 1
  • Slide 2
  • Slide 3
  • Slide 4
  • Slide 5
  • Slide 6
  • Slide 7
  • Slide 8
  • Slide 9
  • Slide 10
  • Slide 11
  • Slide 12
  • Slide 13
  • Slide 14
  • Slide 15
  • Slide 16
  • Slide 17
  • Slide 18
  • Slide 19
  • Slide 20
  • Slide 21
  • Slide 22
  • Slide 23
  • Slide 24
  • Slide 25
  • Slide 26
  • Slide 27
  • Slide 28
  • Slide 29
  • Slide 30
  • Slide 31
  • Slide 32
  • Slide 33
  • Slide 34
  • Slide 35
  • Slide 36
  • Slide 37
  • Slide 38
  • Slide 39
  • Slide 40
  • Slide 41
  • Slide 42
  • Slide 43
  • Slide 44
  • Slide 45
  • Slide 46
  • Slide 47
  • Slide 48
  • Slide 49
  • Slide 50
  • Slide 51
  • Slide 52
  • Slide 53
  • Slide 54
  • Slide 55
  • Slide 56
  • Slide 57
  • Slide 58
  • Slide 59
  • Slide 60
  • Slide 61
  • Slide 62
  • Slide 63
  • Slide 64
  • Slide 65
  • Slide 66
  • Slide 67
  • Slide 68
  • Slide 69
  • Slide 70
  • Slide 71
  • Slide 72
  • Slide 73
  • Slide 74
  • Slide 75
  • Slide 76
  • Slide 77
  • Slide 78
  • Slide 79
  • Slide 80
  • Slide 81
  • Slide 82
  • Slide 83
  • Slide 84
  • Slide 85
  • Slide 86
  • Slide 87
  • Slide 88
  • Slide 89
  • Slide 90
  • Slide 91
  • Slide 92
  • Slide 93
  • Slide 94
  • Slide 95
  • Slide 96
  • Slide 97
  • Slide 98
  • Slide 99
  • Slide 100
  • Slide 101
  • Slide 102
  • Slide 103
Page 75: 第五章 樹

1 100

2 93

3 84

4 77

5 65

6 81

7 79

8 66

9 70

10 50

100

84

77

93

8165

66 70

79

50

1

2 3

4 5 6 7

8 9 10

1 define maxsize 1002 int heap[maxsize]3 int n i4 void InsertHeap(int x)5 if (n == maxsize) HeapFull()6 else7 n++8 i = n9 while ((i gt 1) ampamp (x gt heap[i2]))10 heap[i] = heap[i2]11 i = 212 13 heap[i] = x14 16

582 刪除最大堆積中的最大資料圖 (a) 中的最大堆積在刪除最大資料後其樹形應變成如圖 (b) 所示將最未一個樹葉節點的資料 6 暫挪至樹根節點內如圖 (c) 再將圖 (c) 中非最大堆積的結構調整成為最大堆積 10 和 8 中選大者與 6對調即成圖 (d)

程式 5-14 刪除最大堆積中的最大資料(上接程式 5-13 )1 int DeleteHeap()2 int x i j3 if (n == 0) HeapEmpty()4 else5 x = heap[1]6 heap[1] = heap[n]7 n--8 i = 19 while (i lt= n2)10 if (heap[2i] gt heap[2i+1])11 j = 2i12 else13 j = 2i+114 if (heap[i]gtheap[j]) break15 swap(heap[i] heap[j])16 i = j17 18 return x19 20

其時間複雜度與其堆積高度相同是為O(logn) 其中 n 為堆積中的元素個數

1 100

2 93

3 84

4 77

5 65

6 81

7 79

8 66

9 70

10 50

100

84

77

93

8165

66 70

79

50

1

2 3

4 5 6 7

8 9 10

1 int DeletHeap()2 int x i j3 if(n == 0) HeapEmpty()4 else5 x = heap[1]6 heap[1] = heap[n]7 n--8 i = 19 while (i lt= n2)10 if ([heap[2i]] gt heap[2i+1])11 j = 2i12 else13 j = 2i+114 if (heap[i]gtheap[j]) break15 swap(heap[i] heap[j])16 i = j17 18 return x19 20

583  優先佇列bull 由於最大堆積(或最小堆積)的結構可提供最大元素(或最小元素)而其維護堆積的成本為 O(logn) n 為總元素個數

bull 堆積常被當成「優先佇列」 (priority queue) 使用mdash此時佇列被組織成完備二元樹若新加入佇列的資料有大小關係優先順序的需求時此優先佇列即以 O(logn) 的代價將新資料放入優先佇列(可能是最大或最小堆積依實際大小關係優先順序的需求而定)中mdash提供依元素的優先順序做為處理順序的服務

bull 一般的佇列為先進先出先到的先行處理優先佇列則是依據其優先順序優先順序高者先行處理與來到的先後無關

範例 5-21bull 在「分時」 (time sharing) 的作業系統中我們不希望使用中央處理器 (Central Processing Unit CPU) 時間短的「處理程序」 (process) 等候過久於是有「最短時間工作優先」 (shortest-job-first) 的排程策略此時等候的處理程序即可組織成之一最小堆積而 CPU 每次即取出存於樹根的處理程序予以處理任何新來的處理程序則以 O(logn) 的時間加入此優先佇列(最小堆積)中

bull 注意之前提過的線性佇列新來的直接加入成為新的尾端元素花O(1) 的時間然線性佇列本身沒有大小的關係每次要取出最大(小)的資料得花O(n) 的代價(在所有元素中挑出最大(小)者) n 為佇列內的元素個數而優先佇列是在新元素加入時要花O(logn) 的時間至於取出最大(小)則只須 O(1) 的時間

59  二元搜尋樹bull 二元搜尋樹是一棵二元樹可能是空二元樹若不為空二元樹則滿足下列性質ndash所有節點內的資料內容是相異的 ndash左子節點的資內容 ( 如果有 ) 比父節點的資料內容小 ndash右子節點的資料內容 ( 如果有 ) 比父節點的資料內容大 ndash以左和右子節點為樹根的左子樹和右子樹也是二元搜尋樹

在二元搜尋樹中搜尋資料二元搜尋樹對資料大小的掌握十分明確若恰與節點 p 資料相等則搜尋成功若欲搜尋的資料 x 比樹節點 p 中的資料小 則只須搜尋節點 p 的左子樹若比節點 p 中的資料大 則只須搜尋 p 的右子樹這樣的判斷可以遞迴地應用在各子樹節點 中

程式 5-15 在二元搜尋樹中搜尋資料(遞迴呼叫)1 struct BSTreeNode2 struct BSTreeNode leftchild3 int data4 struct BSTreeNode rightchild5 6 struct BSTreeNode root7 struct BSTreeNode SearchBST(

struct BSTreeNode tree int x)8 if (tree == NULL) return NULL9 if (x == tree-gtdata) return tree10 if (x lt tree-gtdata)11 return SearchBST(tree-gtleftchild x)

12 return SearchBST(tree-gtrightchild x)13

程式 5-16 搜尋二元搜尋樹的資料(非遞迴呼叫)在二元搜尋樹十分龐大時遞迴呼叫的效率不會太好可利用迴圈來完成搜尋的動作以提高執行效率請看程式 5-16

14 struct BSTreeNode Search BST_iterative15 (struct BSTreeNode node int x)16 while (node = NULL)17 if (x == node-gtdata) return node18 if (x lt node-gtdata)19 node = node-gtleftchild20 else21 node = node-gtrightchild22 23 return NULL24

592  新增資料於二元搜尋樹中欲新增資料進入一二元搜尋樹中得先決定該資料在二元搜尋樹中應出現的位置這可透過上節提到的搜尋動作來解決

在二元搜尋樹定義的性質 (1) 中我們限定了元素必須相異所以搜尋新資料的結果必是 NULL

必須將搜尋的結果改為新增資料所應在位置的節點指標

程式 5-17  在二元搜尋樹中新增資料25 int InsertBST(int x)26 struct BSTreeNode p q27 p = root q = NULL28 while (p = NULL)29 q = p30 if (x == p-gtdata) return 0

若不考慮重複則可省略之31 if (x lt p-gtdata)32 p = p-gtleftchild33 else34 p = p-gtrightchild35

3636 p = (struct BSTreeNode ) p = (struct BSTreeNode ) malloc (sizeof(BSTreeNode))malloc (sizeof(BSTreeNode))

3737 p-gtdata = x p-gtdata = x3838 p-gtleftchild = p-gtrightchild = p-gtleftchild = p-gtrightchild = NULLNULL3939 if (root == NULL) root = p if (root == NULL) root = p4040 else if (x lt q-gtdata) else if (x lt q-gtdata)4141 q-gtleftchild = p q-gtleftchild = p4242 else else4343 q-gtrightchild = p q-gtrightchild = p4444 return 1 return 145 45

程式 程式 5-175-17  在二元搜尋樹中新增資料  在二元搜尋樹中新增資料 (( 續續 ))

範例 5-23 圖 (b) 為圖 (a) 新增整數 6 之後的結果圖 (c) (d) (e) (f) 則分別是逐步再加入 28 18

10 21 的結果

bull若目前的二元搜尋樹高度為 h 則新增資料得花O(h) 的時間找到其所應在的位置遂新增資料至二元搜尋樹中的時間複雜之度為 O(h)

593 自二元搜尋樹中刪除資料欲自二元搜尋樹中刪除資料可能會面臨兩種情況(1) 刪除節點 p 位於樹葉節點上如下圖所示 這是比較容易處理的情況

(2) 刪除的節點 p 不是樹葉刪除節點 p 後原 p 所指的節點可由 q ( q 中包含了 p 左子樹中的最大節點值)或 r ( r 中包含了 p 右子樹中的最小節點值)來取代以維持二元搜尋樹中資料的大小關係其結果分別如圖 (b) 和 (c) 所示而圖 (d) 則標明了指標會改變的節點位置即 p 會消失 father 的左或右子樹指標會異動 k(可能是 q 或 r )會移到原來 p 的位置其父節點 f 的左或右子樹指標會異動其中 father 為 p 父節點 f 為 k 的父節點

程式 5-18 自二元搜尋樹中刪除資料46 int DeleteBST (int x)47 Struct BSTreeNode p father k f48 p = root father = NULL49 while (p = NULL)50 if (x == p-gtdata) 找到 x 所在的節點 p 了51 if ((p-gtleftchild == NULL) ampamp 52 (p-gtrightchild == NULL))53 k = NULL p為樹葉節點54 else if (p-gtleftchild = NULL )x位於非子節

點55 f = p 找出 p左子樹的最右樹葉56 k = p-gtleftchild57 while (k-gtrightchild =NULL)58 f = k59 k = k-gtrightchild60 k將挪至原 p處61 if (p == f)62 f-gtleftchild = k-gtleftchild 63 else64 f-gtrightchild = k-gtleftchild 65

66 else p無左子樹67 f = p 找出 p右子樹的最左樹葉68 k = p-gtrightchild69 while (k-gtleftchild = NULL)70 f = k71 k = k -gtleftchild72 73 if (p == f)74 f-gtrightchild = k-gtrightchild 75 else76 f-gtleftchild = k-gtrightchild77 78 if (k = NULL) k挪至原 p處繼承 p的左右指標79 k-gtleftchild = p-gtleftchild80 k-gtrightchild = p-gtrightchild81 82 if (father == NULL) root = k83 else if (x lt father-gtdata) 決定 k是 father的左

或右兒子84 father-gtleftchild = k85 else86 father-gtrightchild = k87 free(p)88 return 1 成功地刪除 x於此傳回 1返回呼叫處89 51~89行為找到 x時應有的處理步驟

90 else 尚未找到 x繼續往下一階層找

91 father = p92 if (x lt p-gtdata)93 p = p-gtleftchild94 else95 p = p-gtrightchild96 97 98 return 0 未找到 x傳回 099

510  樹林 (forest)

bull 定義樹林(森林)是由 n 棵沒有交集的樹所構成的集合 n 0

下圖為一包含三棵樹的樹林

5101 轉換樹林為二元樹 之前我們以二元樹來表示樹因為二元樹有較多

好用的性質在此我們也將樹林轉換成二元樹看待

在 522 節中我們嘗試了用左子右兄弟的方式來表示樹這個方法事實上已將樹轉換成二元樹

即用左子右兄弟的結構重新組織樹林中的每一棵樹這些樹的樹根將視為兄弟而最左邊的樹則視為樹林二元樹的樹根如此一來樹林即可轉換成二元樹

範例 5-25右下圖為右上圖三棵樹所對應的三棵左子右兄弟二元樹

下圖則為上圖樹林所對應的一棵樹林二元樹

演算法 5-1  轉換樹林成二元樹輸入樹林 F = T1 T2 Tn

輸出 F 的二元樹表示 BT=(T1 T2 Tn)1 if (n == 0) 輸出空二元樹 2 else

3 root(T1 T2 Tn) = T1

4 leftsubtree(T1 T2 Tn) =

BT(T11 T12 T1m)5 BT(T11 T12 T1m)為 T1的子樹6 rightsubtree(T1 T2 Tn) =

BT(T2 T3 Tn)

樹林轉換成二元樹的過程樹林轉換成二元樹的過程

  • Slide 1
  • Slide 2
  • Slide 3
  • Slide 4
  • Slide 5
  • Slide 6
  • Slide 7
  • Slide 8
  • Slide 9
  • Slide 10
  • Slide 11
  • Slide 12
  • Slide 13
  • Slide 14
  • Slide 15
  • Slide 16
  • Slide 17
  • Slide 18
  • Slide 19
  • Slide 20
  • Slide 21
  • Slide 22
  • Slide 23
  • Slide 24
  • Slide 25
  • Slide 26
  • Slide 27
  • Slide 28
  • Slide 29
  • Slide 30
  • Slide 31
  • Slide 32
  • Slide 33
  • Slide 34
  • Slide 35
  • Slide 36
  • Slide 37
  • Slide 38
  • Slide 39
  • Slide 40
  • Slide 41
  • Slide 42
  • Slide 43
  • Slide 44
  • Slide 45
  • Slide 46
  • Slide 47
  • Slide 48
  • Slide 49
  • Slide 50
  • Slide 51
  • Slide 52
  • Slide 53
  • Slide 54
  • Slide 55
  • Slide 56
  • Slide 57
  • Slide 58
  • Slide 59
  • Slide 60
  • Slide 61
  • Slide 62
  • Slide 63
  • Slide 64
  • Slide 65
  • Slide 66
  • Slide 67
  • Slide 68
  • Slide 69
  • Slide 70
  • Slide 71
  • Slide 72
  • Slide 73
  • Slide 74
  • Slide 75
  • Slide 76
  • Slide 77
  • Slide 78
  • Slide 79
  • Slide 80
  • Slide 81
  • Slide 82
  • Slide 83
  • Slide 84
  • Slide 85
  • Slide 86
  • Slide 87
  • Slide 88
  • Slide 89
  • Slide 90
  • Slide 91
  • Slide 92
  • Slide 93
  • Slide 94
  • Slide 95
  • Slide 96
  • Slide 97
  • Slide 98
  • Slide 99
  • Slide 100
  • Slide 101
  • Slide 102
  • Slide 103
Page 76: 第五章 樹

582 刪除最大堆積中的最大資料圖 (a) 中的最大堆積在刪除最大資料後其樹形應變成如圖 (b) 所示將最未一個樹葉節點的資料 6 暫挪至樹根節點內如圖 (c) 再將圖 (c) 中非最大堆積的結構調整成為最大堆積 10 和 8 中選大者與 6對調即成圖 (d)

程式 5-14 刪除最大堆積中的最大資料(上接程式 5-13 )1 int DeleteHeap()2 int x i j3 if (n == 0) HeapEmpty()4 else5 x = heap[1]6 heap[1] = heap[n]7 n--8 i = 19 while (i lt= n2)10 if (heap[2i] gt heap[2i+1])11 j = 2i12 else13 j = 2i+114 if (heap[i]gtheap[j]) break15 swap(heap[i] heap[j])16 i = j17 18 return x19 20

其時間複雜度與其堆積高度相同是為O(logn) 其中 n 為堆積中的元素個數

1 100

2 93

3 84

4 77

5 65

6 81

7 79

8 66

9 70

10 50

100

84

77

93

8165

66 70

79

50

1

2 3

4 5 6 7

8 9 10

1 int DeletHeap()2 int x i j3 if(n == 0) HeapEmpty()4 else5 x = heap[1]6 heap[1] = heap[n]7 n--8 i = 19 while (i lt= n2)10 if ([heap[2i]] gt heap[2i+1])11 j = 2i12 else13 j = 2i+114 if (heap[i]gtheap[j]) break15 swap(heap[i] heap[j])16 i = j17 18 return x19 20

583  優先佇列bull 由於最大堆積(或最小堆積)的結構可提供最大元素(或最小元素)而其維護堆積的成本為 O(logn) n 為總元素個數

bull 堆積常被當成「優先佇列」 (priority queue) 使用mdash此時佇列被組織成完備二元樹若新加入佇列的資料有大小關係優先順序的需求時此優先佇列即以 O(logn) 的代價將新資料放入優先佇列(可能是最大或最小堆積依實際大小關係優先順序的需求而定)中mdash提供依元素的優先順序做為處理順序的服務

bull 一般的佇列為先進先出先到的先行處理優先佇列則是依據其優先順序優先順序高者先行處理與來到的先後無關

範例 5-21bull 在「分時」 (time sharing) 的作業系統中我們不希望使用中央處理器 (Central Processing Unit CPU) 時間短的「處理程序」 (process) 等候過久於是有「最短時間工作優先」 (shortest-job-first) 的排程策略此時等候的處理程序即可組織成之一最小堆積而 CPU 每次即取出存於樹根的處理程序予以處理任何新來的處理程序則以 O(logn) 的時間加入此優先佇列(最小堆積)中

bull 注意之前提過的線性佇列新來的直接加入成為新的尾端元素花O(1) 的時間然線性佇列本身沒有大小的關係每次要取出最大(小)的資料得花O(n) 的代價(在所有元素中挑出最大(小)者) n 為佇列內的元素個數而優先佇列是在新元素加入時要花O(logn) 的時間至於取出最大(小)則只須 O(1) 的時間

59  二元搜尋樹bull 二元搜尋樹是一棵二元樹可能是空二元樹若不為空二元樹則滿足下列性質ndash所有節點內的資料內容是相異的 ndash左子節點的資內容 ( 如果有 ) 比父節點的資料內容小 ndash右子節點的資料內容 ( 如果有 ) 比父節點的資料內容大 ndash以左和右子節點為樹根的左子樹和右子樹也是二元搜尋樹

在二元搜尋樹中搜尋資料二元搜尋樹對資料大小的掌握十分明確若恰與節點 p 資料相等則搜尋成功若欲搜尋的資料 x 比樹節點 p 中的資料小 則只須搜尋節點 p 的左子樹若比節點 p 中的資料大 則只須搜尋 p 的右子樹這樣的判斷可以遞迴地應用在各子樹節點 中

程式 5-15 在二元搜尋樹中搜尋資料(遞迴呼叫)1 struct BSTreeNode2 struct BSTreeNode leftchild3 int data4 struct BSTreeNode rightchild5 6 struct BSTreeNode root7 struct BSTreeNode SearchBST(

struct BSTreeNode tree int x)8 if (tree == NULL) return NULL9 if (x == tree-gtdata) return tree10 if (x lt tree-gtdata)11 return SearchBST(tree-gtleftchild x)

12 return SearchBST(tree-gtrightchild x)13

程式 5-16 搜尋二元搜尋樹的資料(非遞迴呼叫)在二元搜尋樹十分龐大時遞迴呼叫的效率不會太好可利用迴圈來完成搜尋的動作以提高執行效率請看程式 5-16

14 struct BSTreeNode Search BST_iterative15 (struct BSTreeNode node int x)16 while (node = NULL)17 if (x == node-gtdata) return node18 if (x lt node-gtdata)19 node = node-gtleftchild20 else21 node = node-gtrightchild22 23 return NULL24

592  新增資料於二元搜尋樹中欲新增資料進入一二元搜尋樹中得先決定該資料在二元搜尋樹中應出現的位置這可透過上節提到的搜尋動作來解決

在二元搜尋樹定義的性質 (1) 中我們限定了元素必須相異所以搜尋新資料的結果必是 NULL

必須將搜尋的結果改為新增資料所應在位置的節點指標

程式 5-17  在二元搜尋樹中新增資料25 int InsertBST(int x)26 struct BSTreeNode p q27 p = root q = NULL28 while (p = NULL)29 q = p30 if (x == p-gtdata) return 0

若不考慮重複則可省略之31 if (x lt p-gtdata)32 p = p-gtleftchild33 else34 p = p-gtrightchild35

3636 p = (struct BSTreeNode ) p = (struct BSTreeNode ) malloc (sizeof(BSTreeNode))malloc (sizeof(BSTreeNode))

3737 p-gtdata = x p-gtdata = x3838 p-gtleftchild = p-gtrightchild = p-gtleftchild = p-gtrightchild = NULLNULL3939 if (root == NULL) root = p if (root == NULL) root = p4040 else if (x lt q-gtdata) else if (x lt q-gtdata)4141 q-gtleftchild = p q-gtleftchild = p4242 else else4343 q-gtrightchild = p q-gtrightchild = p4444 return 1 return 145 45

程式 程式 5-175-17  在二元搜尋樹中新增資料  在二元搜尋樹中新增資料 (( 續續 ))

範例 5-23 圖 (b) 為圖 (a) 新增整數 6 之後的結果圖 (c) (d) (e) (f) 則分別是逐步再加入 28 18

10 21 的結果

bull若目前的二元搜尋樹高度為 h 則新增資料得花O(h) 的時間找到其所應在的位置遂新增資料至二元搜尋樹中的時間複雜之度為 O(h)

593 自二元搜尋樹中刪除資料欲自二元搜尋樹中刪除資料可能會面臨兩種情況(1) 刪除節點 p 位於樹葉節點上如下圖所示 這是比較容易處理的情況

(2) 刪除的節點 p 不是樹葉刪除節點 p 後原 p 所指的節點可由 q ( q 中包含了 p 左子樹中的最大節點值)或 r ( r 中包含了 p 右子樹中的最小節點值)來取代以維持二元搜尋樹中資料的大小關係其結果分別如圖 (b) 和 (c) 所示而圖 (d) 則標明了指標會改變的節點位置即 p 會消失 father 的左或右子樹指標會異動 k(可能是 q 或 r )會移到原來 p 的位置其父節點 f 的左或右子樹指標會異動其中 father 為 p 父節點 f 為 k 的父節點

程式 5-18 自二元搜尋樹中刪除資料46 int DeleteBST (int x)47 Struct BSTreeNode p father k f48 p = root father = NULL49 while (p = NULL)50 if (x == p-gtdata) 找到 x 所在的節點 p 了51 if ((p-gtleftchild == NULL) ampamp 52 (p-gtrightchild == NULL))53 k = NULL p為樹葉節點54 else if (p-gtleftchild = NULL )x位於非子節

點55 f = p 找出 p左子樹的最右樹葉56 k = p-gtleftchild57 while (k-gtrightchild =NULL)58 f = k59 k = k-gtrightchild60 k將挪至原 p處61 if (p == f)62 f-gtleftchild = k-gtleftchild 63 else64 f-gtrightchild = k-gtleftchild 65

66 else p無左子樹67 f = p 找出 p右子樹的最左樹葉68 k = p-gtrightchild69 while (k-gtleftchild = NULL)70 f = k71 k = k -gtleftchild72 73 if (p == f)74 f-gtrightchild = k-gtrightchild 75 else76 f-gtleftchild = k-gtrightchild77 78 if (k = NULL) k挪至原 p處繼承 p的左右指標79 k-gtleftchild = p-gtleftchild80 k-gtrightchild = p-gtrightchild81 82 if (father == NULL) root = k83 else if (x lt father-gtdata) 決定 k是 father的左

或右兒子84 father-gtleftchild = k85 else86 father-gtrightchild = k87 free(p)88 return 1 成功地刪除 x於此傳回 1返回呼叫處89 51~89行為找到 x時應有的處理步驟

90 else 尚未找到 x繼續往下一階層找

91 father = p92 if (x lt p-gtdata)93 p = p-gtleftchild94 else95 p = p-gtrightchild96 97 98 return 0 未找到 x傳回 099

510  樹林 (forest)

bull 定義樹林(森林)是由 n 棵沒有交集的樹所構成的集合 n 0

下圖為一包含三棵樹的樹林

5101 轉換樹林為二元樹 之前我們以二元樹來表示樹因為二元樹有較多

好用的性質在此我們也將樹林轉換成二元樹看待

在 522 節中我們嘗試了用左子右兄弟的方式來表示樹這個方法事實上已將樹轉換成二元樹

即用左子右兄弟的結構重新組織樹林中的每一棵樹這些樹的樹根將視為兄弟而最左邊的樹則視為樹林二元樹的樹根如此一來樹林即可轉換成二元樹

範例 5-25右下圖為右上圖三棵樹所對應的三棵左子右兄弟二元樹

下圖則為上圖樹林所對應的一棵樹林二元樹

演算法 5-1  轉換樹林成二元樹輸入樹林 F = T1 T2 Tn

輸出 F 的二元樹表示 BT=(T1 T2 Tn)1 if (n == 0) 輸出空二元樹 2 else

3 root(T1 T2 Tn) = T1

4 leftsubtree(T1 T2 Tn) =

BT(T11 T12 T1m)5 BT(T11 T12 T1m)為 T1的子樹6 rightsubtree(T1 T2 Tn) =

BT(T2 T3 Tn)

樹林轉換成二元樹的過程樹林轉換成二元樹的過程

  • Slide 1
  • Slide 2
  • Slide 3
  • Slide 4
  • Slide 5
  • Slide 6
  • Slide 7
  • Slide 8
  • Slide 9
  • Slide 10
  • Slide 11
  • Slide 12
  • Slide 13
  • Slide 14
  • Slide 15
  • Slide 16
  • Slide 17
  • Slide 18
  • Slide 19
  • Slide 20
  • Slide 21
  • Slide 22
  • Slide 23
  • Slide 24
  • Slide 25
  • Slide 26
  • Slide 27
  • Slide 28
  • Slide 29
  • Slide 30
  • Slide 31
  • Slide 32
  • Slide 33
  • Slide 34
  • Slide 35
  • Slide 36
  • Slide 37
  • Slide 38
  • Slide 39
  • Slide 40
  • Slide 41
  • Slide 42
  • Slide 43
  • Slide 44
  • Slide 45
  • Slide 46
  • Slide 47
  • Slide 48
  • Slide 49
  • Slide 50
  • Slide 51
  • Slide 52
  • Slide 53
  • Slide 54
  • Slide 55
  • Slide 56
  • Slide 57
  • Slide 58
  • Slide 59
  • Slide 60
  • Slide 61
  • Slide 62
  • Slide 63
  • Slide 64
  • Slide 65
  • Slide 66
  • Slide 67
  • Slide 68
  • Slide 69
  • Slide 70
  • Slide 71
  • Slide 72
  • Slide 73
  • Slide 74
  • Slide 75
  • Slide 76
  • Slide 77
  • Slide 78
  • Slide 79
  • Slide 80
  • Slide 81
  • Slide 82
  • Slide 83
  • Slide 84
  • Slide 85
  • Slide 86
  • Slide 87
  • Slide 88
  • Slide 89
  • Slide 90
  • Slide 91
  • Slide 92
  • Slide 93
  • Slide 94
  • Slide 95
  • Slide 96
  • Slide 97
  • Slide 98
  • Slide 99
  • Slide 100
  • Slide 101
  • Slide 102
  • Slide 103
Page 77: 第五章 樹

程式 5-14 刪除最大堆積中的最大資料(上接程式 5-13 )1 int DeleteHeap()2 int x i j3 if (n == 0) HeapEmpty()4 else5 x = heap[1]6 heap[1] = heap[n]7 n--8 i = 19 while (i lt= n2)10 if (heap[2i] gt heap[2i+1])11 j = 2i12 else13 j = 2i+114 if (heap[i]gtheap[j]) break15 swap(heap[i] heap[j])16 i = j17 18 return x19 20

其時間複雜度與其堆積高度相同是為O(logn) 其中 n 為堆積中的元素個數

1 100

2 93

3 84

4 77

5 65

6 81

7 79

8 66

9 70

10 50

100

84

77

93

8165

66 70

79

50

1

2 3

4 5 6 7

8 9 10

1 int DeletHeap()2 int x i j3 if(n == 0) HeapEmpty()4 else5 x = heap[1]6 heap[1] = heap[n]7 n--8 i = 19 while (i lt= n2)10 if ([heap[2i]] gt heap[2i+1])11 j = 2i12 else13 j = 2i+114 if (heap[i]gtheap[j]) break15 swap(heap[i] heap[j])16 i = j17 18 return x19 20

583  優先佇列bull 由於最大堆積(或最小堆積)的結構可提供最大元素(或最小元素)而其維護堆積的成本為 O(logn) n 為總元素個數

bull 堆積常被當成「優先佇列」 (priority queue) 使用mdash此時佇列被組織成完備二元樹若新加入佇列的資料有大小關係優先順序的需求時此優先佇列即以 O(logn) 的代價將新資料放入優先佇列(可能是最大或最小堆積依實際大小關係優先順序的需求而定)中mdash提供依元素的優先順序做為處理順序的服務

bull 一般的佇列為先進先出先到的先行處理優先佇列則是依據其優先順序優先順序高者先行處理與來到的先後無關

範例 5-21bull 在「分時」 (time sharing) 的作業系統中我們不希望使用中央處理器 (Central Processing Unit CPU) 時間短的「處理程序」 (process) 等候過久於是有「最短時間工作優先」 (shortest-job-first) 的排程策略此時等候的處理程序即可組織成之一最小堆積而 CPU 每次即取出存於樹根的處理程序予以處理任何新來的處理程序則以 O(logn) 的時間加入此優先佇列(最小堆積)中

bull 注意之前提過的線性佇列新來的直接加入成為新的尾端元素花O(1) 的時間然線性佇列本身沒有大小的關係每次要取出最大(小)的資料得花O(n) 的代價(在所有元素中挑出最大(小)者) n 為佇列內的元素個數而優先佇列是在新元素加入時要花O(logn) 的時間至於取出最大(小)則只須 O(1) 的時間

59  二元搜尋樹bull 二元搜尋樹是一棵二元樹可能是空二元樹若不為空二元樹則滿足下列性質ndash所有節點內的資料內容是相異的 ndash左子節點的資內容 ( 如果有 ) 比父節點的資料內容小 ndash右子節點的資料內容 ( 如果有 ) 比父節點的資料內容大 ndash以左和右子節點為樹根的左子樹和右子樹也是二元搜尋樹

在二元搜尋樹中搜尋資料二元搜尋樹對資料大小的掌握十分明確若恰與節點 p 資料相等則搜尋成功若欲搜尋的資料 x 比樹節點 p 中的資料小 則只須搜尋節點 p 的左子樹若比節點 p 中的資料大 則只須搜尋 p 的右子樹這樣的判斷可以遞迴地應用在各子樹節點 中

程式 5-15 在二元搜尋樹中搜尋資料(遞迴呼叫)1 struct BSTreeNode2 struct BSTreeNode leftchild3 int data4 struct BSTreeNode rightchild5 6 struct BSTreeNode root7 struct BSTreeNode SearchBST(

struct BSTreeNode tree int x)8 if (tree == NULL) return NULL9 if (x == tree-gtdata) return tree10 if (x lt tree-gtdata)11 return SearchBST(tree-gtleftchild x)

12 return SearchBST(tree-gtrightchild x)13

程式 5-16 搜尋二元搜尋樹的資料(非遞迴呼叫)在二元搜尋樹十分龐大時遞迴呼叫的效率不會太好可利用迴圈來完成搜尋的動作以提高執行效率請看程式 5-16

14 struct BSTreeNode Search BST_iterative15 (struct BSTreeNode node int x)16 while (node = NULL)17 if (x == node-gtdata) return node18 if (x lt node-gtdata)19 node = node-gtleftchild20 else21 node = node-gtrightchild22 23 return NULL24

592  新增資料於二元搜尋樹中欲新增資料進入一二元搜尋樹中得先決定該資料在二元搜尋樹中應出現的位置這可透過上節提到的搜尋動作來解決

在二元搜尋樹定義的性質 (1) 中我們限定了元素必須相異所以搜尋新資料的結果必是 NULL

必須將搜尋的結果改為新增資料所應在位置的節點指標

程式 5-17  在二元搜尋樹中新增資料25 int InsertBST(int x)26 struct BSTreeNode p q27 p = root q = NULL28 while (p = NULL)29 q = p30 if (x == p-gtdata) return 0

若不考慮重複則可省略之31 if (x lt p-gtdata)32 p = p-gtleftchild33 else34 p = p-gtrightchild35

3636 p = (struct BSTreeNode ) p = (struct BSTreeNode ) malloc (sizeof(BSTreeNode))malloc (sizeof(BSTreeNode))

3737 p-gtdata = x p-gtdata = x3838 p-gtleftchild = p-gtrightchild = p-gtleftchild = p-gtrightchild = NULLNULL3939 if (root == NULL) root = p if (root == NULL) root = p4040 else if (x lt q-gtdata) else if (x lt q-gtdata)4141 q-gtleftchild = p q-gtleftchild = p4242 else else4343 q-gtrightchild = p q-gtrightchild = p4444 return 1 return 145 45

程式 程式 5-175-17  在二元搜尋樹中新增資料  在二元搜尋樹中新增資料 (( 續續 ))

範例 5-23 圖 (b) 為圖 (a) 新增整數 6 之後的結果圖 (c) (d) (e) (f) 則分別是逐步再加入 28 18

10 21 的結果

bull若目前的二元搜尋樹高度為 h 則新增資料得花O(h) 的時間找到其所應在的位置遂新增資料至二元搜尋樹中的時間複雜之度為 O(h)

593 自二元搜尋樹中刪除資料欲自二元搜尋樹中刪除資料可能會面臨兩種情況(1) 刪除節點 p 位於樹葉節點上如下圖所示 這是比較容易處理的情況

(2) 刪除的節點 p 不是樹葉刪除節點 p 後原 p 所指的節點可由 q ( q 中包含了 p 左子樹中的最大節點值)或 r ( r 中包含了 p 右子樹中的最小節點值)來取代以維持二元搜尋樹中資料的大小關係其結果分別如圖 (b) 和 (c) 所示而圖 (d) 則標明了指標會改變的節點位置即 p 會消失 father 的左或右子樹指標會異動 k(可能是 q 或 r )會移到原來 p 的位置其父節點 f 的左或右子樹指標會異動其中 father 為 p 父節點 f 為 k 的父節點

程式 5-18 自二元搜尋樹中刪除資料46 int DeleteBST (int x)47 Struct BSTreeNode p father k f48 p = root father = NULL49 while (p = NULL)50 if (x == p-gtdata) 找到 x 所在的節點 p 了51 if ((p-gtleftchild == NULL) ampamp 52 (p-gtrightchild == NULL))53 k = NULL p為樹葉節點54 else if (p-gtleftchild = NULL )x位於非子節

點55 f = p 找出 p左子樹的最右樹葉56 k = p-gtleftchild57 while (k-gtrightchild =NULL)58 f = k59 k = k-gtrightchild60 k將挪至原 p處61 if (p == f)62 f-gtleftchild = k-gtleftchild 63 else64 f-gtrightchild = k-gtleftchild 65

66 else p無左子樹67 f = p 找出 p右子樹的最左樹葉68 k = p-gtrightchild69 while (k-gtleftchild = NULL)70 f = k71 k = k -gtleftchild72 73 if (p == f)74 f-gtrightchild = k-gtrightchild 75 else76 f-gtleftchild = k-gtrightchild77 78 if (k = NULL) k挪至原 p處繼承 p的左右指標79 k-gtleftchild = p-gtleftchild80 k-gtrightchild = p-gtrightchild81 82 if (father == NULL) root = k83 else if (x lt father-gtdata) 決定 k是 father的左

或右兒子84 father-gtleftchild = k85 else86 father-gtrightchild = k87 free(p)88 return 1 成功地刪除 x於此傳回 1返回呼叫處89 51~89行為找到 x時應有的處理步驟

90 else 尚未找到 x繼續往下一階層找

91 father = p92 if (x lt p-gtdata)93 p = p-gtleftchild94 else95 p = p-gtrightchild96 97 98 return 0 未找到 x傳回 099

510  樹林 (forest)

bull 定義樹林(森林)是由 n 棵沒有交集的樹所構成的集合 n 0

下圖為一包含三棵樹的樹林

5101 轉換樹林為二元樹 之前我們以二元樹來表示樹因為二元樹有較多

好用的性質在此我們也將樹林轉換成二元樹看待

在 522 節中我們嘗試了用左子右兄弟的方式來表示樹這個方法事實上已將樹轉換成二元樹

即用左子右兄弟的結構重新組織樹林中的每一棵樹這些樹的樹根將視為兄弟而最左邊的樹則視為樹林二元樹的樹根如此一來樹林即可轉換成二元樹

範例 5-25右下圖為右上圖三棵樹所對應的三棵左子右兄弟二元樹

下圖則為上圖樹林所對應的一棵樹林二元樹

演算法 5-1  轉換樹林成二元樹輸入樹林 F = T1 T2 Tn

輸出 F 的二元樹表示 BT=(T1 T2 Tn)1 if (n == 0) 輸出空二元樹 2 else

3 root(T1 T2 Tn) = T1

4 leftsubtree(T1 T2 Tn) =

BT(T11 T12 T1m)5 BT(T11 T12 T1m)為 T1的子樹6 rightsubtree(T1 T2 Tn) =

BT(T2 T3 Tn)

樹林轉換成二元樹的過程樹林轉換成二元樹的過程

  • Slide 1
  • Slide 2
  • Slide 3
  • Slide 4
  • Slide 5
  • Slide 6
  • Slide 7
  • Slide 8
  • Slide 9
  • Slide 10
  • Slide 11
  • Slide 12
  • Slide 13
  • Slide 14
  • Slide 15
  • Slide 16
  • Slide 17
  • Slide 18
  • Slide 19
  • Slide 20
  • Slide 21
  • Slide 22
  • Slide 23
  • Slide 24
  • Slide 25
  • Slide 26
  • Slide 27
  • Slide 28
  • Slide 29
  • Slide 30
  • Slide 31
  • Slide 32
  • Slide 33
  • Slide 34
  • Slide 35
  • Slide 36
  • Slide 37
  • Slide 38
  • Slide 39
  • Slide 40
  • Slide 41
  • Slide 42
  • Slide 43
  • Slide 44
  • Slide 45
  • Slide 46
  • Slide 47
  • Slide 48
  • Slide 49
  • Slide 50
  • Slide 51
  • Slide 52
  • Slide 53
  • Slide 54
  • Slide 55
  • Slide 56
  • Slide 57
  • Slide 58
  • Slide 59
  • Slide 60
  • Slide 61
  • Slide 62
  • Slide 63
  • Slide 64
  • Slide 65
  • Slide 66
  • Slide 67
  • Slide 68
  • Slide 69
  • Slide 70
  • Slide 71
  • Slide 72
  • Slide 73
  • Slide 74
  • Slide 75
  • Slide 76
  • Slide 77
  • Slide 78
  • Slide 79
  • Slide 80
  • Slide 81
  • Slide 82
  • Slide 83
  • Slide 84
  • Slide 85
  • Slide 86
  • Slide 87
  • Slide 88
  • Slide 89
  • Slide 90
  • Slide 91
  • Slide 92
  • Slide 93
  • Slide 94
  • Slide 95
  • Slide 96
  • Slide 97
  • Slide 98
  • Slide 99
  • Slide 100
  • Slide 101
  • Slide 102
  • Slide 103
Page 78: 第五章 樹

1 100

2 93

3 84

4 77

5 65

6 81

7 79

8 66

9 70

10 50

100

84

77

93

8165

66 70

79

50

1

2 3

4 5 6 7

8 9 10

1 int DeletHeap()2 int x i j3 if(n == 0) HeapEmpty()4 else5 x = heap[1]6 heap[1] = heap[n]7 n--8 i = 19 while (i lt= n2)10 if ([heap[2i]] gt heap[2i+1])11 j = 2i12 else13 j = 2i+114 if (heap[i]gtheap[j]) break15 swap(heap[i] heap[j])16 i = j17 18 return x19 20

583  優先佇列bull 由於最大堆積(或最小堆積)的結構可提供最大元素(或最小元素)而其維護堆積的成本為 O(logn) n 為總元素個數

bull 堆積常被當成「優先佇列」 (priority queue) 使用mdash此時佇列被組織成完備二元樹若新加入佇列的資料有大小關係優先順序的需求時此優先佇列即以 O(logn) 的代價將新資料放入優先佇列(可能是最大或最小堆積依實際大小關係優先順序的需求而定)中mdash提供依元素的優先順序做為處理順序的服務

bull 一般的佇列為先進先出先到的先行處理優先佇列則是依據其優先順序優先順序高者先行處理與來到的先後無關

範例 5-21bull 在「分時」 (time sharing) 的作業系統中我們不希望使用中央處理器 (Central Processing Unit CPU) 時間短的「處理程序」 (process) 等候過久於是有「最短時間工作優先」 (shortest-job-first) 的排程策略此時等候的處理程序即可組織成之一最小堆積而 CPU 每次即取出存於樹根的處理程序予以處理任何新來的處理程序則以 O(logn) 的時間加入此優先佇列(最小堆積)中

bull 注意之前提過的線性佇列新來的直接加入成為新的尾端元素花O(1) 的時間然線性佇列本身沒有大小的關係每次要取出最大(小)的資料得花O(n) 的代價(在所有元素中挑出最大(小)者) n 為佇列內的元素個數而優先佇列是在新元素加入時要花O(logn) 的時間至於取出最大(小)則只須 O(1) 的時間

59  二元搜尋樹bull 二元搜尋樹是一棵二元樹可能是空二元樹若不為空二元樹則滿足下列性質ndash所有節點內的資料內容是相異的 ndash左子節點的資內容 ( 如果有 ) 比父節點的資料內容小 ndash右子節點的資料內容 ( 如果有 ) 比父節點的資料內容大 ndash以左和右子節點為樹根的左子樹和右子樹也是二元搜尋樹

在二元搜尋樹中搜尋資料二元搜尋樹對資料大小的掌握十分明確若恰與節點 p 資料相等則搜尋成功若欲搜尋的資料 x 比樹節點 p 中的資料小 則只須搜尋節點 p 的左子樹若比節點 p 中的資料大 則只須搜尋 p 的右子樹這樣的判斷可以遞迴地應用在各子樹節點 中

程式 5-15 在二元搜尋樹中搜尋資料(遞迴呼叫)1 struct BSTreeNode2 struct BSTreeNode leftchild3 int data4 struct BSTreeNode rightchild5 6 struct BSTreeNode root7 struct BSTreeNode SearchBST(

struct BSTreeNode tree int x)8 if (tree == NULL) return NULL9 if (x == tree-gtdata) return tree10 if (x lt tree-gtdata)11 return SearchBST(tree-gtleftchild x)

12 return SearchBST(tree-gtrightchild x)13

程式 5-16 搜尋二元搜尋樹的資料(非遞迴呼叫)在二元搜尋樹十分龐大時遞迴呼叫的效率不會太好可利用迴圈來完成搜尋的動作以提高執行效率請看程式 5-16

14 struct BSTreeNode Search BST_iterative15 (struct BSTreeNode node int x)16 while (node = NULL)17 if (x == node-gtdata) return node18 if (x lt node-gtdata)19 node = node-gtleftchild20 else21 node = node-gtrightchild22 23 return NULL24

592  新增資料於二元搜尋樹中欲新增資料進入一二元搜尋樹中得先決定該資料在二元搜尋樹中應出現的位置這可透過上節提到的搜尋動作來解決

在二元搜尋樹定義的性質 (1) 中我們限定了元素必須相異所以搜尋新資料的結果必是 NULL

必須將搜尋的結果改為新增資料所應在位置的節點指標

程式 5-17  在二元搜尋樹中新增資料25 int InsertBST(int x)26 struct BSTreeNode p q27 p = root q = NULL28 while (p = NULL)29 q = p30 if (x == p-gtdata) return 0

若不考慮重複則可省略之31 if (x lt p-gtdata)32 p = p-gtleftchild33 else34 p = p-gtrightchild35

3636 p = (struct BSTreeNode ) p = (struct BSTreeNode ) malloc (sizeof(BSTreeNode))malloc (sizeof(BSTreeNode))

3737 p-gtdata = x p-gtdata = x3838 p-gtleftchild = p-gtrightchild = p-gtleftchild = p-gtrightchild = NULLNULL3939 if (root == NULL) root = p if (root == NULL) root = p4040 else if (x lt q-gtdata) else if (x lt q-gtdata)4141 q-gtleftchild = p q-gtleftchild = p4242 else else4343 q-gtrightchild = p q-gtrightchild = p4444 return 1 return 145 45

程式 程式 5-175-17  在二元搜尋樹中新增資料  在二元搜尋樹中新增資料 (( 續續 ))

範例 5-23 圖 (b) 為圖 (a) 新增整數 6 之後的結果圖 (c) (d) (e) (f) 則分別是逐步再加入 28 18

10 21 的結果

bull若目前的二元搜尋樹高度為 h 則新增資料得花O(h) 的時間找到其所應在的位置遂新增資料至二元搜尋樹中的時間複雜之度為 O(h)

593 自二元搜尋樹中刪除資料欲自二元搜尋樹中刪除資料可能會面臨兩種情況(1) 刪除節點 p 位於樹葉節點上如下圖所示 這是比較容易處理的情況

(2) 刪除的節點 p 不是樹葉刪除節點 p 後原 p 所指的節點可由 q ( q 中包含了 p 左子樹中的最大節點值)或 r ( r 中包含了 p 右子樹中的最小節點值)來取代以維持二元搜尋樹中資料的大小關係其結果分別如圖 (b) 和 (c) 所示而圖 (d) 則標明了指標會改變的節點位置即 p 會消失 father 的左或右子樹指標會異動 k(可能是 q 或 r )會移到原來 p 的位置其父節點 f 的左或右子樹指標會異動其中 father 為 p 父節點 f 為 k 的父節點

程式 5-18 自二元搜尋樹中刪除資料46 int DeleteBST (int x)47 Struct BSTreeNode p father k f48 p = root father = NULL49 while (p = NULL)50 if (x == p-gtdata) 找到 x 所在的節點 p 了51 if ((p-gtleftchild == NULL) ampamp 52 (p-gtrightchild == NULL))53 k = NULL p為樹葉節點54 else if (p-gtleftchild = NULL )x位於非子節

點55 f = p 找出 p左子樹的最右樹葉56 k = p-gtleftchild57 while (k-gtrightchild =NULL)58 f = k59 k = k-gtrightchild60 k將挪至原 p處61 if (p == f)62 f-gtleftchild = k-gtleftchild 63 else64 f-gtrightchild = k-gtleftchild 65

66 else p無左子樹67 f = p 找出 p右子樹的最左樹葉68 k = p-gtrightchild69 while (k-gtleftchild = NULL)70 f = k71 k = k -gtleftchild72 73 if (p == f)74 f-gtrightchild = k-gtrightchild 75 else76 f-gtleftchild = k-gtrightchild77 78 if (k = NULL) k挪至原 p處繼承 p的左右指標79 k-gtleftchild = p-gtleftchild80 k-gtrightchild = p-gtrightchild81 82 if (father == NULL) root = k83 else if (x lt father-gtdata) 決定 k是 father的左

或右兒子84 father-gtleftchild = k85 else86 father-gtrightchild = k87 free(p)88 return 1 成功地刪除 x於此傳回 1返回呼叫處89 51~89行為找到 x時應有的處理步驟

90 else 尚未找到 x繼續往下一階層找

91 father = p92 if (x lt p-gtdata)93 p = p-gtleftchild94 else95 p = p-gtrightchild96 97 98 return 0 未找到 x傳回 099

510  樹林 (forest)

bull 定義樹林(森林)是由 n 棵沒有交集的樹所構成的集合 n 0

下圖為一包含三棵樹的樹林

5101 轉換樹林為二元樹 之前我們以二元樹來表示樹因為二元樹有較多

好用的性質在此我們也將樹林轉換成二元樹看待

在 522 節中我們嘗試了用左子右兄弟的方式來表示樹這個方法事實上已將樹轉換成二元樹

即用左子右兄弟的結構重新組織樹林中的每一棵樹這些樹的樹根將視為兄弟而最左邊的樹則視為樹林二元樹的樹根如此一來樹林即可轉換成二元樹

範例 5-25右下圖為右上圖三棵樹所對應的三棵左子右兄弟二元樹

下圖則為上圖樹林所對應的一棵樹林二元樹

演算法 5-1  轉換樹林成二元樹輸入樹林 F = T1 T2 Tn

輸出 F 的二元樹表示 BT=(T1 T2 Tn)1 if (n == 0) 輸出空二元樹 2 else

3 root(T1 T2 Tn) = T1

4 leftsubtree(T1 T2 Tn) =

BT(T11 T12 T1m)5 BT(T11 T12 T1m)為 T1的子樹6 rightsubtree(T1 T2 Tn) =

BT(T2 T3 Tn)

樹林轉換成二元樹的過程樹林轉換成二元樹的過程

  • Slide 1
  • Slide 2
  • Slide 3
  • Slide 4
  • Slide 5
  • Slide 6
  • Slide 7
  • Slide 8
  • Slide 9
  • Slide 10
  • Slide 11
  • Slide 12
  • Slide 13
  • Slide 14
  • Slide 15
  • Slide 16
  • Slide 17
  • Slide 18
  • Slide 19
  • Slide 20
  • Slide 21
  • Slide 22
  • Slide 23
  • Slide 24
  • Slide 25
  • Slide 26
  • Slide 27
  • Slide 28
  • Slide 29
  • Slide 30
  • Slide 31
  • Slide 32
  • Slide 33
  • Slide 34
  • Slide 35
  • Slide 36
  • Slide 37
  • Slide 38
  • Slide 39
  • Slide 40
  • Slide 41
  • Slide 42
  • Slide 43
  • Slide 44
  • Slide 45
  • Slide 46
  • Slide 47
  • Slide 48
  • Slide 49
  • Slide 50
  • Slide 51
  • Slide 52
  • Slide 53
  • Slide 54
  • Slide 55
  • Slide 56
  • Slide 57
  • Slide 58
  • Slide 59
  • Slide 60
  • Slide 61
  • Slide 62
  • Slide 63
  • Slide 64
  • Slide 65
  • Slide 66
  • Slide 67
  • Slide 68
  • Slide 69
  • Slide 70
  • Slide 71
  • Slide 72
  • Slide 73
  • Slide 74
  • Slide 75
  • Slide 76
  • Slide 77
  • Slide 78
  • Slide 79
  • Slide 80
  • Slide 81
  • Slide 82
  • Slide 83
  • Slide 84
  • Slide 85
  • Slide 86
  • Slide 87
  • Slide 88
  • Slide 89
  • Slide 90
  • Slide 91
  • Slide 92
  • Slide 93
  • Slide 94
  • Slide 95
  • Slide 96
  • Slide 97
  • Slide 98
  • Slide 99
  • Slide 100
  • Slide 101
  • Slide 102
  • Slide 103
Page 79: 第五章 樹

583  優先佇列bull 由於最大堆積(或最小堆積)的結構可提供最大元素(或最小元素)而其維護堆積的成本為 O(logn) n 為總元素個數

bull 堆積常被當成「優先佇列」 (priority queue) 使用mdash此時佇列被組織成完備二元樹若新加入佇列的資料有大小關係優先順序的需求時此優先佇列即以 O(logn) 的代價將新資料放入優先佇列(可能是最大或最小堆積依實際大小關係優先順序的需求而定)中mdash提供依元素的優先順序做為處理順序的服務

bull 一般的佇列為先進先出先到的先行處理優先佇列則是依據其優先順序優先順序高者先行處理與來到的先後無關

範例 5-21bull 在「分時」 (time sharing) 的作業系統中我們不希望使用中央處理器 (Central Processing Unit CPU) 時間短的「處理程序」 (process) 等候過久於是有「最短時間工作優先」 (shortest-job-first) 的排程策略此時等候的處理程序即可組織成之一最小堆積而 CPU 每次即取出存於樹根的處理程序予以處理任何新來的處理程序則以 O(logn) 的時間加入此優先佇列(最小堆積)中

bull 注意之前提過的線性佇列新來的直接加入成為新的尾端元素花O(1) 的時間然線性佇列本身沒有大小的關係每次要取出最大(小)的資料得花O(n) 的代價(在所有元素中挑出最大(小)者) n 為佇列內的元素個數而優先佇列是在新元素加入時要花O(logn) 的時間至於取出最大(小)則只須 O(1) 的時間

59  二元搜尋樹bull 二元搜尋樹是一棵二元樹可能是空二元樹若不為空二元樹則滿足下列性質ndash所有節點內的資料內容是相異的 ndash左子節點的資內容 ( 如果有 ) 比父節點的資料內容小 ndash右子節點的資料內容 ( 如果有 ) 比父節點的資料內容大 ndash以左和右子節點為樹根的左子樹和右子樹也是二元搜尋樹

在二元搜尋樹中搜尋資料二元搜尋樹對資料大小的掌握十分明確若恰與節點 p 資料相等則搜尋成功若欲搜尋的資料 x 比樹節點 p 中的資料小 則只須搜尋節點 p 的左子樹若比節點 p 中的資料大 則只須搜尋 p 的右子樹這樣的判斷可以遞迴地應用在各子樹節點 中

程式 5-15 在二元搜尋樹中搜尋資料(遞迴呼叫)1 struct BSTreeNode2 struct BSTreeNode leftchild3 int data4 struct BSTreeNode rightchild5 6 struct BSTreeNode root7 struct BSTreeNode SearchBST(

struct BSTreeNode tree int x)8 if (tree == NULL) return NULL9 if (x == tree-gtdata) return tree10 if (x lt tree-gtdata)11 return SearchBST(tree-gtleftchild x)

12 return SearchBST(tree-gtrightchild x)13

程式 5-16 搜尋二元搜尋樹的資料(非遞迴呼叫)在二元搜尋樹十分龐大時遞迴呼叫的效率不會太好可利用迴圈來完成搜尋的動作以提高執行效率請看程式 5-16

14 struct BSTreeNode Search BST_iterative15 (struct BSTreeNode node int x)16 while (node = NULL)17 if (x == node-gtdata) return node18 if (x lt node-gtdata)19 node = node-gtleftchild20 else21 node = node-gtrightchild22 23 return NULL24

592  新增資料於二元搜尋樹中欲新增資料進入一二元搜尋樹中得先決定該資料在二元搜尋樹中應出現的位置這可透過上節提到的搜尋動作來解決

在二元搜尋樹定義的性質 (1) 中我們限定了元素必須相異所以搜尋新資料的結果必是 NULL

必須將搜尋的結果改為新增資料所應在位置的節點指標

程式 5-17  在二元搜尋樹中新增資料25 int InsertBST(int x)26 struct BSTreeNode p q27 p = root q = NULL28 while (p = NULL)29 q = p30 if (x == p-gtdata) return 0

若不考慮重複則可省略之31 if (x lt p-gtdata)32 p = p-gtleftchild33 else34 p = p-gtrightchild35

3636 p = (struct BSTreeNode ) p = (struct BSTreeNode ) malloc (sizeof(BSTreeNode))malloc (sizeof(BSTreeNode))

3737 p-gtdata = x p-gtdata = x3838 p-gtleftchild = p-gtrightchild = p-gtleftchild = p-gtrightchild = NULLNULL3939 if (root == NULL) root = p if (root == NULL) root = p4040 else if (x lt q-gtdata) else if (x lt q-gtdata)4141 q-gtleftchild = p q-gtleftchild = p4242 else else4343 q-gtrightchild = p q-gtrightchild = p4444 return 1 return 145 45

程式 程式 5-175-17  在二元搜尋樹中新增資料  在二元搜尋樹中新增資料 (( 續續 ))

範例 5-23 圖 (b) 為圖 (a) 新增整數 6 之後的結果圖 (c) (d) (e) (f) 則分別是逐步再加入 28 18

10 21 的結果

bull若目前的二元搜尋樹高度為 h 則新增資料得花O(h) 的時間找到其所應在的位置遂新增資料至二元搜尋樹中的時間複雜之度為 O(h)

593 自二元搜尋樹中刪除資料欲自二元搜尋樹中刪除資料可能會面臨兩種情況(1) 刪除節點 p 位於樹葉節點上如下圖所示 這是比較容易處理的情況

(2) 刪除的節點 p 不是樹葉刪除節點 p 後原 p 所指的節點可由 q ( q 中包含了 p 左子樹中的最大節點值)或 r ( r 中包含了 p 右子樹中的最小節點值)來取代以維持二元搜尋樹中資料的大小關係其結果分別如圖 (b) 和 (c) 所示而圖 (d) 則標明了指標會改變的節點位置即 p 會消失 father 的左或右子樹指標會異動 k(可能是 q 或 r )會移到原來 p 的位置其父節點 f 的左或右子樹指標會異動其中 father 為 p 父節點 f 為 k 的父節點

程式 5-18 自二元搜尋樹中刪除資料46 int DeleteBST (int x)47 Struct BSTreeNode p father k f48 p = root father = NULL49 while (p = NULL)50 if (x == p-gtdata) 找到 x 所在的節點 p 了51 if ((p-gtleftchild == NULL) ampamp 52 (p-gtrightchild == NULL))53 k = NULL p為樹葉節點54 else if (p-gtleftchild = NULL )x位於非子節

點55 f = p 找出 p左子樹的最右樹葉56 k = p-gtleftchild57 while (k-gtrightchild =NULL)58 f = k59 k = k-gtrightchild60 k將挪至原 p處61 if (p == f)62 f-gtleftchild = k-gtleftchild 63 else64 f-gtrightchild = k-gtleftchild 65

66 else p無左子樹67 f = p 找出 p右子樹的最左樹葉68 k = p-gtrightchild69 while (k-gtleftchild = NULL)70 f = k71 k = k -gtleftchild72 73 if (p == f)74 f-gtrightchild = k-gtrightchild 75 else76 f-gtleftchild = k-gtrightchild77 78 if (k = NULL) k挪至原 p處繼承 p的左右指標79 k-gtleftchild = p-gtleftchild80 k-gtrightchild = p-gtrightchild81 82 if (father == NULL) root = k83 else if (x lt father-gtdata) 決定 k是 father的左

或右兒子84 father-gtleftchild = k85 else86 father-gtrightchild = k87 free(p)88 return 1 成功地刪除 x於此傳回 1返回呼叫處89 51~89行為找到 x時應有的處理步驟

90 else 尚未找到 x繼續往下一階層找

91 father = p92 if (x lt p-gtdata)93 p = p-gtleftchild94 else95 p = p-gtrightchild96 97 98 return 0 未找到 x傳回 099

510  樹林 (forest)

bull 定義樹林(森林)是由 n 棵沒有交集的樹所構成的集合 n 0

下圖為一包含三棵樹的樹林

5101 轉換樹林為二元樹 之前我們以二元樹來表示樹因為二元樹有較多

好用的性質在此我們也將樹林轉換成二元樹看待

在 522 節中我們嘗試了用左子右兄弟的方式來表示樹這個方法事實上已將樹轉換成二元樹

即用左子右兄弟的結構重新組織樹林中的每一棵樹這些樹的樹根將視為兄弟而最左邊的樹則視為樹林二元樹的樹根如此一來樹林即可轉換成二元樹

範例 5-25右下圖為右上圖三棵樹所對應的三棵左子右兄弟二元樹

下圖則為上圖樹林所對應的一棵樹林二元樹

演算法 5-1  轉換樹林成二元樹輸入樹林 F = T1 T2 Tn

輸出 F 的二元樹表示 BT=(T1 T2 Tn)1 if (n == 0) 輸出空二元樹 2 else

3 root(T1 T2 Tn) = T1

4 leftsubtree(T1 T2 Tn) =

BT(T11 T12 T1m)5 BT(T11 T12 T1m)為 T1的子樹6 rightsubtree(T1 T2 Tn) =

BT(T2 T3 Tn)

樹林轉換成二元樹的過程樹林轉換成二元樹的過程

  • Slide 1
  • Slide 2
  • Slide 3
  • Slide 4
  • Slide 5
  • Slide 6
  • Slide 7
  • Slide 8
  • Slide 9
  • Slide 10
  • Slide 11
  • Slide 12
  • Slide 13
  • Slide 14
  • Slide 15
  • Slide 16
  • Slide 17
  • Slide 18
  • Slide 19
  • Slide 20
  • Slide 21
  • Slide 22
  • Slide 23
  • Slide 24
  • Slide 25
  • Slide 26
  • Slide 27
  • Slide 28
  • Slide 29
  • Slide 30
  • Slide 31
  • Slide 32
  • Slide 33
  • Slide 34
  • Slide 35
  • Slide 36
  • Slide 37
  • Slide 38
  • Slide 39
  • Slide 40
  • Slide 41
  • Slide 42
  • Slide 43
  • Slide 44
  • Slide 45
  • Slide 46
  • Slide 47
  • Slide 48
  • Slide 49
  • Slide 50
  • Slide 51
  • Slide 52
  • Slide 53
  • Slide 54
  • Slide 55
  • Slide 56
  • Slide 57
  • Slide 58
  • Slide 59
  • Slide 60
  • Slide 61
  • Slide 62
  • Slide 63
  • Slide 64
  • Slide 65
  • Slide 66
  • Slide 67
  • Slide 68
  • Slide 69
  • Slide 70
  • Slide 71
  • Slide 72
  • Slide 73
  • Slide 74
  • Slide 75
  • Slide 76
  • Slide 77
  • Slide 78
  • Slide 79
  • Slide 80
  • Slide 81
  • Slide 82
  • Slide 83
  • Slide 84
  • Slide 85
  • Slide 86
  • Slide 87
  • Slide 88
  • Slide 89
  • Slide 90
  • Slide 91
  • Slide 92
  • Slide 93
  • Slide 94
  • Slide 95
  • Slide 96
  • Slide 97
  • Slide 98
  • Slide 99
  • Slide 100
  • Slide 101
  • Slide 102
  • Slide 103
Page 80: 第五章 樹

範例 5-21bull 在「分時」 (time sharing) 的作業系統中我們不希望使用中央處理器 (Central Processing Unit CPU) 時間短的「處理程序」 (process) 等候過久於是有「最短時間工作優先」 (shortest-job-first) 的排程策略此時等候的處理程序即可組織成之一最小堆積而 CPU 每次即取出存於樹根的處理程序予以處理任何新來的處理程序則以 O(logn) 的時間加入此優先佇列(最小堆積)中

bull 注意之前提過的線性佇列新來的直接加入成為新的尾端元素花O(1) 的時間然線性佇列本身沒有大小的關係每次要取出最大(小)的資料得花O(n) 的代價(在所有元素中挑出最大(小)者) n 為佇列內的元素個數而優先佇列是在新元素加入時要花O(logn) 的時間至於取出最大(小)則只須 O(1) 的時間

59  二元搜尋樹bull 二元搜尋樹是一棵二元樹可能是空二元樹若不為空二元樹則滿足下列性質ndash所有節點內的資料內容是相異的 ndash左子節點的資內容 ( 如果有 ) 比父節點的資料內容小 ndash右子節點的資料內容 ( 如果有 ) 比父節點的資料內容大 ndash以左和右子節點為樹根的左子樹和右子樹也是二元搜尋樹

在二元搜尋樹中搜尋資料二元搜尋樹對資料大小的掌握十分明確若恰與節點 p 資料相等則搜尋成功若欲搜尋的資料 x 比樹節點 p 中的資料小 則只須搜尋節點 p 的左子樹若比節點 p 中的資料大 則只須搜尋 p 的右子樹這樣的判斷可以遞迴地應用在各子樹節點 中

程式 5-15 在二元搜尋樹中搜尋資料(遞迴呼叫)1 struct BSTreeNode2 struct BSTreeNode leftchild3 int data4 struct BSTreeNode rightchild5 6 struct BSTreeNode root7 struct BSTreeNode SearchBST(

struct BSTreeNode tree int x)8 if (tree == NULL) return NULL9 if (x == tree-gtdata) return tree10 if (x lt tree-gtdata)11 return SearchBST(tree-gtleftchild x)

12 return SearchBST(tree-gtrightchild x)13

程式 5-16 搜尋二元搜尋樹的資料(非遞迴呼叫)在二元搜尋樹十分龐大時遞迴呼叫的效率不會太好可利用迴圈來完成搜尋的動作以提高執行效率請看程式 5-16

14 struct BSTreeNode Search BST_iterative15 (struct BSTreeNode node int x)16 while (node = NULL)17 if (x == node-gtdata) return node18 if (x lt node-gtdata)19 node = node-gtleftchild20 else21 node = node-gtrightchild22 23 return NULL24

592  新增資料於二元搜尋樹中欲新增資料進入一二元搜尋樹中得先決定該資料在二元搜尋樹中應出現的位置這可透過上節提到的搜尋動作來解決

在二元搜尋樹定義的性質 (1) 中我們限定了元素必須相異所以搜尋新資料的結果必是 NULL

必須將搜尋的結果改為新增資料所應在位置的節點指標

程式 5-17  在二元搜尋樹中新增資料25 int InsertBST(int x)26 struct BSTreeNode p q27 p = root q = NULL28 while (p = NULL)29 q = p30 if (x == p-gtdata) return 0

若不考慮重複則可省略之31 if (x lt p-gtdata)32 p = p-gtleftchild33 else34 p = p-gtrightchild35

3636 p = (struct BSTreeNode ) p = (struct BSTreeNode ) malloc (sizeof(BSTreeNode))malloc (sizeof(BSTreeNode))

3737 p-gtdata = x p-gtdata = x3838 p-gtleftchild = p-gtrightchild = p-gtleftchild = p-gtrightchild = NULLNULL3939 if (root == NULL) root = p if (root == NULL) root = p4040 else if (x lt q-gtdata) else if (x lt q-gtdata)4141 q-gtleftchild = p q-gtleftchild = p4242 else else4343 q-gtrightchild = p q-gtrightchild = p4444 return 1 return 145 45

程式 程式 5-175-17  在二元搜尋樹中新增資料  在二元搜尋樹中新增資料 (( 續續 ))

範例 5-23 圖 (b) 為圖 (a) 新增整數 6 之後的結果圖 (c) (d) (e) (f) 則分別是逐步再加入 28 18

10 21 的結果

bull若目前的二元搜尋樹高度為 h 則新增資料得花O(h) 的時間找到其所應在的位置遂新增資料至二元搜尋樹中的時間複雜之度為 O(h)

593 自二元搜尋樹中刪除資料欲自二元搜尋樹中刪除資料可能會面臨兩種情況(1) 刪除節點 p 位於樹葉節點上如下圖所示 這是比較容易處理的情況

(2) 刪除的節點 p 不是樹葉刪除節點 p 後原 p 所指的節點可由 q ( q 中包含了 p 左子樹中的最大節點值)或 r ( r 中包含了 p 右子樹中的最小節點值)來取代以維持二元搜尋樹中資料的大小關係其結果分別如圖 (b) 和 (c) 所示而圖 (d) 則標明了指標會改變的節點位置即 p 會消失 father 的左或右子樹指標會異動 k(可能是 q 或 r )會移到原來 p 的位置其父節點 f 的左或右子樹指標會異動其中 father 為 p 父節點 f 為 k 的父節點

程式 5-18 自二元搜尋樹中刪除資料46 int DeleteBST (int x)47 Struct BSTreeNode p father k f48 p = root father = NULL49 while (p = NULL)50 if (x == p-gtdata) 找到 x 所在的節點 p 了51 if ((p-gtleftchild == NULL) ampamp 52 (p-gtrightchild == NULL))53 k = NULL p為樹葉節點54 else if (p-gtleftchild = NULL )x位於非子節

點55 f = p 找出 p左子樹的最右樹葉56 k = p-gtleftchild57 while (k-gtrightchild =NULL)58 f = k59 k = k-gtrightchild60 k將挪至原 p處61 if (p == f)62 f-gtleftchild = k-gtleftchild 63 else64 f-gtrightchild = k-gtleftchild 65

66 else p無左子樹67 f = p 找出 p右子樹的最左樹葉68 k = p-gtrightchild69 while (k-gtleftchild = NULL)70 f = k71 k = k -gtleftchild72 73 if (p == f)74 f-gtrightchild = k-gtrightchild 75 else76 f-gtleftchild = k-gtrightchild77 78 if (k = NULL) k挪至原 p處繼承 p的左右指標79 k-gtleftchild = p-gtleftchild80 k-gtrightchild = p-gtrightchild81 82 if (father == NULL) root = k83 else if (x lt father-gtdata) 決定 k是 father的左

或右兒子84 father-gtleftchild = k85 else86 father-gtrightchild = k87 free(p)88 return 1 成功地刪除 x於此傳回 1返回呼叫處89 51~89行為找到 x時應有的處理步驟

90 else 尚未找到 x繼續往下一階層找

91 father = p92 if (x lt p-gtdata)93 p = p-gtleftchild94 else95 p = p-gtrightchild96 97 98 return 0 未找到 x傳回 099

510  樹林 (forest)

bull 定義樹林(森林)是由 n 棵沒有交集的樹所構成的集合 n 0

下圖為一包含三棵樹的樹林

5101 轉換樹林為二元樹 之前我們以二元樹來表示樹因為二元樹有較多

好用的性質在此我們也將樹林轉換成二元樹看待

在 522 節中我們嘗試了用左子右兄弟的方式來表示樹這個方法事實上已將樹轉換成二元樹

即用左子右兄弟的結構重新組織樹林中的每一棵樹這些樹的樹根將視為兄弟而最左邊的樹則視為樹林二元樹的樹根如此一來樹林即可轉換成二元樹

範例 5-25右下圖為右上圖三棵樹所對應的三棵左子右兄弟二元樹

下圖則為上圖樹林所對應的一棵樹林二元樹

演算法 5-1  轉換樹林成二元樹輸入樹林 F = T1 T2 Tn

輸出 F 的二元樹表示 BT=(T1 T2 Tn)1 if (n == 0) 輸出空二元樹 2 else

3 root(T1 T2 Tn) = T1

4 leftsubtree(T1 T2 Tn) =

BT(T11 T12 T1m)5 BT(T11 T12 T1m)為 T1的子樹6 rightsubtree(T1 T2 Tn) =

BT(T2 T3 Tn)

樹林轉換成二元樹的過程樹林轉換成二元樹的過程

  • Slide 1
  • Slide 2
  • Slide 3
  • Slide 4
  • Slide 5
  • Slide 6
  • Slide 7
  • Slide 8
  • Slide 9
  • Slide 10
  • Slide 11
  • Slide 12
  • Slide 13
  • Slide 14
  • Slide 15
  • Slide 16
  • Slide 17
  • Slide 18
  • Slide 19
  • Slide 20
  • Slide 21
  • Slide 22
  • Slide 23
  • Slide 24
  • Slide 25
  • Slide 26
  • Slide 27
  • Slide 28
  • Slide 29
  • Slide 30
  • Slide 31
  • Slide 32
  • Slide 33
  • Slide 34
  • Slide 35
  • Slide 36
  • Slide 37
  • Slide 38
  • Slide 39
  • Slide 40
  • Slide 41
  • Slide 42
  • Slide 43
  • Slide 44
  • Slide 45
  • Slide 46
  • Slide 47
  • Slide 48
  • Slide 49
  • Slide 50
  • Slide 51
  • Slide 52
  • Slide 53
  • Slide 54
  • Slide 55
  • Slide 56
  • Slide 57
  • Slide 58
  • Slide 59
  • Slide 60
  • Slide 61
  • Slide 62
  • Slide 63
  • Slide 64
  • Slide 65
  • Slide 66
  • Slide 67
  • Slide 68
  • Slide 69
  • Slide 70
  • Slide 71
  • Slide 72
  • Slide 73
  • Slide 74
  • Slide 75
  • Slide 76
  • Slide 77
  • Slide 78
  • Slide 79
  • Slide 80
  • Slide 81
  • Slide 82
  • Slide 83
  • Slide 84
  • Slide 85
  • Slide 86
  • Slide 87
  • Slide 88
  • Slide 89
  • Slide 90
  • Slide 91
  • Slide 92
  • Slide 93
  • Slide 94
  • Slide 95
  • Slide 96
  • Slide 97
  • Slide 98
  • Slide 99
  • Slide 100
  • Slide 101
  • Slide 102
  • Slide 103
Page 81: 第五章 樹

59  二元搜尋樹bull 二元搜尋樹是一棵二元樹可能是空二元樹若不為空二元樹則滿足下列性質ndash所有節點內的資料內容是相異的 ndash左子節點的資內容 ( 如果有 ) 比父節點的資料內容小 ndash右子節點的資料內容 ( 如果有 ) 比父節點的資料內容大 ndash以左和右子節點為樹根的左子樹和右子樹也是二元搜尋樹

在二元搜尋樹中搜尋資料二元搜尋樹對資料大小的掌握十分明確若恰與節點 p 資料相等則搜尋成功若欲搜尋的資料 x 比樹節點 p 中的資料小 則只須搜尋節點 p 的左子樹若比節點 p 中的資料大 則只須搜尋 p 的右子樹這樣的判斷可以遞迴地應用在各子樹節點 中

程式 5-15 在二元搜尋樹中搜尋資料(遞迴呼叫)1 struct BSTreeNode2 struct BSTreeNode leftchild3 int data4 struct BSTreeNode rightchild5 6 struct BSTreeNode root7 struct BSTreeNode SearchBST(

struct BSTreeNode tree int x)8 if (tree == NULL) return NULL9 if (x == tree-gtdata) return tree10 if (x lt tree-gtdata)11 return SearchBST(tree-gtleftchild x)

12 return SearchBST(tree-gtrightchild x)13

程式 5-16 搜尋二元搜尋樹的資料(非遞迴呼叫)在二元搜尋樹十分龐大時遞迴呼叫的效率不會太好可利用迴圈來完成搜尋的動作以提高執行效率請看程式 5-16

14 struct BSTreeNode Search BST_iterative15 (struct BSTreeNode node int x)16 while (node = NULL)17 if (x == node-gtdata) return node18 if (x lt node-gtdata)19 node = node-gtleftchild20 else21 node = node-gtrightchild22 23 return NULL24

592  新增資料於二元搜尋樹中欲新增資料進入一二元搜尋樹中得先決定該資料在二元搜尋樹中應出現的位置這可透過上節提到的搜尋動作來解決

在二元搜尋樹定義的性質 (1) 中我們限定了元素必須相異所以搜尋新資料的結果必是 NULL

必須將搜尋的結果改為新增資料所應在位置的節點指標

程式 5-17  在二元搜尋樹中新增資料25 int InsertBST(int x)26 struct BSTreeNode p q27 p = root q = NULL28 while (p = NULL)29 q = p30 if (x == p-gtdata) return 0

若不考慮重複則可省略之31 if (x lt p-gtdata)32 p = p-gtleftchild33 else34 p = p-gtrightchild35

3636 p = (struct BSTreeNode ) p = (struct BSTreeNode ) malloc (sizeof(BSTreeNode))malloc (sizeof(BSTreeNode))

3737 p-gtdata = x p-gtdata = x3838 p-gtleftchild = p-gtrightchild = p-gtleftchild = p-gtrightchild = NULLNULL3939 if (root == NULL) root = p if (root == NULL) root = p4040 else if (x lt q-gtdata) else if (x lt q-gtdata)4141 q-gtleftchild = p q-gtleftchild = p4242 else else4343 q-gtrightchild = p q-gtrightchild = p4444 return 1 return 145 45

程式 程式 5-175-17  在二元搜尋樹中新增資料  在二元搜尋樹中新增資料 (( 續續 ))

範例 5-23 圖 (b) 為圖 (a) 新增整數 6 之後的結果圖 (c) (d) (e) (f) 則分別是逐步再加入 28 18

10 21 的結果

bull若目前的二元搜尋樹高度為 h 則新增資料得花O(h) 的時間找到其所應在的位置遂新增資料至二元搜尋樹中的時間複雜之度為 O(h)

593 自二元搜尋樹中刪除資料欲自二元搜尋樹中刪除資料可能會面臨兩種情況(1) 刪除節點 p 位於樹葉節點上如下圖所示 這是比較容易處理的情況

(2) 刪除的節點 p 不是樹葉刪除節點 p 後原 p 所指的節點可由 q ( q 中包含了 p 左子樹中的最大節點值)或 r ( r 中包含了 p 右子樹中的最小節點值)來取代以維持二元搜尋樹中資料的大小關係其結果分別如圖 (b) 和 (c) 所示而圖 (d) 則標明了指標會改變的節點位置即 p 會消失 father 的左或右子樹指標會異動 k(可能是 q 或 r )會移到原來 p 的位置其父節點 f 的左或右子樹指標會異動其中 father 為 p 父節點 f 為 k 的父節點

程式 5-18 自二元搜尋樹中刪除資料46 int DeleteBST (int x)47 Struct BSTreeNode p father k f48 p = root father = NULL49 while (p = NULL)50 if (x == p-gtdata) 找到 x 所在的節點 p 了51 if ((p-gtleftchild == NULL) ampamp 52 (p-gtrightchild == NULL))53 k = NULL p為樹葉節點54 else if (p-gtleftchild = NULL )x位於非子節

點55 f = p 找出 p左子樹的最右樹葉56 k = p-gtleftchild57 while (k-gtrightchild =NULL)58 f = k59 k = k-gtrightchild60 k將挪至原 p處61 if (p == f)62 f-gtleftchild = k-gtleftchild 63 else64 f-gtrightchild = k-gtleftchild 65

66 else p無左子樹67 f = p 找出 p右子樹的最左樹葉68 k = p-gtrightchild69 while (k-gtleftchild = NULL)70 f = k71 k = k -gtleftchild72 73 if (p == f)74 f-gtrightchild = k-gtrightchild 75 else76 f-gtleftchild = k-gtrightchild77 78 if (k = NULL) k挪至原 p處繼承 p的左右指標79 k-gtleftchild = p-gtleftchild80 k-gtrightchild = p-gtrightchild81 82 if (father == NULL) root = k83 else if (x lt father-gtdata) 決定 k是 father的左

或右兒子84 father-gtleftchild = k85 else86 father-gtrightchild = k87 free(p)88 return 1 成功地刪除 x於此傳回 1返回呼叫處89 51~89行為找到 x時應有的處理步驟

90 else 尚未找到 x繼續往下一階層找

91 father = p92 if (x lt p-gtdata)93 p = p-gtleftchild94 else95 p = p-gtrightchild96 97 98 return 0 未找到 x傳回 099

510  樹林 (forest)

bull 定義樹林(森林)是由 n 棵沒有交集的樹所構成的集合 n 0

下圖為一包含三棵樹的樹林

5101 轉換樹林為二元樹 之前我們以二元樹來表示樹因為二元樹有較多

好用的性質在此我們也將樹林轉換成二元樹看待

在 522 節中我們嘗試了用左子右兄弟的方式來表示樹這個方法事實上已將樹轉換成二元樹

即用左子右兄弟的結構重新組織樹林中的每一棵樹這些樹的樹根將視為兄弟而最左邊的樹則視為樹林二元樹的樹根如此一來樹林即可轉換成二元樹

範例 5-25右下圖為右上圖三棵樹所對應的三棵左子右兄弟二元樹

下圖則為上圖樹林所對應的一棵樹林二元樹

演算法 5-1  轉換樹林成二元樹輸入樹林 F = T1 T2 Tn

輸出 F 的二元樹表示 BT=(T1 T2 Tn)1 if (n == 0) 輸出空二元樹 2 else

3 root(T1 T2 Tn) = T1

4 leftsubtree(T1 T2 Tn) =

BT(T11 T12 T1m)5 BT(T11 T12 T1m)為 T1的子樹6 rightsubtree(T1 T2 Tn) =

BT(T2 T3 Tn)

樹林轉換成二元樹的過程樹林轉換成二元樹的過程

  • Slide 1
  • Slide 2
  • Slide 3
  • Slide 4
  • Slide 5
  • Slide 6
  • Slide 7
  • Slide 8
  • Slide 9
  • Slide 10
  • Slide 11
  • Slide 12
  • Slide 13
  • Slide 14
  • Slide 15
  • Slide 16
  • Slide 17
  • Slide 18
  • Slide 19
  • Slide 20
  • Slide 21
  • Slide 22
  • Slide 23
  • Slide 24
  • Slide 25
  • Slide 26
  • Slide 27
  • Slide 28
  • Slide 29
  • Slide 30
  • Slide 31
  • Slide 32
  • Slide 33
  • Slide 34
  • Slide 35
  • Slide 36
  • Slide 37
  • Slide 38
  • Slide 39
  • Slide 40
  • Slide 41
  • Slide 42
  • Slide 43
  • Slide 44
  • Slide 45
  • Slide 46
  • Slide 47
  • Slide 48
  • Slide 49
  • Slide 50
  • Slide 51
  • Slide 52
  • Slide 53
  • Slide 54
  • Slide 55
  • Slide 56
  • Slide 57
  • Slide 58
  • Slide 59
  • Slide 60
  • Slide 61
  • Slide 62
  • Slide 63
  • Slide 64
  • Slide 65
  • Slide 66
  • Slide 67
  • Slide 68
  • Slide 69
  • Slide 70
  • Slide 71
  • Slide 72
  • Slide 73
  • Slide 74
  • Slide 75
  • Slide 76
  • Slide 77
  • Slide 78
  • Slide 79
  • Slide 80
  • Slide 81
  • Slide 82
  • Slide 83
  • Slide 84
  • Slide 85
  • Slide 86
  • Slide 87
  • Slide 88
  • Slide 89
  • Slide 90
  • Slide 91
  • Slide 92
  • Slide 93
  • Slide 94
  • Slide 95
  • Slide 96
  • Slide 97
  • Slide 98
  • Slide 99
  • Slide 100
  • Slide 101
  • Slide 102
  • Slide 103
Page 82: 第五章 樹

在二元搜尋樹中搜尋資料二元搜尋樹對資料大小的掌握十分明確若恰與節點 p 資料相等則搜尋成功若欲搜尋的資料 x 比樹節點 p 中的資料小 則只須搜尋節點 p 的左子樹若比節點 p 中的資料大 則只須搜尋 p 的右子樹這樣的判斷可以遞迴地應用在各子樹節點 中

程式 5-15 在二元搜尋樹中搜尋資料(遞迴呼叫)1 struct BSTreeNode2 struct BSTreeNode leftchild3 int data4 struct BSTreeNode rightchild5 6 struct BSTreeNode root7 struct BSTreeNode SearchBST(

struct BSTreeNode tree int x)8 if (tree == NULL) return NULL9 if (x == tree-gtdata) return tree10 if (x lt tree-gtdata)11 return SearchBST(tree-gtleftchild x)

12 return SearchBST(tree-gtrightchild x)13

程式 5-16 搜尋二元搜尋樹的資料(非遞迴呼叫)在二元搜尋樹十分龐大時遞迴呼叫的效率不會太好可利用迴圈來完成搜尋的動作以提高執行效率請看程式 5-16

14 struct BSTreeNode Search BST_iterative15 (struct BSTreeNode node int x)16 while (node = NULL)17 if (x == node-gtdata) return node18 if (x lt node-gtdata)19 node = node-gtleftchild20 else21 node = node-gtrightchild22 23 return NULL24

592  新增資料於二元搜尋樹中欲新增資料進入一二元搜尋樹中得先決定該資料在二元搜尋樹中應出現的位置這可透過上節提到的搜尋動作來解決

在二元搜尋樹定義的性質 (1) 中我們限定了元素必須相異所以搜尋新資料的結果必是 NULL

必須將搜尋的結果改為新增資料所應在位置的節點指標

程式 5-17  在二元搜尋樹中新增資料25 int InsertBST(int x)26 struct BSTreeNode p q27 p = root q = NULL28 while (p = NULL)29 q = p30 if (x == p-gtdata) return 0

若不考慮重複則可省略之31 if (x lt p-gtdata)32 p = p-gtleftchild33 else34 p = p-gtrightchild35

3636 p = (struct BSTreeNode ) p = (struct BSTreeNode ) malloc (sizeof(BSTreeNode))malloc (sizeof(BSTreeNode))

3737 p-gtdata = x p-gtdata = x3838 p-gtleftchild = p-gtrightchild = p-gtleftchild = p-gtrightchild = NULLNULL3939 if (root == NULL) root = p if (root == NULL) root = p4040 else if (x lt q-gtdata) else if (x lt q-gtdata)4141 q-gtleftchild = p q-gtleftchild = p4242 else else4343 q-gtrightchild = p q-gtrightchild = p4444 return 1 return 145 45

程式 程式 5-175-17  在二元搜尋樹中新增資料  在二元搜尋樹中新增資料 (( 續續 ))

範例 5-23 圖 (b) 為圖 (a) 新增整數 6 之後的結果圖 (c) (d) (e) (f) 則分別是逐步再加入 28 18

10 21 的結果

bull若目前的二元搜尋樹高度為 h 則新增資料得花O(h) 的時間找到其所應在的位置遂新增資料至二元搜尋樹中的時間複雜之度為 O(h)

593 自二元搜尋樹中刪除資料欲自二元搜尋樹中刪除資料可能會面臨兩種情況(1) 刪除節點 p 位於樹葉節點上如下圖所示 這是比較容易處理的情況

(2) 刪除的節點 p 不是樹葉刪除節點 p 後原 p 所指的節點可由 q ( q 中包含了 p 左子樹中的最大節點值)或 r ( r 中包含了 p 右子樹中的最小節點值)來取代以維持二元搜尋樹中資料的大小關係其結果分別如圖 (b) 和 (c) 所示而圖 (d) 則標明了指標會改變的節點位置即 p 會消失 father 的左或右子樹指標會異動 k(可能是 q 或 r )會移到原來 p 的位置其父節點 f 的左或右子樹指標會異動其中 father 為 p 父節點 f 為 k 的父節點

程式 5-18 自二元搜尋樹中刪除資料46 int DeleteBST (int x)47 Struct BSTreeNode p father k f48 p = root father = NULL49 while (p = NULL)50 if (x == p-gtdata) 找到 x 所在的節點 p 了51 if ((p-gtleftchild == NULL) ampamp 52 (p-gtrightchild == NULL))53 k = NULL p為樹葉節點54 else if (p-gtleftchild = NULL )x位於非子節

點55 f = p 找出 p左子樹的最右樹葉56 k = p-gtleftchild57 while (k-gtrightchild =NULL)58 f = k59 k = k-gtrightchild60 k將挪至原 p處61 if (p == f)62 f-gtleftchild = k-gtleftchild 63 else64 f-gtrightchild = k-gtleftchild 65

66 else p無左子樹67 f = p 找出 p右子樹的最左樹葉68 k = p-gtrightchild69 while (k-gtleftchild = NULL)70 f = k71 k = k -gtleftchild72 73 if (p == f)74 f-gtrightchild = k-gtrightchild 75 else76 f-gtleftchild = k-gtrightchild77 78 if (k = NULL) k挪至原 p處繼承 p的左右指標79 k-gtleftchild = p-gtleftchild80 k-gtrightchild = p-gtrightchild81 82 if (father == NULL) root = k83 else if (x lt father-gtdata) 決定 k是 father的左

或右兒子84 father-gtleftchild = k85 else86 father-gtrightchild = k87 free(p)88 return 1 成功地刪除 x於此傳回 1返回呼叫處89 51~89行為找到 x時應有的處理步驟

90 else 尚未找到 x繼續往下一階層找

91 father = p92 if (x lt p-gtdata)93 p = p-gtleftchild94 else95 p = p-gtrightchild96 97 98 return 0 未找到 x傳回 099

510  樹林 (forest)

bull 定義樹林(森林)是由 n 棵沒有交集的樹所構成的集合 n 0

下圖為一包含三棵樹的樹林

5101 轉換樹林為二元樹 之前我們以二元樹來表示樹因為二元樹有較多

好用的性質在此我們也將樹林轉換成二元樹看待

在 522 節中我們嘗試了用左子右兄弟的方式來表示樹這個方法事實上已將樹轉換成二元樹

即用左子右兄弟的結構重新組織樹林中的每一棵樹這些樹的樹根將視為兄弟而最左邊的樹則視為樹林二元樹的樹根如此一來樹林即可轉換成二元樹

範例 5-25右下圖為右上圖三棵樹所對應的三棵左子右兄弟二元樹

下圖則為上圖樹林所對應的一棵樹林二元樹

演算法 5-1  轉換樹林成二元樹輸入樹林 F = T1 T2 Tn

輸出 F 的二元樹表示 BT=(T1 T2 Tn)1 if (n == 0) 輸出空二元樹 2 else

3 root(T1 T2 Tn) = T1

4 leftsubtree(T1 T2 Tn) =

BT(T11 T12 T1m)5 BT(T11 T12 T1m)為 T1的子樹6 rightsubtree(T1 T2 Tn) =

BT(T2 T3 Tn)

樹林轉換成二元樹的過程樹林轉換成二元樹的過程

  • Slide 1
  • Slide 2
  • Slide 3
  • Slide 4
  • Slide 5
  • Slide 6
  • Slide 7
  • Slide 8
  • Slide 9
  • Slide 10
  • Slide 11
  • Slide 12
  • Slide 13
  • Slide 14
  • Slide 15
  • Slide 16
  • Slide 17
  • Slide 18
  • Slide 19
  • Slide 20
  • Slide 21
  • Slide 22
  • Slide 23
  • Slide 24
  • Slide 25
  • Slide 26
  • Slide 27
  • Slide 28
  • Slide 29
  • Slide 30
  • Slide 31
  • Slide 32
  • Slide 33
  • Slide 34
  • Slide 35
  • Slide 36
  • Slide 37
  • Slide 38
  • Slide 39
  • Slide 40
  • Slide 41
  • Slide 42
  • Slide 43
  • Slide 44
  • Slide 45
  • Slide 46
  • Slide 47
  • Slide 48
  • Slide 49
  • Slide 50
  • Slide 51
  • Slide 52
  • Slide 53
  • Slide 54
  • Slide 55
  • Slide 56
  • Slide 57
  • Slide 58
  • Slide 59
  • Slide 60
  • Slide 61
  • Slide 62
  • Slide 63
  • Slide 64
  • Slide 65
  • Slide 66
  • Slide 67
  • Slide 68
  • Slide 69
  • Slide 70
  • Slide 71
  • Slide 72
  • Slide 73
  • Slide 74
  • Slide 75
  • Slide 76
  • Slide 77
  • Slide 78
  • Slide 79
  • Slide 80
  • Slide 81
  • Slide 82
  • Slide 83
  • Slide 84
  • Slide 85
  • Slide 86
  • Slide 87
  • Slide 88
  • Slide 89
  • Slide 90
  • Slide 91
  • Slide 92
  • Slide 93
  • Slide 94
  • Slide 95
  • Slide 96
  • Slide 97
  • Slide 98
  • Slide 99
  • Slide 100
  • Slide 101
  • Slide 102
  • Slide 103
Page 83: 第五章 樹

程式 5-15 在二元搜尋樹中搜尋資料(遞迴呼叫)1 struct BSTreeNode2 struct BSTreeNode leftchild3 int data4 struct BSTreeNode rightchild5 6 struct BSTreeNode root7 struct BSTreeNode SearchBST(

struct BSTreeNode tree int x)8 if (tree == NULL) return NULL9 if (x == tree-gtdata) return tree10 if (x lt tree-gtdata)11 return SearchBST(tree-gtleftchild x)

12 return SearchBST(tree-gtrightchild x)13

程式 5-16 搜尋二元搜尋樹的資料(非遞迴呼叫)在二元搜尋樹十分龐大時遞迴呼叫的效率不會太好可利用迴圈來完成搜尋的動作以提高執行效率請看程式 5-16

14 struct BSTreeNode Search BST_iterative15 (struct BSTreeNode node int x)16 while (node = NULL)17 if (x == node-gtdata) return node18 if (x lt node-gtdata)19 node = node-gtleftchild20 else21 node = node-gtrightchild22 23 return NULL24

592  新增資料於二元搜尋樹中欲新增資料進入一二元搜尋樹中得先決定該資料在二元搜尋樹中應出現的位置這可透過上節提到的搜尋動作來解決

在二元搜尋樹定義的性質 (1) 中我們限定了元素必須相異所以搜尋新資料的結果必是 NULL

必須將搜尋的結果改為新增資料所應在位置的節點指標

程式 5-17  在二元搜尋樹中新增資料25 int InsertBST(int x)26 struct BSTreeNode p q27 p = root q = NULL28 while (p = NULL)29 q = p30 if (x == p-gtdata) return 0

若不考慮重複則可省略之31 if (x lt p-gtdata)32 p = p-gtleftchild33 else34 p = p-gtrightchild35

3636 p = (struct BSTreeNode ) p = (struct BSTreeNode ) malloc (sizeof(BSTreeNode))malloc (sizeof(BSTreeNode))

3737 p-gtdata = x p-gtdata = x3838 p-gtleftchild = p-gtrightchild = p-gtleftchild = p-gtrightchild = NULLNULL3939 if (root == NULL) root = p if (root == NULL) root = p4040 else if (x lt q-gtdata) else if (x lt q-gtdata)4141 q-gtleftchild = p q-gtleftchild = p4242 else else4343 q-gtrightchild = p q-gtrightchild = p4444 return 1 return 145 45

程式 程式 5-175-17  在二元搜尋樹中新增資料  在二元搜尋樹中新增資料 (( 續續 ))

範例 5-23 圖 (b) 為圖 (a) 新增整數 6 之後的結果圖 (c) (d) (e) (f) 則分別是逐步再加入 28 18

10 21 的結果

bull若目前的二元搜尋樹高度為 h 則新增資料得花O(h) 的時間找到其所應在的位置遂新增資料至二元搜尋樹中的時間複雜之度為 O(h)

593 自二元搜尋樹中刪除資料欲自二元搜尋樹中刪除資料可能會面臨兩種情況(1) 刪除節點 p 位於樹葉節點上如下圖所示 這是比較容易處理的情況

(2) 刪除的節點 p 不是樹葉刪除節點 p 後原 p 所指的節點可由 q ( q 中包含了 p 左子樹中的最大節點值)或 r ( r 中包含了 p 右子樹中的最小節點值)來取代以維持二元搜尋樹中資料的大小關係其結果分別如圖 (b) 和 (c) 所示而圖 (d) 則標明了指標會改變的節點位置即 p 會消失 father 的左或右子樹指標會異動 k(可能是 q 或 r )會移到原來 p 的位置其父節點 f 的左或右子樹指標會異動其中 father 為 p 父節點 f 為 k 的父節點

程式 5-18 自二元搜尋樹中刪除資料46 int DeleteBST (int x)47 Struct BSTreeNode p father k f48 p = root father = NULL49 while (p = NULL)50 if (x == p-gtdata) 找到 x 所在的節點 p 了51 if ((p-gtleftchild == NULL) ampamp 52 (p-gtrightchild == NULL))53 k = NULL p為樹葉節點54 else if (p-gtleftchild = NULL )x位於非子節

點55 f = p 找出 p左子樹的最右樹葉56 k = p-gtleftchild57 while (k-gtrightchild =NULL)58 f = k59 k = k-gtrightchild60 k將挪至原 p處61 if (p == f)62 f-gtleftchild = k-gtleftchild 63 else64 f-gtrightchild = k-gtleftchild 65

66 else p無左子樹67 f = p 找出 p右子樹的最左樹葉68 k = p-gtrightchild69 while (k-gtleftchild = NULL)70 f = k71 k = k -gtleftchild72 73 if (p == f)74 f-gtrightchild = k-gtrightchild 75 else76 f-gtleftchild = k-gtrightchild77 78 if (k = NULL) k挪至原 p處繼承 p的左右指標79 k-gtleftchild = p-gtleftchild80 k-gtrightchild = p-gtrightchild81 82 if (father == NULL) root = k83 else if (x lt father-gtdata) 決定 k是 father的左

或右兒子84 father-gtleftchild = k85 else86 father-gtrightchild = k87 free(p)88 return 1 成功地刪除 x於此傳回 1返回呼叫處89 51~89行為找到 x時應有的處理步驟

90 else 尚未找到 x繼續往下一階層找

91 father = p92 if (x lt p-gtdata)93 p = p-gtleftchild94 else95 p = p-gtrightchild96 97 98 return 0 未找到 x傳回 099

510  樹林 (forest)

bull 定義樹林(森林)是由 n 棵沒有交集的樹所構成的集合 n 0

下圖為一包含三棵樹的樹林

5101 轉換樹林為二元樹 之前我們以二元樹來表示樹因為二元樹有較多

好用的性質在此我們也將樹林轉換成二元樹看待

在 522 節中我們嘗試了用左子右兄弟的方式來表示樹這個方法事實上已將樹轉換成二元樹

即用左子右兄弟的結構重新組織樹林中的每一棵樹這些樹的樹根將視為兄弟而最左邊的樹則視為樹林二元樹的樹根如此一來樹林即可轉換成二元樹

範例 5-25右下圖為右上圖三棵樹所對應的三棵左子右兄弟二元樹

下圖則為上圖樹林所對應的一棵樹林二元樹

演算法 5-1  轉換樹林成二元樹輸入樹林 F = T1 T2 Tn

輸出 F 的二元樹表示 BT=(T1 T2 Tn)1 if (n == 0) 輸出空二元樹 2 else

3 root(T1 T2 Tn) = T1

4 leftsubtree(T1 T2 Tn) =

BT(T11 T12 T1m)5 BT(T11 T12 T1m)為 T1的子樹6 rightsubtree(T1 T2 Tn) =

BT(T2 T3 Tn)

樹林轉換成二元樹的過程樹林轉換成二元樹的過程

  • Slide 1
  • Slide 2
  • Slide 3
  • Slide 4
  • Slide 5
  • Slide 6
  • Slide 7
  • Slide 8
  • Slide 9
  • Slide 10
  • Slide 11
  • Slide 12
  • Slide 13
  • Slide 14
  • Slide 15
  • Slide 16
  • Slide 17
  • Slide 18
  • Slide 19
  • Slide 20
  • Slide 21
  • Slide 22
  • Slide 23
  • Slide 24
  • Slide 25
  • Slide 26
  • Slide 27
  • Slide 28
  • Slide 29
  • Slide 30
  • Slide 31
  • Slide 32
  • Slide 33
  • Slide 34
  • Slide 35
  • Slide 36
  • Slide 37
  • Slide 38
  • Slide 39
  • Slide 40
  • Slide 41
  • Slide 42
  • Slide 43
  • Slide 44
  • Slide 45
  • Slide 46
  • Slide 47
  • Slide 48
  • Slide 49
  • Slide 50
  • Slide 51
  • Slide 52
  • Slide 53
  • Slide 54
  • Slide 55
  • Slide 56
  • Slide 57
  • Slide 58
  • Slide 59
  • Slide 60
  • Slide 61
  • Slide 62
  • Slide 63
  • Slide 64
  • Slide 65
  • Slide 66
  • Slide 67
  • Slide 68
  • Slide 69
  • Slide 70
  • Slide 71
  • Slide 72
  • Slide 73
  • Slide 74
  • Slide 75
  • Slide 76
  • Slide 77
  • Slide 78
  • Slide 79
  • Slide 80
  • Slide 81
  • Slide 82
  • Slide 83
  • Slide 84
  • Slide 85
  • Slide 86
  • Slide 87
  • Slide 88
  • Slide 89
  • Slide 90
  • Slide 91
  • Slide 92
  • Slide 93
  • Slide 94
  • Slide 95
  • Slide 96
  • Slide 97
  • Slide 98
  • Slide 99
  • Slide 100
  • Slide 101
  • Slide 102
  • Slide 103
Page 84: 第五章 樹

程式 5-16 搜尋二元搜尋樹的資料(非遞迴呼叫)在二元搜尋樹十分龐大時遞迴呼叫的效率不會太好可利用迴圈來完成搜尋的動作以提高執行效率請看程式 5-16

14 struct BSTreeNode Search BST_iterative15 (struct BSTreeNode node int x)16 while (node = NULL)17 if (x == node-gtdata) return node18 if (x lt node-gtdata)19 node = node-gtleftchild20 else21 node = node-gtrightchild22 23 return NULL24

592  新增資料於二元搜尋樹中欲新增資料進入一二元搜尋樹中得先決定該資料在二元搜尋樹中應出現的位置這可透過上節提到的搜尋動作來解決

在二元搜尋樹定義的性質 (1) 中我們限定了元素必須相異所以搜尋新資料的結果必是 NULL

必須將搜尋的結果改為新增資料所應在位置的節點指標

程式 5-17  在二元搜尋樹中新增資料25 int InsertBST(int x)26 struct BSTreeNode p q27 p = root q = NULL28 while (p = NULL)29 q = p30 if (x == p-gtdata) return 0

若不考慮重複則可省略之31 if (x lt p-gtdata)32 p = p-gtleftchild33 else34 p = p-gtrightchild35

3636 p = (struct BSTreeNode ) p = (struct BSTreeNode ) malloc (sizeof(BSTreeNode))malloc (sizeof(BSTreeNode))

3737 p-gtdata = x p-gtdata = x3838 p-gtleftchild = p-gtrightchild = p-gtleftchild = p-gtrightchild = NULLNULL3939 if (root == NULL) root = p if (root == NULL) root = p4040 else if (x lt q-gtdata) else if (x lt q-gtdata)4141 q-gtleftchild = p q-gtleftchild = p4242 else else4343 q-gtrightchild = p q-gtrightchild = p4444 return 1 return 145 45

程式 程式 5-175-17  在二元搜尋樹中新增資料  在二元搜尋樹中新增資料 (( 續續 ))

範例 5-23 圖 (b) 為圖 (a) 新增整數 6 之後的結果圖 (c) (d) (e) (f) 則分別是逐步再加入 28 18

10 21 的結果

bull若目前的二元搜尋樹高度為 h 則新增資料得花O(h) 的時間找到其所應在的位置遂新增資料至二元搜尋樹中的時間複雜之度為 O(h)

593 自二元搜尋樹中刪除資料欲自二元搜尋樹中刪除資料可能會面臨兩種情況(1) 刪除節點 p 位於樹葉節點上如下圖所示 這是比較容易處理的情況

(2) 刪除的節點 p 不是樹葉刪除節點 p 後原 p 所指的節點可由 q ( q 中包含了 p 左子樹中的最大節點值)或 r ( r 中包含了 p 右子樹中的最小節點值)來取代以維持二元搜尋樹中資料的大小關係其結果分別如圖 (b) 和 (c) 所示而圖 (d) 則標明了指標會改變的節點位置即 p 會消失 father 的左或右子樹指標會異動 k(可能是 q 或 r )會移到原來 p 的位置其父節點 f 的左或右子樹指標會異動其中 father 為 p 父節點 f 為 k 的父節點

程式 5-18 自二元搜尋樹中刪除資料46 int DeleteBST (int x)47 Struct BSTreeNode p father k f48 p = root father = NULL49 while (p = NULL)50 if (x == p-gtdata) 找到 x 所在的節點 p 了51 if ((p-gtleftchild == NULL) ampamp 52 (p-gtrightchild == NULL))53 k = NULL p為樹葉節點54 else if (p-gtleftchild = NULL )x位於非子節

點55 f = p 找出 p左子樹的最右樹葉56 k = p-gtleftchild57 while (k-gtrightchild =NULL)58 f = k59 k = k-gtrightchild60 k將挪至原 p處61 if (p == f)62 f-gtleftchild = k-gtleftchild 63 else64 f-gtrightchild = k-gtleftchild 65

66 else p無左子樹67 f = p 找出 p右子樹的最左樹葉68 k = p-gtrightchild69 while (k-gtleftchild = NULL)70 f = k71 k = k -gtleftchild72 73 if (p == f)74 f-gtrightchild = k-gtrightchild 75 else76 f-gtleftchild = k-gtrightchild77 78 if (k = NULL) k挪至原 p處繼承 p的左右指標79 k-gtleftchild = p-gtleftchild80 k-gtrightchild = p-gtrightchild81 82 if (father == NULL) root = k83 else if (x lt father-gtdata) 決定 k是 father的左

或右兒子84 father-gtleftchild = k85 else86 father-gtrightchild = k87 free(p)88 return 1 成功地刪除 x於此傳回 1返回呼叫處89 51~89行為找到 x時應有的處理步驟

90 else 尚未找到 x繼續往下一階層找

91 father = p92 if (x lt p-gtdata)93 p = p-gtleftchild94 else95 p = p-gtrightchild96 97 98 return 0 未找到 x傳回 099

510  樹林 (forest)

bull 定義樹林(森林)是由 n 棵沒有交集的樹所構成的集合 n 0

下圖為一包含三棵樹的樹林

5101 轉換樹林為二元樹 之前我們以二元樹來表示樹因為二元樹有較多

好用的性質在此我們也將樹林轉換成二元樹看待

在 522 節中我們嘗試了用左子右兄弟的方式來表示樹這個方法事實上已將樹轉換成二元樹

即用左子右兄弟的結構重新組織樹林中的每一棵樹這些樹的樹根將視為兄弟而最左邊的樹則視為樹林二元樹的樹根如此一來樹林即可轉換成二元樹

範例 5-25右下圖為右上圖三棵樹所對應的三棵左子右兄弟二元樹

下圖則為上圖樹林所對應的一棵樹林二元樹

演算法 5-1  轉換樹林成二元樹輸入樹林 F = T1 T2 Tn

輸出 F 的二元樹表示 BT=(T1 T2 Tn)1 if (n == 0) 輸出空二元樹 2 else

3 root(T1 T2 Tn) = T1

4 leftsubtree(T1 T2 Tn) =

BT(T11 T12 T1m)5 BT(T11 T12 T1m)為 T1的子樹6 rightsubtree(T1 T2 Tn) =

BT(T2 T3 Tn)

樹林轉換成二元樹的過程樹林轉換成二元樹的過程

  • Slide 1
  • Slide 2
  • Slide 3
  • Slide 4
  • Slide 5
  • Slide 6
  • Slide 7
  • Slide 8
  • Slide 9
  • Slide 10
  • Slide 11
  • Slide 12
  • Slide 13
  • Slide 14
  • Slide 15
  • Slide 16
  • Slide 17
  • Slide 18
  • Slide 19
  • Slide 20
  • Slide 21
  • Slide 22
  • Slide 23
  • Slide 24
  • Slide 25
  • Slide 26
  • Slide 27
  • Slide 28
  • Slide 29
  • Slide 30
  • Slide 31
  • Slide 32
  • Slide 33
  • Slide 34
  • Slide 35
  • Slide 36
  • Slide 37
  • Slide 38
  • Slide 39
  • Slide 40
  • Slide 41
  • Slide 42
  • Slide 43
  • Slide 44
  • Slide 45
  • Slide 46
  • Slide 47
  • Slide 48
  • Slide 49
  • Slide 50
  • Slide 51
  • Slide 52
  • Slide 53
  • Slide 54
  • Slide 55
  • Slide 56
  • Slide 57
  • Slide 58
  • Slide 59
  • Slide 60
  • Slide 61
  • Slide 62
  • Slide 63
  • Slide 64
  • Slide 65
  • Slide 66
  • Slide 67
  • Slide 68
  • Slide 69
  • Slide 70
  • Slide 71
  • Slide 72
  • Slide 73
  • Slide 74
  • Slide 75
  • Slide 76
  • Slide 77
  • Slide 78
  • Slide 79
  • Slide 80
  • Slide 81
  • Slide 82
  • Slide 83
  • Slide 84
  • Slide 85
  • Slide 86
  • Slide 87
  • Slide 88
  • Slide 89
  • Slide 90
  • Slide 91
  • Slide 92
  • Slide 93
  • Slide 94
  • Slide 95
  • Slide 96
  • Slide 97
  • Slide 98
  • Slide 99
  • Slide 100
  • Slide 101
  • Slide 102
  • Slide 103
Page 85: 第五章 樹

592  新增資料於二元搜尋樹中欲新增資料進入一二元搜尋樹中得先決定該資料在二元搜尋樹中應出現的位置這可透過上節提到的搜尋動作來解決

在二元搜尋樹定義的性質 (1) 中我們限定了元素必須相異所以搜尋新資料的結果必是 NULL

必須將搜尋的結果改為新增資料所應在位置的節點指標

程式 5-17  在二元搜尋樹中新增資料25 int InsertBST(int x)26 struct BSTreeNode p q27 p = root q = NULL28 while (p = NULL)29 q = p30 if (x == p-gtdata) return 0

若不考慮重複則可省略之31 if (x lt p-gtdata)32 p = p-gtleftchild33 else34 p = p-gtrightchild35

3636 p = (struct BSTreeNode ) p = (struct BSTreeNode ) malloc (sizeof(BSTreeNode))malloc (sizeof(BSTreeNode))

3737 p-gtdata = x p-gtdata = x3838 p-gtleftchild = p-gtrightchild = p-gtleftchild = p-gtrightchild = NULLNULL3939 if (root == NULL) root = p if (root == NULL) root = p4040 else if (x lt q-gtdata) else if (x lt q-gtdata)4141 q-gtleftchild = p q-gtleftchild = p4242 else else4343 q-gtrightchild = p q-gtrightchild = p4444 return 1 return 145 45

程式 程式 5-175-17  在二元搜尋樹中新增資料  在二元搜尋樹中新增資料 (( 續續 ))

範例 5-23 圖 (b) 為圖 (a) 新增整數 6 之後的結果圖 (c) (d) (e) (f) 則分別是逐步再加入 28 18

10 21 的結果

bull若目前的二元搜尋樹高度為 h 則新增資料得花O(h) 的時間找到其所應在的位置遂新增資料至二元搜尋樹中的時間複雜之度為 O(h)

593 自二元搜尋樹中刪除資料欲自二元搜尋樹中刪除資料可能會面臨兩種情況(1) 刪除節點 p 位於樹葉節點上如下圖所示 這是比較容易處理的情況

(2) 刪除的節點 p 不是樹葉刪除節點 p 後原 p 所指的節點可由 q ( q 中包含了 p 左子樹中的最大節點值)或 r ( r 中包含了 p 右子樹中的最小節點值)來取代以維持二元搜尋樹中資料的大小關係其結果分別如圖 (b) 和 (c) 所示而圖 (d) 則標明了指標會改變的節點位置即 p 會消失 father 的左或右子樹指標會異動 k(可能是 q 或 r )會移到原來 p 的位置其父節點 f 的左或右子樹指標會異動其中 father 為 p 父節點 f 為 k 的父節點

程式 5-18 自二元搜尋樹中刪除資料46 int DeleteBST (int x)47 Struct BSTreeNode p father k f48 p = root father = NULL49 while (p = NULL)50 if (x == p-gtdata) 找到 x 所在的節點 p 了51 if ((p-gtleftchild == NULL) ampamp 52 (p-gtrightchild == NULL))53 k = NULL p為樹葉節點54 else if (p-gtleftchild = NULL )x位於非子節

點55 f = p 找出 p左子樹的最右樹葉56 k = p-gtleftchild57 while (k-gtrightchild =NULL)58 f = k59 k = k-gtrightchild60 k將挪至原 p處61 if (p == f)62 f-gtleftchild = k-gtleftchild 63 else64 f-gtrightchild = k-gtleftchild 65

66 else p無左子樹67 f = p 找出 p右子樹的最左樹葉68 k = p-gtrightchild69 while (k-gtleftchild = NULL)70 f = k71 k = k -gtleftchild72 73 if (p == f)74 f-gtrightchild = k-gtrightchild 75 else76 f-gtleftchild = k-gtrightchild77 78 if (k = NULL) k挪至原 p處繼承 p的左右指標79 k-gtleftchild = p-gtleftchild80 k-gtrightchild = p-gtrightchild81 82 if (father == NULL) root = k83 else if (x lt father-gtdata) 決定 k是 father的左

或右兒子84 father-gtleftchild = k85 else86 father-gtrightchild = k87 free(p)88 return 1 成功地刪除 x於此傳回 1返回呼叫處89 51~89行為找到 x時應有的處理步驟

90 else 尚未找到 x繼續往下一階層找

91 father = p92 if (x lt p-gtdata)93 p = p-gtleftchild94 else95 p = p-gtrightchild96 97 98 return 0 未找到 x傳回 099

510  樹林 (forest)

bull 定義樹林(森林)是由 n 棵沒有交集的樹所構成的集合 n 0

下圖為一包含三棵樹的樹林

5101 轉換樹林為二元樹 之前我們以二元樹來表示樹因為二元樹有較多

好用的性質在此我們也將樹林轉換成二元樹看待

在 522 節中我們嘗試了用左子右兄弟的方式來表示樹這個方法事實上已將樹轉換成二元樹

即用左子右兄弟的結構重新組織樹林中的每一棵樹這些樹的樹根將視為兄弟而最左邊的樹則視為樹林二元樹的樹根如此一來樹林即可轉換成二元樹

範例 5-25右下圖為右上圖三棵樹所對應的三棵左子右兄弟二元樹

下圖則為上圖樹林所對應的一棵樹林二元樹

演算法 5-1  轉換樹林成二元樹輸入樹林 F = T1 T2 Tn

輸出 F 的二元樹表示 BT=(T1 T2 Tn)1 if (n == 0) 輸出空二元樹 2 else

3 root(T1 T2 Tn) = T1

4 leftsubtree(T1 T2 Tn) =

BT(T11 T12 T1m)5 BT(T11 T12 T1m)為 T1的子樹6 rightsubtree(T1 T2 Tn) =

BT(T2 T3 Tn)

樹林轉換成二元樹的過程樹林轉換成二元樹的過程

  • Slide 1
  • Slide 2
  • Slide 3
  • Slide 4
  • Slide 5
  • Slide 6
  • Slide 7
  • Slide 8
  • Slide 9
  • Slide 10
  • Slide 11
  • Slide 12
  • Slide 13
  • Slide 14
  • Slide 15
  • Slide 16
  • Slide 17
  • Slide 18
  • Slide 19
  • Slide 20
  • Slide 21
  • Slide 22
  • Slide 23
  • Slide 24
  • Slide 25
  • Slide 26
  • Slide 27
  • Slide 28
  • Slide 29
  • Slide 30
  • Slide 31
  • Slide 32
  • Slide 33
  • Slide 34
  • Slide 35
  • Slide 36
  • Slide 37
  • Slide 38
  • Slide 39
  • Slide 40
  • Slide 41
  • Slide 42
  • Slide 43
  • Slide 44
  • Slide 45
  • Slide 46
  • Slide 47
  • Slide 48
  • Slide 49
  • Slide 50
  • Slide 51
  • Slide 52
  • Slide 53
  • Slide 54
  • Slide 55
  • Slide 56
  • Slide 57
  • Slide 58
  • Slide 59
  • Slide 60
  • Slide 61
  • Slide 62
  • Slide 63
  • Slide 64
  • Slide 65
  • Slide 66
  • Slide 67
  • Slide 68
  • Slide 69
  • Slide 70
  • Slide 71
  • Slide 72
  • Slide 73
  • Slide 74
  • Slide 75
  • Slide 76
  • Slide 77
  • Slide 78
  • Slide 79
  • Slide 80
  • Slide 81
  • Slide 82
  • Slide 83
  • Slide 84
  • Slide 85
  • Slide 86
  • Slide 87
  • Slide 88
  • Slide 89
  • Slide 90
  • Slide 91
  • Slide 92
  • Slide 93
  • Slide 94
  • Slide 95
  • Slide 96
  • Slide 97
  • Slide 98
  • Slide 99
  • Slide 100
  • Slide 101
  • Slide 102
  • Slide 103
Page 86: 第五章 樹

程式 5-17  在二元搜尋樹中新增資料25 int InsertBST(int x)26 struct BSTreeNode p q27 p = root q = NULL28 while (p = NULL)29 q = p30 if (x == p-gtdata) return 0

若不考慮重複則可省略之31 if (x lt p-gtdata)32 p = p-gtleftchild33 else34 p = p-gtrightchild35

3636 p = (struct BSTreeNode ) p = (struct BSTreeNode ) malloc (sizeof(BSTreeNode))malloc (sizeof(BSTreeNode))

3737 p-gtdata = x p-gtdata = x3838 p-gtleftchild = p-gtrightchild = p-gtleftchild = p-gtrightchild = NULLNULL3939 if (root == NULL) root = p if (root == NULL) root = p4040 else if (x lt q-gtdata) else if (x lt q-gtdata)4141 q-gtleftchild = p q-gtleftchild = p4242 else else4343 q-gtrightchild = p q-gtrightchild = p4444 return 1 return 145 45

程式 程式 5-175-17  在二元搜尋樹中新增資料  在二元搜尋樹中新增資料 (( 續續 ))

範例 5-23 圖 (b) 為圖 (a) 新增整數 6 之後的結果圖 (c) (d) (e) (f) 則分別是逐步再加入 28 18

10 21 的結果

bull若目前的二元搜尋樹高度為 h 則新增資料得花O(h) 的時間找到其所應在的位置遂新增資料至二元搜尋樹中的時間複雜之度為 O(h)

593 自二元搜尋樹中刪除資料欲自二元搜尋樹中刪除資料可能會面臨兩種情況(1) 刪除節點 p 位於樹葉節點上如下圖所示 這是比較容易處理的情況

(2) 刪除的節點 p 不是樹葉刪除節點 p 後原 p 所指的節點可由 q ( q 中包含了 p 左子樹中的最大節點值)或 r ( r 中包含了 p 右子樹中的最小節點值)來取代以維持二元搜尋樹中資料的大小關係其結果分別如圖 (b) 和 (c) 所示而圖 (d) 則標明了指標會改變的節點位置即 p 會消失 father 的左或右子樹指標會異動 k(可能是 q 或 r )會移到原來 p 的位置其父節點 f 的左或右子樹指標會異動其中 father 為 p 父節點 f 為 k 的父節點

程式 5-18 自二元搜尋樹中刪除資料46 int DeleteBST (int x)47 Struct BSTreeNode p father k f48 p = root father = NULL49 while (p = NULL)50 if (x == p-gtdata) 找到 x 所在的節點 p 了51 if ((p-gtleftchild == NULL) ampamp 52 (p-gtrightchild == NULL))53 k = NULL p為樹葉節點54 else if (p-gtleftchild = NULL )x位於非子節

點55 f = p 找出 p左子樹的最右樹葉56 k = p-gtleftchild57 while (k-gtrightchild =NULL)58 f = k59 k = k-gtrightchild60 k將挪至原 p處61 if (p == f)62 f-gtleftchild = k-gtleftchild 63 else64 f-gtrightchild = k-gtleftchild 65

66 else p無左子樹67 f = p 找出 p右子樹的最左樹葉68 k = p-gtrightchild69 while (k-gtleftchild = NULL)70 f = k71 k = k -gtleftchild72 73 if (p == f)74 f-gtrightchild = k-gtrightchild 75 else76 f-gtleftchild = k-gtrightchild77 78 if (k = NULL) k挪至原 p處繼承 p的左右指標79 k-gtleftchild = p-gtleftchild80 k-gtrightchild = p-gtrightchild81 82 if (father == NULL) root = k83 else if (x lt father-gtdata) 決定 k是 father的左

或右兒子84 father-gtleftchild = k85 else86 father-gtrightchild = k87 free(p)88 return 1 成功地刪除 x於此傳回 1返回呼叫處89 51~89行為找到 x時應有的處理步驟

90 else 尚未找到 x繼續往下一階層找

91 father = p92 if (x lt p-gtdata)93 p = p-gtleftchild94 else95 p = p-gtrightchild96 97 98 return 0 未找到 x傳回 099

510  樹林 (forest)

bull 定義樹林(森林)是由 n 棵沒有交集的樹所構成的集合 n 0

下圖為一包含三棵樹的樹林

5101 轉換樹林為二元樹 之前我們以二元樹來表示樹因為二元樹有較多

好用的性質在此我們也將樹林轉換成二元樹看待

在 522 節中我們嘗試了用左子右兄弟的方式來表示樹這個方法事實上已將樹轉換成二元樹

即用左子右兄弟的結構重新組織樹林中的每一棵樹這些樹的樹根將視為兄弟而最左邊的樹則視為樹林二元樹的樹根如此一來樹林即可轉換成二元樹

範例 5-25右下圖為右上圖三棵樹所對應的三棵左子右兄弟二元樹

下圖則為上圖樹林所對應的一棵樹林二元樹

演算法 5-1  轉換樹林成二元樹輸入樹林 F = T1 T2 Tn

輸出 F 的二元樹表示 BT=(T1 T2 Tn)1 if (n == 0) 輸出空二元樹 2 else

3 root(T1 T2 Tn) = T1

4 leftsubtree(T1 T2 Tn) =

BT(T11 T12 T1m)5 BT(T11 T12 T1m)為 T1的子樹6 rightsubtree(T1 T2 Tn) =

BT(T2 T3 Tn)

樹林轉換成二元樹的過程樹林轉換成二元樹的過程

  • Slide 1
  • Slide 2
  • Slide 3
  • Slide 4
  • Slide 5
  • Slide 6
  • Slide 7
  • Slide 8
  • Slide 9
  • Slide 10
  • Slide 11
  • Slide 12
  • Slide 13
  • Slide 14
  • Slide 15
  • Slide 16
  • Slide 17
  • Slide 18
  • Slide 19
  • Slide 20
  • Slide 21
  • Slide 22
  • Slide 23
  • Slide 24
  • Slide 25
  • Slide 26
  • Slide 27
  • Slide 28
  • Slide 29
  • Slide 30
  • Slide 31
  • Slide 32
  • Slide 33
  • Slide 34
  • Slide 35
  • Slide 36
  • Slide 37
  • Slide 38
  • Slide 39
  • Slide 40
  • Slide 41
  • Slide 42
  • Slide 43
  • Slide 44
  • Slide 45
  • Slide 46
  • Slide 47
  • Slide 48
  • Slide 49
  • Slide 50
  • Slide 51
  • Slide 52
  • Slide 53
  • Slide 54
  • Slide 55
  • Slide 56
  • Slide 57
  • Slide 58
  • Slide 59
  • Slide 60
  • Slide 61
  • Slide 62
  • Slide 63
  • Slide 64
  • Slide 65
  • Slide 66
  • Slide 67
  • Slide 68
  • Slide 69
  • Slide 70
  • Slide 71
  • Slide 72
  • Slide 73
  • Slide 74
  • Slide 75
  • Slide 76
  • Slide 77
  • Slide 78
  • Slide 79
  • Slide 80
  • Slide 81
  • Slide 82
  • Slide 83
  • Slide 84
  • Slide 85
  • Slide 86
  • Slide 87
  • Slide 88
  • Slide 89
  • Slide 90
  • Slide 91
  • Slide 92
  • Slide 93
  • Slide 94
  • Slide 95
  • Slide 96
  • Slide 97
  • Slide 98
  • Slide 99
  • Slide 100
  • Slide 101
  • Slide 102
  • Slide 103
Page 87: 第五章 樹

3636 p = (struct BSTreeNode ) p = (struct BSTreeNode ) malloc (sizeof(BSTreeNode))malloc (sizeof(BSTreeNode))

3737 p-gtdata = x p-gtdata = x3838 p-gtleftchild = p-gtrightchild = p-gtleftchild = p-gtrightchild = NULLNULL3939 if (root == NULL) root = p if (root == NULL) root = p4040 else if (x lt q-gtdata) else if (x lt q-gtdata)4141 q-gtleftchild = p q-gtleftchild = p4242 else else4343 q-gtrightchild = p q-gtrightchild = p4444 return 1 return 145 45

程式 程式 5-175-17  在二元搜尋樹中新增資料  在二元搜尋樹中新增資料 (( 續續 ))

範例 5-23 圖 (b) 為圖 (a) 新增整數 6 之後的結果圖 (c) (d) (e) (f) 則分別是逐步再加入 28 18

10 21 的結果

bull若目前的二元搜尋樹高度為 h 則新增資料得花O(h) 的時間找到其所應在的位置遂新增資料至二元搜尋樹中的時間複雜之度為 O(h)

593 自二元搜尋樹中刪除資料欲自二元搜尋樹中刪除資料可能會面臨兩種情況(1) 刪除節點 p 位於樹葉節點上如下圖所示 這是比較容易處理的情況

(2) 刪除的節點 p 不是樹葉刪除節點 p 後原 p 所指的節點可由 q ( q 中包含了 p 左子樹中的最大節點值)或 r ( r 中包含了 p 右子樹中的最小節點值)來取代以維持二元搜尋樹中資料的大小關係其結果分別如圖 (b) 和 (c) 所示而圖 (d) 則標明了指標會改變的節點位置即 p 會消失 father 的左或右子樹指標會異動 k(可能是 q 或 r )會移到原來 p 的位置其父節點 f 的左或右子樹指標會異動其中 father 為 p 父節點 f 為 k 的父節點

程式 5-18 自二元搜尋樹中刪除資料46 int DeleteBST (int x)47 Struct BSTreeNode p father k f48 p = root father = NULL49 while (p = NULL)50 if (x == p-gtdata) 找到 x 所在的節點 p 了51 if ((p-gtleftchild == NULL) ampamp 52 (p-gtrightchild == NULL))53 k = NULL p為樹葉節點54 else if (p-gtleftchild = NULL )x位於非子節

點55 f = p 找出 p左子樹的最右樹葉56 k = p-gtleftchild57 while (k-gtrightchild =NULL)58 f = k59 k = k-gtrightchild60 k將挪至原 p處61 if (p == f)62 f-gtleftchild = k-gtleftchild 63 else64 f-gtrightchild = k-gtleftchild 65

66 else p無左子樹67 f = p 找出 p右子樹的最左樹葉68 k = p-gtrightchild69 while (k-gtleftchild = NULL)70 f = k71 k = k -gtleftchild72 73 if (p == f)74 f-gtrightchild = k-gtrightchild 75 else76 f-gtleftchild = k-gtrightchild77 78 if (k = NULL) k挪至原 p處繼承 p的左右指標79 k-gtleftchild = p-gtleftchild80 k-gtrightchild = p-gtrightchild81 82 if (father == NULL) root = k83 else if (x lt father-gtdata) 決定 k是 father的左

或右兒子84 father-gtleftchild = k85 else86 father-gtrightchild = k87 free(p)88 return 1 成功地刪除 x於此傳回 1返回呼叫處89 51~89行為找到 x時應有的處理步驟

90 else 尚未找到 x繼續往下一階層找

91 father = p92 if (x lt p-gtdata)93 p = p-gtleftchild94 else95 p = p-gtrightchild96 97 98 return 0 未找到 x傳回 099

510  樹林 (forest)

bull 定義樹林(森林)是由 n 棵沒有交集的樹所構成的集合 n 0

下圖為一包含三棵樹的樹林

5101 轉換樹林為二元樹 之前我們以二元樹來表示樹因為二元樹有較多

好用的性質在此我們也將樹林轉換成二元樹看待

在 522 節中我們嘗試了用左子右兄弟的方式來表示樹這個方法事實上已將樹轉換成二元樹

即用左子右兄弟的結構重新組織樹林中的每一棵樹這些樹的樹根將視為兄弟而最左邊的樹則視為樹林二元樹的樹根如此一來樹林即可轉換成二元樹

範例 5-25右下圖為右上圖三棵樹所對應的三棵左子右兄弟二元樹

下圖則為上圖樹林所對應的一棵樹林二元樹

演算法 5-1  轉換樹林成二元樹輸入樹林 F = T1 T2 Tn

輸出 F 的二元樹表示 BT=(T1 T2 Tn)1 if (n == 0) 輸出空二元樹 2 else

3 root(T1 T2 Tn) = T1

4 leftsubtree(T1 T2 Tn) =

BT(T11 T12 T1m)5 BT(T11 T12 T1m)為 T1的子樹6 rightsubtree(T1 T2 Tn) =

BT(T2 T3 Tn)

樹林轉換成二元樹的過程樹林轉換成二元樹的過程

  • Slide 1
  • Slide 2
  • Slide 3
  • Slide 4
  • Slide 5
  • Slide 6
  • Slide 7
  • Slide 8
  • Slide 9
  • Slide 10
  • Slide 11
  • Slide 12
  • Slide 13
  • Slide 14
  • Slide 15
  • Slide 16
  • Slide 17
  • Slide 18
  • Slide 19
  • Slide 20
  • Slide 21
  • Slide 22
  • Slide 23
  • Slide 24
  • Slide 25
  • Slide 26
  • Slide 27
  • Slide 28
  • Slide 29
  • Slide 30
  • Slide 31
  • Slide 32
  • Slide 33
  • Slide 34
  • Slide 35
  • Slide 36
  • Slide 37
  • Slide 38
  • Slide 39
  • Slide 40
  • Slide 41
  • Slide 42
  • Slide 43
  • Slide 44
  • Slide 45
  • Slide 46
  • Slide 47
  • Slide 48
  • Slide 49
  • Slide 50
  • Slide 51
  • Slide 52
  • Slide 53
  • Slide 54
  • Slide 55
  • Slide 56
  • Slide 57
  • Slide 58
  • Slide 59
  • Slide 60
  • Slide 61
  • Slide 62
  • Slide 63
  • Slide 64
  • Slide 65
  • Slide 66
  • Slide 67
  • Slide 68
  • Slide 69
  • Slide 70
  • Slide 71
  • Slide 72
  • Slide 73
  • Slide 74
  • Slide 75
  • Slide 76
  • Slide 77
  • Slide 78
  • Slide 79
  • Slide 80
  • Slide 81
  • Slide 82
  • Slide 83
  • Slide 84
  • Slide 85
  • Slide 86
  • Slide 87
  • Slide 88
  • Slide 89
  • Slide 90
  • Slide 91
  • Slide 92
  • Slide 93
  • Slide 94
  • Slide 95
  • Slide 96
  • Slide 97
  • Slide 98
  • Slide 99
  • Slide 100
  • Slide 101
  • Slide 102
  • Slide 103
Page 88: 第五章 樹

範例 5-23 圖 (b) 為圖 (a) 新增整數 6 之後的結果圖 (c) (d) (e) (f) 則分別是逐步再加入 28 18

10 21 的結果

bull若目前的二元搜尋樹高度為 h 則新增資料得花O(h) 的時間找到其所應在的位置遂新增資料至二元搜尋樹中的時間複雜之度為 O(h)

593 自二元搜尋樹中刪除資料欲自二元搜尋樹中刪除資料可能會面臨兩種情況(1) 刪除節點 p 位於樹葉節點上如下圖所示 這是比較容易處理的情況

(2) 刪除的節點 p 不是樹葉刪除節點 p 後原 p 所指的節點可由 q ( q 中包含了 p 左子樹中的最大節點值)或 r ( r 中包含了 p 右子樹中的最小節點值)來取代以維持二元搜尋樹中資料的大小關係其結果分別如圖 (b) 和 (c) 所示而圖 (d) 則標明了指標會改變的節點位置即 p 會消失 father 的左或右子樹指標會異動 k(可能是 q 或 r )會移到原來 p 的位置其父節點 f 的左或右子樹指標會異動其中 father 為 p 父節點 f 為 k 的父節點

程式 5-18 自二元搜尋樹中刪除資料46 int DeleteBST (int x)47 Struct BSTreeNode p father k f48 p = root father = NULL49 while (p = NULL)50 if (x == p-gtdata) 找到 x 所在的節點 p 了51 if ((p-gtleftchild == NULL) ampamp 52 (p-gtrightchild == NULL))53 k = NULL p為樹葉節點54 else if (p-gtleftchild = NULL )x位於非子節

點55 f = p 找出 p左子樹的最右樹葉56 k = p-gtleftchild57 while (k-gtrightchild =NULL)58 f = k59 k = k-gtrightchild60 k將挪至原 p處61 if (p == f)62 f-gtleftchild = k-gtleftchild 63 else64 f-gtrightchild = k-gtleftchild 65

66 else p無左子樹67 f = p 找出 p右子樹的最左樹葉68 k = p-gtrightchild69 while (k-gtleftchild = NULL)70 f = k71 k = k -gtleftchild72 73 if (p == f)74 f-gtrightchild = k-gtrightchild 75 else76 f-gtleftchild = k-gtrightchild77 78 if (k = NULL) k挪至原 p處繼承 p的左右指標79 k-gtleftchild = p-gtleftchild80 k-gtrightchild = p-gtrightchild81 82 if (father == NULL) root = k83 else if (x lt father-gtdata) 決定 k是 father的左

或右兒子84 father-gtleftchild = k85 else86 father-gtrightchild = k87 free(p)88 return 1 成功地刪除 x於此傳回 1返回呼叫處89 51~89行為找到 x時應有的處理步驟

90 else 尚未找到 x繼續往下一階層找

91 father = p92 if (x lt p-gtdata)93 p = p-gtleftchild94 else95 p = p-gtrightchild96 97 98 return 0 未找到 x傳回 099

510  樹林 (forest)

bull 定義樹林(森林)是由 n 棵沒有交集的樹所構成的集合 n 0

下圖為一包含三棵樹的樹林

5101 轉換樹林為二元樹 之前我們以二元樹來表示樹因為二元樹有較多

好用的性質在此我們也將樹林轉換成二元樹看待

在 522 節中我們嘗試了用左子右兄弟的方式來表示樹這個方法事實上已將樹轉換成二元樹

即用左子右兄弟的結構重新組織樹林中的每一棵樹這些樹的樹根將視為兄弟而最左邊的樹則視為樹林二元樹的樹根如此一來樹林即可轉換成二元樹

範例 5-25右下圖為右上圖三棵樹所對應的三棵左子右兄弟二元樹

下圖則為上圖樹林所對應的一棵樹林二元樹

演算法 5-1  轉換樹林成二元樹輸入樹林 F = T1 T2 Tn

輸出 F 的二元樹表示 BT=(T1 T2 Tn)1 if (n == 0) 輸出空二元樹 2 else

3 root(T1 T2 Tn) = T1

4 leftsubtree(T1 T2 Tn) =

BT(T11 T12 T1m)5 BT(T11 T12 T1m)為 T1的子樹6 rightsubtree(T1 T2 Tn) =

BT(T2 T3 Tn)

樹林轉換成二元樹的過程樹林轉換成二元樹的過程

  • Slide 1
  • Slide 2
  • Slide 3
  • Slide 4
  • Slide 5
  • Slide 6
  • Slide 7
  • Slide 8
  • Slide 9
  • Slide 10
  • Slide 11
  • Slide 12
  • Slide 13
  • Slide 14
  • Slide 15
  • Slide 16
  • Slide 17
  • Slide 18
  • Slide 19
  • Slide 20
  • Slide 21
  • Slide 22
  • Slide 23
  • Slide 24
  • Slide 25
  • Slide 26
  • Slide 27
  • Slide 28
  • Slide 29
  • Slide 30
  • Slide 31
  • Slide 32
  • Slide 33
  • Slide 34
  • Slide 35
  • Slide 36
  • Slide 37
  • Slide 38
  • Slide 39
  • Slide 40
  • Slide 41
  • Slide 42
  • Slide 43
  • Slide 44
  • Slide 45
  • Slide 46
  • Slide 47
  • Slide 48
  • Slide 49
  • Slide 50
  • Slide 51
  • Slide 52
  • Slide 53
  • Slide 54
  • Slide 55
  • Slide 56
  • Slide 57
  • Slide 58
  • Slide 59
  • Slide 60
  • Slide 61
  • Slide 62
  • Slide 63
  • Slide 64
  • Slide 65
  • Slide 66
  • Slide 67
  • Slide 68
  • Slide 69
  • Slide 70
  • Slide 71
  • Slide 72
  • Slide 73
  • Slide 74
  • Slide 75
  • Slide 76
  • Slide 77
  • Slide 78
  • Slide 79
  • Slide 80
  • Slide 81
  • Slide 82
  • Slide 83
  • Slide 84
  • Slide 85
  • Slide 86
  • Slide 87
  • Slide 88
  • Slide 89
  • Slide 90
  • Slide 91
  • Slide 92
  • Slide 93
  • Slide 94
  • Slide 95
  • Slide 96
  • Slide 97
  • Slide 98
  • Slide 99
  • Slide 100
  • Slide 101
  • Slide 102
  • Slide 103
Page 89: 第五章 樹

bull若目前的二元搜尋樹高度為 h 則新增資料得花O(h) 的時間找到其所應在的位置遂新增資料至二元搜尋樹中的時間複雜之度為 O(h)

593 自二元搜尋樹中刪除資料欲自二元搜尋樹中刪除資料可能會面臨兩種情況(1) 刪除節點 p 位於樹葉節點上如下圖所示 這是比較容易處理的情況

(2) 刪除的節點 p 不是樹葉刪除節點 p 後原 p 所指的節點可由 q ( q 中包含了 p 左子樹中的最大節點值)或 r ( r 中包含了 p 右子樹中的最小節點值)來取代以維持二元搜尋樹中資料的大小關係其結果分別如圖 (b) 和 (c) 所示而圖 (d) 則標明了指標會改變的節點位置即 p 會消失 father 的左或右子樹指標會異動 k(可能是 q 或 r )會移到原來 p 的位置其父節點 f 的左或右子樹指標會異動其中 father 為 p 父節點 f 為 k 的父節點

程式 5-18 自二元搜尋樹中刪除資料46 int DeleteBST (int x)47 Struct BSTreeNode p father k f48 p = root father = NULL49 while (p = NULL)50 if (x == p-gtdata) 找到 x 所在的節點 p 了51 if ((p-gtleftchild == NULL) ampamp 52 (p-gtrightchild == NULL))53 k = NULL p為樹葉節點54 else if (p-gtleftchild = NULL )x位於非子節

點55 f = p 找出 p左子樹的最右樹葉56 k = p-gtleftchild57 while (k-gtrightchild =NULL)58 f = k59 k = k-gtrightchild60 k將挪至原 p處61 if (p == f)62 f-gtleftchild = k-gtleftchild 63 else64 f-gtrightchild = k-gtleftchild 65

66 else p無左子樹67 f = p 找出 p右子樹的最左樹葉68 k = p-gtrightchild69 while (k-gtleftchild = NULL)70 f = k71 k = k -gtleftchild72 73 if (p == f)74 f-gtrightchild = k-gtrightchild 75 else76 f-gtleftchild = k-gtrightchild77 78 if (k = NULL) k挪至原 p處繼承 p的左右指標79 k-gtleftchild = p-gtleftchild80 k-gtrightchild = p-gtrightchild81 82 if (father == NULL) root = k83 else if (x lt father-gtdata) 決定 k是 father的左

或右兒子84 father-gtleftchild = k85 else86 father-gtrightchild = k87 free(p)88 return 1 成功地刪除 x於此傳回 1返回呼叫處89 51~89行為找到 x時應有的處理步驟

90 else 尚未找到 x繼續往下一階層找

91 father = p92 if (x lt p-gtdata)93 p = p-gtleftchild94 else95 p = p-gtrightchild96 97 98 return 0 未找到 x傳回 099

510  樹林 (forest)

bull 定義樹林(森林)是由 n 棵沒有交集的樹所構成的集合 n 0

下圖為一包含三棵樹的樹林

5101 轉換樹林為二元樹 之前我們以二元樹來表示樹因為二元樹有較多

好用的性質在此我們也將樹林轉換成二元樹看待

在 522 節中我們嘗試了用左子右兄弟的方式來表示樹這個方法事實上已將樹轉換成二元樹

即用左子右兄弟的結構重新組織樹林中的每一棵樹這些樹的樹根將視為兄弟而最左邊的樹則視為樹林二元樹的樹根如此一來樹林即可轉換成二元樹

範例 5-25右下圖為右上圖三棵樹所對應的三棵左子右兄弟二元樹

下圖則為上圖樹林所對應的一棵樹林二元樹

演算法 5-1  轉換樹林成二元樹輸入樹林 F = T1 T2 Tn

輸出 F 的二元樹表示 BT=(T1 T2 Tn)1 if (n == 0) 輸出空二元樹 2 else

3 root(T1 T2 Tn) = T1

4 leftsubtree(T1 T2 Tn) =

BT(T11 T12 T1m)5 BT(T11 T12 T1m)為 T1的子樹6 rightsubtree(T1 T2 Tn) =

BT(T2 T3 Tn)

樹林轉換成二元樹的過程樹林轉換成二元樹的過程

  • Slide 1
  • Slide 2
  • Slide 3
  • Slide 4
  • Slide 5
  • Slide 6
  • Slide 7
  • Slide 8
  • Slide 9
  • Slide 10
  • Slide 11
  • Slide 12
  • Slide 13
  • Slide 14
  • Slide 15
  • Slide 16
  • Slide 17
  • Slide 18
  • Slide 19
  • Slide 20
  • Slide 21
  • Slide 22
  • Slide 23
  • Slide 24
  • Slide 25
  • Slide 26
  • Slide 27
  • Slide 28
  • Slide 29
  • Slide 30
  • Slide 31
  • Slide 32
  • Slide 33
  • Slide 34
  • Slide 35
  • Slide 36
  • Slide 37
  • Slide 38
  • Slide 39
  • Slide 40
  • Slide 41
  • Slide 42
  • Slide 43
  • Slide 44
  • Slide 45
  • Slide 46
  • Slide 47
  • Slide 48
  • Slide 49
  • Slide 50
  • Slide 51
  • Slide 52
  • Slide 53
  • Slide 54
  • Slide 55
  • Slide 56
  • Slide 57
  • Slide 58
  • Slide 59
  • Slide 60
  • Slide 61
  • Slide 62
  • Slide 63
  • Slide 64
  • Slide 65
  • Slide 66
  • Slide 67
  • Slide 68
  • Slide 69
  • Slide 70
  • Slide 71
  • Slide 72
  • Slide 73
  • Slide 74
  • Slide 75
  • Slide 76
  • Slide 77
  • Slide 78
  • Slide 79
  • Slide 80
  • Slide 81
  • Slide 82
  • Slide 83
  • Slide 84
  • Slide 85
  • Slide 86
  • Slide 87
  • Slide 88
  • Slide 89
  • Slide 90
  • Slide 91
  • Slide 92
  • Slide 93
  • Slide 94
  • Slide 95
  • Slide 96
  • Slide 97
  • Slide 98
  • Slide 99
  • Slide 100
  • Slide 101
  • Slide 102
  • Slide 103
Page 90: 第五章 樹

593 自二元搜尋樹中刪除資料欲自二元搜尋樹中刪除資料可能會面臨兩種情況(1) 刪除節點 p 位於樹葉節點上如下圖所示 這是比較容易處理的情況

(2) 刪除的節點 p 不是樹葉刪除節點 p 後原 p 所指的節點可由 q ( q 中包含了 p 左子樹中的最大節點值)或 r ( r 中包含了 p 右子樹中的最小節點值)來取代以維持二元搜尋樹中資料的大小關係其結果分別如圖 (b) 和 (c) 所示而圖 (d) 則標明了指標會改變的節點位置即 p 會消失 father 的左或右子樹指標會異動 k(可能是 q 或 r )會移到原來 p 的位置其父節點 f 的左或右子樹指標會異動其中 father 為 p 父節點 f 為 k 的父節點

程式 5-18 自二元搜尋樹中刪除資料46 int DeleteBST (int x)47 Struct BSTreeNode p father k f48 p = root father = NULL49 while (p = NULL)50 if (x == p-gtdata) 找到 x 所在的節點 p 了51 if ((p-gtleftchild == NULL) ampamp 52 (p-gtrightchild == NULL))53 k = NULL p為樹葉節點54 else if (p-gtleftchild = NULL )x位於非子節

點55 f = p 找出 p左子樹的最右樹葉56 k = p-gtleftchild57 while (k-gtrightchild =NULL)58 f = k59 k = k-gtrightchild60 k將挪至原 p處61 if (p == f)62 f-gtleftchild = k-gtleftchild 63 else64 f-gtrightchild = k-gtleftchild 65

66 else p無左子樹67 f = p 找出 p右子樹的最左樹葉68 k = p-gtrightchild69 while (k-gtleftchild = NULL)70 f = k71 k = k -gtleftchild72 73 if (p == f)74 f-gtrightchild = k-gtrightchild 75 else76 f-gtleftchild = k-gtrightchild77 78 if (k = NULL) k挪至原 p處繼承 p的左右指標79 k-gtleftchild = p-gtleftchild80 k-gtrightchild = p-gtrightchild81 82 if (father == NULL) root = k83 else if (x lt father-gtdata) 決定 k是 father的左

或右兒子84 father-gtleftchild = k85 else86 father-gtrightchild = k87 free(p)88 return 1 成功地刪除 x於此傳回 1返回呼叫處89 51~89行為找到 x時應有的處理步驟

90 else 尚未找到 x繼續往下一階層找

91 father = p92 if (x lt p-gtdata)93 p = p-gtleftchild94 else95 p = p-gtrightchild96 97 98 return 0 未找到 x傳回 099

510  樹林 (forest)

bull 定義樹林(森林)是由 n 棵沒有交集的樹所構成的集合 n 0

下圖為一包含三棵樹的樹林

5101 轉換樹林為二元樹 之前我們以二元樹來表示樹因為二元樹有較多

好用的性質在此我們也將樹林轉換成二元樹看待

在 522 節中我們嘗試了用左子右兄弟的方式來表示樹這個方法事實上已將樹轉換成二元樹

即用左子右兄弟的結構重新組織樹林中的每一棵樹這些樹的樹根將視為兄弟而最左邊的樹則視為樹林二元樹的樹根如此一來樹林即可轉換成二元樹

範例 5-25右下圖為右上圖三棵樹所對應的三棵左子右兄弟二元樹

下圖則為上圖樹林所對應的一棵樹林二元樹

演算法 5-1  轉換樹林成二元樹輸入樹林 F = T1 T2 Tn

輸出 F 的二元樹表示 BT=(T1 T2 Tn)1 if (n == 0) 輸出空二元樹 2 else

3 root(T1 T2 Tn) = T1

4 leftsubtree(T1 T2 Tn) =

BT(T11 T12 T1m)5 BT(T11 T12 T1m)為 T1的子樹6 rightsubtree(T1 T2 Tn) =

BT(T2 T3 Tn)

樹林轉換成二元樹的過程樹林轉換成二元樹的過程

  • Slide 1
  • Slide 2
  • Slide 3
  • Slide 4
  • Slide 5
  • Slide 6
  • Slide 7
  • Slide 8
  • Slide 9
  • Slide 10
  • Slide 11
  • Slide 12
  • Slide 13
  • Slide 14
  • Slide 15
  • Slide 16
  • Slide 17
  • Slide 18
  • Slide 19
  • Slide 20
  • Slide 21
  • Slide 22
  • Slide 23
  • Slide 24
  • Slide 25
  • Slide 26
  • Slide 27
  • Slide 28
  • Slide 29
  • Slide 30
  • Slide 31
  • Slide 32
  • Slide 33
  • Slide 34
  • Slide 35
  • Slide 36
  • Slide 37
  • Slide 38
  • Slide 39
  • Slide 40
  • Slide 41
  • Slide 42
  • Slide 43
  • Slide 44
  • Slide 45
  • Slide 46
  • Slide 47
  • Slide 48
  • Slide 49
  • Slide 50
  • Slide 51
  • Slide 52
  • Slide 53
  • Slide 54
  • Slide 55
  • Slide 56
  • Slide 57
  • Slide 58
  • Slide 59
  • Slide 60
  • Slide 61
  • Slide 62
  • Slide 63
  • Slide 64
  • Slide 65
  • Slide 66
  • Slide 67
  • Slide 68
  • Slide 69
  • Slide 70
  • Slide 71
  • Slide 72
  • Slide 73
  • Slide 74
  • Slide 75
  • Slide 76
  • Slide 77
  • Slide 78
  • Slide 79
  • Slide 80
  • Slide 81
  • Slide 82
  • Slide 83
  • Slide 84
  • Slide 85
  • Slide 86
  • Slide 87
  • Slide 88
  • Slide 89
  • Slide 90
  • Slide 91
  • Slide 92
  • Slide 93
  • Slide 94
  • Slide 95
  • Slide 96
  • Slide 97
  • Slide 98
  • Slide 99
  • Slide 100
  • Slide 101
  • Slide 102
  • Slide 103
Page 91: 第五章 樹

(2) 刪除的節點 p 不是樹葉刪除節點 p 後原 p 所指的節點可由 q ( q 中包含了 p 左子樹中的最大節點值)或 r ( r 中包含了 p 右子樹中的最小節點值)來取代以維持二元搜尋樹中資料的大小關係其結果分別如圖 (b) 和 (c) 所示而圖 (d) 則標明了指標會改變的節點位置即 p 會消失 father 的左或右子樹指標會異動 k(可能是 q 或 r )會移到原來 p 的位置其父節點 f 的左或右子樹指標會異動其中 father 為 p 父節點 f 為 k 的父節點

程式 5-18 自二元搜尋樹中刪除資料46 int DeleteBST (int x)47 Struct BSTreeNode p father k f48 p = root father = NULL49 while (p = NULL)50 if (x == p-gtdata) 找到 x 所在的節點 p 了51 if ((p-gtleftchild == NULL) ampamp 52 (p-gtrightchild == NULL))53 k = NULL p為樹葉節點54 else if (p-gtleftchild = NULL )x位於非子節

點55 f = p 找出 p左子樹的最右樹葉56 k = p-gtleftchild57 while (k-gtrightchild =NULL)58 f = k59 k = k-gtrightchild60 k將挪至原 p處61 if (p == f)62 f-gtleftchild = k-gtleftchild 63 else64 f-gtrightchild = k-gtleftchild 65

66 else p無左子樹67 f = p 找出 p右子樹的最左樹葉68 k = p-gtrightchild69 while (k-gtleftchild = NULL)70 f = k71 k = k -gtleftchild72 73 if (p == f)74 f-gtrightchild = k-gtrightchild 75 else76 f-gtleftchild = k-gtrightchild77 78 if (k = NULL) k挪至原 p處繼承 p的左右指標79 k-gtleftchild = p-gtleftchild80 k-gtrightchild = p-gtrightchild81 82 if (father == NULL) root = k83 else if (x lt father-gtdata) 決定 k是 father的左

或右兒子84 father-gtleftchild = k85 else86 father-gtrightchild = k87 free(p)88 return 1 成功地刪除 x於此傳回 1返回呼叫處89 51~89行為找到 x時應有的處理步驟

90 else 尚未找到 x繼續往下一階層找

91 father = p92 if (x lt p-gtdata)93 p = p-gtleftchild94 else95 p = p-gtrightchild96 97 98 return 0 未找到 x傳回 099

510  樹林 (forest)

bull 定義樹林(森林)是由 n 棵沒有交集的樹所構成的集合 n 0

下圖為一包含三棵樹的樹林

5101 轉換樹林為二元樹 之前我們以二元樹來表示樹因為二元樹有較多

好用的性質在此我們也將樹林轉換成二元樹看待

在 522 節中我們嘗試了用左子右兄弟的方式來表示樹這個方法事實上已將樹轉換成二元樹

即用左子右兄弟的結構重新組織樹林中的每一棵樹這些樹的樹根將視為兄弟而最左邊的樹則視為樹林二元樹的樹根如此一來樹林即可轉換成二元樹

範例 5-25右下圖為右上圖三棵樹所對應的三棵左子右兄弟二元樹

下圖則為上圖樹林所對應的一棵樹林二元樹

演算法 5-1  轉換樹林成二元樹輸入樹林 F = T1 T2 Tn

輸出 F 的二元樹表示 BT=(T1 T2 Tn)1 if (n == 0) 輸出空二元樹 2 else

3 root(T1 T2 Tn) = T1

4 leftsubtree(T1 T2 Tn) =

BT(T11 T12 T1m)5 BT(T11 T12 T1m)為 T1的子樹6 rightsubtree(T1 T2 Tn) =

BT(T2 T3 Tn)

樹林轉換成二元樹的過程樹林轉換成二元樹的過程

  • Slide 1
  • Slide 2
  • Slide 3
  • Slide 4
  • Slide 5
  • Slide 6
  • Slide 7
  • Slide 8
  • Slide 9
  • Slide 10
  • Slide 11
  • Slide 12
  • Slide 13
  • Slide 14
  • Slide 15
  • Slide 16
  • Slide 17
  • Slide 18
  • Slide 19
  • Slide 20
  • Slide 21
  • Slide 22
  • Slide 23
  • Slide 24
  • Slide 25
  • Slide 26
  • Slide 27
  • Slide 28
  • Slide 29
  • Slide 30
  • Slide 31
  • Slide 32
  • Slide 33
  • Slide 34
  • Slide 35
  • Slide 36
  • Slide 37
  • Slide 38
  • Slide 39
  • Slide 40
  • Slide 41
  • Slide 42
  • Slide 43
  • Slide 44
  • Slide 45
  • Slide 46
  • Slide 47
  • Slide 48
  • Slide 49
  • Slide 50
  • Slide 51
  • Slide 52
  • Slide 53
  • Slide 54
  • Slide 55
  • Slide 56
  • Slide 57
  • Slide 58
  • Slide 59
  • Slide 60
  • Slide 61
  • Slide 62
  • Slide 63
  • Slide 64
  • Slide 65
  • Slide 66
  • Slide 67
  • Slide 68
  • Slide 69
  • Slide 70
  • Slide 71
  • Slide 72
  • Slide 73
  • Slide 74
  • Slide 75
  • Slide 76
  • Slide 77
  • Slide 78
  • Slide 79
  • Slide 80
  • Slide 81
  • Slide 82
  • Slide 83
  • Slide 84
  • Slide 85
  • Slide 86
  • Slide 87
  • Slide 88
  • Slide 89
  • Slide 90
  • Slide 91
  • Slide 92
  • Slide 93
  • Slide 94
  • Slide 95
  • Slide 96
  • Slide 97
  • Slide 98
  • Slide 99
  • Slide 100
  • Slide 101
  • Slide 102
  • Slide 103
Page 92: 第五章 樹

程式 5-18 自二元搜尋樹中刪除資料46 int DeleteBST (int x)47 Struct BSTreeNode p father k f48 p = root father = NULL49 while (p = NULL)50 if (x == p-gtdata) 找到 x 所在的節點 p 了51 if ((p-gtleftchild == NULL) ampamp 52 (p-gtrightchild == NULL))53 k = NULL p為樹葉節點54 else if (p-gtleftchild = NULL )x位於非子節

點55 f = p 找出 p左子樹的最右樹葉56 k = p-gtleftchild57 while (k-gtrightchild =NULL)58 f = k59 k = k-gtrightchild60 k將挪至原 p處61 if (p == f)62 f-gtleftchild = k-gtleftchild 63 else64 f-gtrightchild = k-gtleftchild 65

66 else p無左子樹67 f = p 找出 p右子樹的最左樹葉68 k = p-gtrightchild69 while (k-gtleftchild = NULL)70 f = k71 k = k -gtleftchild72 73 if (p == f)74 f-gtrightchild = k-gtrightchild 75 else76 f-gtleftchild = k-gtrightchild77 78 if (k = NULL) k挪至原 p處繼承 p的左右指標79 k-gtleftchild = p-gtleftchild80 k-gtrightchild = p-gtrightchild81 82 if (father == NULL) root = k83 else if (x lt father-gtdata) 決定 k是 father的左

或右兒子84 father-gtleftchild = k85 else86 father-gtrightchild = k87 free(p)88 return 1 成功地刪除 x於此傳回 1返回呼叫處89 51~89行為找到 x時應有的處理步驟

90 else 尚未找到 x繼續往下一階層找

91 father = p92 if (x lt p-gtdata)93 p = p-gtleftchild94 else95 p = p-gtrightchild96 97 98 return 0 未找到 x傳回 099

510  樹林 (forest)

bull 定義樹林(森林)是由 n 棵沒有交集的樹所構成的集合 n 0

下圖為一包含三棵樹的樹林

5101 轉換樹林為二元樹 之前我們以二元樹來表示樹因為二元樹有較多

好用的性質在此我們也將樹林轉換成二元樹看待

在 522 節中我們嘗試了用左子右兄弟的方式來表示樹這個方法事實上已將樹轉換成二元樹

即用左子右兄弟的結構重新組織樹林中的每一棵樹這些樹的樹根將視為兄弟而最左邊的樹則視為樹林二元樹的樹根如此一來樹林即可轉換成二元樹

範例 5-25右下圖為右上圖三棵樹所對應的三棵左子右兄弟二元樹

下圖則為上圖樹林所對應的一棵樹林二元樹

演算法 5-1  轉換樹林成二元樹輸入樹林 F = T1 T2 Tn

輸出 F 的二元樹表示 BT=(T1 T2 Tn)1 if (n == 0) 輸出空二元樹 2 else

3 root(T1 T2 Tn) = T1

4 leftsubtree(T1 T2 Tn) =

BT(T11 T12 T1m)5 BT(T11 T12 T1m)為 T1的子樹6 rightsubtree(T1 T2 Tn) =

BT(T2 T3 Tn)

樹林轉換成二元樹的過程樹林轉換成二元樹的過程

  • Slide 1
  • Slide 2
  • Slide 3
  • Slide 4
  • Slide 5
  • Slide 6
  • Slide 7
  • Slide 8
  • Slide 9
  • Slide 10
  • Slide 11
  • Slide 12
  • Slide 13
  • Slide 14
  • Slide 15
  • Slide 16
  • Slide 17
  • Slide 18
  • Slide 19
  • Slide 20
  • Slide 21
  • Slide 22
  • Slide 23
  • Slide 24
  • Slide 25
  • Slide 26
  • Slide 27
  • Slide 28
  • Slide 29
  • Slide 30
  • Slide 31
  • Slide 32
  • Slide 33
  • Slide 34
  • Slide 35
  • Slide 36
  • Slide 37
  • Slide 38
  • Slide 39
  • Slide 40
  • Slide 41
  • Slide 42
  • Slide 43
  • Slide 44
  • Slide 45
  • Slide 46
  • Slide 47
  • Slide 48
  • Slide 49
  • Slide 50
  • Slide 51
  • Slide 52
  • Slide 53
  • Slide 54
  • Slide 55
  • Slide 56
  • Slide 57
  • Slide 58
  • Slide 59
  • Slide 60
  • Slide 61
  • Slide 62
  • Slide 63
  • Slide 64
  • Slide 65
  • Slide 66
  • Slide 67
  • Slide 68
  • Slide 69
  • Slide 70
  • Slide 71
  • Slide 72
  • Slide 73
  • Slide 74
  • Slide 75
  • Slide 76
  • Slide 77
  • Slide 78
  • Slide 79
  • Slide 80
  • Slide 81
  • Slide 82
  • Slide 83
  • Slide 84
  • Slide 85
  • Slide 86
  • Slide 87
  • Slide 88
  • Slide 89
  • Slide 90
  • Slide 91
  • Slide 92
  • Slide 93
  • Slide 94
  • Slide 95
  • Slide 96
  • Slide 97
  • Slide 98
  • Slide 99
  • Slide 100
  • Slide 101
  • Slide 102
  • Slide 103
Page 93: 第五章 樹

66 else p無左子樹67 f = p 找出 p右子樹的最左樹葉68 k = p-gtrightchild69 while (k-gtleftchild = NULL)70 f = k71 k = k -gtleftchild72 73 if (p == f)74 f-gtrightchild = k-gtrightchild 75 else76 f-gtleftchild = k-gtrightchild77 78 if (k = NULL) k挪至原 p處繼承 p的左右指標79 k-gtleftchild = p-gtleftchild80 k-gtrightchild = p-gtrightchild81 82 if (father == NULL) root = k83 else if (x lt father-gtdata) 決定 k是 father的左

或右兒子84 father-gtleftchild = k85 else86 father-gtrightchild = k87 free(p)88 return 1 成功地刪除 x於此傳回 1返回呼叫處89 51~89行為找到 x時應有的處理步驟

90 else 尚未找到 x繼續往下一階層找

91 father = p92 if (x lt p-gtdata)93 p = p-gtleftchild94 else95 p = p-gtrightchild96 97 98 return 0 未找到 x傳回 099

510  樹林 (forest)

bull 定義樹林(森林)是由 n 棵沒有交集的樹所構成的集合 n 0

下圖為一包含三棵樹的樹林

5101 轉換樹林為二元樹 之前我們以二元樹來表示樹因為二元樹有較多

好用的性質在此我們也將樹林轉換成二元樹看待

在 522 節中我們嘗試了用左子右兄弟的方式來表示樹這個方法事實上已將樹轉換成二元樹

即用左子右兄弟的結構重新組織樹林中的每一棵樹這些樹的樹根將視為兄弟而最左邊的樹則視為樹林二元樹的樹根如此一來樹林即可轉換成二元樹

範例 5-25右下圖為右上圖三棵樹所對應的三棵左子右兄弟二元樹

下圖則為上圖樹林所對應的一棵樹林二元樹

演算法 5-1  轉換樹林成二元樹輸入樹林 F = T1 T2 Tn

輸出 F 的二元樹表示 BT=(T1 T2 Tn)1 if (n == 0) 輸出空二元樹 2 else

3 root(T1 T2 Tn) = T1

4 leftsubtree(T1 T2 Tn) =

BT(T11 T12 T1m)5 BT(T11 T12 T1m)為 T1的子樹6 rightsubtree(T1 T2 Tn) =

BT(T2 T3 Tn)

樹林轉換成二元樹的過程樹林轉換成二元樹的過程

  • Slide 1
  • Slide 2
  • Slide 3
  • Slide 4
  • Slide 5
  • Slide 6
  • Slide 7
  • Slide 8
  • Slide 9
  • Slide 10
  • Slide 11
  • Slide 12
  • Slide 13
  • Slide 14
  • Slide 15
  • Slide 16
  • Slide 17
  • Slide 18
  • Slide 19
  • Slide 20
  • Slide 21
  • Slide 22
  • Slide 23
  • Slide 24
  • Slide 25
  • Slide 26
  • Slide 27
  • Slide 28
  • Slide 29
  • Slide 30
  • Slide 31
  • Slide 32
  • Slide 33
  • Slide 34
  • Slide 35
  • Slide 36
  • Slide 37
  • Slide 38
  • Slide 39
  • Slide 40
  • Slide 41
  • Slide 42
  • Slide 43
  • Slide 44
  • Slide 45
  • Slide 46
  • Slide 47
  • Slide 48
  • Slide 49
  • Slide 50
  • Slide 51
  • Slide 52
  • Slide 53
  • Slide 54
  • Slide 55
  • Slide 56
  • Slide 57
  • Slide 58
  • Slide 59
  • Slide 60
  • Slide 61
  • Slide 62
  • Slide 63
  • Slide 64
  • Slide 65
  • Slide 66
  • Slide 67
  • Slide 68
  • Slide 69
  • Slide 70
  • Slide 71
  • Slide 72
  • Slide 73
  • Slide 74
  • Slide 75
  • Slide 76
  • Slide 77
  • Slide 78
  • Slide 79
  • Slide 80
  • Slide 81
  • Slide 82
  • Slide 83
  • Slide 84
  • Slide 85
  • Slide 86
  • Slide 87
  • Slide 88
  • Slide 89
  • Slide 90
  • Slide 91
  • Slide 92
  • Slide 93
  • Slide 94
  • Slide 95
  • Slide 96
  • Slide 97
  • Slide 98
  • Slide 99
  • Slide 100
  • Slide 101
  • Slide 102
  • Slide 103
Page 94: 第五章 樹

90 else 尚未找到 x繼續往下一階層找

91 father = p92 if (x lt p-gtdata)93 p = p-gtleftchild94 else95 p = p-gtrightchild96 97 98 return 0 未找到 x傳回 099

510  樹林 (forest)

bull 定義樹林(森林)是由 n 棵沒有交集的樹所構成的集合 n 0

下圖為一包含三棵樹的樹林

5101 轉換樹林為二元樹 之前我們以二元樹來表示樹因為二元樹有較多

好用的性質在此我們也將樹林轉換成二元樹看待

在 522 節中我們嘗試了用左子右兄弟的方式來表示樹這個方法事實上已將樹轉換成二元樹

即用左子右兄弟的結構重新組織樹林中的每一棵樹這些樹的樹根將視為兄弟而最左邊的樹則視為樹林二元樹的樹根如此一來樹林即可轉換成二元樹

範例 5-25右下圖為右上圖三棵樹所對應的三棵左子右兄弟二元樹

下圖則為上圖樹林所對應的一棵樹林二元樹

演算法 5-1  轉換樹林成二元樹輸入樹林 F = T1 T2 Tn

輸出 F 的二元樹表示 BT=(T1 T2 Tn)1 if (n == 0) 輸出空二元樹 2 else

3 root(T1 T2 Tn) = T1

4 leftsubtree(T1 T2 Tn) =

BT(T11 T12 T1m)5 BT(T11 T12 T1m)為 T1的子樹6 rightsubtree(T1 T2 Tn) =

BT(T2 T3 Tn)

樹林轉換成二元樹的過程樹林轉換成二元樹的過程

  • Slide 1
  • Slide 2
  • Slide 3
  • Slide 4
  • Slide 5
  • Slide 6
  • Slide 7
  • Slide 8
  • Slide 9
  • Slide 10
  • Slide 11
  • Slide 12
  • Slide 13
  • Slide 14
  • Slide 15
  • Slide 16
  • Slide 17
  • Slide 18
  • Slide 19
  • Slide 20
  • Slide 21
  • Slide 22
  • Slide 23
  • Slide 24
  • Slide 25
  • Slide 26
  • Slide 27
  • Slide 28
  • Slide 29
  • Slide 30
  • Slide 31
  • Slide 32
  • Slide 33
  • Slide 34
  • Slide 35
  • Slide 36
  • Slide 37
  • Slide 38
  • Slide 39
  • Slide 40
  • Slide 41
  • Slide 42
  • Slide 43
  • Slide 44
  • Slide 45
  • Slide 46
  • Slide 47
  • Slide 48
  • Slide 49
  • Slide 50
  • Slide 51
  • Slide 52
  • Slide 53
  • Slide 54
  • Slide 55
  • Slide 56
  • Slide 57
  • Slide 58
  • Slide 59
  • Slide 60
  • Slide 61
  • Slide 62
  • Slide 63
  • Slide 64
  • Slide 65
  • Slide 66
  • Slide 67
  • Slide 68
  • Slide 69
  • Slide 70
  • Slide 71
  • Slide 72
  • Slide 73
  • Slide 74
  • Slide 75
  • Slide 76
  • Slide 77
  • Slide 78
  • Slide 79
  • Slide 80
  • Slide 81
  • Slide 82
  • Slide 83
  • Slide 84
  • Slide 85
  • Slide 86
  • Slide 87
  • Slide 88
  • Slide 89
  • Slide 90
  • Slide 91
  • Slide 92
  • Slide 93
  • Slide 94
  • Slide 95
  • Slide 96
  • Slide 97
  • Slide 98
  • Slide 99
  • Slide 100
  • Slide 101
  • Slide 102
  • Slide 103
Page 95: 第五章 樹

510  樹林 (forest)

bull 定義樹林(森林)是由 n 棵沒有交集的樹所構成的集合 n 0

下圖為一包含三棵樹的樹林

5101 轉換樹林為二元樹 之前我們以二元樹來表示樹因為二元樹有較多

好用的性質在此我們也將樹林轉換成二元樹看待

在 522 節中我們嘗試了用左子右兄弟的方式來表示樹這個方法事實上已將樹轉換成二元樹

即用左子右兄弟的結構重新組織樹林中的每一棵樹這些樹的樹根將視為兄弟而最左邊的樹則視為樹林二元樹的樹根如此一來樹林即可轉換成二元樹

範例 5-25右下圖為右上圖三棵樹所對應的三棵左子右兄弟二元樹

下圖則為上圖樹林所對應的一棵樹林二元樹

演算法 5-1  轉換樹林成二元樹輸入樹林 F = T1 T2 Tn

輸出 F 的二元樹表示 BT=(T1 T2 Tn)1 if (n == 0) 輸出空二元樹 2 else

3 root(T1 T2 Tn) = T1

4 leftsubtree(T1 T2 Tn) =

BT(T11 T12 T1m)5 BT(T11 T12 T1m)為 T1的子樹6 rightsubtree(T1 T2 Tn) =

BT(T2 T3 Tn)

樹林轉換成二元樹的過程樹林轉換成二元樹的過程

  • Slide 1
  • Slide 2
  • Slide 3
  • Slide 4
  • Slide 5
  • Slide 6
  • Slide 7
  • Slide 8
  • Slide 9
  • Slide 10
  • Slide 11
  • Slide 12
  • Slide 13
  • Slide 14
  • Slide 15
  • Slide 16
  • Slide 17
  • Slide 18
  • Slide 19
  • Slide 20
  • Slide 21
  • Slide 22
  • Slide 23
  • Slide 24
  • Slide 25
  • Slide 26
  • Slide 27
  • Slide 28
  • Slide 29
  • Slide 30
  • Slide 31
  • Slide 32
  • Slide 33
  • Slide 34
  • Slide 35
  • Slide 36
  • Slide 37
  • Slide 38
  • Slide 39
  • Slide 40
  • Slide 41
  • Slide 42
  • Slide 43
  • Slide 44
  • Slide 45
  • Slide 46
  • Slide 47
  • Slide 48
  • Slide 49
  • Slide 50
  • Slide 51
  • Slide 52
  • Slide 53
  • Slide 54
  • Slide 55
  • Slide 56
  • Slide 57
  • Slide 58
  • Slide 59
  • Slide 60
  • Slide 61
  • Slide 62
  • Slide 63
  • Slide 64
  • Slide 65
  • Slide 66
  • Slide 67
  • Slide 68
  • Slide 69
  • Slide 70
  • Slide 71
  • Slide 72
  • Slide 73
  • Slide 74
  • Slide 75
  • Slide 76
  • Slide 77
  • Slide 78
  • Slide 79
  • Slide 80
  • Slide 81
  • Slide 82
  • Slide 83
  • Slide 84
  • Slide 85
  • Slide 86
  • Slide 87
  • Slide 88
  • Slide 89
  • Slide 90
  • Slide 91
  • Slide 92
  • Slide 93
  • Slide 94
  • Slide 95
  • Slide 96
  • Slide 97
  • Slide 98
  • Slide 99
  • Slide 100
  • Slide 101
  • Slide 102
  • Slide 103
Page 96: 第五章 樹

5101 轉換樹林為二元樹 之前我們以二元樹來表示樹因為二元樹有較多

好用的性質在此我們也將樹林轉換成二元樹看待

在 522 節中我們嘗試了用左子右兄弟的方式來表示樹這個方法事實上已將樹轉換成二元樹

即用左子右兄弟的結構重新組織樹林中的每一棵樹這些樹的樹根將視為兄弟而最左邊的樹則視為樹林二元樹的樹根如此一來樹林即可轉換成二元樹

範例 5-25右下圖為右上圖三棵樹所對應的三棵左子右兄弟二元樹

下圖則為上圖樹林所對應的一棵樹林二元樹

演算法 5-1  轉換樹林成二元樹輸入樹林 F = T1 T2 Tn

輸出 F 的二元樹表示 BT=(T1 T2 Tn)1 if (n == 0) 輸出空二元樹 2 else

3 root(T1 T2 Tn) = T1

4 leftsubtree(T1 T2 Tn) =

BT(T11 T12 T1m)5 BT(T11 T12 T1m)為 T1的子樹6 rightsubtree(T1 T2 Tn) =

BT(T2 T3 Tn)

樹林轉換成二元樹的過程樹林轉換成二元樹的過程

  • Slide 1
  • Slide 2
  • Slide 3
  • Slide 4
  • Slide 5
  • Slide 6
  • Slide 7
  • Slide 8
  • Slide 9
  • Slide 10
  • Slide 11
  • Slide 12
  • Slide 13
  • Slide 14
  • Slide 15
  • Slide 16
  • Slide 17
  • Slide 18
  • Slide 19
  • Slide 20
  • Slide 21
  • Slide 22
  • Slide 23
  • Slide 24
  • Slide 25
  • Slide 26
  • Slide 27
  • Slide 28
  • Slide 29
  • Slide 30
  • Slide 31
  • Slide 32
  • Slide 33
  • Slide 34
  • Slide 35
  • Slide 36
  • Slide 37
  • Slide 38
  • Slide 39
  • Slide 40
  • Slide 41
  • Slide 42
  • Slide 43
  • Slide 44
  • Slide 45
  • Slide 46
  • Slide 47
  • Slide 48
  • Slide 49
  • Slide 50
  • Slide 51
  • Slide 52
  • Slide 53
  • Slide 54
  • Slide 55
  • Slide 56
  • Slide 57
  • Slide 58
  • Slide 59
  • Slide 60
  • Slide 61
  • Slide 62
  • Slide 63
  • Slide 64
  • Slide 65
  • Slide 66
  • Slide 67
  • Slide 68
  • Slide 69
  • Slide 70
  • Slide 71
  • Slide 72
  • Slide 73
  • Slide 74
  • Slide 75
  • Slide 76
  • Slide 77
  • Slide 78
  • Slide 79
  • Slide 80
  • Slide 81
  • Slide 82
  • Slide 83
  • Slide 84
  • Slide 85
  • Slide 86
  • Slide 87
  • Slide 88
  • Slide 89
  • Slide 90
  • Slide 91
  • Slide 92
  • Slide 93
  • Slide 94
  • Slide 95
  • Slide 96
  • Slide 97
  • Slide 98
  • Slide 99
  • Slide 100
  • Slide 101
  • Slide 102
  • Slide 103
Page 97: 第五章 樹

範例 5-25右下圖為右上圖三棵樹所對應的三棵左子右兄弟二元樹

下圖則為上圖樹林所對應的一棵樹林二元樹

演算法 5-1  轉換樹林成二元樹輸入樹林 F = T1 T2 Tn

輸出 F 的二元樹表示 BT=(T1 T2 Tn)1 if (n == 0) 輸出空二元樹 2 else

3 root(T1 T2 Tn) = T1

4 leftsubtree(T1 T2 Tn) =

BT(T11 T12 T1m)5 BT(T11 T12 T1m)為 T1的子樹6 rightsubtree(T1 T2 Tn) =

BT(T2 T3 Tn)

樹林轉換成二元樹的過程樹林轉換成二元樹的過程

  • Slide 1
  • Slide 2
  • Slide 3
  • Slide 4
  • Slide 5
  • Slide 6
  • Slide 7
  • Slide 8
  • Slide 9
  • Slide 10
  • Slide 11
  • Slide 12
  • Slide 13
  • Slide 14
  • Slide 15
  • Slide 16
  • Slide 17
  • Slide 18
  • Slide 19
  • Slide 20
  • Slide 21
  • Slide 22
  • Slide 23
  • Slide 24
  • Slide 25
  • Slide 26
  • Slide 27
  • Slide 28
  • Slide 29
  • Slide 30
  • Slide 31
  • Slide 32
  • Slide 33
  • Slide 34
  • Slide 35
  • Slide 36
  • Slide 37
  • Slide 38
  • Slide 39
  • Slide 40
  • Slide 41
  • Slide 42
  • Slide 43
  • Slide 44
  • Slide 45
  • Slide 46
  • Slide 47
  • Slide 48
  • Slide 49
  • Slide 50
  • Slide 51
  • Slide 52
  • Slide 53
  • Slide 54
  • Slide 55
  • Slide 56
  • Slide 57
  • Slide 58
  • Slide 59
  • Slide 60
  • Slide 61
  • Slide 62
  • Slide 63
  • Slide 64
  • Slide 65
  • Slide 66
  • Slide 67
  • Slide 68
  • Slide 69
  • Slide 70
  • Slide 71
  • Slide 72
  • Slide 73
  • Slide 74
  • Slide 75
  • Slide 76
  • Slide 77
  • Slide 78
  • Slide 79
  • Slide 80
  • Slide 81
  • Slide 82
  • Slide 83
  • Slide 84
  • Slide 85
  • Slide 86
  • Slide 87
  • Slide 88
  • Slide 89
  • Slide 90
  • Slide 91
  • Slide 92
  • Slide 93
  • Slide 94
  • Slide 95
  • Slide 96
  • Slide 97
  • Slide 98
  • Slide 99
  • Slide 100
  • Slide 101
  • Slide 102
  • Slide 103
Page 98: 第五章 樹

演算法 5-1  轉換樹林成二元樹輸入樹林 F = T1 T2 Tn

輸出 F 的二元樹表示 BT=(T1 T2 Tn)1 if (n == 0) 輸出空二元樹 2 else

3 root(T1 T2 Tn) = T1

4 leftsubtree(T1 T2 Tn) =

BT(T11 T12 T1m)5 BT(T11 T12 T1m)為 T1的子樹6 rightsubtree(T1 T2 Tn) =

BT(T2 T3 Tn)

樹林轉換成二元樹的過程樹林轉換成二元樹的過程

  • Slide 1
  • Slide 2
  • Slide 3
  • Slide 4
  • Slide 5
  • Slide 6
  • Slide 7
  • Slide 8
  • Slide 9
  • Slide 10
  • Slide 11
  • Slide 12
  • Slide 13
  • Slide 14
  • Slide 15
  • Slide 16
  • Slide 17
  • Slide 18
  • Slide 19
  • Slide 20
  • Slide 21
  • Slide 22
  • Slide 23
  • Slide 24
  • Slide 25
  • Slide 26
  • Slide 27
  • Slide 28
  • Slide 29
  • Slide 30
  • Slide 31
  • Slide 32
  • Slide 33
  • Slide 34
  • Slide 35
  • Slide 36
  • Slide 37
  • Slide 38
  • Slide 39
  • Slide 40
  • Slide 41
  • Slide 42
  • Slide 43
  • Slide 44
  • Slide 45
  • Slide 46
  • Slide 47
  • Slide 48
  • Slide 49
  • Slide 50
  • Slide 51
  • Slide 52
  • Slide 53
  • Slide 54
  • Slide 55
  • Slide 56
  • Slide 57
  • Slide 58
  • Slide 59
  • Slide 60
  • Slide 61
  • Slide 62
  • Slide 63
  • Slide 64
  • Slide 65
  • Slide 66
  • Slide 67
  • Slide 68
  • Slide 69
  • Slide 70
  • Slide 71
  • Slide 72
  • Slide 73
  • Slide 74
  • Slide 75
  • Slide 76
  • Slide 77
  • Slide 78
  • Slide 79
  • Slide 80
  • Slide 81
  • Slide 82
  • Slide 83
  • Slide 84
  • Slide 85
  • Slide 86
  • Slide 87
  • Slide 88
  • Slide 89
  • Slide 90
  • Slide 91
  • Slide 92
  • Slide 93
  • Slide 94
  • Slide 95
  • Slide 96
  • Slide 97
  • Slide 98
  • Slide 99
  • Slide 100
  • Slide 101
  • Slide 102
  • Slide 103
Page 99: 第五章 樹

樹林轉換成二元樹的過程樹林轉換成二元樹的過程

  • Slide 1
  • Slide 2
  • Slide 3
  • Slide 4
  • Slide 5
  • Slide 6
  • Slide 7
  • Slide 8
  • Slide 9
  • Slide 10
  • Slide 11
  • Slide 12
  • Slide 13
  • Slide 14
  • Slide 15
  • Slide 16
  • Slide 17
  • Slide 18
  • Slide 19
  • Slide 20
  • Slide 21
  • Slide 22
  • Slide 23
  • Slide 24
  • Slide 25
  • Slide 26
  • Slide 27
  • Slide 28
  • Slide 29
  • Slide 30
  • Slide 31
  • Slide 32
  • Slide 33
  • Slide 34
  • Slide 35
  • Slide 36
  • Slide 37
  • Slide 38
  • Slide 39
  • Slide 40
  • Slide 41
  • Slide 42
  • Slide 43
  • Slide 44
  • Slide 45
  • Slide 46
  • Slide 47
  • Slide 48
  • Slide 49
  • Slide 50
  • Slide 51
  • Slide 52
  • Slide 53
  • Slide 54
  • Slide 55
  • Slide 56
  • Slide 57
  • Slide 58
  • Slide 59
  • Slide 60
  • Slide 61
  • Slide 62
  • Slide 63
  • Slide 64
  • Slide 65
  • Slide 66
  • Slide 67
  • Slide 68
  • Slide 69
  • Slide 70
  • Slide 71
  • Slide 72
  • Slide 73
  • Slide 74
  • Slide 75
  • Slide 76
  • Slide 77
  • Slide 78
  • Slide 79
  • Slide 80
  • Slide 81
  • Slide 82
  • Slide 83
  • Slide 84
  • Slide 85
  • Slide 86
  • Slide 87
  • Slide 88
  • Slide 89
  • Slide 90
  • Slide 91
  • Slide 92
  • Slide 93
  • Slide 94
  • Slide 95
  • Slide 96
  • Slide 97
  • Slide 98
  • Slide 99
  • Slide 100
  • Slide 101
  • Slide 102
  • Slide 103