勝者樹和敗者樹


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

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

       勝者樹與敗者樹可以在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替換,然后對樹進行調整。勝者樹的調整是從葉子節點到根節點的自下而上的調整,每次都比較雙親節點的左右孩子節點,並把優勝者的下標志存儲在雙親節點中。

 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存儲內部節點

int loserTree[K];               /* 存儲中間節點值,下標0處存儲冠軍節點 */
int leaves[K+1];                /* 從下標1開始存儲葉子節點值,下標0處存儲一個最小值節點 */

void adjust(int i)
{
    int parent=(i+K-1)/2;      /* 求出父節點的下標 */
    while(parent>0)
    {
        if(leaves[i]>leaves[loserTree[parent]])
        {
            int temp=loserTree[parent];
            loserTree[parent]=i;
            /* i指向的是優勝者 */
            i= temp;
        }
        parent = parent / 2;
    }
    loserTree[0]=i;
}

void initLoserTree()
{
    int i;
    for(i=1;i<K+1;i++)
        scanf("%d",&leaves[i]);
    leaves[0]=MIN;
    for(int i=0;i<K;i++)
        loserTree[i]=0;
    for(int i=K;i>0;i--)
        adjust(i);
}

 

     


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM