二叉樹是一種很常見的數據結構,但要注意的是,二叉樹並非樹的特殊狀況,二叉樹與樹是兩種不同的數據結構。java
一、 二叉樹的定義算法
2、二叉樹爲什麼不是特殊的樹數組
3、二叉樹的五種基本形態緩存
4、二叉樹相關術語數據結構
5、二叉樹的主要性質(6個)app
6、二叉樹的存儲結構(2種)函數
7、二叉樹的遍歷算法(4種)測試
8、二叉樹的基本應用:二叉排序樹、平衡二叉樹、赫夫曼樹及赫夫曼編碼this
若是你知道樹的定義(有限個結點組成的具備層次關係的集合),那麼就很好理解二叉樹了。定義:二叉樹是n(n≥0)個結點的有限集,二叉樹是每一個結點最多有兩個子樹的樹結構,它由一個根結點及左子樹和右子樹組成。(這裏的左子樹和右子樹也是二叉樹)。編碼
值得注意的是,二叉樹和「度至多爲2的有序樹」幾乎同樣,但,二叉樹不是樹的特殊情形。具體分析以下
二叉樹的子樹有左右之分,不能顛倒。無序樹的子樹無左右之分。
當有序樹有兩個子樹時,確實能夠看作一顆二叉樹,但當只有一個子樹時,就沒有了左右之分,如圖所示:
滿二叉樹:全部葉子結點所有集中在最後一層,這樣的二叉樹稱爲滿二叉樹。(注意:國內的定義是每一層的結點都達到最大值時纔算是滿二叉樹;而國際定義爲,不存在度爲1的結點,即結點的度要麼爲2要麼爲0,這樣的二叉樹就稱爲滿二叉樹。這兩種概念徹底不一樣,既然在國內,咱們就默認第一種定義就好)。
徹底二叉樹:若是將一顆深度爲K的二叉樹按從上到下、從左到右的順序進行編號,若是各結點的編號與深度爲K的滿二叉樹相同位置的編號徹底對應,那麼這就是一顆徹底二叉樹。如圖所示:
二叉樹的性質是基於它的結構而得來的,這些性質沒必要死記,使用到再查詢或者本身根據二叉樹結構進行推理便可。
性質1:非空二叉樹的葉子結點數等於雙分支結點數加1。
證實:設二叉樹的葉子結點數爲X,單分支結點數爲Y,雙分支結點數爲Z。則總結點數=X+Y+Z,總分支數=Y+2Z。
因爲二叉樹除了根結點外其餘結點都有惟一的分支指向它,因此總分支數=總結點數-1.。
結合三個方程:總分支數=總結點數-1,即Y+2Z = X+Y+Z-1。化簡獲得X = Z + 1。即葉子結點數等於雙分支結點數加1。
性質2:在二叉樹的第i層上最多有2 i-1 個結點 (i>=1)。
證實:二叉樹結點最多的狀況即爲滿二叉樹的狀況,因爲是滿二叉樹,每一個結點都有兩個孩子,因此下一層是上一層的2倍,構成了公比爲2的等比數列,而第一層只有根結點,因此首項是1。因此二叉樹的第i層上最多有2 i-1 個結點。
性質3:高度(或深度)爲K的二叉樹最多有2k - 1個結點(K>=1)。
證實:本性質其實就是性質2中描述的等比數列的前項和的問題。
性質4:若對含 n 個結點的徹底二叉樹從上到下且從左至右進行 1 至 n 的編號,則對徹底二叉樹中任意一個編號爲 i 的結點:
(1) 若 i=1,則該結點是二叉樹的根,無雙親, 不然,編號爲 [i/2] 的結點爲其雙親結點;
(2) 若 2i>n,則該結點無左孩子, 不然,編號爲 2i 的結點爲其左孩子結點;
(3) 若 2i+1>n,則該結點無右孩子結點, 不然,編號爲2i+1 的結點爲其右孩子結點。
性質5:具備n個結點的徹底二叉樹的深度爲[log2n]+1或者[log2(n+1)],其中[log2n]+1是向下取整,[log2(n+1)]是向上取整。
性質6:Catalan函數性質:給定n個結點,能構成H(n)種結構不一樣的樹。H(n) = c(2n,n) / (n+1)。
爲了方便說明,咱們使用下圖樹1做爲案例樹。
順序存儲是使用一個數組來存儲二叉樹,咱們通常將二叉樹按照性質4的作法,即從上到下且從左至右進行 1 至 n 的編號,而後編號與數組下標對應,按照編號依次將對應的結點信息存儲數組中便可。
第一步:給二叉樹編號:
注意,編號5的位置是沒有結點的,可是咱們這樣編號的目的是爲了更好的應用性質4,而性質4描述的是徹底二叉樹,因此咱們編號時要將普通二叉樹看作徹底二叉樹來進行編號,因此即便編號5即便沒有結點也須要進行編號。
第二步:按編號存儲到數組BTree[]中
第三步:按照性質4的規律取元素
性質4主要是描述結點的編號和雙親編號或孩子編號之間的數學關係,即咱們知道了一個結點的編號,那麼它的雙親編號和孩子孩子咱們都能計算獲得並從數組中取出來。
例如,結合性質4咱們來計算編號3的雙親和孩子:選取出編號3即BTree[3],就能夠知道編號3的元素爲C;雙親結點編號 = 3/2 = 1 ≥ 1,因此C結點的雙親爲BTree[1]即A;左孩子編號 = 3*2 = 6 ≤ 7,因此C的右孩子爲BTree[6] = E;右孩子編號 = 3*2+1 = 7 ≤ 7,因此C的右孩子爲Btree[7] = F。
結論:像編號5這種狀況會佔用存儲空間,因此這種存儲方式最適合用於存儲徹底二叉樹,而存儲通常的二叉樹則會浪費大量空間。
根據二叉樹的結構,咱們使用下面的鏈式結點來存儲一個二叉樹結點。
因此樹1對應的鏈式存儲結構爲:
根據二叉樹的結構特色:通常二叉樹由左子樹、根結點和右子樹組成。這三個元素:左子樹(L)、根結點(N)、右子樹(R)有6種中排列組合,即NLR、LNR、LRN、NRL、RNL、RLN。而從左往右和從右往左這種遍歷順序是對稱結構的,採用一種順序便可,因此二叉樹按照三個元素的排列順序遍歷就造成了:NLR(先序遍歷)、LNR(中序遍歷)和LRN(後序遍歷)。
ps:二叉樹的這三種遍歷要用遞歸的思想去理解。
先序遍歷(NLR):根左右
1)訪問根結點
2)先序遍歷左子樹
3)先序遍歷右子樹
中序遍歷(LNR):左根右
1)中序遍歷左子樹
2)訪問根結點
3)中序遍歷右子樹
後序遍歷(LRN):左右根
1)後序遍歷左子樹
2)後序遍歷右子樹
3)訪問根結點
java實現(遍歷樹1):
1 package test; 2 3 import org.junit.Test; 4 5 /** 6 * 二叉樹的遍歷 7 * @author Fzz 8 * @date 2018年1月17日 9 * @Description TODO: 10 */ 11 public class BinaryTreeTraversal { 12 private StringBuffer sb = new StringBuffer(); 13 //先序遍歷(數組) 14 public String first(Object[] o,int i){ 15 //訪問根結點 16 sb.append(o[i]); 17 //遍歷左子樹 18 int left = i*2; 19 if(left<o.length&&o[left]!=null) 20 first(o,left); 21 //遍歷右子樹 22 int right = i*2+1; 23 if(right<o.length&&o[right]!=null) 24 first(o,right); 25 return sb.toString(); 26 } 27 28 //中序遍歷 29 public String mid(Object[] o,int i){ 30 //遍歷左子樹 31 int left = i*2; 32 if(left<o.length&&o[left]!=null) 33 mid(o,left); 34 //訪問根結點 35 sb.append(o[i]); 36 //遍歷右子樹 37 int right = i*2+1; 38 if(right<o.length&&o[right]!=null) 39 mid(o,right); 40 return sb.toString(); 41 } 42 43 //後序遍歷 44 public String last(Object[] o,int i){ 45 //遍歷左子樹 46 int left = i*2; 47 if(left<o.length&&o[left]!=null) 48 last(o,left); 49 //遍歷右子樹 50 int right = i*2+1; 51 if(right<o.length&&o[right]!=null) 52 last(o,right); 53 //訪問根結點 54 sb.append(o[i]); 55 return sb.toString(); 56 } 57 58 //將緩存區設爲空 59 public void setBufferNull(){ 60 this.sb = this.sb.delete(0, sb.length()); 61 } 62 63 @Test 64 public void test(){ 65 Character[] o = {null,'A','B','C','D',null,'E','F'}; 66 //遍歷前先清空緩存區 67 this.setBufferNull(); 68 String s = first(o,1); 69 System.out.println("先序遍歷結果:"+s); 70 this.setBufferNull(); 71 s = mid(o,1); 72 System.out.println("中序遍歷結果:"+s); 73 this.setBufferNull(); 74 s = last(o,1); 75 System.out.println("後序遍歷結果:"+s); 76 } 77 }
測試結果:
層次遍歷
層次遍歷比較簡單,即按照從上往下、從左往右一層一層遍歷便可。層次遍歷是現實,若是遍歷的是順序存儲(數組存儲)的二叉樹,因爲存儲的時候就是按照從上往下從左往右的順序存儲的,直接按順序取出便可;若是是鏈式存儲的二叉樹,須要使用一個循環隊列進行操做:先將根結點入隊,當前結點是隊頭結點,將其出隊並訪問,若是當前結點的左結點不爲空將左結點入隊,若是當前結點的右結點不爲空將其入隊。因此出隊順序也是從左到右依次出隊。
二叉排序樹,又稱二叉查找樹(Binary Search Tree),亦稱二叉搜索樹。
一、定義
二叉排序樹或者是一棵空樹,或者是具備下列性質的二叉樹:
1 BiTree* InsertBST(BiTree *t,int key) 2 { 3 if (t == NULL) 4 { 5 t = new BiTree(); 6 t->lchild = t->rchild = NULL; 7 t->data = key; 8 return t; 9 } 10 11 if (key < t->data) 12 t->lchild = InsertBST(t->lchild, key); 13 else 14 t->rchild = InsertBST(t->rchild, key); 15 16 return t; 17 }
private void deleteNode(BinarySortTree p) { //TODOAuto-generatedmethodstub if(p!=null) { //若是結點有左子樹 /*1。若p有左子樹,找到其左子樹的最右邊的葉子結點r,用該葉子結點r來替代p,把r的左孩子 做爲r的父親的右孩子。 2。若p沒有左子樹,直接用p的右孩子取代它。 */ if(p.lChild!=null) { BinarySortTree r=p.lChild; BinarySortTree prev=p.lChild; while(r.rChild!=null) { prev=r; r=r.rChild; } p.data=r.data; //若r不是p的左子樹,p的左子樹不變,r的左子樹做爲r的父結點的右孩子結點 if(prev!=r) { prev.rChild=r.lChild; } else { //若r是p的左子樹,則p的左子樹指向r的左子樹 p.lChild=r.lChild; } } else { p=p.rChild; } } }
平衡二叉樹又稱爲AVL樹,是一種特殊的二叉排序樹,即左右兩個子樹高度之差不超過1,而且左右兩個子樹都是平衡二叉樹的二叉排序樹稱爲平衡二叉樹。
爲何要構造平衡二叉樹呢?對於通常的二叉排序樹,其指望高度(即爲一棵平衡樹時)爲log2n,其各操做的時間複雜度(O(log2n))同時也由此而決定。因爲AVL樹的左右子樹高度之差不超過1,其高度通常都良好地維持在O(log(n)),大大下降了操做的時間複雜度。
平衡二叉樹的實現算法:關鍵在於左右子樹的平衡。具體的算法有:紅黑樹、AVL算法、Treap、伸展樹、SBT來實現。有興趣的能夠自行搜索,這裏就再也不描述。
赫夫曼樹又叫作最優二叉樹,特色是帶權路徑長度最短。
一、相關術語