敗者樹 和 勝者樹---數組實現

來源:互聯網
上載者:User

標籤:

轉自:http://blog.csdn.net/sqx2011/article/details/8241734

勝者樹和敗者樹都是完全二叉樹,是樹形選擇排序的一種變型。每個葉子結點相當於一個選手,每個中間結點相當於一場比賽,每一層相當於一輪比賽。

      不同的是,勝者樹的中間結點記錄的是勝者的標號;而敗者樹的中間結點記錄的敗者的標號。

       勝者樹與敗者樹可以在log(n)的時間內找到最值。任何一個葉子結點的值改變後,利用中間結點的資訊,還是能夠快速地找到最值。在k路歸併排序中經常用到。

一、勝者樹

       勝者樹的一個優點是,如果一個選手的值改變了,可以很容易地修改這棵勝者樹。只需要沿著從該結點到根結點的路徑修改這棵二叉樹,而不必改變其他比賽的結果。

Fig. 1

Fig.1是一個勝者樹的樣本。規定數值小者勝。

  1. b3 PK b4,b3勝b4負,內部結點ls[4]的值為3;
  2. b3 PK b0,b3勝b0負,內部結點ls[2]的值為3;
  3. b1 PK b2,b1勝b2負,內部結點ls[3]的值為1;
  4. b3 PK b1,b3勝b1負,內部結點ls[1]的值為3。.

當Fig. 1中葉子結點b3的值變為11時,重構的勝者樹如Fig. 2所示。

  1. b3 PK b4,b3勝b4負,內部結點ls[4]的值為3;
  2. b3 PK b0,b0勝b3負,內部結點ls[2]的值為0;
  3. b1 PK b2,b1勝b2負,內部結點ls[3]的值為1;
  4. b0 PK b1,b1勝b0負,內部結點ls[1]的值為1。.

Fig. 2

用勝者樹對n個節點實現排序操作,構建勝者樹和構建堆比較相似,區別在於勝者樹只有葉子節點存放了資料,中間節點記錄的是葉子節點間的關係。

leaves[n+1]:共有n個葉子節點,儲存下標從1到n

successTree[n]:儲存中間節點,儲存下標從1到n-1

對successTree中的資料從n-1到1,按照優勝策略不斷調整內部節點的數值,最後得到一顆勝者樹

冠軍節點的下標儲存在successTree[1]裡,實現排序操作時,將該葉子節點的值列印輸出,並用一個比葉子節點中所有值都大的值MAX替換,然後對樹進行調整。勝者樹的調整是從葉子節點到根節點的自下而上的調整,每次都比較雙親節點的左右孩子節點,並把優勝者的下標誌儲存在雙親節點中。

[cpp] view plaincopy 
  1. #include <stdio.h>  
  2.  #define K 10  
  3.  #define MAX 65535  
  4.  int leaves[K+1];  
  5.  int successTree[K];  
  6.    
  7.  /* 對於單個內部節點進行調整 */  
  8.  void adjust(int i)  
  9.  {  
  10.      int m,n;  
  11.      if(2 * i < K)               /* 擷取它的左孩子結點 */  
  12.          m = successTree[2 * i];  
  13.      else  
  14.          m = 2 * i - K + 1;  
  15.      if(2*i+1<K)                 /* 擷取它的右孩子節點 */  
  16.          n = successTree[2*i+1];  
  17.      else  
  18.          n = 2 * i + - K + 2;  
  19.      successTree[i] = leaves[m] > leaves[n] ? n : m; /* 進行勝負判定 */  
  20.  }  
  21.  /* 初始化葉子節點並對內部節點進行類似於堆的調整 */  
  22.  void initTree()  
  23.  {  
  24.      for(int i=1;i<K+1;i++)  
  25.          scanf("%d", &leaves[i]);  
  26.      for(int i=K-1;i>0;i--)  
  27.          adjust(i);  
  28.  }  
  29.  /* 自下而上對勝者樹進行調整 */  
  30.  void adjustToRoot(int i)  
  31.  {  
  32.      int parent = (i + K - 1) / 2; /* 對從當前節點到根節點路徑上的所有 
  33.                                     * 節點進行調整 */  
  34.      while(parent>0)  
  35.      {  
  36.          adjust(parent);  
  37.          parent = parent / 2;  
  38.      }  
  39.  }  
  40.    
  41.  int main()  
  42.  {  
  43.      freopen("in","r",stdin);  
  44.      initTree();  
  45.      for(int i=1;i<K+1;i++)      /* 每次用最大值替換掉冠軍節點,並對樹 
  46.                                   * 進行調整,最終得到升序排序的序列 */  
  47.      {  
  48.          printf("%d ", leaves[successTree[1]]);  
  49.          leaves[successTree[1]]=MAX;  
  50.          adjustToRoot(successTree[1]);  
  51.      }  
  52.      return 0;  
  53.  }  
 二、敗者樹

       敗者樹是勝者樹的一種變體。在敗者樹中,用父結點記錄其左右子結點進行比賽的敗者,而讓勝者參加下一輪的比賽。敗者樹的根結點記錄的是敗者,需要加一個結點來記錄整個比賽的勝利者。採用敗者樹可以簡化重構的過程。

Fig. 3

