樹的3種經常使用鏈表結構node
1 雙親表示法(順序存儲結構)算法
優勢:parent(tree, x)操做能夠在常量時間內實現數組
缺點:求結點的孩子時須要遍歷整個結構this
用一組連續的存儲空間來存儲樹的結點,同時在每一個結點中附加一個指示器(整數域) ,用以指示雙親結點的位置(下標值) 。spa
圖所示是一棵樹及其雙親表示的存儲結構。這種存儲結構利用了任一結點的父結點惟一的性質。能夠方便地直接找到任一結點的父結點,但求結點的子結點時須要掃描整個數組。prototype
代碼實現:3d
1 // 1.雙親表示法 2 // 優勢:parent(tree, x)操做能夠在常量時間內實現 3 // 缺點:求結點的孩子時須要遍歷整個結構 4 function ParentTree() { 5 this.nodes = []; 6 } 7 ParentTree.prototype = { 8 constructor: ParentTree, 9 getDepth: function () { 10 var maxDepth = 0; 11 12 for (var i = 0; i < this.nodes.length; i++) { 13 var dep = 0; 14 for (var j = i; j >= 0; j = this.nodes[i].parent) dep++; 15 if (dep > maxDepth) maxDepth = dep; 16 } 17 18 return maxDepth; 19 } 20 }; 21 function ParentTreeNode(data, parent) { 22 // type: ParentTree 23 this.data = data || null; 24 // 雙親位置域 {Number} 25 this.parent = parent || 0; 26 } 27 var pt = new ParentTree(); 28 pt.nodes.push(new ParentTreeNode('R', -1)); 29 pt.nodes.push(new ParentTreeNode('A', 0)); 30 pt.nodes.push(new ParentTreeNode('B', 0)); 31 pt.nodes.push(new ParentTreeNode('C', 0)); 32 pt.nodes.push(new ParentTreeNode('D', 1)); 33 pt.nodes.push(new ParentTreeNode('E', 1)); 34 pt.nodes.push(new ParentTreeNode('F', 3)); 35 pt.nodes.push(new ParentTreeNode('G', 6)); 36 pt.nodes.push(new ParentTreeNode('H', 6)); 37 pt.nodes.push(new ParentTreeNode('I', 6));
2 孩子鏈表表示法指針
樹中每一個結點有多個指針域,每一個指針指向其一棵子樹的根結點。有兩種結點結構。code
⑴ 定長結點結構blog
指針域的數目就是樹的度。
其特色是:鏈表結構簡單,但指針域的浪費明顯。結點結構以下圖(a)所示。在一棵有n個結點,度爲k的樹中必有n(k-1)+1空指針域。
⑵ 不定長結點結構
樹中每一個結點的指針域數量不一樣,是該結點的度,如圖(b) 所示。沒有多餘的指針域,但操做不便。
⑶ 複合鏈表結構
對於樹中的每一個結點,其孩子結點用帶頭結點的單鏈表表示,表結點和頭結點的結構以下圖所示。
n個結點的樹有n個(孩子)單鏈表(葉子結點的孩子鏈表爲空),而n個頭結點又組成一個線性表且以順序存儲結構表示。
複合鏈表結構代碼:
1 // 孩子表示法 2 3 function ChildTree() { 4 this.nodes = []; 5 } 6 ChildTree.prototype = { 7 constructor: ChildTree, 8 getDepth: function () { 9 var self = this; 10 return function subDepth(rootIndex) { 11 if (!self.nodes[rootIndex]) return 1; 12 13 for (var sd = 1, p = self.nodes[rootIndex]; p; p = p.next) { 14 var d = subDepth(p.child); 15 if (d > sd) sd = d; 16 } 17 18 return sd + 1; 19 }(this.data[0]); 20 } 21 }; 22 /** 23 * 24 * @param {*} data 25 * @param {ChildTreeNode} firstChild 孩子鏈表頭指針 26 * @constructor 27 */ 28 function ChildTreeBox(data, firstChild) { 29 this.data = data; 30 this.firstChild = firstChild; 31 } 32 /** 33 * 孩子結點 34 * 35 * @param {Number} child 36 * @param {ChildTreeNode} next 37 * @constructor 38 */ 39 function ChildTreeNode(child, next) { 40 this.child = child; 41 this.next = next; 42 }
孩子表示法便於涉及孩子的操做的實現,但不適用於parent操做。
咱們能夠把雙親表示法和孩子表示法結合起來。
3 孩子兄弟表示法(二叉樹表示法)
以二叉鏈表做爲樹的存儲結構。
兩個指針域:分別指向結點的第一個子結點和下一個兄弟結點。結點類型定義以下:
1 // 孩子兄弟表示法(二叉樹表示法) 2 // 可增設一個parent域實現parent操做 3 function ChildSiblingTree(data) { 4 this.data = data || null; 5 this.firstChild = null; 6 this.nextSibling = null; 7 } 8 ChildSiblingTree.prototype = { 9 // 輸出孩子兄弟鏈表表示的樹的各邊 10 print: function print() { 11 for (var child = this.firstChild; child; child = child.nextSibling) { 12 console.log('%c %c', this.data, child.data); 13 print.call(child); 14 } 15 }, 16 // 求孩子兄弟鏈表表示的樹的葉子數目 17 leafCount: function leafCount() { 18 if (!this.firstChild) return 1; 19 else { 20 var count = 0; 21 for (var child = this.firstChild; child; child = child.nextSibling) { 22 count += leafCount.call(child); 23 } 24 return count; 25 } 26 }, 27 // 求樹的度 28 getDegree: function getDegree() { 29 if (!this.firstChild) return 0; 30 else { 31 var degree = 0; 32 for (var p = this.firstChild; p; p = p.nextSibling) degree++; 33 34 for (p = this.firstChild; p; p = p.nextSibling) { 35 var d = getDegree.call(p); 36 if (d > degree) degree = d; 37 } 38 39 return degree; 40 } 41 }, 42 getDepth: function getDepth() { 43 if (this === global) return 0; 44 else { 45 for (var maxd = 0, p = this.firstChild; p; p = p.nextSibling) { 46 var d = getDepth.call(p); 47 if (d > maxd) maxd = d; 48 } 49 50 return maxd + 1; 51 } 52 } 53 };
森林與二叉樹的轉換
因爲二叉樹和樹均可用二叉鏈表做爲存儲結構,對比各自的結點結構能夠看出,以二叉鏈表做爲媒介能夠導出樹和二叉樹之間的一個對應關係。
◆ 從物理結構來看,樹和二叉樹的二叉鏈表是相同的,只是對指針的邏輯解釋不一樣而已。
◆ 從樹的二叉鏈表表示的定義可知,任何一棵和樹對應的二叉樹,其右子樹必定爲空。
1 樹轉換成二叉樹
對於通常的樹,能夠方便地轉換成一棵惟一的二叉樹與之對應。將樹轉換成二叉樹在「孩子兄弟表示法」中已給出,其詳細步驟是:
⑴ 加虛線。在樹的每層按從「左至右」的順序在兄弟結點之間加虛線相連。
⑵ 去連線。除最左的第一個子結點外,父結點與全部其它子結點的連線都去掉。
⑶ 旋轉。將樹順時針旋轉450,原有的實線左斜。
⑷ 整型。將旋轉後樹中的全部虛線改成實線,並向右斜。
2 二叉樹轉換成樹
對於一棵轉換後的二叉樹,如何還原成原來的樹? 其步驟是:
⑴ 加虛線。若某結點i是其父結點的左子樹的根結點,則將該結點i的右子結點以及沿右子鏈不斷地搜索全部的右子結點,將全部這些右子結點與i結點的父結點之間加虛線相連,以下圖a所示。
⑵ 去連線。去掉二叉樹中全部父結點與其右子結點之間的連線,以下圖b所示。
⑶ 規整化。將圖中各結點按層次排列且將全部的虛線變成實線,以下圖c所示。
3 森林轉換成二叉樹
當通常的樹轉換成二叉樹後,二叉樹的右子樹必爲空。若把森林中的第二棵樹(轉換成二叉樹後)的根結點做爲第一棵樹(二叉樹)的根結點的兄弟結點,則可導出森林轉換成二叉樹的轉換算法以下:
設F={T1, T2,⋯,Tn}是森林,則按如下規則可轉換成一棵二叉樹B=(root,LB,RB)
① 若n=0,則B是空樹。
② 若n>0,則二叉樹B的根是森林T1的根root(T1),B的左子樹LB是B(T11,T12, ⋯,T1m) ,其中T11,T12, ⋯,T1m是T1的子樹(轉換後),而其右子樹RB是從森林F’={T2, T3,⋯,Tn}轉換而成的二叉樹。
轉換步驟:
① 將F={T1, T2,⋯,Tn} 中的每棵樹轉換成二叉樹。
② 按給出的森林中樹的次序,從最後一棵二叉樹開始,每棵二叉樹做爲前一棵二叉樹的根結點的右子樹,依次類推,則第一棵樹的根結點就是轉換後生成的二叉樹的根結點。
4 二叉樹轉換成森林
若B=(root,LB,RB)是一棵二叉樹,則能夠將其轉換成由若干棵樹構成的森林:F={T1, T2,⋯,Tn} 。
轉換算法:
① 若B是空樹,則F爲空。
② 若B非空,則F中第一棵樹T1的根root(T1)就是二叉樹的根root, T1中根結點的子森林F1是由樹B的左子樹LB轉換而成的森林;F中除T1外其他樹組成的的森林F’={T2, T3,⋯,Tn} 是由B右子樹RB轉換獲得的森林。
上述轉換規則是遞歸的,能夠寫出其遞歸算法。如下給出具體的還原步驟。
① 去連線。將二叉樹B的根結點與其右子結點以及沿右子結點鏈方向的全部右子結點的連線所有去掉,獲得若干棵孤立的二叉樹,每一棵就是原來森林F中的樹依次對應的二叉樹,如圖(b)所示。
② 二叉樹的還原。將各棵孤立的二叉樹按二叉樹還原爲樹的方法還原成通常的樹,如圖(c)所示。
樹和森林的遍歷
1 樹的遍歷
由樹結構的定義可知,樹的遍歷有二種方法。
⑴ 先序遍歷:先訪問根結點,而後依次先序遍歷完每棵子樹。如圖的樹,先序遍歷的次序是: ABCDEFGIJHK
⑵ 後序遍歷:先依次後序遍歷完每棵子樹,而後訪問根結點。如圖的樹,後序遍歷的次序是: CDBFGIJHEKA
說明:
◆ 樹的先序遍歷實質上與將樹轉換成二叉樹後對二叉樹的先序遍歷相同。
◆ 樹的後序遍歷實質上與將樹轉換成二叉樹後對二叉樹的中序遍歷相同。
2 森林的遍歷
設F={T1, T2,⋯,Tn}是森林,對F的遍歷有二種方法。
⑴ 先序遍歷:按先序遍歷樹的方式依次遍歷F中的每棵樹。
⑵ 中序遍歷:按後序遍歷樹的方式依次遍歷F中的每棵樹。