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

歡迎訪問 生活随笔!

生活随笔

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

编程问答

数据结构:树(Tree)【详解】

發布時間:2024/8/1 编程问答 23 豆豆
生活随笔 收集整理的這篇文章主要介紹了 数据结构:树(Tree)【详解】 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

友情鏈接:數據結構專欄

目錄

    • 【知識框架】
    • 一、樹的基本概念
      • 1、樹的定義
      • 2、基本術語
      • 3、樹的性質
    • 二、樹的存儲結構
      • 1、雙親表示法
      • 2、孩子表示法
      • 3、孩子兄弟表示法
  • 二叉樹
    • 一、二叉樹的概念
      • 1、二叉樹的定義
      • 2、幾個特殊的二叉樹
        • (1)斜樹
        • (2)滿二叉樹
        • (3)完全二叉樹
        • (4)二叉排序樹
        • (5)平衡二叉樹
      • 3、二叉樹的性質
      • 4、二叉樹的存儲結構
        • (1)順序存儲結構
        • (2)鏈式存儲結構
    • 二、遍歷二叉樹
      • 1、先序遍歷
      • 2、中序遍歷
      • 3、后序遍歷
      • 4、遞歸算法和非遞歸算法的轉換
        • (1)中序遍歷的非遞歸算法
        • (2)先序遍歷的非遞歸算法
        • (3)后序遍歷的非遞歸算法
      • 5、層次遍歷
      • 6、由遍歷序列構造二叉樹
    • 三、線索二叉樹
      • 1、線索二叉樹原理
      • 2、線索二叉樹的結構實現
      • 3、二叉樹的線索化
        • (1)中序線索二叉樹
        • (2)先序和后序線索二叉樹
    • 四、樹、森林與二叉樹的轉化
      • 1、樹轉換為二叉樹
      • 2、森林轉化為二叉樹
    • 五、樹和森林的遍歷
      • 1、樹的遍歷
      • 2、森林的遍歷
  • 樹與二叉樹的應用
    • 一、二叉排序樹
      • 1、定義
      • 2、二叉排序樹的常見操作
        • (1)查找操作
        • (2)插入操作
        • (3)刪除操作
      • 3、小結(引申出平衡二叉樹)
    • 二、平衡二叉樹
      • 1、定義
      • 2、平衡二叉樹的查找
      • 3、平衡二叉樹的插入
    • 三、哈夫曼樹和哈夫曼編碼
      • 1、哈夫曼樹的定義和原理
      • 2、哈夫曼樹的構造
      • 3、哈夫曼編碼
  • 附錄
    • 上文鏈接
    • 下文鏈接
    • 專欄
    • 參考資料

【知識框架】

一、樹的基本概念

1、樹的定義