Fig. 3是一棵敗者樹。規定數大者敗。

  1. b3 PK b4,b3勝b4負,內部結點ls[4]的值為4;
  2. b3 PK b0,b3勝b0負,內部結點ls[2]的值為0;
  3. b1 PK b2,b1勝b2負,內部結點ls[3]的值為2;
  4. b3 PK b1,b3勝b1負,內部結點ls[1]的值為1;
  5. 在根結點ls[1]上又加了一個結點ls[0]=3,記錄的最後的勝者。

敗者樹重構過程如下:

  • 將新進入選擇樹的結點與其父結點進行比賽:將敗者存放在父結點中;而勝者再與上一級的父結點比較。
  • 比賽沿著到根結點的路徑不斷進行,直到ls[1]處。把敗者存放在結點ls[1]中,勝者存放在ls[0]中。

Fig. 4

       Fig. 4是當b3變為13時,敗者樹的重構圖。

       注意,敗者樹的重構跟勝者樹是不一樣的,敗者樹的重構只需要與其父結點比較,而勝者樹則需要和兄弟節點比較。對照Fig. 3來看,b3與結點ls[4]的原值比較,ls[4]中存放的原值是結點4,即b3與b4比較,b3負b4勝,則修改ls[4]的值為結點3。同理,以此類推,沿著根結點不斷比賽,直至結束。

        敗者樹常常用於多路外部排序,對於K個已經排好序的檔案,將其歸併為一個有序檔案。敗者樹的葉子節點是資料節點,兩兩分組,內部節點記錄左右子樹中的“敗者”,優勝者往上傳遞一直到根節點,如果規定優勝者是兩個數中的較小者,則根節點記錄的是最後一次比較中的敗者,也就是第二小的數,而用一個變數來記錄最小的數。把最小值輸出以後,用一個新的值替換最小值節點的值(在檔案歸併的時候,如果檔案已經讀完,可以用一個無窮大的數來替換),接下來維護敗者樹,從更新的節點往上,一次與父節點比較,將敗者更新,勝者繼續比較。

        注意:當葉子節點的個數變動的時候需要完全重新構建整棵樹。

       比較敗者樹和堆的效能

       敗者樹在維護的時候,比較次數是logn+1,  敗者樹從下往上維護,每上一層,只需要和父節點比較一次,而堆是自上往下維護,每一層需要和左右子節點都比較,需要比較兩次,從這個角度,敗者樹比堆更優一點,但是,敗者樹每一次維護,必然是從葉子節點到根節點的一條路徑,而堆維護的時候有可能在中間某個層次停止,這樣敗者樹雖然每層比堆比較的次數少,但是堆比較的層數可能比較少。

        

       從n個數中找出最大的k個,分別用堆和敗者樹來實現

       堆實現: 維護一個大小為k的小頂堆,每來一個數都和堆頂進行比較,如果比堆頂小,直接捨棄,否則替換堆頂,維護堆,直到n個數都處理完畢,時間複雜度為O(nlogk)

 

       敗者樹實現:當用數組來實現敗者樹時, 維護一個葉子節點個數為k的敗者樹,注意是葉子節點個數而不是所有節點個數,數字較小者取勝,則最頂層儲存的是值最小的葉子節點,每來一個數和最小值比較,如果比最小值還小,直接捨棄,否則替換最小值的節點值,從下往上維護敗者樹,最後的k個葉子節點中儲存的就是所有數中值最大的k的,時間複雜度為O(nlogk)

     

       用數組實現敗者樹的時候,因為只有葉子節點儲存的是資料,因此敗者樹使用的記憶體空間是堆的兩倍。

       完全樹的內部,度數為2的節點個數是葉子節點個數減一 ,所以使用的數組大小為2k-1, 如果把最值也存入數組中,則需要的數組大小為2k

 

       敗者樹的構造

       思路: 先構造一顆空的敗者樹,然後把葉子節點一個一個的插入敗者樹,自底向上不斷的調整,保持內部節點儲存的都是失敗者的節點編號,優勝者一直向上不斷比較,最終得到一顆合格的敗者樹。

 

leaves[K+1] : 葉子節點的個數為K,下標從1到K,下標0處儲存一個最小值,用來初始化敗者樹

loserTree[K]: 冠軍節點儲存在下標0,下標1到K-1儲存內部節點

[cpp] view plaincopy 
  1. int loserTree[K];               /* 儲存中間節點值,下標0處儲存冠軍節點 */  
  2. int leaves[K+1];                /* 從下標1開始儲存葉子節點值,下標0處儲存一個最小值節點 */  
  3.   
  4. void adjust(int i)  
  5. {  
  6.     int parent=(i+K-1)/2;      /* 求出父節點的下標 */  
  7.     while(parent>0)  
  8.     {  
  9.         if(leaves[i]>leaves[loserTree[parent]])  
  10.         {  
  11.             int temp=loserTree[parent];  
  12.             loserTree[parent]=i;  
  13.             /* i指向的是優勝者 */  
  14.             i= temp;  
  15.         }  
  16.         parent = parent / 2;  
  17.     }  
  18.     loserTree[0]=i;  
  19. }  
  20.   
  21. void initLoserTree()  
  22. {  
  23.     int i;  
  24.     for(i=1;i<K+1;i++)  
  25.         scanf("%d",&leaves[i]);  
  26.     leaves[0]=MIN;  
  27.     for(int i=0;i<K;i++)  
  28.         loserTree[i]=0;  
  29.     for(int i=K;i>0;i--)  
  30.         adjust(i);  
  31. }  

(轉)敗者樹 和 勝者樹---數組實現

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.