Redis研究-3.3數據結構之樹與查找、排序等(後續)

3.樹、二叉樹、森林之間的轉換 算法

   前面咱們又說到,二叉樹中的節點咱們能夠表示成一個具備左孩子域、右孩子域、雙親域、自身數據域的一個數據結構,那麼對於通常的樹或者森林中的節點來講,能不能也這樣子表示呢?答案是能夠的,表示成二叉樹節點的形式,咱們就能很好的使用二叉樹的一些特性和算法。 網絡

在二叉樹中,left表示節點的左孩子、right表示節點的右孩子,那麼,對於通常的樹節點來看,若是存在孩子,第一個孩子就是對應的left區域,若是有第二個、第三個孩子等,就用right造成一個鏈表,那麼,這種樹就轉換爲二叉樹啦,只是這裏兩個指針域的說法不太同樣而已。實際上,咱們對於節點來講,咱們能夠改進一下獲得以下的表示: 數據結構

//修改後的通常的樹節點表示
typedef struct gTBiTreeNode{
    struct gTBiTreeNode *left;
    struct gTBiTreeNode *right;
    void *data;
    struct gTBiTreeNode *next;//兄弟節點
}gTBiTreeNode,*gTBiTreeNode;



3.1 樹轉換爲二叉樹 學習

  上面已經改造了樹節點的表示,那麼通常的樹怎麼轉換爲咱們常見的二叉樹呢?只須要三個步驟便可: 優化

  1).加線。在全部的兄弟節點之間加一條鏈接線; 編碼

  2).去線。對數中的每一個節點,只保留他與第一個孩子節點的鏈接,刪除他與其餘孩子節點之間的鏈接線。 spa

  3).層次調整。以樹的根爲軸心,將整棵樹順時針旋轉必定的角度,使之井井有條。這裏要注意的是,第一個孩子是二叉樹節點的左孩子,兄弟轉換過來的孩子是節點的右孩子。 翻譯

咱們用圖來表示一下 指針

 

 

 

咱們重點來看看,怎麼調整成最後的這個層次了,首先咱們應該清楚第三個步驟調整的原則: code

  第一個孩子是節點的左孩子,那麼B固然是A的左孩子啦。根據第二條原則,兄弟轉換過來的孩子是節點的右孩子,由於C是B的兄弟,因此,轉換過來後,就變成了B的右孩子。一樣的,由於E是B的作孩子,因此轉換後固然是B的左孩子。一樣的,根據第二個原則,F是E的兄弟,因此,轉換後,F變成了E的右孩子,G先前是F的兄弟,如今變成了F的右孩子。一樣的,咱們的先前的樹中C的第一個孩子就是H,因此,如今H固然是C的左孩子,一樣的,由於D是C的兄弟,因此如今變成了C的右孩子。I在之前的樹上就是D的第一個孩子,因此,如今是D的左孩子,又由於J先前是I的兄弟,因此,如今變成了I的右孩子。

經過上面的文字描述,咱們要特別注意,第二個原則,就是"兄弟孩子變成了節點的右孩子這個說法".

3.2 森林轉換爲二叉樹

什麼是森林?森林固然是由不少的樹組成的啦。那麼,咱們固然能夠把其中的每一顆樹看作是兄弟,所以,咱們就能夠獲得下面的轉換步驟了。

1).把每棵樹轉換成一顆二叉樹;

2).第一顆二叉樹保持不動,從第二棵二叉樹開始,依次把後一棵樹的根節點做爲前一棵二叉樹的根節點的右孩子,而後用線鏈接起來。

用圖來演示一下:

 

OK,應該說清楚了,那麼,二叉樹又怎麼轉換成樹呢?

3.3 二叉樹轉換成樹

前面咱們已經從樹轉換成二叉樹了,他要經歷過三個步驟,分別是加線,去線,調整層次,那麼,二叉樹轉換爲樹,也就是這個過程的一個逆過程,怎麼作呢?