樹是n(n>=0)個結點的有限集。當n = 0時,稱為空樹。在任意一棵非空樹中應滿足:

  • 有且僅有一個特定的稱為根的結點。
  • 當n>1時,其余節點可分為m(m>0)個互不相交的有限集T1,T2,…,Tm,其中每個集合本身又是一棵樹,并且稱為根的子樹。
  • 顯然,樹的定義是遞歸的,即在樹的定義中又用到了自身,樹是一種遞歸的數據結構。樹作為一種邏輯結構,同時也是一種分層結構,具有以下兩個特點:

  • 樹的根結點沒有前驅,除根結點外的所有結點有且只有一個前驅。
  • 樹中所有結點可以有零個或多個后繼。
  • 因此n個結點的樹中有n-1條邊。

    2、基本術語

    下面結合圖示來說明一下樹的一些基本術語和概念。

  • 考慮結點K。根A到結點K的唯一路徑上的任意結點,稱為結點K的祖先。如結點B是結點K的祖先,而結點K是結點B的子孫。路徑上最接近結點K的結點E稱為K的雙親,而K為結點E的孩子。根A是樹中唯一沒有雙親的結點。有相同雙親的結點稱為兄弟,如結點K和結點L有相同的雙親E,即K和L為兄弟。
  • 樹中一個結點的孩子個數稱為該結點的度,樹中結點的最大度數稱為樹的度。如結點B的度為2,結點D的度為3,樹的度為3。
  • 度大于0的結點稱為分支結點(又稱非終端結點);度為0(沒有子女結點)的結點稱為葉子結點(又稱終端結點)。在分支結點中,每個結點的分支數就是該結點的度。
  • 結點的深度、高度和層次。
    結點的層次從樹根開始定義,根結點為第1層,它的子結點為第2層,以此類推。雙親在同一層的結點互為堂兄弟,圖中結點G與E,F,H,I,J互為堂兄弟。
    結點的深度是從根結點開始自頂向下逐層累加的。
    結點的高度是從葉結點開始自底向上逐層累加的。
    樹的高度(或深度)是樹中結點的最大層數。圖中樹的高度為4。
  • 有序樹和無序樹。樹中結點的各子樹從左到右是有次序的,不能互換,稱該樹為有序樹,否則稱為無序樹。假設圖為有序樹,若將子結點位置互換,則變成一棵不同的樹。
  • 路徑和路徑長度。樹中兩個結點之間的路徑是由這兩個結點之間所經過的結點序列構成的,而路徑長度是路徑上所經過的邊的個數。
    注意:由于樹中的分支是有向的,即從雙親指向孩子,所以樹中的路徑是從上向下的,同一雙親的兩個孩子之間不存在路徑。
  • 森林。森林是m (m≥0)棵互不相交的樹的集合。森林的概念與樹的概念十分相近,因為只要把樹的根結點刪去就成了森林。反之,只要給m棵獨立的樹加上一個結點,并把這m棵樹作為該結點的子樹,則森林就變成了樹。
  • 注意:上述概念無須刻意記憶, 根據實例理解即可。

    3、樹的性質

    樹具有如下最基本的性質:

  • 樹中的結點數等于所有結點的度數加1.
  • 度為mmm的樹中第iii層上至多有mi?1m^{i-1}mi?1個結點(i>=1i>=1i>=1
  • 高度為hhhmmm叉樹至多有(mh?1)/(m?1)(m^h-1)/(m-1)(mh?1)/(m?1)個結點。
  • 具有nnn個結點的mmm叉樹的最小高度為[logm(n(m?1)+1)][log_m(n(m-1)+1)][logm?(n(m?1)+1)]
  • 二、樹的存儲結構

    在介紹以下三種存儲結構的過程中,我們都以下面這個樹為例子。

    1、雙親表示法

    我們假設以一組連續空間存儲樹的結點,同時在每個結點中,附設一個指示器指示其雙親結點到鏈表中的位置。也就是說,每個結點除了知道自已是誰以外,還知道它的雙親在哪里。

    其中data是數據域,存儲結點的數據信息。而parent是指針域,存儲該結點的雙親在數組中的下標。
    以下是我們的雙親表示法的結點結構定義代碼。

    /*樹的雙親表示法結點結構定義*/ #define MAX_TREE_SIZE 100 typedef int TElemType; //樹結點的數據類型,目前暫定為整型 /*結點結構*/ typedef struct PTNode{TElemType data; //結點數據int parent; //雙親位置 }PTNode; /*樹結構*/ typedef struct{PTNode nodes[MAX_TREE_SIZE]; //結點數組int r, n; //根的位置和結點數 }PTree;

    這樣的存儲結構,我們可以根據結點的parent 指針很容易找到它的雙親結點,所用的時間復雜度為0(1),直到parent為-1時,表示找到了樹結點的根。可如果我們要知道結點的孩子是什么,對不起,請遍歷整個結構才行。

    2、孩子表示法

    具體辦法是,把每個結點的孩子結點排列起來,以單鏈表作存儲結構,則n個結點有n個孩子鏈表,如果是葉子結點則此單鏈表為空。然后n個頭指針又組成-一個線性表,采用順序存儲結構,存放進一個一維數組中,如圖所示。

    為此,設計兩種結點結構,一個是孩子鏈表的孩子結點。

    其中child是數據域,用來存儲某個結點在表頭數組中的下標。next 是指針域,用來存儲指向某結點的下一個孩子結點的指針。

    另一個是表頭數組的表頭結點。

    其中data是數據域,存儲某結點的數據信息。firstchild 是頭指針域,存儲該結點的孩子鏈表的頭指針。

    以下是我們的孩子表示法的結構定義代碼。

    /*樹的孩子表示法結構定義*/ #define MAX_TREE_SIZE 100 /*孩子結點*/ typedef struct CTNode{int child;struct CTNode *next; }*ChildPtr; /*表頭結點*/ typedef struct{TElemType data;ChildPtr firstchild; }CTBox; /*樹結構*/ typedef struct{CTBox nodes[MAX_TREE_SIZE]; //結點數組int r, n; //根的位置和結點數 }

    這樣的結構對于我們要查找某個結點的某個孩子,或者找某個結點的兄弟,只需要查找這個結點的孩子單鏈表即可。對于遍歷整棵樹也是很方便的,對頭結點的數組循環即可。
    但是,這也存在著問題,我如何知道某個結點的雙親是誰呢?比較麻煩,需要整棵樹遍歷才行,難道就不可以把雙親表示法和孩子表示法綜合一下嗎? 當然是可以,這個讀者可自己嘗試結合一下,在次不做贅述。

    3、孩子兄弟表示法

    剛才我們分別從雙親的角度和從孩子的角度研究樹的存儲結構,如果我們從樹結點的兄弟的角度又會如何呢?當然,對于樹這樣的層級結構來說,只研究結點的兄弟是不行的,我們觀察后發現,任意一棵樹, 它的結點的第一個孩子如果存在就是唯一的,它的右兄弟如果存在也是唯一的。 因此,我們設置兩個指針,分別指向該結點的第一個孩子和此結點的右兄弟
    結點的結構如下:

    其中data是數據域,firstchild 為指針域,存儲該結點的第一個孩子結點的存儲地址,rightsib 是指針域,存儲該結點的右兄弟結點的存儲地址。
    這種表示法,給查找某個結點的某個孩子帶來了方便。
    結構定義代碼如下。

    /*樹的孩子兄弟表示法結構定義*/ typedef struct CSNode{TElemtype data;struct CSNode *firstchild, *rightsib; } CSNode, *CSTree;

    于是通過這種結構,我們就把原來的樹變成了這個樣子:

    這不就是個二叉樹么?
    沒錯,其實這個表示法的最大好處就是它把一棵復雜的樹變成了一棵二叉樹
    接下來,我們詳細介紹二叉樹。

    二叉樹

    一、二叉樹的概念

    1、二叉樹的定義

    二叉樹是另一種樹形結構,其特點是每個結點至多只有兩棵子樹( 即二叉樹中不存在度大于2的結點),并且二叉樹的子樹有左右之分,其次序不能任意顛倒。
    與樹相似,二叉樹也以遞歸的形式定義。二叉樹是n (n≥0) 個結點的有限集合:

  • 或者為空二叉樹,即n=0。
  • 或者由一個根結點和兩個互不相交的被稱為根的左子樹和右子樹組成。左子樹和右子樹又分別是一棵二叉樹。
  • 二叉樹是有序樹,若將其左、右子樹顛倒,則成為另一棵不同的二叉樹。即使樹中結點只有一棵子樹,也要區分它是左子樹還是右子樹。二叉樹的5種基本形態如圖所示。

    2、幾個特殊的二叉樹

    (1)斜樹

    所有的結點都只有左子樹的二叉樹叫左斜樹。所有結點都是只有右子樹的二叉樹叫右斜樹。這兩者統稱為斜樹。

    (2)滿二叉樹

    一棵高度為hhh,且含有2h?12^h-12h?1個結點的二叉樹稱為滿二叉樹,即樹中的每層都含有最多的結點。滿二叉樹的葉子結點都集中在二叉樹的最下一層,并且除葉子結點之外的每個結點度數均為222。可以對滿二叉樹按層序編號:約定編號從根結點(根結點編號為111)起,自上而下,自左向右。這樣,每個結點對應一個編號,對于編號為i的結點,若有雙親,則其雙親為i/2i/2i/2,若有左孩子,則左孩子為2i2i2i;若有右孩子,則右孩子為2i+12i+12i+1

    (3)完全二叉樹

    高度為hhh、有nnn個結點的二叉樹,當且僅當其每個結點都與高度為hhh的滿二叉樹中編號為1~n的結點一一對應時,稱為完全二叉樹,如圖所示。其特點如下:

  • i≤n/2i≤n/2in/2, 則結點iii為分支結點,否則為葉子結點。
  • 葉子結點只可能在層次最大的兩層上出現。對于最大層次中的葉子結點,都依次排列在該層最左邊的位置上。
  • 若有度為111的結點,則只可能有一個,且該結點只有左孩子而無右孩子(重要特征)。
  • 按層序編號后,一旦出現某結點(編號為iii)為葉子結點或只有左孩子,則編號大于iii的結點均為葉子結點。
  • nnn為奇數,則每個分支結點都有左孩子和右孩子;若nnn為偶數,則編號最大的分支結點(編號為n/2n/2n/2)只有左孩子,沒有右孩子,其余分支結點左、右孩子都有。
  • (4)二叉排序樹

    左子樹上所有結點的關鍵字均小于根結點的關鍵字;右子樹上的所有結點的關鍵字均大于根結點的關鍵字;左子樹和右子樹又各是一棵二叉排序樹。

    (5)平衡二叉樹

    樹上任一結點的左子樹和右子樹的深度之差不超過1。

    3、二叉樹的性質

  • 任意一棵樹,若結點數量為nnn,則邊的數量為n?1n-1n?1
  • 非空二叉樹上的葉子結點數等于度為222的結點數加111,即no=n2+1n_o=n_2+ 1no?=n2?+1
  • 非空二叉樹上第kkk層上至多有2k?12^{k-1}2k?1個結點(k≥1)(k≥1)(k1)
  • 高度為hhh的二叉樹至多有2h?12^h-12h?1個結點(h≥1)(h≥1)(h1)
  • 對完全二叉樹按從上到下、從左到右的順序依次編號1,2..?,n1,2..*,n1,2..?,n,則有以下關系:
    • i>1i>1i>1時,結點iii的雙親的編號為i/2i/2i/2,即當iii為偶數時, 它是雙親的左孩子;當i為奇數時,它是雙親的右孩子。
    • 2i≤n2i≤n2in時,結點iii的左孩子編號為2i2i2i, 否則無左孩子。
    • 2i+1≤n2i+1≤n2i+1n時,結點iii的右孩子編號為2i+12i+12i+1,否則無右孩子。
    • 結點iii所在層次(深度)為{log2i}+1\{log_2i\}+ 1{log2?i}+1
  • 具有nnn(n>0)(n>0)(n>0)結點的完全二叉樹的高度為{log2n}+1\{log_2n\}+1{log2?n}+1
  • 4、二叉樹的存儲結構

    (1)順序存儲結構

    二叉樹的順序存儲是指用一組地址連續的存儲單元依次自上而下、自左至右存儲完全二叉樹上的結點元素,即將完全二叉樹上編號為iii的結點元素存儲在一維數組下標為i?1i-1i?1的分量中。
    依據二叉樹的性質,完全二叉樹和滿二叉樹采用順序存儲比較合適,樹中結點的序號可以唯一地反映結點之間的邏輯關系,這樣既能最大可能地節省存儲空間,又能利用數組元素的下標值確定結點在二叉樹中的位置,以及結點之間的關系。
    但對于一般的二叉樹,為了讓數組下標能反映二叉樹中結點之間的邏輯關系,只能添加一些并不存在的空結點,讓其每個結點與完全二叉樹上的結點相對照,再存儲到一維數組的相應分量中。然而,在最壞情況下,一個高度為hhh且只有hhh個結點的單支樹卻需要占據近2h?12h-12h?1個存儲單元。二叉樹的順序存儲結構如圖所示,其中0表示并不存在的空結點。

    (2)鏈式存儲結構

    既然順序存儲適用性不強,我們就要考慮鏈式存儲結構。二叉樹每個結點最多有兩個孩子,所以為它設計一個數據域和兩個指針域是比較自然的想法,我們稱這樣的鏈表叫做二叉鏈表

    其中data是數據域,lchild 和rchild都是指針域,分別存放指向左孩子和右孩子的指針。
    以下是我們的二叉鏈表的結點結構定義代碼。

    /*二叉樹的二叉鏈表結點構造定義*/ /*結點結構*/ typedef struct BiTNode{TElemType data; //結點數據struct BiTNode *lchild, *rchild; //左右孩子指針 } BiTNode, *BiTree;


    容易驗證,在含有nnn個結點的二叉鏈表中,含有n+1n + 1n+1個空鏈域


    二、遍歷二叉樹

    二叉樹的遍歷( traversing binary tree )是指從根結點出發,按照某種次序依次訪問二叉樹中所有結點,使得每個結點被訪問一次且僅被訪問一次。

    1、先序遍歷

    先序遍歷(PreOrder) 的操作過程如下:
    若二叉樹為空,則什么也不做,否則,
    1)訪問根結點;
    2)先序遍歷左子樹;
    3)先序遍歷右子樹。


    對應的遞歸算法如下:

    void PreOrder(BiTree T){if(T != NULL){visit(T); //訪問根節點PreOrder(T->lchild); //遞歸遍歷左子樹PreOrder(T->rchild); //遞歸遍歷右子樹} }

    2、中序遍歷

    中序遍歷( InOrder)的操作過程如下:
    若二叉樹為空,則什么也不做,否則,
    1)中序遍歷左子樹;
    2)訪問根結點;
    3)中序遍歷右子樹。


    對應的遞歸算法如下:

    void InOrder(BiTree T){if(T != NULL){InOrder(T->lchild); //遞歸遍歷左子樹visit(T); //訪問根結點InOrder(T->rchild); //遞歸遍歷右子樹} }

    3、后序遍歷

    后序遍歷(PostOrder) 的操作過程如下:
    若二叉樹為空,則什么也不做,否則,
    1)后序遍歷左子樹;
    2)后序遍歷右子樹;
    3)訪問根結點。


    對應的遞歸算法如下:

    void PostOrder(BiTree T){if(T != NULL){PostOrder(T->lchild); //遞歸遍歷左子樹PostOrder(T->rchild); //遞歸遍歷右子樹visit(T); //訪問根結點} }

    三種遍歷算法中,遞歸遍歷左、右子樹的順序都是固定的,只是訪問根結點的順序不同。不管采用哪種遍歷算法,每個結點都訪問一次且僅訪問一次,故時間復雜度都是O(n)。在遞歸遍歷中,遞歸工作棧的棧深恰好為樹的深度,所以在最壞情況下,二叉樹是有n個結點且深度為n的單支樹,遍歷算法的空間復雜度為O(n)。

    4、遞歸算法和非遞歸算法的轉換

    我們以下圖的樹為例子。

    (1)中序遍歷的非遞歸算法

    借助棧,我們來分析中序遍歷的訪問過程:

  • 沿著根的左孩子,依次入棧,直到左孩子為空,說明已找到可以輸出的結點,此時棧內元素依次為ABD。
  • 棧頂元素出棧并訪問:若其右孩子為空,繼續執行步驟2;若其右孩子不空,將右子樹轉執行步驟1。
  • 棧頂D出棧并訪問,它是中序序列的第一個結點; D右孩子為空,棧頂B出棧并訪問; B右孩子不空,將其右孩子E入棧,E左孩子為空,棧頂E出棧并訪問; E右孩子為空,棧頂A出棧并訪問; A右孩子不空,將其右孩子C入棧,C左孩子為空,棧頂C出棧并訪問。由此得到中序序列DBEAC。
    根據分析可以寫出中序遍歷的非遞歸算法如下:

    void InOrder2(BiTree T){InitStack(S); //初始化棧SBiTree p = T; //p是遍歷指針while(p || !IsEmpty(S)){ //棧不空或p不空時循環if(p){Push(S, p); //當前節點入棧p = p->lchild; //左孩子不空,一直向左走}else{Pop(S, p); //棧頂元素出棧visit(p); //訪問出棧結點p = p->rchild; //向右子樹走,p賦值為當前結點的右孩子}} }

    (2)先序遍歷的非遞歸算法

    先序遍歷和中序遍歷的基本思想是類似的,只需把訪問結點操作放在入棧操作的前面。先序遍歷的非遞歸算法如下:

    void PreOrder2(BiTree T){InitStack(S); //初始化棧SBiTree p = T; //p是遍歷指針while(p || !IsEmpty(S)){ //棧不空或p不空時循環if(p){visit(p); //訪問出棧結點Push(S, p); //當前節點入棧p = p->lchild; //左孩子不空,一直向左走}else{Pop(S, p); //棧頂元素出棧p = p->rchild; //向右子樹走,p賦值為當前結點的右孩子}} }

    (3)后序遍歷的非遞歸算法

    后序遍歷的非遞歸實現是三種遍歷方法中最難的。因為在后序遍歷中,要保證左孩了和右孩子都已被訪問并且左孩子在右孩子前訪問才能訪問根結點,這就為流程的控制帶來了難題。

    算法思想:后序非遞歸遍歷二叉樹是先訪問左子樹,再訪問右子樹,最后訪問根結點。

  • 沿著根的左孩子,依次入棧,直到左孩子為空。此時棧內元素依次為ABD。
  • 讀棧頂元素:若其右孩子不空且未被訪問過,將右子樹轉執行①;否則,棧頂元素出棧并訪問。
  • 棧頂D的右孩子為空,出棧并訪問,它是后序序列的第一個結點;棧頂B的右孩子不空且未被訪問過,E入棧,棧頂E的左右孩子均為空,出棧并訪問;棧頂B的右孩子不空但已被訪問,B出棧并訪問;棧項A的右孩子不空且未被訪問過,C入棧,棧項C的左右孩子均為空,出棧并訪問;棧頂A的右孩子不空但已被訪問,A出棧并訪問。由此得到后序序列DEBCA。
    在上述思想的第②步中,必須分清返回時是從左子樹返回的還是從右子樹返回的,因此設定一個輔助指針r,指向最近訪問過的結點。也可在結點中增加一個標志域,記錄是否已被訪問。

    后序遍歷的非遞歸算法如下:

    void PostOrder2(BiTree T){InitStack(S);p = T;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; //轉向右push(S, p); //壓入棧p = p->lchild; //再走到最左}else{ //否則,彈出結點并訪問pop(S, p); //將結點彈出visit(p->data); //訪問該結點r = p; //記錄最近訪問過的結點p = NULL;}}} }

    5、層次遍歷

    下圖為二叉樹的層次遍歷,即按照箭頭所指方向,按照1,2,3, 4的層次順序,對二叉樹中的各個結點進行訪問。

    要進行層次遍歷,需要借助一個隊列。先將二叉樹根結點入隊,然后出隊,訪問出隊結點,若它有左子樹,則將左子樹根結點入隊;若它有右子樹,則將右子樹根結點入隊。然后出隊,訪問出隊結…如此反復,直至隊列為空。
    二叉樹的層次遍歷算法如下:

    void LevelOrder(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); //右子樹不空,則右子樹根節點入隊}} }

    6、由遍歷序列構造二叉樹

    由二叉樹的先序序列和中序序列可以唯一地確定一棵二叉樹。
    在先序遍歷序列中,第一個結點一定是二叉樹的根結點;而在中序遍歷中,根結點必然將中序序列分割成兩個子序列,前一個子序列是根結點的左子樹的中序序列,后一個子序列是根結點的右子樹的中序序列。根據這兩個子序列,在先序序列中找到對應的左子序列和右子序列。在先序序列中,左子序列的第一個結點是左子樹的根結點,右子序列的第一個結點是右子樹的根結點。
    如此遞歸地進行下去,便能唯一地確定這棵二叉樹
    同理,由二叉樹的后序序列和中序序列也可以唯一地確定一棵二叉樹。
    因為后序序列的最后一個結點就如同先序序列的第一個結點,可以將中序序列分割成兩個子序列,然后采用類似的方法遞歸地進行劃分,進而得到一棵二叉樹。
    由二叉樹的層序序列和中序序列也可以唯一地確定一棵二叉樹。
    要注意的是,若只知道二叉樹的先序序列和后序序列,則無法唯一確定一棵二叉樹。
    例如,求先序序列( ABCDEFGH)和中序序列( BCAEDGHFI)所確定的二叉樹
    首先,由先序序列可知A為二叉樹的根結點。中序序列中A之前的BC為左子樹的中序序列,EDGHFI為右子樹的中序序列。然后由先序序列可知B是左子樹的根結點,D是右子樹的根結點。以此類推,就能將剩下的結點繼續分解下去,最后得到的二叉樹如圖?所示。

    三、線索二叉樹

    1、線索二叉樹原理

    遍歷二叉樹是以一定的規則將二叉樹中的結點排列成一個線性序列,從而得到幾種遍歷序列,使得該序列中的每個結點(第一個和最后一個結點除外)都有一個直接前驅和直接后繼。
    傳統的二叉鏈表存儲僅能體現一種父子關系,不能直接得到結點在遍歷中的前驅或后繼。

    首先我們要來看看這空指針有多少個呢?對于一個有n個結點的二叉鏈表,每個結點有指向左右孩子的兩個指針域,所以一共是2n個指針域。而n個結點的二叉樹一共有n-1 條分支線數,也就是說,其實是存在2n- (n-1) =n+1個空指針域。

    由此設想能否利用這些空指針來存放指向其前驅或后繼的指針?這樣就可以像遍歷單鏈表那樣方便地遍歷二叉樹。引入線索二叉樹正是為了加快查找結點前驅和后繼的速度。
    我們把這種指向前驅和后繼的指針稱為線索,加上線索的二叉鏈表稱為線索鏈表,相應的二叉樹就稱為線索二叉樹(Threaded Binary Tree)。

    其結點結構如下所示:

    其中

    • ltag為0時指向該結點的左孩子,為1時指向該結點的前驅。
    • rtag為0時指向該結點的右孩子,為1時指向該結點的后繼。

    因此對于上圖的二叉鏈表圖可以修改為下圖的樣子。

    2、線索二叉樹的結構實現

    二叉樹的線索存儲結構代碼如下:

    typedef struct ThreadNode{ElemType data; //數據元素struct ThreadNode *lchild, *rchild; //左、右孩子指針int ltag, rtag; //左、右線索標志 }ThreadNode, *ThreadTree;

    3、二叉樹的線索化

    二叉樹的線索化是將二叉鏈表中的空指針改為指向前驅或后繼的線索。而前驅或后繼的信息只有在遍歷時才能得到,因此線索化的實質就是遍歷一次二叉樹,線索化的過程就是在遍歷的過程中修改空指針的過程。

    (1)中序線索二叉樹

    以中序線索二叉樹的建立為例。附設指針pre指向剛剛訪問過的結點,指針p指向正在訪問的結點,即pre指向p的前驅。在中序遍歷的過程中,檢查p的左指針是否為空,若為空就將它指向pre;檢查pre的右指針是否為空,若為空就將它指向p,如下圖所示。

    通過中序遍歷對二叉樹線索化的遞歸算法如下:

    void InThread(ThreadTree p, ThreadTree pre){if(p != NULL){InThread(p->lchild, pre); //遞歸,線索化左子樹if(p->lchild == NULL){ //左子樹為空,建立前驅線索p->lchild = pre;p->ltag = 1;}if(pre != NULL && pre->rchild == NULL){pre->rchild = p; //建立前驅結點的后繼線索pre->rtag = 1;}pre = p; //標記當前結點成為剛剛訪問過的結點InThread(p->rchild, pre); //遞歸,線索化右子樹} }

    你會發現,除了中間的代碼,和二叉樹中序遍歷的遞歸代碼幾乎完全一樣。只不過將本是訪問結點的功能改成了線索化的功能。

    通過中序遍歷建立中序線索二叉樹的主過程算法如下:

    void CreateInThread(ThreadTree T){ThreadTree pre = NULL;if(T != NULL){InThread(T, pre); //線索化二叉樹pre->rchild = NULL; //處理遍歷的最后一個結點pre->rtag = 1;} }

    為了方便,可以在二叉樹的線索鏈表上也添加一個頭結點,令其lchild域的指針指向二叉樹的根結點,其rchild域的指針指向中序遍歷時訪問的最后一個結點;令二叉樹中序序列中的第一個結點的lchild域指針和最后一個結點的rchild域指針均指向頭結點。這好比為二叉樹建立了一個雙向線索鏈表,方便從前往后或從后往前對線索二叉樹進行遍歷,如下圖所示。

    遍歷的代碼如下:

    /*T指向頭結點,頭結點左鏈lchild指向根結點,頭結點右鏈rchild指向中序遍 的最后一個結點。中序遍歷二叉線索鏈表表示的二叉樹T*/ void InOrderTraverse_Thr(BiThrTree T){BiThrTree p;p = T->lchild; //p指向根結點//空樹或遍歷結束時,p==T(最后一個結點指向根結點)while(p != T){ //當ltag==0時循環到中序序列第一個結點while(p->ltag == 0){ p = p->lchild; //p指向p的左子樹}visit(p); //訪問該結點//后繼線索為1且不是指向頭指針while(p->rtag == 1 && p->rchild != T){ p = p->rchild; //p指向p的后繼visit(p); //訪問該節點}//p進至其右子樹根,開始對右子樹根進行遍歷p = p->rchild; } }

    從這段代碼也可以看出,它等于是一個鏈表的掃描,所以時間復雜度為0(n)。
    由于它充分利用了空指針域的空間(這等于節省了空間),又保證了創建時的一次遍歷就可以終生受用前驅后繼的信息(這意味著節省了時間)。所以在實際問題中,如果所用的二叉樹需經常遍歷或查找結點時需要某種遍歷序列中的前驅和后繼,那么采用線索二叉鏈表的存儲結構就是非常不錯的選擇。

    (2)先序和后序線索二叉樹

    上面給出了建立中序線索二叉樹的代碼,建立先序線索二叉樹和后序線索二叉樹的代碼類似,只需變動線索化改造的代碼段與調用線索化左右子樹遞歸函數的位置。
    以圖(a)的二叉樹為例,其先序序列為ABCDF,后序序列為CDBFA,可得出其先序和后序線索二叉樹分別如圖(b)和( c)所示:

    如何在先序線索二叉樹中找結點的后繼?如果有左孩子,則左孩子就是其后繼;如果無左孩子但有右孩子,則右孩子就是其后繼;如果為葉結點,則右鏈域直接指示了結點的后繼。
    在后序線索二叉樹中找結點的后繼較為復雜,可分3種情況:①若結點x是二叉樹的根,則其后繼為空;②若結點x是其雙親的右孩子,或是其雙親的左孩子且其雙親沒有右子樹,則其后繼即為雙親;③若結點x是其雙親的左孩子,且其雙親有右子樹,則其后繼為雙親的右子樹上按后序遍歷列出的第一個結點。圖( c)中找結點B的后繼無法通過鏈域找到,可見在后序線索二叉樹上找后繼時需知道結點雙親,即需采用帶標志域的三叉鏈表作為存儲結構。

    四、樹、森林與二叉樹的轉化

    在講樹的存儲結構時,我們提到了樹的孩子兄弟法可以將一棵樹用二叉鏈表進行存儲,所以借助二叉鏈表,樹和二叉樹可以相互進行轉換。從物理結構來看,它們的二叉鏈表也是相同的,只是解釋不太一樣而已。 因此,只要我們設定一定的規則,用二叉樹來表示樹,甚至表示森林都是可以的,森林與二叉樹也可以互相進行轉換。

    1、樹轉換為二叉樹

    樹轉換為二義樹的規則:每個結點左指針指向它的第一個孩子,右指針指向它在樹中的相鄰右兄弟,這個規則又稱“左孩子右兄弟”。由于根結點沒有兄弟,所以對應的二叉樹沒有右子樹。

    樹轉換成二叉樹的畫法:

  • 在兄弟結點之間加一連線;
  • 對每個結點,只保留它與第一個孩子的連線,而與其他孩子的連線全部抹掉;
  • 以樹根為軸心,順時針旋轉45°。
  • 2、森林轉化為二叉樹

    森林是由若干棵樹組成的,所以完全可以理解為,森林中的每一棵樹都是兄弟,可以按照兄弟的處理辦法來操作。
    森林轉換成二叉樹的畫法:

  • 將森林中的每棵樹轉換成相應的二叉樹;
  • 每棵樹的根也可視為兄弟關系,在每棵樹的根之間加一根連線;
  • 以第一棵樹的根為軸心順時針旋轉45°。
  • 至于二叉樹轉換為樹或者二叉樹轉換為森林只不過是上面步驟的逆過程,在此不做贅述。

    五、樹和森林的遍歷

    1、樹的遍歷

    樹的遍歷是指用某種方式訪問樹中的每個結點,且僅訪問一次。主要有兩種方式:

  • 先根遍歷。若樹非空,先訪問根結點,再依次遍歷根結點的每棵子樹,遍歷子樹時仍遵循先根后子樹的規則。其遍歷序列與這棵樹相應二叉樹的先序序列相同。
  • 后根遍歷。若樹非空,先依次遍歷根結點的每棵子樹,再訪問根結點,遍歷子樹時仍遵循先子樹后根的規則。其遍歷序列與這棵樹相應二叉樹的中序序列相同。
  • 下圖的樹的先根遍歷序列為ABEFCDG,后根遍歷序列為EFBCGDA。

    另外,樹也有層次遍歷,與二叉樹的層次遍歷思想基本相同,即按層序依次訪問各結點。

    2、森林的遍歷

    按照森林和樹相互遞歸的定義,可得到森林的兩種遍歷方法。

  • 先序遍歷森林。若森林為非空,則按如下規則進行遍歷:
    ●訪問森林中第一棵樹的根結點。
    ●先序遍歷第一棵樹中根結點的子樹森林。
    ●先序遍歷除去第一棵樹之后剩余的樹構成的森林。
  • 后序遍歷森林。森林為非空時,按如下規則進行遍歷:
    ●后序遍歷森林中第一棵樹的根結點的子樹森林。
    ●訪問第一棵樹的根結點。
    ●后序遍歷除去第一棵樹之后剩余的樹構成的森林。
  • 圖5.17的森林的先序遍歷序列為ABCDEFGHI,后序遍歷序列為BCDAFEHIG。

    當森林轉換成二叉樹時,其第一棵樹的子樹森林轉換成左子樹,剩余樹的森林轉換成右子樹,可知森林的先序和后序遍歷即為其對應二叉樹的先序和中序遍歷。

    樹與二叉樹的應用

    一、二叉排序樹

    1、定義

    二叉排序樹(也稱二叉查找樹)或者是一棵空樹,或者是具有下列特性的二叉樹:

  • 若左子樹非空,則左子樹上所有結點的值均小于根結點的值。
  • 若右子樹非空,則右子樹上所有結點的值均大于根結點的值。
  • 左、右子樹也分別是一棵二叉排序樹。
  • 根據二叉排序樹的定義,左子樹結點值<根結點值<右子樹結點值,所以對二叉排序樹進行中序遍歷,可以得到一個遞增的有序序列。例如,下圖所示二叉排序樹的中序遍歷序列為123468。

    2、二叉排序樹的常見操作

    構造一個二叉樹的結構:

    /*二叉樹的二叉鏈表結點結構定義*/ typedef struct BiTNode {int data; //結點數據struct BiTNode *lchild, *rchild; //左右孩子指針 } BiTNode, *BiTree;

    (1)查找操作

    /* 遞歸查找二叉排序樹T中是否存在key 指針f指向T的雙親,其初始調用值為NULL 若查找成功,則指針p指向該數據元素結點,并返回TRUE 否則指針p指向查找路徑上訪問的最后一個結點并返回FALSE */ bool SearchBST(BiTree T, int key, BiTree f, BiTree *p){if(!T){*p = f;return FALSE;}else if(key == T->data){//查找成功*p = T;return TRUE;}else if(key < T->data){return SearchBST(T->lchild, key, T, p); //在左子樹繼續查找}else{return SearchBST(T->rchild, key, T, p); //在右子樹繼續查找} }

    (2)插入操作

    有了二叉排序樹的查找函數,那么所謂的二叉排序樹的插入,其實也就是將關鍵字放到樹中的合適位置而已。

    /* 當二叉排序樹T中不存在關鍵字等于key的數據元素時 插入key并返回TRUE,否則返回FALSE */ bool InsertBST(BiTree *T, int key){BiTree p, s;if(!SearchBST(*T, key, NULL, &p)){//查找不成功s = (BiTree)malloc(sizeof(BiTNode));s->data = key;s->lchild = s->rchild = NULL;if(!p){*T = s; //插入s為新的根節點}else if(key < p->data){p->lchild = s; //插入s為左孩子}else{p->rchild = s; //插入s為右孩子}return TRUE;}else{return FALSE; //樹種已有關鍵字相同的結點,不再插入} }

    有了二叉排序樹的插入代碼,我們要實現二叉排序樹的構建就非常容易了,幾個例子:

    int i; int a[10] = {62, 88, 58, 47, 35, 73, 51, 99, 37, 93}; BiTree T = NULL; for(i = 0; i<10; i++){InsertBST(&T, a[i]); }

    上面的代碼就可以創建一棵下圖這樣的樹。

    (3)刪除操作

    二叉排序樹的查找和插入都很簡單,但是刪除操作就要復雜一些,此時要刪除的結點有三種情況:

  • 葉子結點;
  • 僅有左或右子樹的結點;
  • 左右子樹都有的結點;
  • 前兩種情況都很簡單,第一種只需刪除該結點不需要做其他操作;第二種刪除后需讓被刪除結點的直接后繼接替它的位置;復雜就復雜在第三種,此時我們需要遍歷得到被刪除結點的直接前驅或者直接后繼來接替它的位置,然后再刪除
    第三種情況如下圖所示:

    代碼如下:

    /* 若二叉排序樹T中存在關鍵字等于key的數據元素時,則刪除該數據元素結點, 并返回TRUE;否則返回FALSE */ bool DeleteBST(BiTree *T, int key){if(!*T){return FALSE; }else{if(key == (*T)->data){//找到關鍵字等于key的數據元素return Delete(T);}else if(key < (*T) -> data){return DeleteBST((*T) -> lchild, key);}else{return DeleteBST((*T) -> rchild, key);}} }

    下面是Delete()方法:

    /*從二叉排序樹中刪除結點p,并重接它的左或右子樹。*/ bool Delete(BiTree *p){BiTree q, s;if(p->rchild == NULL){//右子樹為空則只需重接它的左子樹q = *p;*p = (*p)->lchild;free(q);}else if((*p)->lchild == NULL){//左子樹為空則只需重接它的右子樹q = *p;*p = (*p)->rchild;free(q);}else{//左右子樹均不空q = *p;s = (*p)->lchild; //先轉左while(s->rchild){//然后向右到盡頭,找待刪結點的前驅q = s;s = s->rchild;}//此時s指向被刪結點的直接前驅,p指向s的父母節點p->data = s->data; //被刪除結點的值替換成它的直接前驅的值if(q != *p){q->rchild = s->lchild; //重接q的右子樹}else{q->lchild = s->lchild; //重接q的左子樹}pree(s);}return TRUE; }

    3、小結(引申出平衡二叉樹)

    二叉排序樹的優點明顯,插入刪除的時間性能比較好。而對于二叉排序樹的查找,走的就是從根結點到要查找的結點的路徑,其比較次數等于給定值的結點在二叉排序樹的層數。極端情況,最少為1次,即根結點就是要找的結點,最多也不會超過樹的深度。也就是說,二叉排序樹的查找性能取決于二叉排序樹的形狀。可問題就在于,二叉排序樹的形狀是不確定的。
    例如{62,88,58,47,35,73,51,99,37,93}\{62,88,58,47,35,73,51,99,37,93\}{62,88,58,47,35,73,51,99,37,93}這樣的數組,我們可以構建如下左圖的二叉排序樹。但如果數組元素的次序是從小到大有序,如{35,37,47,51,58,62,73,88,93,99},則二叉排序樹就成了極端的右斜樹,如下面右圖的二叉排序樹:

    也就是說,我們希望二叉排序樹是比較平衡的,即其深度與完全二叉樹相同,那么查找的時間復雜也就為O(logn)O(logn)O(logn),近似于折半查找。
    不平衡的最壞情況就是像上面右圖的斜樹,查找時間復雜度為O(n)O(n)O(n),這等同于順序查找。
    因此,如果我們希望對一個集合按二叉排序樹查找,最好是把它構建成一棵平衡的二叉排序樹


    二、平衡二叉樹

    1、定義

    平衡二叉樹(Self-Balancing Binary Search Tree 或 Height-Balanced Binary Search Tree)是一種二叉排序樹,其中每一個節點的左子樹和右子樹的高度差至多等于1。
    它是一種高度平衡的二叉排序樹。它要么是一棵空樹, 要么它的左子樹和右子樹都是平衡二叉樹,且左子樹和右子樹的深度之差的絕對值不超過1。我們將二叉樹上結點的左子樹深度減去右子樹深度的值稱為平衡因子BF (Balance Factor) , 那么平衡二叉樹上所有結點的平衡因子只可能是-1、0和1。只要二叉樹上有一個結點的平衡因子的絕對值大于1,則該二叉樹就是不平衡的。

    2、平衡二叉樹的查找

    在平衡二叉樹上進行查找的過程與二叉排序樹的相同。因此,在查找過程中,與給定值進行比較的關鍵字個數不超過樹的深度。假設以nhn_hnh?表示深度為hhh的平衡樹中含有的最少結點數。顯然,有n0=0,n1=1,n2=2n_0=0,n_1=1,n_2=2n0?=0,n1?=1,n2?=2,并且有nh=nh?1+nh?2+1n_h=n_{h-1}+n_{h-2}+1nh?=nh?1?+nh?2?+1。可以證明,含有nnn個結點的平衡二叉樹的最大深度為O(log2n)O(log2n)O(log2n),因此平衡二叉樹的平均查找長度為O(log2n)O(log2n)O(log2n) 如下圖所示。

    3、平衡二叉樹的插入

    二叉排序樹保證平衡的基本思想如下:每當在二叉排序樹中插入(或刪除)一個結點時,首先檢查其插入路徑上的結點是否因為此次操作而導致了不平衡。若導致了不平衡,則先找到插入路徑上離插入結點最近的平衡因子的絕對值大于1的結點A,再對以A為根的子樹,在保持二叉排序樹特性的前提下,調整各結點的位置關系,使之重新達到平衡。
    注意:每次調整的對象都是最小不平衡子樹,即以插入路徑上離插入結點最近的平衡因子的絕對值大于1的結點作為根的子樹。下圖中的虛線框內為最小不平衡子樹。

    平衡二叉樹的插入過程的前半部分與二叉排序樹相同,但在新結點插入后,若造成查找路徑上的某個結點不再平衡,則需要做出相應的調整。可將調整的規律歸納為下列4種情況:

  • LL平衡旋轉(右單旋轉)。由于在結點A的左孩子(L)的左子樹(L)上插入了新結點,A的平衡因子由1增至2,導致以A為根的子樹失去平衡,需要一次向右的旋轉操作。將A的左孩子B向右上旋轉代替A成為根結點,將A結點向右下旋轉成為B的右子樹的根結點,而B的原右子樹則作為A結點的左子樹。
    如下圖所示,結點旁的數值代表結點的平衡因子,而用方塊表示相應結點的子樹,下方數值代表該子樹的高度。
  • RR平衡旋轉(左單旋轉)。由于在結點A的右孩子?的右子樹?上插入了 新結點,A的平衡因子由-1減至-2,導致以A為根的子樹失去平衡,需要一次向左的旋轉操作。將A的右孩子B向左上旋轉代替A成為根結點,將A結點向左下旋轉成為B的左子樹的根結點,而B的原左子樹則作為A結點的右子樹。
  • LR平衡旋轉(先左后右雙旋轉)。由于在A的左孩子(L)的右子樹?上插入新結點,A的平衡因子由1增至2,導致以A為根的子樹失去平衡,需要進行兩次旋轉操作,先左旋轉后右旋轉。先將A結點的左孩子B的右子樹的根結點C向左上旋轉提升到B結點的位置(即進行一次RR平衡旋轉(左單旋轉)),然后再把該C結點向右上旋轉提升到A結點的位置(即進行一次LL平衡旋轉(右單旋轉))。
  • RL平衡旋轉(先右后左雙旋轉)。由于在A的右孩子?的左子樹(L)上插入新結點,A的平衡因子由-1減至-2,導致以A為根的子樹失去平衡,需要進行兩次旋轉操作,先右旋轉后左旋轉。先將A結點的右孩子B的左子樹的根結點C向右上旋轉提升到B結點的位置(即進行一次LL平衡旋轉(右單旋轉)),然后再把該C結點向左上旋轉提升到A結點的位置(即進行一次RR平衡旋轉(左單旋轉))。
    注意: LR和RL旋轉時,新結點究竟是插入C的左子樹還是插入C的右子樹不影響旋轉過程,而上圖中是以插入C的左子樹中為例。
  • 舉個例子:
    假設關鍵字序列為15,3,7,10,9,8{15,3, 7, 10, 9, 8}15,3,7,10,9,8,通過該序列生成平衡二叉樹的過程如下圖所示。

    二叉排序樹還有另外的平衡算法,如紅黑樹(Red Black Tree)等,與平衡二叉樹(AVL樹)相比各有優勢。


    三、哈夫曼樹和哈夫曼編碼

    1、哈夫曼樹的定義和原理

    在許多應用中,樹中結點常常被賦予一個表示某種意義的數值,稱為該結點的。從樹的根到任意結點的路徑長度(經過的邊數)與該結點上權值的乘積,稱為該結點的帶權路徑長度。樹中所有葉結點的帶權路徑長度之和稱為該樹的帶權路徑長度,記為WPL=∑i=1nwiliWPL = \displaystyle\sum_{i=1}^{n} w_il_iWPL=i=1n?wi?li?式中,wiw_iwi?是第i個葉結點所帶的權值,lil_ili?是該葉結點到根結點的路徑長度。
    在含有n個帶權葉結點的二叉樹中,其中帶權路徑長度(WPL)最小的二叉樹稱為哈夫曼樹,也稱最優二叉樹。例如,下圖中的3棵二叉樹都有4個葉子結點a, b,c,d,分別帶權7,5,2,4,它們的帶權路徑長度分別為

    a. WPL = 7x2 + 5x2 + 2x2 + 4x2 = 36。
    b. WPL = 4x2 + 7x3 + 5x3 + 2x1 = 46。
    c. WPL = 7x1 + 5x2 + 2x3 + 4x3 = 35。
    其中,圖c樹的WPL最小。可以驗證,它恰好為哈夫曼樹。

    2、哈夫曼樹的構造

    步驟:

  • 先把有權值的葉子結點按照從大到小(從小到大也可以)的順序排列成一個有序序列。
  • 取最后兩個最小權值的結點作為一個新節點的兩個子結點,注意相對較小的是左孩子。
  • 用第2步構造的新結點替掉它的兩個子節點,插入有序序列中,保持從大到小排列。
  • 重復步驟2到步驟3,直到根節點出現。
  • 看圖就清晰了,如下圖所示:

    3、哈夫曼編碼

    赫夫曼當前研究這種最優樹的目的是為了解決當年遠距離通信(主要是電報)的數據傳輸的最優化問題。
    哈夫曼編碼是一種被廣泛應用而且非常有效的數據壓縮編碼。
    比如我們有一段文字內容為“ BADCADFEED”要網絡傳輸給別人,顯然用二進制的數字(0和1)來表示是很自然的想法。我們現在這段文字只有六個字母ABCDEF,那么我們可以用相應的二進制數據表示,如下表所示:

    這樣按照固定長度編碼編碼后就是“001000011010000011101100100011”,對方接收時可以按照3位一分來譯碼。如果一篇文章很長,這樣的二進制串也將非常的可怕。而且事實上,不管是英文、中文或是其他語言,字母或漢字的出現頻率是不相同的。
    假設六個字母的頻率為A 27,B 8,C 15,D 15,E 30,F 5,合起來正好是
    100%。那就意味著,我們完全可以重新按照赫夫曼樹來規劃它們。
    下圖左圖為構造赫夫曼樹的過程的權值顯示。右圖為將權值左分支改為0,右分支改為1后的赫夫曼樹。

    這棵哈夫曼樹的WPL為:
    WPL=2?(15+27+30)+3?15+4?(5+8)=241WPL=2*(15+27+30) + 3*15 + 4*(5+8)=241WPL=2?(15+27+30)+3?15+4?(5+8)=241
    此時,我們對這六個字母用其從樹根到葉子所經過路徑的0或1來編碼,可以得到如下表所示這樣的定義。

    若沒有一個編碼是另一個編碼的前綴,則稱這樣的編碼為前綴編碼。
    我們將文字內容為“ BADCADFEED”再次編碼,對比可以看到結果串變小了。

    • 原編碼二進制串: 000011000011101100100011 (共 30個字符)
    • 新編碼二進制串: 10100101010111100(共25個字符)

    也就是說,我們的數據被壓縮了,節約了大約17%的存儲或傳輸成本。

    注意:
    0和1究竟是表示左子樹還是右子樹沒有明確規定。左、右孩子結點的順序是任意的,所以構造出的哈夫曼樹并不唯一,但各哈夫曼樹的帶權路徑長度WPL相同且為最優。此外,如有若干權值相同的結點,則構造出的哈夫曼樹更可能不同,但WPL必然相同且是最優的。


    附錄

    上文鏈接

    數據結構:串

    下文鏈接

    數據結構:圖

    專欄

    數據結構專欄

    參考資料

    1、嚴蔚敏、吳偉民:《數據結構(C語言版)》
    2、程杰:《大話數據結構》
    3、王道論壇:《數據結構考研復習指導》
    4、托馬斯·科爾曼等人:《算法導論》

    總結

    以上是生活随笔為你收集整理的数据结构:树(Tree)【详解】的全部內容,希望文章能夠幫你解決所遇到的問題。

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

    主站蜘蛛池模板: www.夜夜爽 | 久久久精品欧美 | 免费福利视频网站 | 中文字幕不卡一区 | 一区二区国产精品 | 亚洲精品人人 | 亚洲精品一区二区潘金莲 | 少妇又色又爽又黄的视频 | 亚洲人午夜射精精品日韩 | 91九色中文 | 全部免费毛片 | 韩国美女福利视频 | 91看片在线 | 国产欧美日韩在线播放 | 国产自偷自拍 | 中文字幕在线观看免费 | 深夜久久| 亚洲精品中文字幕在线 | 91精品国产一区二区三区蜜臀 | 亚洲精品久久久久久一区二区 | 99热成人 | 国产又粗又长又硬免费视频 | 屁屁影院第一页 | 成人av网站大全 | av动漫免费看 | 欧美 亚洲 一区 | 伊人中文字幕在线观看 | 天堂va蜜桃一区二区三区 | 亚洲男人第一av | 欧美色xxx | 成人久草 | 国产精品无码专区 | 日本大奶视频 | 88久久精品无码一区二区毛片 | 伊人手机在线视频 | 日韩 欧美 综合 | 97精品自拍| 二级毛片 | 日批免费观看 | 无码精品久久久久久久 | 国产jzjzjz丝袜老师水多 | 吃奶摸下的激烈视频 | 亚州av在线播放 | 亚洲精品激情视频 | 精品成人网 | 久久激情网 | 一级性生活黄色片 | 欧美久久久精品 | 91传媒在线视频 | 久久久久久久久久电影 | 免费大片黄在线观看视频网站 | 91视频首页 | 男女激情视频网站 | 欧美一区二区三区视频在线 | 国产精品国产三级国产普通话对白 | 日本中文字幕在线视频 | 午夜毛片在线观看 | 人体一级片 | 免费午夜网站 | 国产精久久久 | 三级av片 | 国产一区二区三区视频网站 | 国产剧情一区二区三区 | 久久av无码精品人妻系列试探 | 114国产精品久久免费观看 | 日韩欧美精品一区二区 | 人人爱人人插 | 国内精品久久久久久久久 | 日本二三区| 亚洲一区激情 | 一级h片 | 牛牛影视免费观看 | 中国av免费 | 日本日皮视频 | 日本久久久久久 | 久久久久99精品成人片 | 日韩av图片 | 少妇一边呻吟一边说使劲视频 | 久久国产免费看 | 在线观看免费 | 精品国产乱码久久久久久久 | 女同激情久久av久久 | 天天色天天搞 | 亚洲一区二区三区观看 | 98色| 性做久久久久久久久久 | 午夜精品福利一区二区蜜股av | 日韩爱爱视频 | 亚洲熟妇无码一区二区三区 | 国产一级片| 欧美性猛交7777777 | 国产精品无码永久免费不卡 | 免费禁漫天堂a3d | 国产精品久久久久久久免费观看 | 日韩手机在线观看 | 熟女毛毛多熟妇人妻aⅴ在线毛片 | 超碰成人久久 | 精品三级在线观看 | 高潮毛片 |