前面咱們已經提到了線性表,棧,隊列等數據結構,他們有一個共同的特性,就是結構中每個元素都是一對一的,但是在現實中,還有不少一對多的狀況須要處理,因此咱們須要研究這種一對多的數據結構 —— 樹,並運用它的特性來解決咱們在編程中遇到的問題。html
1、樹的定義java
1,樹Tree是n(n >= 0) 個結點的有限集。n = 0時稱爲空樹 在任意一棵非空的樹中,node
(1)有且僅有一個特定的根結點算法
(2)當n>1時,其他節點可分爲m(m > 0)個互不相交的有限集T1,T2,.....,Tm,其中每個集合又是一棵樹,而且稱爲根的子樹。以下圖所示編程
注意:1,根節點是惟一的 2,子樹的個數沒有限制,但它們必定是互不相交的數組
2,結點分類數據結構
(1)結點包含一個數據元素以及若干指向其子樹的分支。ide
(2)結點擁有的子樹數量稱爲結點的度優化
(3)度爲0的結點稱爲葉節點或終端節點ui
(4)度不爲0的結點稱爲非終端結點或分支結點
(5)一棵樹的度是樹內各節點的度的最大值
3,結點間的關係
結點的子樹的根稱爲該結點的孩子,該結點稱爲孩子的雙親。同一個雙親的孩子之間互稱兄弟。結點的祖先是從根到該結點所經分支上的全部結點。反之,以某結點爲根的子樹中的任一結點都成爲該結點的子孫。
4,樹的其餘相關概念
(1)結點的層次從根開始定義,根爲第一層,根的孩子爲第二層,以此類推。
(2)其雙親在同一層的結點互爲表兄弟
(3)樹中結點的最大層次稱爲樹的深度或高度
(4)若是將樹中結點的各子樹當作從左至右是有次序的,不能互換的,則稱該樹爲有序樹,不然稱爲無序樹。
(5)森林是m(m >= 0)棵互不相交的樹的集合
2、樹的存儲結構
咱們介紹三種不一樣的表示方法:雙親表示法、孩子表示法、孩子兄弟表示法。
1,雙親表示法
結點可能沒有孩子,但必定有雙親。假設咱們以一組連續空間存儲樹的結點,同時在每一個結點中,附設一個指示器指示其雙親結點在數組中的位置。因爲根節點沒有雙親,因此咱們約定根節點的位置域設爲-1.下面是示例
下標 | data | parent |
0 | A | -1 |
1 | B | 0 |
2 | C | 0 |
3 | D | 1 |
4 | E | 2 |
5 | F | 2 |
6 | G | 3 |
7 | H | 3 |
8 | I | 3 |
9 | J | 4 |
這樣的存儲結構,咱們能夠根據結點的parent指針很容易找到雙親,時間複雜度O(1)。但若是咱們要知道結點的孩子呢?對不起,請遍歷整個結構才行。那麼能不能改進一下呢?
咱們增長一個結點最左邊孩子的域,不妨叫他長子域,這樣很容易獲得結點的孩子。若是沒有孩子的葉結點,這個長子域就設爲-1,以下表
下標 | data | parent | firstchild |
0 | A | -1 | 1 |
1 | B | 0 | 3 |
2 | C | 0 | 4 |
3 | D | 1 | 6 |
4 | E | 2 | 9 |
5 | F | 2 | -1 |
6 | G | 3 | -1 |
7 | H | 3 | -1 |
8 | I | 3 | -1 |
9 | J | 4 | -1 |
另一個問題場景,咱們關注各兄弟之間的關係,雙親表示法沒法體現這樣的關係,怎麼辦呢?能夠增長一個右兄弟域來體現兄弟關係,也就是說,每個結點若是它存在右兄弟,就記錄下右兄弟的下標,一樣的若是右兄弟不存在,就賦值爲-1.
下標 | data | parent | rightsib |
0 | A | -1 | -1 |
1 | B | 0 | 2 |
2 | C | 0 | -1 |
3 | D | 1 | -1 |
4 | E | 2 | 5 |
5 | F | 2 | -1 |
6 | G | 3 | 7 |
7 | H | 3 | 8 |
8 | I | 3 | -1 |
9 | J | 4 | -1 |
若是節點的孩子不少,超過了兩個,咱們又關注節點的雙親,又關注節點的孩子,還關注結點的兄弟,並且還對時間遍歷要求高,那麼咱們能夠把此結構擴展爲有各個域都包含。存儲結構的設計是一個很是靈活的過程,一個存儲結構設計的是否合理,取決於基於改存儲結構的運算是否適合、是否方便,時間複雜度好很差等。
2,孩子表示法
如今咱們換一種不一樣的考慮方法。因爲樹中每一個結點可能有多棵子樹,能夠考慮用多重鏈表即每一個節點有多個指針域,其中每一個指針指向一棵子樹的根節點,咱們把這種方法叫作多重鏈表表示法。
不過,樹的每一個結點的度,也就是孩子的個數是不一樣的,因此設計兩種方案來解決。
方案一
第一種方案是指針域的個數等於樹的度。
對於上面做爲示例的樹來講,度是3
這種方法對於樹中結點度相差很大時,是浪費空間的。
方案二
每一個結點指針域的個數等於該結點的度,專門取宇哥位置來存儲結點指針域的個數,結構以下
這種方案克服了空間浪費的缺陷,但因爲每一個結點的鏈表不一樣,加上要維護結點的度的值,在運算上會有時間上的損耗。
爲了同時知足空間不浪費,又使節點結構相同,咱們引出 —— 孩子表示法。 把每一個節點的孩子結點排列起來,以單鏈表做爲存儲結構,則n個結點有n個孩子鏈表,若是是葉子結點則此單鏈表爲空。而後n個頭指針又組成一個線性表,採用順序存儲結構,存放進一個一維數組。爲了快速找到某個結點的雙親,咱們把雙親表示法和孩子表示法綜合一下以下圖。
這種方法叫作雙親孩子表示法,是對孩子表示法的改進。結構定義以下
public class ParentChildDemo <T>{ PCTBox[] nodes; //表頭數組 int r; //根節點的角標 int n; //總結點樹 int parent; //雙親結點的角標,根節點爲-1 private class PCTBox<T>{ T data; ChildNode firstchild; } private class ChildNode{ int child; //存儲本結點在表頭數組中的下標 ChildNode next; } }
3,孩子兄弟表示法
任意一棵樹,它的結點的第一個孩子若是存在就是惟一的,它的右兄弟若是存在也是惟一的。所以咱們設置兩個指針,分別指向該結點的第一個孩子和此節點的右兄弟。
若是有必要徹底能夠再增長一個parent指針域來解決快速查找雙親的問題。這個表示法最大的好處就是它把一棵複雜的樹變成了一棵二叉樹。
3、二叉樹 重點來了!!
對於在某個階段都是兩種結果的情形,好比開和關,0和1,真和假,上和下,正與反等,都適合用二叉樹來表示
1,二叉樹的定義
二叉樹 Binary Tree 是n個結點的有限集合,該集合或者爲空集,或者由一個根節點和兩棵互不相交的、分別稱爲根節點的左子樹和右子樹的二叉樹組成。形以下圖
2,二叉樹的特色:
(1)每一個結點最多有兩棵子樹
(2)左子樹和右子樹有順序,不能顛倒
(3)若是某結點只有一棵子樹,那也要區分它是左子樹仍是右子樹。
3,特殊二叉樹
(1)斜樹。全部結點都只有左子樹的二叉樹叫作左斜樹。全部結點都是隻有右子樹的二叉樹叫右斜樹。
(2)滿二叉樹。 全部分支結點都存在左子樹和右子樹,而且全部葉子都在同一層上,這樣的二叉樹稱爲滿二叉樹。
(3)徹底二叉樹。對一棵具備n個結點的二叉樹按照層序編號,若是編號爲i的結點與一樣深度的滿二叉樹中編號爲i的結點在二叉樹中的位置徹底相同,則這棵二叉樹稱爲徹底二叉樹。簡單來講就是最後一個結點的以前結點是按照滿來排列的,沒有空檔。矮的子樹必定是右子樹,且比左子樹最多矮一層。最下層的葉子必定集中在左部連續位置。一樣結點數的二叉樹,徹底二叉樹的深度最小。
4,二叉樹的性質
(1)在二叉樹的第i層上至多有2i-1個結點。
(2)深度爲k的二叉樹至多有2k-1個結點
(3)對任何一棵二叉樹T,若是其終端結點數爲n0,度爲2的結點數爲n2,則n0=n2+1
(4)具備n個結點的徹底二叉樹的深度爲。
表示不大於x的最大整數。
(5)若是對一棵有n個結點的徹底二叉樹的結點按層序編號,對任意結點有:
若是i=1,則i是二叉樹的根;若是i>1,則其雙親是
若是2i>n,則結點i無左孩子(結點i是葉子結點);不然其左孩子是結點2i
若是2i+1>n,則結點i無右孩子;不然其右孩子是2i+1
4、二叉樹的存儲結構
1,順序存儲結構
二叉樹是一種特殊的樹,用順序存儲結構能夠實現。用一維數組存儲二叉樹中的結點,而且節點的存儲位置,也就是數組的下標,要能體現結點之間的邏輯關係。舉例以下
將這棵樹存入數組中(打叉的元素表示不存在)以下
這種方式若是趕上右斜樹,那麼會浪費不少空間,因此順序存儲方式通常只用於徹底二叉樹。
2,二叉鏈表
二叉樹每一個結點最多有兩個孩子,因此爲它設計一個數據域和兩個指針域,咱們稱爲二叉鏈表
public class BiTNode<T> { T data; BiTNode lchild; BiTNode rchild; }
5、遍歷二叉樹
1,二叉樹的遍歷是指從根節點出發,按照某種次序依次訪問二叉樹中的全部結點,使得每一個結點被訪問一次且僅被訪問一次。
二叉樹遍歷不一樣於線性結構,在一個結點訪問完成後面臨多個選擇。
2,二叉樹遍歷方法
(1)前序遍歷
前序遍歷、中序遍歷等遍歷名稱的「前」 「中」是指雙親結點和子結點訪問時雙親結點在前被訪問仍是在中間被訪問
若樹爲空,則空操做返回;先訪問根節點,而後前序遍歷左子樹,再前序遍歷右子樹。(先父後子,先左後右,根左右)
結果是ABDGHCEIF
(2)中序遍歷
從根結點開始(但並不先訪問根結點),中序遍歷根節點的左子樹,而後訪問根節點,最後中序遍歷右子樹(左根右)
結果是GDHBAEICF
(3)後序遍歷
若空,返回空操做;不然從左到右先葉子後結點的方式遍歷訪問左右子樹,而後訪問根節點。(左右根)
結果:GHDBIEFCA
(4)層序遍歷
若空,則空操做返回,不然從樹的第一層,也就是根節點開始訪問,從上而下逐層遍歷,在同一層中,按從左到右的順序對結點逐個訪問
結果:ABCDEFGHI
研究這些遍歷方法其實就是在把樹中的結點變成有意義的線性序列,經過不一樣的遍歷方法提供了對結點的不一樣處理方式。
首先是結點定義
public static class Node<T>{ private T data; private Node lChild; private Node rChild; public Node(T data) { this.data = data; } public void setNode(T data){ this.data = data; } }
/** * 前序遍歷的遞歸寫法 */ public void PreOrderTraverse1(Node root){ if (root == null) return; System.out.println(root.data); PreOrderTraverse1(root.lChild); PreOrderTraverse1(root.rChild); } /** * 前序遍歷的非遞歸寫法 * 遞歸轉成棧 */ public void PreOrderTraverse2(Node root){ ArrayStack<Node> a = new ArrayStack<>(); while (root != null || !a.isEmpty()){ while (root != null){ System.out.println(root.data); a.push(root); root = root.lChild; } if (!a.isEmpty()){ root = a.pop(); root = root.rChild; } } } /** * 中序遍歷的遞歸寫法 */ public void InOrderTraverse1(Node root){ Node node = root; if (node == null){ return; } InOrderTraverse1(node.lChild); System.out.println(node.data); InOrderTraverse1(node.rChild); } /** * 中序遍歷的非遞歸寫法 */ public void InOrderTraverse2(Node root){ ArrayStack<Node> a = new ArrayStack<>(); while (root != null || !a.isEmpty()){ while (root.lChild != null){ a.push(root); root = root.lChild; } if (!a.isEmpty()){ root = a.pop(); System.out.println(root.data); root = root.rChild; } } } /** * 後序遍歷的遞歸寫法 */ public void PostOrderTraverse1(Node root){ if (root == null){ return; } PostOrderTraverse1(root.lChild); PostOrderTraverse1(root.rChild); System.out.println(root.data); } /** * 後序遍歷的非遞歸寫法 —— 雙棧法 * 將前序遍歷的中左右,調換變成左右中 * */ public void PostOrderTraverse2(Node root){ ArrayStack<Node> a1 = new ArrayStack<>(); ArrayStack<Node> a2 = new ArrayStack<>(); Node r = root; while (r != null || !a1.isEmpty()){ while (r != null){ a1.push(r); a2.push(r); r = r.rChild; } if (!a1.isEmpty()){ r = a1.pop(); r = r.lChild; } } while (!a2.isEmpty()){ r = a2.pop(); System.out.println(r.data); } } /** * 利用隊列實現層序遍歷(能夠不用優先隊列,這個優先隊列Demo是我前面隨筆寫的,順便拿來用用) */ public void LevelOrderTraverse(Node root) throws Exception { PriorityQueueDemo<Node> p = new PriorityQueueDemo<>(10); Node t; p.add(root); while (p.size() != 0){ t = p.poll(); System.out.println(t.data); if (t.lChild != null) p.add(t.lChild); if (t.rChild != null) p.add(t.rChild); } }
二叉樹遍歷的兩個性質: 已知前序遍歷序列和中序遍歷序列,能夠惟一肯定一棵二叉樹
已知後序遍歷序列和中序遍歷序列,能夠惟一肯定一棵二叉樹
6、二叉樹的創建
咱們要創建一棵以下左圖的樹,爲了能讓每一個結點確認是否有左右孩子,咱們對它進行了擴展,變成以下右圖的樣子。咱們稱右圖爲左圖的擴展二叉樹。擴展二叉樹能夠作到一個遍歷序列就肯定一棵二叉樹。
上右圖的前序遍歷結果爲 AB#D##C##,咱們把這樣的遍歷結果輸入下面程序中就能夠創建左圖二叉樹了
private Object[] results; public BiTree(Object[] arr){ this.results = arr; } private static int index = 0; public Node<T> buildBiTree(){ if (index >= results.length || results[index].equals('#')){ index++; return null; } Node<T> node = new Node<T>((T)results[index++]); node.lChild = buildBiTree(); node.rChild = buildBiTree(); return node; }
7、線索二叉樹
1,咱們來看以下的鏈式二叉樹
裏面右許多的空指針^沒有被利用起來,咱們來計算一下,一個n個結點的二叉樹,有2n個指針域,n-1條分支線路,也就是說有2n-n+1=n+1個空指針域在浪費着。
另外,咱們在遍歷時,知道上圖的中序遍歷結果是HDIBJEAFCG,此時咱們能夠知道好比D的前驅是H,後繼是I,但咱們在沒有遍歷的狀況下是不知道的。綜合以上兩點,咱們能夠利用那些空地址,存放結點在某種遍歷次序下的前驅和後繼結點的位置。
咱們把這種指向前驅和後繼的指針稱爲線索,加上線索的二叉鏈表稱爲線索鏈表,相應的二叉樹稱爲線索二叉樹(Threaded Binary Tree)
咱們讓全部空閒的左指針指向前驅,全部空閒的右指針指向後繼;而且增長ltag域和rtag域區分左右指針指向的究竟是孩子仍是前驅後繼。
其中 ltag和rtag爲0時指向的是左孩子或右孩子,爲1時指向的是前驅或後繼。
2,線索二叉樹的實現
由於前驅和後繼只有在遍歷過程當中才能拿到,因此實現線索二叉樹的過程實質上就是在遍歷過程當中修改空指針的過程。
private Node pre;//根節點的pre是head,head的rtag = 1public void Threading(Node root) { Node r = root; if (root == null){ return; }else { Threading(r.lChild); if (r.lChild == null){ r.ltag = 1; r.lChild = pre; } if ( pre != null&&pre.rChild == null){ pre.rtag = 1; pre.rChild = r; } pre = r; Threading(r.rChild); } }
這個遞歸實現只不過是把中序遍歷中訪問結點數據的代碼改爲了修改指針的代碼。
有了前驅後繼,咱們就能夠經過二叉線索樹來遍歷二叉樹了。對於中序遍從來說,先查找線索鏈表的第一個節點,也就是最左方向上的最後一個節點,而後若是有右線索先尋找後繼節點,查找到斷線索(有右節點啦)就往下找一個右節點,繼續這樣摸下去,其實說到底就是有線索先按線索找(注意線索上的節點是須要訪問的),等到線索斷了就往下找右孩子節點(找到右孩子結點後按照中序遍歷先左孩子後雙親結點再右孩子的順序遍歷該子樹)。
/** * 按照二叉線索樹的線索遍歷 */ public void InOrderTraverse_Threaded(Node root){ Node r = root; while (r != null) { while (r.lChild != null && r.ltag == 0) { r = r.lChild; } System.out.println(r.data); while (r.rtag == 1) { r = r.rChild; System.out.println(r.data); } r = r.rChild; } }
能夠看到若是所用的二叉樹須要常常遍歷或者須要某種遍歷序列中的前驅和後繼來查找結點,那麼採用線索二叉鏈表的存儲結構是很是不錯的選擇。
8、樹,森林與二叉樹的轉換。
1,樹轉化爲二叉樹
步驟: (1)加線。在全部的兄弟結點之間加一條線
(2)去線。對樹中的每一個結點,只保留它與第一個孩子結點的連線,刪除它與其餘孩子結點的連線。
(3)層次調整。以樹的根節點爲軸線,將整棵樹順時針旋轉必定的角度,使之井井有條。 注:上面提到的第一個孩子是二叉樹的左孩子,第一個孩子的兄弟轉換過來的是二叉樹的右孩子。
2,森林轉化爲二叉樹
把森林中的每一棵樹認爲是兄弟,按照上面兄弟的處理辦法來操做。步驟以下:
(1)把每棵樹轉化成二叉樹
(2)第一棵樹不動,從第二棵二叉樹開始,依次把後一棵二叉樹的根節點做爲前一棵二叉樹的根節點的右孩子,用線連起來。當全部二叉樹都鏈接起來以後就獲得了森林轉化來的二叉樹。
3,二叉樹轉化爲樹
把樹轉二叉樹的過程反過來作,步驟以下:
(1)加線。若是某結點的左孩子存在,則將這個左孩子的右孩子結點、右孩子的右孩子結點、…… 。總之就是將左孩子的n個右孩子結點都做爲此結點的孩子鏈接起來。
(2)去線。刪除原二叉樹中全部結點與其右孩子的連線
(3)層次調整,使之結構井井有條。
4,二叉樹轉化爲森林
看一棵二叉樹能轉換成一棵樹仍是森林,標準就是要看這棵二叉樹的根節點有沒有右孩子,有就是森林,沒有就是二叉樹。
二叉樹轉森林的步驟以下:
(1)從根節點開始,若右孩子存在,則把與右孩子的連線斷掉,知道全部的右孩子連線都刪除。
(2)再將每棵分離出來的二叉樹轉換爲樹便可
5,樹與森林的遍歷
(1)樹的遍歷方式分兩種: 第一種是先根遍歷樹。即先訪問樹的根結點,再依次訪問根的每棵子樹。
第二種是後根遍歷,即先依次後根遍歷每棵子樹,再訪問根節點。例如上面圖中的樹,先根遍歷結果爲ABEFCDG,後根遍歷結果爲EFBCGDA
(2)森林的遍歷也分爲兩種:
前序遍歷:先訪問森林中第一棵樹的根結點,而後再依次先根遍歷根的每棵子樹,再依次用一樣的方式遍歷除去第一棵樹的剩餘樹構成的森林。如上面圖中的森林結果爲ABCDEFGHJI
後序遍歷:先訪問森林中的第一棵樹,後根遍歷的方式遍歷每棵樹,而後再訪問根節點,再依次用一樣的方式遍歷去除第一棵樹的剩餘樹構成的森林,結果爲BCDAFEJHIG
咱們分析發現,森林的前序遍歷和二叉樹的前序遍歷結果相同,森林的後序遍歷和二叉樹的中序遍歷結果相同。也就是說當以二叉鏈表做爲樹的存儲結構時,樹的遍歷徹底能夠借用二叉樹的遍歷算法來實現。
9、赫夫曼樹及其應用
1,赫夫曼樹
從樹中一個結點到另外一個結點之間的分支構成兩個結點之間的路徑,路徑上的分支數目稱做路徑長度。樹的路徑長度就是從樹根到每一結點的路徑長度之和。
若是考慮帶權的結點,結點的帶權路徑長度就是從該結點到樹根之間的路徑長度與結點上權的乘積。帶權路徑長度WPL最小的二叉樹稱做赫夫曼樹。
例如,給定
分數 | 0~59 | 60~69 | 70~79 | 80~89 | 90~100 |
所佔比例% | 5 | 15 | 40 | 30 | 10 |
步驟:(1)把結點按權值從小到大排成一個有序序列 A5,E10,B15,D30,C40
(2)取前兩個最小權值的結點做爲新結點N1的兩個子結點,小的爲左孩子
(3)將N1替換A與E插入到序列中,即N1 15,B15,D30,C40,重複(2)
(4)反覆重複(2),(3)步驟,完成創建
經過上面的步驟,咱們能夠得出構造赫夫曼樹的赫夫曼算法描述:
1,根據給定的n個權值構成n棵二叉樹的集合F={T1,T2,...,Tn},其中每棵二叉樹Ti中只有一個帶權爲wi的根節點,其左右子樹爲空。
2,在F中選取兩棵根節點的權值最小的樹做爲左右子樹構造一棵新二叉樹,而且置新二叉樹的根節點的權值爲左右子樹上根節點的權值之和。
3,在F中刪除這兩棵樹,同時將新獲得的二叉樹加入F
4,重複2,3直到F只含一棵樹爲止。這棵樹即是赫夫曼樹。
代碼以下:
//結點結構 import java.util.Comparator; public class TNode<T> implements Comparable{ private T data; private int weight; private TNode lChild; private TNode rChild; private Comparator comparator; public TNode(T data, int weight){ this.data = data; this.weight = weight; this.lChild = null; this.rChild = null; } public TNode(T data, int weight, TNode lChild, TNode rChild){ this(data, weight); this.lChild = lChild; this.rChild = rChild; } public Comparator getComparator(){ return this.comparator; } public void setlChild(TNode node){ this.lChild = node; } public void setrChild(TNode node){ this.rChild = node; } public int getWeight(){ return this.weight; } public T getData(){ return data; } public TNode getlChild(){ return lChild; } public TNode getrChild(){ return rChild; } @Override public int compareTo(Object o) { return ((TNode)o).weight - this.weight; } } class TNodeComparator implements Comparator{ @Override public int compare(Object o1, Object o2) { return ((TNode)o1).getWeight() - ((TNode)o2).getWeight(); } }
//建立霍夫曼樹 import java.util.LinkedList; public class HuffmanTree<T> { LinkedList<TNode<T>> tnodes = new LinkedList<>(); public HuffmanTree(LinkedList<TNode<T>> tnodes){ this.tnodes = tnodes; } public TNode<T> buildHuffmanTree(){ tnodes.sort(new TNodeComparator()); while (tnodes.size() > 1) { TNode<T> newTNode = buildBiTree(tnodes.remove(0), tnodes.remove(0)); tnodes.add(0, newTNode); tnodes.sort(new TNodeComparator()); } return tnodes.remove(0); } public void PreOrderTraverse(TNode tNode){ if (tNode == null){ return; }else { T tdata = (T) tNode.getData(); if (tdata != null){ System.out.println(tdata); } PreOrderTraverse(tNode.getlChild()); PreOrderTraverse(tNode.getrChild()); } } private TNode buildBiTree(TNode<T> tNode, TNode<T> tNode1) { TNode<T> t1 = new TNode<T>(null, tNode.getWeight() + tNode1.getWeight(), tNode, tNode1); return t1; } }
2,赫夫曼樹的應用 —— 赫夫曼編碼
當年赫夫曼研究赫夫曼樹,就是爲了解決遠距離通訊中數據傳輸的最優化問題。好比咱們有一段文字內容爲「BADCADFEED」要發送給別人,用二進制是顯然的方法。咱們這段文字中包含ABCDEF六種字母,咱們就能夠相應的編碼爲
A | B | C | D | E | F |
000 | 001 | 010 | 011 | 100 | 101 |
傳輸時就按照上面編碼的對應二進制來傳輸,解碼時也按照三位一分隔來解碼。假設咱們有很長的一段字符,六個字母的頻率爲A 27, B 8, C 15, D 15, E 30 , F 5,合起來正好是100%,咱們就能夠用赫夫曼樹來規劃它們。
咱們對左圖的赫夫曼樹的權值左分支改成0,右分支改成1,這樣就能夠將六個葉子結點按照路徑從新編碼
A | B | C | D | E | F |
01 | 1001 | 101 | 00 | 11 | 1000 |
這樣壓縮了許多傳輸成本。當咱們接收到傳輸過來的新編碼時,要按照約定好的赫夫曼樹來解碼。由於長度不均,因此必須任意字符的編碼都不是另外一個字符編碼的前綴,這叫作前綴編碼,而赫夫曼樹的編碼恰好知足這種條件。
赫夫曼樹時前綴編碼,且是最優前綴編碼。下面是嚴蔚敏老師數據結構中的證實
總結:
本章重點是二叉樹部分的前序、中序、後序以及層序遍歷,代碼要會寫,遞歸方法代碼優雅但容易棧溢出,非遞歸方法不易棧溢出但代碼要比較難理解。線索二叉樹的構造和遍歷也要理解。
原文出處:https://www.cnblogs.com/Joey777210/p/11985685.html