標籤:
轉自:http://blog.csdn.net/sqx2011/article/details/8241734
勝者樹和敗者樹都是完全二叉樹,是樹形選擇排序的一種變型。每個葉子結點相當於一個選手,每個中間結點相當於一場比賽,每一層相當於一輪比賽。
不同的是,勝者樹的中間結點記錄的是勝者的標號;而敗者樹的中間結點記錄的敗者的標號。
勝者樹與敗者樹可以在log(n)的時間內找到最值。任何一個葉子結點的值改變後,利用中間結點的資訊,還是能夠快速地找到最值。在k路歸併排序中經常用到。
一、勝者樹
勝者樹的一個優點是,如果一個選手的值改變了,可以很容易地修改這棵勝者樹。只需要沿著從該結點到根結點的路徑修改這棵二叉樹,而不必改變其他比賽的結果。
Fig. 1
Fig.1是一個勝者樹的樣本。規定數值小者勝。
- b3 PK b4,b3勝b4負,內部結點ls[4]的值為3;
- b3 PK b0,b3勝b0負,內部結點ls[2]的值為3;
- b1 PK b2,b1勝b2負,內部結點ls[3]的值為1;
- b3 PK b1,b3勝b1負,內部結點ls[1]的值為3。.
當Fig. 1中葉子結點b3的值變為11時,重構的勝者樹如Fig. 2所示。
- b3 PK b4,b3勝b4負,內部結點ls[4]的值為3;
- b3 PK b0,b0勝b3負,內部結點ls[2]的值為0;
- b1 PK b2,b1勝b2負,內部結點ls[3]的值為1;
- 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
- #include <stdio.h>
- #define K 10
- #define MAX 65535
- int leaves[K+1];
- int successTree[K];
-
- /* 對於單個內部節點進行調整 */
- void adjust(int i)
- {
- int m,n;
- if(2 * i < K) /* 擷取它的左孩子結點 */
- m = successTree[2 * i];
- else
- m = 2 * i - K + 1;
- if(2*i+1<K) /* 擷取它的右孩子節點 */
- n = successTree[2*i+1];
- else
- n = 2 * i + - K + 2;
- successTree[i] = leaves[m] > leaves[n] ? n : m; /* 進行勝負判定 */
- }
- /* 初始化葉子節點並對內部節點進行類似於堆的調整 */
- void initTree()
- {
- for(int i=1;i<K+1;i++)
- scanf("%d", &leaves[i]);
- for(int i=K-1;i>0;i--)
- adjust(i);
- }
- /* 自下而上對勝者樹進行調整 */
- void adjustToRoot(int i)
- {
- int parent = (i + K - 1) / 2; /* 對從當前節點到根節點路徑上的所有
- * 節點進行調整 */
- while(parent>0)
- {
- adjust(parent);
- parent = parent / 2;
- }
- }
-
- int main()
- {
- freopen("in","r",stdin);
- initTree();
- for(int i=1;i<K+1;i++) /* 每次用最大值替換掉冠軍節點,並對樹
- * 進行調整,最終得到升序排序的序列 */
- {
- printf("%d ", leaves[successTree[1]]);
- leaves[successTree[1]]=MAX;
- adjustToRoot(successTree[1]);
- }
- return 0;
- }
二、敗者樹
敗者樹是勝者樹的一種變體。在敗者樹中,用父結點記錄其左右子結點進行比賽的敗者,而讓勝者參加下一輪的比賽。敗者樹的根結點記錄的是敗者,需要加一個結點來記錄整個比賽的勝利者。採用敗者樹可以簡化重構的過程。
Fig. 3
Fig. 3是一棵敗者樹。規定數大者敗。
- b3 PK b4,b3勝b4負,內部結點ls[4]的值為4;
- b3 PK b0,b3勝b0負,內部結點ls[2]的值為0;
- b1 PK b2,b1勝b2負,內部結點ls[3]的值為2;
- b3 PK b1,b3勝b1負,內部結點ls[1]的值為1;
- 在根結點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
- 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);
- }
(轉)敗者樹 和 勝者樹---數組實現