日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

王道408数据结构——第五章 树与二叉树

發布時間:2023/12/4 编程问答 25 豆豆
生活随笔 收集整理的這篇文章主要介紹了 王道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;}} }

后序遍歷的非遞歸算法

  • 沿著根的左孩子,依次入棧,直到左孩子為空。
  • 讀棧頂元素:若其右孩子不空且未被訪問過,進入右孩子并執行1??;否則元素出棧并訪問。需要設定一個輔助指針指向最近訪問過的結點,用于區分訪問該結點時,其上一個結點是它的左子樹還是右子樹。
  • void postOrder(BTree T){initStack(S);BTree p = T;BTree r = NULL; // 記錄訪問的上一個結點while(p || isEmpty(S)){if(p){ //一直走到樹的最左邊push(S, p);p = p->lchild;}else{getTop(S, p);if(p->rchild && p->rchild != r){ // 若未訪問過右子樹,進入p = p-> rchild;}else{ // 右子樹已訪問過,訪問根結點pop(S, p);visit(p);r = p; // 記錄最近訪問的結點p = NULL; // 遍歷完該子樹,置空}} } }

    從棧底結點再加上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的右指針。
    中序遍歷線索化代碼如下

    void creatInThread(ThreadTree T){ThreadTree pre = NULL;if(T != NULL){inThread(T, pre);pre->rchild = NULL; // 處理遍歷的最后一個結點pre->rtag = 1;} }void inThread(ThreadTree &p, ThreadTree &pre){if( p!= NULL ){ inThread(p->lchild, pre);// visitif( p->lhild == NULL ){p->lhild = pre;p->ltag = 1;}if( pre != NULL && pre->rchild == NULL ){pre->rchild = p;pre->rtag = 1;}pre = p;inThread(p->rchild, pre);} }

    為了方便,可以在二叉樹的線索鏈表上添加一個頭結點,令其lchild域指向二叉樹的根結點,其rchild域指向中序遍歷的最后一個結點,再把中序遍歷的第一個結點的lchild域指向頭結點。這樣就為二叉樹建立了一個雙向線索鏈表。

    建立先序線索二叉樹和后序線索二叉樹的代碼類似,只需變動線索化改造的代碼段以及調用左右子樹遞歸函數的位置。

    先序線索化與后序線索化最多有1個空指針域;中序線索化最多有2個空指針域。

    遍歷線索二叉樹

    中序線索二叉樹的結點隱含了線索二叉樹的前驅后繼信息,在對其進行遍歷時,只要先找到序列中的第一個結點,然后依次找結點的后繼即可。
    不含頭結點的中序線索二叉樹遍歷算法如下

    void inOrder(ThreadTree T){ThreadTree p = firstNode(T); // 獲取遍歷的起始結點while( p != NULL ){visit(p);p = nextNode(p); // 獲得下一個遍歷結點} }ThreadTree firstNode(ThreadTree p){while( p->ltag == 0 ){p = p->lchild;}return p; }ThreadTree nextNode(ThreadTree p){if( p->rtag == 0)return firstNode(p->rchild); // 返回右子樹的最左結點,即下一個要遍歷的結點elsereturn p->rchild; }

    對于先序線索二叉樹,如果有左孩子,則左孩子就是其直接后繼;如果無左孩子但是有右孩子,則右孩子就是其直接后繼;如果是葉結點,其右鏈域指向了結點的后繼。

    對于后繼線索二叉樹,其尋找后繼需要知道結點雙親,需采用帶標志域的三叉鏈表作為存儲結構。

    七、森林

    森林是m棵互不相交的樹的集合。只需把樹的根結點刪除就成了森林;反之,只要給m棵獨立的樹加上一個結點,并把這m棵樹作為該結點的子樹,則森林就成了樹。

    樹轉換為二叉樹

    二叉樹和樹都可以用二叉鏈表作為存儲結構,給定一棵樹,可以找到唯一一棵二叉樹與之對應。

    對于一棵樹,每個結點左指針指向它的第一個孩子,右指針指向它在樹中的相鄰右兄弟。
    這種規則下,根結點只有左孩子。

    森林轉換為二叉樹

    先將森林中的每一棵樹轉換為二叉樹,由于任何一棵樹對應的二叉樹右子樹必空,只需把所有二叉樹的根結點用其右指針連接起來即可,即將所有樹的根結點視為兄弟結點。

    二叉樹轉換為森林

    若二叉樹非空,則二叉樹根的右子樹棵視為其余樹形成的二叉樹,將其與根斷開,以此類推,把所有子樹釋放。再將每棵二叉樹依次轉換成樹,就得到了原森林。
    二叉樹轉換成樹或森林也是唯一的。

    樹的遍歷

    • 先根遍歷
      若樹非空,先訪問根結點,再依次遍歷根結點的每棵子樹。
      先根遍歷的遍歷序列與對應二叉樹的先序序列相同
    • 后根遍歷(中根遍歷)
      若樹非空,先依次遍歷根結點的每棵子樹,再訪問根結點。
      后根遍歷的遍歷序列與對應二叉樹的中序序列相同
    • 層次遍歷

    森林的遍歷

    • 先序遍歷森林
      若森林非空,按如下規則進行遍歷:
      • 訪問森林中第一棵樹的根結點
      • 先序遍歷第一棵樹中根結點的子樹森林
      • 先序遍歷其余樹的森林
    • 中序遍歷森林
      若森林非空,按如下規則進行遍歷 (實際上就是依次后根遍歷森林中的每一棵樹) :
      • 中序遍歷森林中第一棵樹的根結點的子樹森林
      • 訪問第一棵樹的根結點
      • 中序遍歷其余樹的森林

    森林的先序遍歷和中序遍歷即為對應二叉樹的先序和中序遍歷。

    八、二叉排序樹(BST)

    對于二叉排序樹(二叉查找樹),若左子樹非空,則左子樹的所有結點值均小于根結點的值,且也為一棵二叉排序樹;若右子樹非空,則右子樹的所有結點值均大于根結點的值,且也為一棵二叉排序樹。
    二叉排序樹可以是空樹。

    對二叉排序樹進行中序遍歷,可以得到一個有序序列。

    BST的插入

    按照如下規則遞歸進行:

  • 若樹空,則直接插入結點
  • 若關鍵字k小于根結點,則插入到左子樹
  • 若關鍵字k大于根結點,則插入到右子樹
  • 插入的結點一定是一個新添加的葉結點,且是查找失敗時路徑上訪問的最后一個結點的孩子。
    若插入序列是有序的,則會形成一個傾斜的單支樹,導致二叉樹的性能顯著變壞。

    BST的刪除

    分為三種情況進行:

  • 若被刪除結點z是葉結點,直接刪除
  • 若結點z只有左子樹或只有右子樹,讓z的子樹成為z父結點的子樹替代z的位置
  • 若結點z有左、右兩棵子樹,則令z的直接后繼(或直接前驅)代替z,再按第一或第二種情況考慮。
  • 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?=0n1=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。

    平衡二叉樹的插入

    保持二叉樹平衡的基本思路:每當插入或刪除一個結點,檢查該結點到根結點路徑上的每個結點的平衡因子,調整不平衡的最小子樹的結構,在保持二叉排序樹特性的前提下,使之重新平衡。

    對于一個新結點,先按照普通二叉排序樹的規則進行插入操作,再找到其最小不平衡樹,分情況進行調整:

  • LL平衡旋轉(右單旋轉):在結點A的左孩子(L)的左子樹(L)上插入了新結點,使A的平衡因子增加為2,導致A為根的子樹失去平衡。
    進行一次向右的旋轉操作:將A的左孩子B向右上旋轉,代替A成為根結點;將A結點向右下旋轉,成為B的右子樹的根結點;而B的原右子樹則作為A的左子樹。
  • RR平衡旋轉(左單旋轉):在結點A的右孩子(R)的右子樹(R)上插入了新結點,使A的平衡因子減少為-2,導致A為根的子樹失去平衡。
    進行一次向左的旋轉操作:將A 的右孩子B向左上旋轉,代替A成為根結點;將A結點向左下旋轉成為B的左子樹的根結點;而B的原左子樹則成為A的右子樹。
  • LR平衡旋轉(向左后右雙旋轉):在A的左孩子(L)的右子樹(R)上插入了新結點。
    進行兩次旋轉操作,先左旋轉再右旋轉:先將A結點的左孩子B的右子樹根結點C向左上旋轉提升到B結點的位置,此時問題轉化為情形1,只需將C結點再向右上旋轉提升到A結點的位置。
  • RL平衡旋轉(向右后左雙旋轉):在A的右孩子(R)的左子樹(L)上插入了新結點。
    進行兩次旋轉操作,先右旋轉再左旋轉:先將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個結點分別作為n棵僅含一個結點的二叉樹,構成森林F;
  • 構造一個新結點,從F中選取兩棵根節點權值最小的樹作為新節點的左右子樹,新結點的權值置為左右子樹根節點權值之和;
  • 從F中刪除剛才選出的兩棵樹,同時將新得到的樹加入F中;
  • 重復步驟2、3,直到F僅剩一棵樹。
  • 從構造過程可以看出哈夫曼樹具有如下特點:

    • 每個初始結點都稱為葉結點,且權值越小的結點到根節點的路徑越長。
    • 構造過程新建了n-1個結點,因此哈夫曼樹的總結點樹為2n-1。
    • 哈夫曼樹中不存在度為1的結點。

    哈夫曼編碼

    若允許不同字符用不等長的二進制位表示,稱這種編碼為可變長度編碼
    若任何一個編碼都不是其余編碼的前綴,則稱這種編碼為前綴編碼
    利用哈夫曼樹可以設計出總長度最短的二進制前綴編碼。

    總結

    以上是生活随笔為你收集整理的王道408数据结构——第五章 树与二叉树的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。