王道408数据结构——第五章 树与二叉树
文章目錄
- 一、樹的基本概念
- 樹的性質
- 二、二叉樹
- 滿二叉樹
- 完全二叉樹
- 二叉排序樹
- 平衡二叉樹
- 二叉樹的性質
- 完全二叉樹的性質
- 三、二叉樹的儲存結構
- 順序儲存
- 鏈式存儲
- 四、樹的儲存方式
- 雙親表示法
- 孩子表示法
- 孩子兄弟表示法(二叉樹表示法)
- 五、二叉樹的遍歷
- 先序遍歷(preOrder、NLR)
- 中序遍歷(inOrder、LNR)
- 后序遍歷(postOrder、LRN)
- 中序遍歷的非遞歸算法
- 先序遍歷的非遞歸算法
- 后序遍歷的非遞歸算法
- 層次遍歷
- 由遍歷序列構造二叉樹
- 六、線索二叉樹
- 二叉線索化
- 遍歷線索二叉樹
- 七、森林
- 樹轉換為二叉樹
- 森林轉換為二叉樹
- 二叉樹轉換為森林
- 樹的遍歷
- 森林的遍歷
- 八、二叉排序樹(BST)
- BST的插入
- BST的刪除
- BST的查找
- BST與二分查找
- 九、平衡二叉樹
- 平衡二叉樹的插入
- 十、哈夫曼樹
- 構造哈夫曼樹
- 哈夫曼編碼
一、樹的基本概念
樹的定義是遞歸的,樹本身也是一種遞歸的數據結構。其作為一種邏輯結構,同時也是一種分層結構。樹適合表示具有層次結構的數據。
度:一個結點的的孩子個數
樹的度:樹中結點的最大度數
數中的分支是有向的,即從雙親指向孩子,所以數中的路徑只能是從上往下的。同一個雙親的孩子間不存在路徑。
樹的性質
- 樹中結點等于所有結點的度數之和加1,即 總邊數+1=度數之和
- 度為 m 的樹中,第 i 層上至多有mi?1m^{i-1}mi?1個結點
- 高度為 h 的 m 叉樹至多有mh?1m?1\frac{m^h-1}{m-1}m?1mh?1?個結點
- 具有 n 個結點的 m 叉樹最小高度為?log?m(n(m?1)+1)?\lceil \log_m(n(m-1)+1)\rceil?logm?(n(m?1)+1)?
二、二叉樹
二叉樹是一種特殊的樹形結構,特點是每個結點至多只有兩棵子樹,但其度可以小于2;并且二叉樹的子樹有左右之分,即使樹中結點只有一棵子樹,也要區分其是左子樹還是右子樹。
滿二叉樹
高度為h,且含有2h?12^{h-1}2h?1個結點的二叉樹稱為滿二叉樹,即樹中每層都有最多的結點。
根結點從1開始編號,若結點編號為 i,其雙親為?i/2?\lfloor i/2\rfloor?i/2? 其左孩子為2i,右孩子為2i+1。
完全二叉樹
性質:
- 若結點編號 i≤?n/2?i \leq \lfloor n/2 \rfloori≤?n/2?,則結點為分支結點,否則為葉子系結點。
- 葉子結點只可能在層次最大的兩層上出現。最有有一個度為1的結點,且該結點只有左孩子。
- 若n為奇數,則每個分支結點都有左右孩子
二叉排序樹
左子樹上所有結點的關鍵字都小于根結點,右子樹上的所有結點關鍵字都大于根結點。
平衡二叉樹
樹上任一結點的左右子樹深度之差不超過1
二叉樹的性質
- 非空二叉樹的葉子結點數等于度為2的結點數+1,即 n0=n2+1n_0=n_2+1n0?=n2?+1
- 非空二叉樹上第 k 層至多有2k?12^{k-1}2k?1個結點
- 高度為 h 的二叉樹至多有 2h?12^h-12h?1個結點
- 結點數為n的二叉樹有(2n)!n!(n+1)!\frac{(2n)!}{n!(n+1)!}n!(n+1)!(2n)!?種形態(卡特蘭數)
完全二叉樹的性質
- 結點 i 的雙親編號為?i2?\lfloor \frac{i}{2} \rfloor?2i??,即當 i 為偶數時(左孩子),其雙親的編號為 i/2,當i為奇數時,其雙親編號為(i-1)/2。
- 推論:具有n個結點的完全二叉樹,編號最大的分支節點為?n2?\lfloor \frac{n}{2}\rfloor?2n??
- 結點 i 所在層次為?log?2[i(2?1)+1]?=?log?2i?+1\lceil \log_2[i(2-1)+1]\rceil=\lfloor \log_2i\rfloor+1?log2?[i(2?1)+1]?=?log2?i?+1
三、二叉樹的儲存結構
順序儲存
用一組地址連續的存儲單元依次自上而下、自左至右儲存完全二叉樹的結點元素。
對于一般的二叉樹,必須添加一些空結點。
鏈式存儲
在含有n個結點的二叉鏈表中,含有n+1個空鏈域
四、樹的儲存方式
雙親表示法
采用一組連續空間來儲存每個結點,同時在每個結點中增設一個偽指針,指示其雙親結點在數組中的位置。根結點的下標為0,其偽指針域為-1。
該存儲結構可以很快得到每個結點的雙親位置,但求結點孩子時需要遍歷整個結構。
孩子表示法
為每個結點創建一個鏈表,將該結點的孩子都用單鏈表接起來。再將所有結點順序存儲在一個數組中,數組中每個元素不但儲存結點,還設置一個指針域,指向該結點的孩子鏈表。n個結點就有n個孩子鏈表(葉子結點的孩子鏈表為空表)。
這種方式尋找子女的操作非常直接,而尋找雙親的操作需要遍歷所有孩子鏈表。
孩子兄弟表示法(二叉樹表示法)
以二叉鏈表作為樹的存儲結構。
二叉樹的左指針指向其第一個孩子,右指針指向其下一個兄弟。沿著右指針可以找到所有兄弟結點。
最大優點是可以方便實現樹到二叉樹的轉換,易于找到結點的孩子。缺點是查找雙親結點比較麻煩,可以添加一個parent域指向父結點來解決。
五、二叉樹的遍歷
先序遍歷(preOrder、NLR)
void preOrder(BiTree T){if(T != NULL){visit(T);preOrder(T->lchild);preOrder(T->rchild);} }中序遍歷(inOrder、LNR)
void inOrder(BiTree T){if(T != NULL){inOrder(T->lchild);visit(T);inOrder(T->rchild);} }后序遍歷(postOrder、LRN)
無論哪種遍歷,訪問左右子樹的順序都是固定的,只是訪問根結點的順序不同。
每個結點都只訪問一次,時間復雜度均為O(n)。
遞歸工作棧的棧深恰為樹的高度。在最壞情況下,n個結點的樹高為n,空間復雜度為O(n)。
中序遍歷的非遞歸算法
關鍵是用棧記錄當前結點的祖先
void inOrder(BiTree T){initStack(S);BiTree p = T; // 遍歷指針while( !isEmpty(S) || p ){if(p){push(S, p);p = p-> lchild; // 一路向左}else{ // 無法向左下繼續前進,訪問子樹根結點,進入根結點右子樹pop(S, p);visit(p);p = p->rchild;}} }先序遍歷的非遞歸算法
先序遍歷和中序遍歷的基本思想類似,只需把訪問結點操作放在入棧操作前
void inOrder(BiTree T){initStack(S);BiTree p = T;while( !isEmpty(S) || p ){if(p){visit(p);push(S, p);p = p-> lchild;}else{pop(S, p);p = p->rchild;}} }后序遍歷的非遞歸算法
從棧底結點再加上p結點,剛好構成從根結點到p結點的一條路徑。
層次遍歷
void leverOrder(BiTree T){initQueue(Q);BiTree p;enQueue(Q, T);while( !isEmpty(Q) ){deQueue(Q, p);visit(p);if(p->lchild != NULL)enQueue(Q, p->lchild);if(p->rchild != NULL)enQueue(Q, p->rchild);} }由遍歷序列構造二叉樹
由二叉樹的先序序列和中序序列可以唯一確定一個二叉樹
在先序遍歷序列中,第一個結點一定是二叉樹的根結點;而在中序遍歷中,根結點必然將中序序列分割成兩個子序列。
由二叉樹的后序序列和中序序列可以唯一確定一個二叉樹
后序序列的最后一個結點一定是二叉樹的根結點。
由二叉樹的層次遍歷和中序遍歷可以唯一確定一個二叉樹
六、線索二叉樹
增加兩個標志域表示指針域是指向左(右)孩子還是指向前驅(后繼)。
以這種結點結構構成的二叉鏈表作為二叉樹的存儲結構,其中指示結點前驅及后繼信息的指針稱作線索。加上線索的二叉樹稱為線索二叉樹。
引入線索二叉樹能夠加快查找結點前驅和后繼的速度,像遍歷單鏈表那樣方便地遍歷二叉樹。
線索化的實質就是遍歷一次二叉樹。
二叉線索化
使用指針pre指向剛剛訪問過的結點,p指向正在訪問的結點,即pre指向p的前驅。
在遍歷的過程中,檢查p的左指針是否為空,若為空就將其指向pre;同樣的檢查pre的右指針。
中序遍歷線索化代碼如下
為了方便,可以在二叉樹的線索鏈表上添加一個頭結點,令其lchild域指向二叉樹的根結點,其rchild域指向中序遍歷的最后一個結點,再把中序遍歷的第一個結點的lchild域指向頭結點。這樣就為二叉樹建立了一個雙向線索鏈表。
建立先序線索二叉樹和后序線索二叉樹的代碼類似,只需變動線索化改造的代碼段以及調用左右子樹遞歸函數的位置。
先序線索化與后序線索化最多有1個空指針域;中序線索化最多有2個空指針域。
遍歷線索二叉樹
中序線索二叉樹的結點隱含了線索二叉樹的前驅后繼信息,在對其進行遍歷時,只要先找到序列中的第一個結點,然后依次找結點的后繼即可。
不含頭結點的中序線索二叉樹遍歷算法如下
對于先序線索二叉樹,如果有左孩子,則左孩子就是其直接后繼;如果無左孩子但是有右孩子,則右孩子就是其直接后繼;如果是葉結點,其右鏈域指向了結點的后繼。
對于后繼線索二叉樹,其尋找后繼需要知道結點雙親,需采用帶標志域的三叉鏈表作為存儲結構。
七、森林
森林是m棵互不相交的樹的集合。只需把樹的根結點刪除就成了森林;反之,只要給m棵獨立的樹加上一個結點,并把這m棵樹作為該結點的子樹,則森林就成了樹。
樹轉換為二叉樹
二叉樹和樹都可以用二叉鏈表作為存儲結構,給定一棵樹,可以找到唯一一棵二叉樹與之對應。
對于一棵樹,每個結點左指針指向它的第一個孩子,右指針指向它在樹中的相鄰右兄弟。
這種規則下,根結點只有左孩子。
森林轉換為二叉樹
先將森林中的每一棵樹轉換為二叉樹,由于任何一棵樹對應的二叉樹右子樹必空,只需把所有二叉樹的根結點用其右指針連接起來即可,即將所有樹的根結點視為兄弟結點。
二叉樹轉換為森林
若二叉樹非空,則二叉樹根的右子樹棵視為其余樹形成的二叉樹,將其與根斷開,以此類推,把所有子樹釋放。再將每棵二叉樹依次轉換成樹,就得到了原森林。
二叉樹轉換成樹或森林也是唯一的。
樹的遍歷
- 先根遍歷
若樹非空,先訪問根結點,再依次遍歷根結點的每棵子樹。
先根遍歷的遍歷序列與對應二叉樹的先序序列相同 - 后根遍歷(中根遍歷)
若樹非空,先依次遍歷根結點的每棵子樹,再訪問根結點。
后根遍歷的遍歷序列與對應二叉樹的中序序列相同 - 層次遍歷
森林的遍歷
- 先序遍歷森林
若森林非空,按如下規則進行遍歷:- 訪問森林中第一棵樹的根結點
- 先序遍歷第一棵樹中根結點的子樹森林
- 先序遍歷其余樹的森林
- 中序遍歷森林
若森林非空,按如下規則進行遍歷 (實際上就是依次后根遍歷森林中的每一棵樹) :- 中序遍歷森林中第一棵樹的根結點的子樹森林
- 訪問第一棵樹的根結點
- 中序遍歷其余樹的森林
森林的先序遍歷和中序遍歷即為對應二叉樹的先序和中序遍歷。
八、二叉排序樹(BST)
對于二叉排序樹(二叉查找樹),若左子樹非空,則左子樹的所有結點值均小于根結點的值,且也為一棵二叉排序樹;若右子樹非空,則右子樹的所有結點值均大于根結點的值,且也為一棵二叉排序樹。
二叉排序樹可以是空樹。
對二叉排序樹進行中序遍歷,可以得到一個有序序列。
BST的插入
按照如下規則遞歸進行:
插入的結點一定是一個新添加的葉結點,且是查找失敗時路徑上訪問的最后一個結點的孩子。
若插入序列是有序的,則會形成一個傾斜的單支樹,導致二叉樹的性能顯著變壞。
BST的刪除
分為三種情況進行:
BST的查找
從根結點開始,將給定值與根結點關鍵字比較:
二叉排序樹的查找效率,主要取決于樹的高度。若二叉樹左右子樹高度之差不超過1(平衡二叉樹),則平均查找長度為O(log?2n)O(\log_2n)O(log2?n),若二叉排序樹每個結點都只有一個結點,平均查找長度為O(n)O(n)O(n)。
BST與二分查找
從查找過程看,二叉排序樹與二分查找十分相似,其平均時間性能差不多;但二分查找的判定樹唯一,二叉排序樹則不唯一。
從結構的維護角度看,二叉排序樹無序移動結點,只需修改指針即可完成插入刪除操作,平均執行時間是O(log?2n)O(\log_2n)O(log2?n);二分查找的對象是有序順序表,若插入刪除結點,所花時間是O(n)O(n)O(n)。
若有序表是靜態查找表,宜采用順序表作為存儲結構,采用二分查找進行查找操作。
若有序表是動態查找表,宜采用二叉排序樹作為其邏輯結構。
九、平衡二叉樹
為避免樹的高度增長過快,降低二叉排序樹的性能,規定插入和刪除二叉樹的結點時,保證任意結點的左右子樹高度差不超過1。
以nhn_hnh?表示深度為h的平衡樹中含有的最少結點數,有遞推公式nh=nh?1+nh?2+1n_h=n_{h-1}+n_{h-2}+1nh?=nh?1?+nh?2?+1,且n0=0n_0=0n0?=0,n1=1n_1=1n1?=1。
含有n個結點的平衡二叉樹最大深度為O(log?2n)O(\log_2n)O(log2?n),平均查找長度也為O(log?2n)O(\log_2n)O(log2?n)。
平衡因子:結點左右子樹的高度差,取值范圍為-1、0、1。
平衡二叉樹的插入
保持二叉樹平衡的基本思路:每當插入或刪除一個結點,檢查該結點到根結點路徑上的每個結點的平衡因子,調整不平衡的最小子樹的結構,在保持二叉排序樹特性的前提下,使之重新平衡。
對于一個新結點,先按照普通二叉排序樹的規則進行插入操作,再找到其最小不平衡樹,分情況進行調整:
進行一次向右的旋轉操作:將A的左孩子B向右上旋轉,代替A成為根結點;將A結點向右下旋轉,成為B的右子樹的根結點;而B的原右子樹則作為A的左子樹。
進行一次向左的旋轉操作:將A 的右孩子B向左上旋轉,代替A成為根結點;將A結點向左下旋轉成為B的左子樹的根結點;而B的原左子樹則成為A的右子樹。
進行兩次旋轉操作,先左旋轉再右旋轉:先將A結點的左孩子B的右子樹根結點C向左上旋轉提升到B結點的位置,此時問題轉化為情形1,只需將C結點再向右上旋轉提升到A結點的位置。
進行兩次旋轉操作,先右旋轉再左旋轉:先將A結點的右孩子B的左子樹根節點C向右上旋轉提升到B結點的位置,此時問題轉化為情景2,只需將C結點再向左上旋轉提升到A結點的位置。
十、哈夫曼樹
為樹中結點賦予一個數值,成為該結點的權。從根結點到任意結點的路徑長度lll(經過的邊數)與該節點上權值www的乘積稱為該結點的帶權路徑長度。樹中所有葉結點的帶權路徑長度之和稱為樹的帶權路徑長度。即WPL=∑i=1nwiliWPL=\sum_{i=1}^nw_il_iWPL=∑i=1n?wi?li?。
在含有n個帶權葉結點的二叉樹中,WPL最小的二叉樹稱為哈夫曼樹,也稱最優二叉樹。
構造哈夫曼樹
給定n個權值分別為w1,w2...wnw_1,w_2...w_nw1?,w2?...wn?的結點,構造算法如下:
從構造過程可以看出哈夫曼樹具有如下特點:
- 每個初始結點都稱為葉結點,且權值越小的結點到根節點的路徑越長。
- 構造過程新建了n-1個結點,因此哈夫曼樹的總結點樹為2n-1。
- 哈夫曼樹中不存在度為1的結點。
哈夫曼編碼
若允許不同字符用不等長的二進制位表示,稱這種編碼為可變長度編碼。
若任何一個編碼都不是其余編碼的前綴,則稱這種編碼為前綴編碼。
利用哈夫曼樹可以設計出總長度最短的二進制前綴編碼。
總結
以上是生活随笔為你收集整理的王道408数据结构——第五章 树与二叉树的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 王道408数据结构——第四章 串(KMP
- 下一篇: 王道408数据结构——第六章 图