任然是

1).加線。若是節點的左孩子存在,則將這個左孩子的右孩子節點、右孩子的右孩子節點、。。。都做爲這個節點的孩子節點,將該節點與這些右孩子節點連線。

2).去線。刪除原二叉樹中全部節點與其右孩子節點的連線。

3).層次調整。

有圖有真相。

 

 

so easy,不是麼?

3.4 二叉樹轉換成森林?

   一棵樹可否轉換成森林,判斷的標準很簡單,就是看這個二叉樹的根節點有沒有右孩子節點,若是有,那就能夠轉換。轉換步驟是:

1).從根節點開始,若右孩子存在,則把與右孩子及誒單的鏈接線刪除,分離之後,繼續迭代。

2).將每棵分離後的二叉樹轉換爲樹便可。

上圖

估計是傻瓜也能看得懂了吧???O(∩_∩)O哈哈~

3.5 哈夫曼編碼

  我不知道你們在大學時候有沒有學過運籌學,運籌學裏面很重要的一個分支就是講動態規劃的(哈哈,在下本科就是數學系的哈,當時的運籌學考了67分,低分飄過,不過最高分也就是72啦)。在某些求解最優化問題的算法中,每一個步驟都面臨着多種選擇,動態規劃是這種問題的殺手級算法,可是有時候又會顯得有點笨重,因此,在這個時候,咱們須要一種更簡單、更高效的算法,貪心算法就是這樣一種算法,貪心算法的核心就是在每一步都作出當時看起來最佳的選擇,或者叫作局部最優的選擇,經過這種選擇來獲得最後的一個全局最優解。固然,這只是一種但願,因此,貪心算法並不保證能獲得一個最優解。咱們這裏就先學習一種貪心算法-哈夫曼編碼。

在說這玩意兒以前,先看個咱們現實生活中的例子(這個例子來自《大話數據結構》,請各位參考)。裏面就是說,老師在給學生評「不及格」、「及格」、「中等」、「良好」、「優秀」的時候,是根據學生的分數段來進行的,一般狀況下,咱們使用下面的一個結構來判斷:

int degree(int score){
  if(score<60){
    printf("%s","不及格");
  }else if(score<70){
    printf("%s","及格");
  }else if(score<80){
    printf("%s","中等");
  }else if(score<90){
    printf("%s","良好");
  }else{
    printf("%s","優秀");
  }
}



獲得的圖化結構是:

當咱們看到在實際的學習生活中,學生的成績階段比例是以下所示的時候,咱們就會感到這個算法是大有問題的了


分數 0-59 60-69 70-79 80-89 90-100
比例 5% 15% 40% 30% 10%
,要查看70分以上的學生數據,至少要經過3此比較才能作出判斷,那麼,怎樣來改進呢?
int degree(int score){
  if(score<80){
    if(score<70){
        if(score<60){
           printf("%s","不及格");
        }else{
          printf("%s","及格");
        }
    }else {
        printf("%s","中等");
    }
  }else if(score<90){
      printf("%s","良好");
  }else {
    printf("%s","優秀");
  }
}



經過此次改進之後,70-79之間的分數最多須要兩次就能判斷了,是否是更優化了呢?二叉樹的表示方法以下:

 

 

假如,如今有1000學生,那麼沒改進以前,須要的判斷次數是3150次,而改進後,須要用到的次數是2200次,效果很明顯,特別是數據量大的時候。

 爲了說清楚接下來的內容,有幾個概念須要明確一下:

 

1).從樹中一個節點到另一個節點之間的分支構成兩個節點之間的路徑,路徑上的分支數據叫作路徑長度(走得通的路徑)。

2).樹的路徑長度是從根到每個節點的路徑長度之和。樹A的路徑是:1+1+2+2+3+3+4+4=20.

3).節點的帶權的路徑長度是從該節點到樹根之間的路徑長度與節點上權的乘積。樹A中的及格的帶權路徑是15*2=30;

