1 / 103

第五章 樹

第五章 樹. 資料結構與演算法. 徐熊健. 目錄. 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 專有名詞.

Download Presentation

第五章 樹

An Image/Link below is provided (as is) to download presentation Download Policy: Content on the Website is provided to you AS IS for your information and personal use and may not be sold / licensed / shared on other websites without getting consent from its author. Content is provided to you AS IS for your information and personal use only. Download presentation by click this link. While downloading, if for some reason you are not able to download a presentation, the publisher may have deleted the file from their server. During download, if you can't get a presentation, the file might be deleted by the publisher.

E N D

Presentation Transcript


  1. 第五章 樹 資料結構與演算法 徐熊健

  2. 目錄 5.1樹的概念 5.2樹的表示法 5.3二元樹 5.4二元樹表示法 5.5二元樹的走訪 5.6二元樹的複製與相等測試 5.7引線二元樹 5.8 堆積 5.9 二元搜尋樹 5.10 樹林

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

  4. 生物演化樹(evolutionary tree)

  5. More Examples

  6. 5.1.2 專有名詞 定義: 樹為節點所組成的有限集合,其中 (1) 存在一特殊節點 R 稱為樹根 (root) ; (2) 其它節點可分割成n個無交集的集合: T1, T2, …, Tn,n  0,而T1、T2、…Tn本身皆為樹,稱其為 R 的子樹 (subtree) 。 A recursive definition !! 本章所提到的樹皆為「有根樹」 (rooted tree)。

  7. 5.1.2 專有名詞(續) • 節點 (node): 圖中圓圈和向下的分支(branch) • 樹根 (root): 節點A • 分支度 (degree): 一個節點其所有子樹的數目 • 樹葉 (leaf):分支度為0的節點 • 內部節點(internal node)、非終端節點(non-terminal node) • 深度 (depth): 樹的最高階層(level)

  8. 5.1.2 專有名詞(續) • 兒子 (son): 任何節點 X,其子樹的樹根 Y,為 X 的兒子 • 父親 (father, parent): Y 是 X 的「父親」 • 兄弟 (sibling, brother) : 共有一個父親的數個節點 • 祖先 (ancestor): 該節點走向樹根所經過的所有節點 • 後裔 (descendant): 任一節點的所有子樹節點

  9. 5.2 樹的表示法 • 用陣列表示樹 ? • 用鍵結串列表示樹 ! 令分支度=k 若T 共有n個節點,則需要n個鍵結節點,計有nk 個指標;然而除了樹根外,每個節點只須由唯一的指標所指向,遂只需要有n-1個指標空間。用不著的指標空間多達nk-(n-1)=n(k-1)+1個,比用得著的還多,著實浪費。

  10. 5.2.1一般化的串列表示 • 串列是一個有限且有順序的元素集合 A = (a1, a2, … , an) 每個元素ai,1in,皆有相同的資料型態 • 倘若不限定各個元素都具有相同的資料型態,則稱此串列為「一般化串列」 (generalized list) (ai 可以是一個節點或是一棵樹) • 可用一般化串列來表示一棵樹T: T = (R, T1, T2, … , Tn) 其中R 為T 的樹根,而T1, T2, … , Tn 為R 的子樹,Ti可能是一個節點也可能是一棵樹(一般化串列)

  11. 範例5-1 以一般化串列表示樹 • (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 T3 T2

  12. 程式5-1 一般化串列鍵結節點的宣告 1 struct TreeNode 2 { int tag; 3 union 4 { int data; 5 struct TreeNode *Tlink; 6 } node; 7 struct TreeNode *link; 8 };

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

  14. 5.2.2 左子右兄弟表示法 • 每個節點都有唯一的最左 兒子 (leftmost child) ; • 每個節點都有最靠近它的 右兄弟。

  15. 5.2.3 分支度為 2 的二元樹表示法 • 分支度為 2 的樹又稱為 二元樹 (binary tree) • 二元樹中任一節點皆有 2個指標分別指向該節點的左子樹和右子樹。 • 二元樹可以表示任何樹!事實上二元樹有許多很好的性質,我們在一下節中介紹。

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

  17. 5.3 二元樹 • 二元樹是一個由節點組成的有限集合,可以是空集合,或是包含了 (1) 一個樹根節點; (2) 其它節點分割成兩個沒有交集的二元樹, 其一為左子樹 (left subtree) , 另一為右子樹 (right subtree) • 樹和二元樹之間的差異: • 樹不允許空節點,而空二元樹是允許的; • (2) 樹的子樹沒有順序,而二元樹的子樹有左右之分。

  18. 5.3.1 樹和二元樹的基本性質 樹或二元樹皆擁有下面的性質: 定理5-1: 若一棵樹 T的總節點數為 V,總邊數為E,則 V = E + 1。

  19. 證明:利用數學歸納法: 歸納法的基礎:當V=1時,即T 只有一個樹根節點,E為0。所以V = E + 1成立。 歸納法的假設:假設當1 V  K 時,該式成立。 歸納法的推論:當V = K + 1時,令此樹的樹根為R,而R有m個子樹T1, T2, …, Tm(如圖5-12所示),分別有個V1, V2, …, Vm個節點。 其中V1 +V2 + …+ Vm =K。若E1, E2, …, Em 分別為此m個子樹的邊數,則 E = E1 +E2+ …+ Em+ m (1) 根據歸納法的假設可知: V1 = E1+1 V2 = E2+1 ‧ ‧ ‧ Vm= Em+1 所以 V1 +V2 + …+Vm = E1 + E2 + …+ Em +m (2) 然而 V = V1 +V2 + …+Vm+1(3) 將式 (2) 代入式 (3) 可得: V = E1+ E2+ …+ Em+ m + 1 引用式 (1) ,則可得 V = E + 1。 由數學歸納法的原理可得證本定理。

  20. 5.3.2 二元樹的性質 (a) 稱為歪斜樹 (skew tree) ; (b) 稱為完備二元樹 (complete binary tree) (其樹葉節點皆在相鄰階層,完整定義在後) 同樣數目的樹節點存放在歪斜樹上,會花較多的階層;放在完備二元樹上,則只須最少的階層。

  21. = = 2k-1 5.3.2 二元樹的性質(續) 定理5-2: (1) 二元樹上階層 i 的節點數目最多為 2i-1,i1; (2) 深度為 k 的二元樹,其節點數目最多為 2k-1,k1。 • 定理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階層最多會有 22k-1 = 2k = 2i-1 節點。 由數學歸納法的原理可知本定理得證。 (2) 由 (1) 可知:二元樹在階層 i 的最多節點數為2i-1;那麼深度k 的二元樹最多可有節點如下:

  22. 對二元樹而言,任一節點的分支度可能為 0、1、或2;分支度為 0 者是為樹葉。 • 樹葉的數目與分支度為 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。

  23. 完滿二元樹 (full binary tree) 定義:一個深度為 k 的完滿二元樹即為一深度為 k 且有 2k-1 個節點的二元樹,k  0。 • 由定理5-2(2)可知:深度為k的二元樹,其最多節點數為2k-1。這樣的樹會將樹上所有可能存放節點的位置都放滿,完滿 (full) 之名因而生成。

  24. 完備二元樹 (complete binary tree) 我們可對二元樹上節點予以編號,階層小者先編,同一階層則自左向右編號,這樣的編號方式,使我們定義出完備(complete) 二元樹。 定義:深度為k,節點數為 n 的二元樹稱為完備二元樹,若且唯若 (1) k = 1時,樹有一個節點; (2) 當k  2 且 1i<k 時, 深度 i 有 2i-1個節點, 且第 k 層的節點皆由 第k-1層的分支由左至右 逐一連接而成。 完滿或完備二元樹之所以受人注意,乃因它們可以將節點以較少的階層數存放。

  25. 定理5-4:n個節點的完備或完滿二元樹,其深度為 log2(n+1)。 證明: 完滿或完備二元樹的節點皆自階層 1 起排放,任一階層 i 可排放 2i-1 個節點,遂由定理5-2得知,此 n 個節點所佔的深度為 log2(n+1)。

  26. 正規二元樹 (formal binary tree) • 在二元樹中非樹葉的節點,又稱為內部節點 (internal node) 。若所有內部節點恰有 2 個子節點,即成為正規二元樹。 定義:若二元樹中所有內部節點恰有2個子 節點,則稱之為正規二元樹 。 • 正規二元樹常被引用在單淘汰賽制的各項賽程安排。樹葉為參賽隊伍,內部節點為優勝隊伍,樹根即為冠軍隊伍。 • 在單淘汰賽制中自n 個隊伍中產生冠軍,須舉辦 n-1場比賽。

  27. 5.4 二元樹的表示法 5.4.1以陣列表示二元樹 定理5-5:若一個具有n 個節點的完備二元樹以循序的方式編號,並依序存放在陣列 A 中,即第i 個節點放在 A[i] 中,1 i n,則 (1) 節點 i 的父節點位在 A[i/2]處,i ≠ 1 (i = 1時,其節點正為樹根,無父節點) (2) 若2in,則節點 i 的左兒子節點位在 A[2i]處;若2i >n,節點 i 沒有左子節點 (3) 若2i+1 n,節點 i 的右兒子節點位 A[2i+1]處;若2i+1>n,節點i 沒有左子節點

  28. 證明:先用數學歸納法證明 (2) : • 歸納法的基礎:當 i = 1時,其左子節點確實在 A[2] 處(除非 n<2,此時節點1沒有左子節點)。 • 歸納法的假設:假設當I = k 時,其左子節點確在 A[2i] = A[2k]處(除非2i 已大於n,此時節點 i 無左子節點)。 • 歸納法的推論:當i = k+1時,因第k節點的左子節點在A[2k] 處,其右子節點則位在 A[2k+1] 處;那麼第k+1節點的左子節點應緊臨 A[2k+1],亦即位於 A[2k+2] = A[2(k+1)] 處(除非 2i 已大於n,此時節點 i 無左子節點)。 • 由數學歸納法的原理,得證定理5-5 (2) 。 (3) 由於 (2) 成立,而且節點 i 的右子節點會緊臨 A[2i],亦即其位於 A[2i+1]處,除非 i 根本沒有右子節點;得證定理5-5 (3) 。 由 (2) 和 (3) ,我們可知定理 5-5 (1) 成立。

  29. ‧ ‧ ‧ ‧ ‧ ‧ ‧ ‧ ‧ ‧ ‧ ‧ ‧ ‧ ‧

  30. 5.4.2 以鍵結串列表示二元樹 • 以陣列來表示完備二元樹算是相當方便,但 • 一般的二元樹將導致陣列空間的浪費; • 如果二元樹中節點的新增、刪除動作頻繁,則會造成陣列對應資料的搬動隨而頻繁。 • 以鍵結串列表示二元樹 為方便辦識樹根,我們常用root 指標指向樹根,做為樹鍵結串列的起點。

  31. 陣列表示和鍵結表示的空間需求 • 完備二元樹的陣列表示和鍵結表示皆沒浪費儲存資料的空間; • 但歪斜樹的陣列表示,比起鍵結表示則浪費許多。 • 以n 個節點的歪斜樹而言,陣列表示法可能需要2n-1 個陣列元素;其中 2n-1-n 個陣列元素空間是浪費的,而鍵結表示法只需要n 個節點空間。

  32. 二元樹節點的宣告 程式5-2 1 struct BTreeNode 2 { struct BtreeNode *leftchiled; 3 int data; 4 struct BTreeNode *rightchild; 5 }; 6 struct BTreeNode *root;

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

  34. 5.5 二元樹的走訪 • 假如一組資料已用二元樹的組織在一起,總有需求對其全數資料做動作,例如計算所有數目、印出所有資料、在所有資料中搜尋某項資料、…等。此時即須對此二元樹做走訪 (traversal) 的運算,利用走訪二元樹的同時,將計算、列印或搜尋的動作完成。 • 事實上走訪即在決定二元樹上資料被處理(計算、列印或搜尋)的順序。我們也希望走訪的演算法對任何節點皆一致,是容易撰寫程式實作的。 • 若對一個二元樹上的節點而言,V表示處理節點上的資料,L 表示走訪其左子樹,R 表示走訪其右子樹。

  35. 5.5 二元樹的走訪(續) • 因為對稱的緣故,我們只考慮先走訪左邊再走訪右邊的情形,那麼右圖只剩下三種走訪方式。 • 以V所在的相對位置,分別對此三種走訪方式取名如下: (1) LVR 中序走訪 (inorder traversal) 或中序表示法 (infix notation); (2) LRV 後序走訪 (postorder traversal) 或後序表示法 (postfix notation); (3) VLR 前序走訪 (preorder traversal) 或前序表示法 (prefix notation)。

  36. 5.5.1 中序走訪 • 利用遞迴的方式撰寫中序走訪的程序;希望把二元樹中序走訪的順序印出來。 程式5-3二元樹的中序走訪 1 struct BTreeNode 2 { struct BTreeNode *leftchild; 3 char data; 4 struct BTreeNode *rightchild; 5 }; 6 struct BTreeNode *root; 7 void inorder(struct BTreeNode *node) 8 { if (node != NULL) 9 { inorder(node->leftchild); 10 cout << node->data; 11 inorder(node->rightchild); 12 } 13}

  37. 範例 5-12 6 struct BTreeNode *root; 7 void inorder(struct BTreeNode *node) 8 { if (node != NULL) 9 { inorder(node->leftchild); 10 cout << node->data; 11 inorder(node->rightchild); 12 } 13 } 此為運算式二元樹 其對應中序表示運算式為 : (A+B)*C+D/(E-F)。

  38. 6 struct BTreeNode *root; 7 void inorder(struct BTreeNode *node) 8 { if (node != NULL) 9 { inorder(node->leftchild); 10 cout << node->data; 11 inorder(node->rightchild); 12 } 13 } (A+B)*C+D/(E-F) 如何加上 ( )?

  39. 5.5.2 後序走訪 程式5-4二元樹的後序走訪 14 void postorder(struct BTreeNode *node) 15 { if (node !=NULL) 16 { postorder(node->leftchild); 17 postorder(node->rightchild); 18 cout << node->data; 19 } 20 } 以後序走訪,可得到: AB+C*DEF-/+ 其正為對應的後序運算式。

  40. 5.5.3 前序走訪 程式5-5二元樹的前序走訪 21 void preorder(struct BTreeNode *node) 22 { if (node != NULL) 23 { cout << node->data; 24 preorder(node->leftchild); 25 preorder(node->rightchild); 26 } 27 } 以前序走訪,可得到: +*+ABC/D-EF 其正為對應的前序運算式。

  41. 5.5.4 利用堆疊和迴圈取代遞迴,進行二元樹的走訪 1 struct BTreeNode 2 { struct BTreeNode *leftchild; 3 char data; 4 struct BtreeNode *rightchild; 5 }; 6 struct BTreeNode *root; 7 struct StackNode 8 { struct BTreeNode *treenode; 9 struct StackNode *next; 10}; 11 struct StackNode *top;

  42. 12 void push_node(struct BtreeNode *node ) 13 { struct StackNode *old_top; 14 old_top = top; 15 top = (struct StackNode *) malloc (sizeof(struct StackNode)); 16 top->treenode = node; 17 top->next = old_top; 18 } 19 struct BTreeNode *pop_node() 20 { struct BTreeNode *Tnode; 21 struct StackNode *old_top; 22 if (top == NULL) 23 StackEmpty(); 24 else 25 { Tnode = top->treenode; 26 old_top = top; 27 top = top->next 28 free(old_top); 29 return Tnode; 30 } 31 }

  43. 6 struct BTreeNode *root; 7 void inorder(struct BTreeNode *node) 8 { if (node != NULL) 9 { inorder(node->leftchild); 10 cout << node->data; 11 inorder(node->rightchild); 12 } 13 } 32 void inorder_Stack(struct BtreeNode* node) 33 { do 34 { while (node != NULL) 35 { push_node(node); 36 node = node->leftchild; 37 } // push all left children 38 if (top != NULL) 39 { node = pop_node(); 40 cout << node->data; 41 node = node->rightchild; 42 } // inorder printing and check right child 43 } while ((top!=NULL)||(node!=NULL)); 44 } // top==NULL 堆疊已空;node==NULL沒有右子樹

  44. 中序表示為: DBGEHAFIC 32 void inorder_Stack() 33 { struct BTreeNode * node; 34 node = root; 35 do 36 { while (node != NULL) 37 { push_node(node); 38 node = node->leftchild; 39 } 40 if (top != NULL) 41 { node = pop_node(); 42 cout << node->data; 43 node = node->rightchild; 44 } 45 }while(top!=NULL)||(node!=NULL)); 46 }

  45. 程式5-5二元樹的前序走訪 21 void preorder(struct BTreeNode *node) 22 { if (node != NULL) 23 { cout << node->data; 24 preorder(node->leftchild); 25 preorder(node->rightchild); 26 } 27 } 45 void Preorder_Stack(struct BSTreeNode * node) 46 { do 47 { while (node != NULL) 48 { cout << node->data); 49 push_node(node); 50 node = node->leftchild; 51 } // push all left children 52 if (top != NULL) 53 { node = pop_node(); 54 node = node->rightchild; 55 } 56 } while ((top!=NULL)||(node!=NULL)); 57 }

  46. 程式5-4二元樹的後序走訪 14 void postorder(struct BTreeNode *node) 15 { if (node !=NULL) 16 { postorder(node->leftchild); 17 postorder(node->rightchild); 18 cout << node->data; 19 } 20 } 58 void Postorder_Stack(struct BTreeNode * node) 59 { do 60 { while (node != NULL) 61 { cout << node->data; 62 push_node(node); 63 node = node->rightchild; 64 } // push all left children 65 if (top != NULL) 66 { node = pop_node(); 67 node = node->leftchild; 68 } 69 } while ((top!=NULL)||(node!=NULL)); 70 }

  47. 5.5.5 階層走訪 • 「階層走訪」 (level-order traversal) 是依階層的順序,進行二元樹的走訪,先走訪階層小的節點,後走訪階層大的節點,同一階層者則依自左向右的順序走訪。 • 對下圖的二元樹,進行階層走訪的結果為: ABCDEFGHI。 • 對同一階層而言,先走訪的節點,其子節點亦在下一階層中先被走訪。 • 這種「先進先出」的特性,恰可用佇列予以儲存與處理。

  48. 程式5-7利用佇列進行二元樹的階層走訪 1 struct BTreeNode 2 { struct BTreeNode *leftchild; 3 char data; 4 struct BTreeNode *rightchild; 5 }; 6 struct BTreeNode *root; 7 struct QNode 8 { struct BTreeNode *treenode; 9 struct QNode *next; 10 }; • struct QNode *front, *rear; 12 void AddQueue(struct BTreeNode *Tnode) 13 { struct QNode *node; 14 node=(struct QNode *)malloc(sizeof(struct QNode)); 15 node->treenode = Tnode; 16 node->next = NULL; 17 if (front == NULL) front = node; • else rear->next = node; • rear = node; 21 }

  49. 22 struct BTreeNode *DeleteQueue() 23 { struct BTreeNode *Tnode; 24 struct QNode *old_front; 25 if (rear == NULL) 26 QueueEmpty(); 27 else 28 { Tnode = front->treenode; 29 old_front = front; 30 front = front->next; 31 free(old_front); 32 return Tnode; 33 } 34 } 35 void LevelOrder (struct BTreeNode *node) 36 { AddQueue(node); 37 while (front !=NULL) 38 { node = DeleteQueue(); 39 cout << node->data; 40 if (node->leftchild != NULL) 41 AddQueue(node->leftchild); 42 if (node->rightchild != NULL) 43 AddQueue(node->rightchild); 44 } 45 }

More Related