4).樹的帶權路徑路徑是樹中全部葉子節點的帶權路徑長度之和。樹A的帶權路徑是:5*1+15*2+40*3+30*4+10*4=315;

5).帶權路徑長度WPL最小的二叉樹就是哈夫曼樹。

 

那麼,怎麼來構建哈夫曼樹呢?遵循如下步驟

1).先把帶有全職的葉子節點按照從小到大的順序來排列成一個有序序列。

2).從這個有序序列中選擇較小的兩個來構造一個新的二叉樹,較小的權值的節點做爲新二叉樹的左孩子,較大的做爲右孩子,新的二叉樹的根節點的權值是兩個孩子的權值之和。

3).從序列中刪除已經選擇的兩個較小權值的節點,並把步驟2中構造的新二叉樹的根節點帶到這個序列中排序。

4).重複步驟二、3就能夠獲得最終的哈夫曼樹。

 

咱們從樹B來看怎麼構造一個哈夫曼樹:

 

 

新排序:N=15,B,D,C

 

 

從新排序:N=30,D,C

從新排序:C,N=60

構造後的這顆樹的帶權路徑=40*1+30*2+15*3+10*4+5*4=205;

而原來的這顆樹的帶權路徑是:5*3+15*3+40*2+30*2+10*2=220;

咱們算一下須要多少判斷步驟3050次,反而比不是哈夫曼樹的算法還低效?那這說明哈夫曼樹沒用麼?不是的。

 

咱們再看一下:

假設我要給你遠程發送一段「BADCADFEED」的內容,網絡傳輸中,通常都是用二進制來表示的,在這段文字中出現了A,B,C,D,E,F這6個字符,假設咱們分別以三位二進制來代替一個字母,則能夠獲得下面的對應表:


A B C D E F
000 001 010 011 100 101

那麼,這段內容的編碼就是:001000011010000011101100100011,一共是30位。可是咱們發現,在這段內容中,各個字母出現的頻率是不同的,他們出現的頻率分別是:


A B C D E F
0.2 0.1 0.1 0.3 0.2 0.1

也就是說,咱們能夠用哈夫曼樹來進行一個構建

 

咱們將權值作分支改成0,右分支改成1:

因此,獲得的對應的字母編碼映射表是:


A B C D E F
110 11110 11111 0 10 1110

那麼,使用這種編碼之後,發送內容應該是:111101100111111100111010100,一共是27位。

也就是說,咱們改進後,節約了10的存儲或者傳輸成本。

實際上,咱們從上面來看到,這其實就是一種變長的編碼,核心思想就是將高頻的字符用最短的碼字來表示,低頻的用長的碼字來表示。

其實咱們還能夠獲得,咱們構造的這棵二叉樹,是一顆滿二叉樹。

哈夫曼編碼的正確性的證實請參考由殷建平、徐雲等人翻譯的《算法導論-原書第三版》第248頁。

二叉搜索樹:就是在二叉樹的基礎上知足一個特性的二叉樹就叫作二叉搜索樹,這個特性就是對於樹上任何節點X,其左子樹的關鍵字不能超過X的關鍵字,其右子樹的的關鍵字不能小於X的關鍵字。

紅黑樹:就是在二叉搜索樹的每一個節點上增長了一個顏色屬性,這個屬性有兩個黑色和紅色取值。同時要知足下面的特性:

1).每一個節點要麼是紅色的,要麼是黑色的。

2).根節點是黑色的。

3).每一個葉子節點是黑色的;

4).若是一個節點是紅色的,則他的兩個子節點都是黑色的;

5).對每一個節點,從該節點到其全部的後代葉節點的簡單路勁上,均包含相同數目的黑色節點。

 遇到其餘的樹,再來寫,基本上都是在二叉樹的基礎上加了一些限制

下一節,將繼續說排序、查找

歡迎拍磚,同時也能夠加QQ:359311095討論

相關文章
相關標籤/搜索