理解敗者樹的初始化

利用敗者樹進行外部排序時,要根據葉節點構造敗者樹。仔細理解了這個構造的過程。

I.

一個直接的思路就是類似我們手工構造一個敗者樹的過程。開始初始化所有敗者樹的節點值爲-1。依次填含有葉節點的節點。A包含葉節點,比較A的左右孩子節點(左孩子可能不是葉節點,右孩子是葉節點),敗者填入A。勝者與A的父節點B比較,如果B沒有填值,則勝者填入B;如果B已經填值,用勝者值與B的值比較,敗者值填入B,然後又獲得新的勝者值。再用新的勝者值與B的父節點C比較。重複類似的比較直至勝者值被填入某個節點中。

一個問題:填完所有的子節點含有葉節點的節點後,敗者樹就初始化完?一個不嚴格的理解是:雖然敗者樹的節點數只是numLeaves,沒有包含葉節點,我們可以假想這個樹包含葉節點,並且值都是-1。填完後所有子節點包含葉節點的節點後,意味着所有葉節點都已經插入到敗者樹中,則敗者樹初始化完成。

 

/*

  初始化一個敗者樹,勝者是Key值小的元素。

 numLeaves:葉節點個數

 KeyT leaves[numLeaves+1]:節點數組下標從1開始,保存每個葉節點的Key值

  intloserTree[numLeaves]:只存儲每個葉節點在數組leaves中的下標。loserTree[0]存儲冠軍,所以節點數組下標1開始。

*/

template <class KeyT>

void InitTree(KeyT * leaves,intnumLeaves,int *loserTree){

 

   for(int i=0;i<numLeaves;i++){ //初始化每個樹的節點值爲-1

       loserTree[i]=-1;

    }

 

   int rchildInx=-1,lchildInx=-1;

   //依次填入每個節點,同時調整父節點,保證每填入一個節點後就是一個正確的敗者樹。

   for(int i=numLeaves-1; i>=(numLeaves/2); --i){

       //左孩子的值

       if(2*i>=numLeaves && 2*i<2*num){

           lchildInx = 2*i-numLeaves+1;

       }else{

           assert(2*i>=0 && 2*i<num);

           lchildInx = loserTree[2*i];

       }

       //右孩子的值

       rchildInx = 2*i+1;

 

       //比較後得到敗者和勝者。

       int victorInx=-1;

       if(leaves[rchildInx]>leaves[lchildInx]){

           loserTree[i] = rchildInx;

           victorInx = lchildInx;

       }else{

           loserTree[i] = lchildInx;

           victorInx = rchildInx;

       }

 

       //尋找勝者的位置

       int j=i/2;       

       while(j>=0){

           if(loserTree[j] == -1){

                loserTree[j] = victorInx;

                break;

           }else{

               if(leaves[victorInx]>leaves[loserTree[j]){

                    int temp = loserTree[j];

                    loserTree[j] = victorInx;

                    victorInx = temp;

                }

                j=j/2;

           }

       }

    }

}

 

 

II.

書上使用了另外更好的方法。把構成初始敗者樹的過程看成調整的過程。調整的過程是:當我們已經構造好一個敗者樹,取走勝者,然後在這個勝者對應的葉節點中填入一個值後,我們就要調整新的樹,使其成爲敗者樹。那麼假設有k個葉節點保存在數組A[k]中。

 

如何構造初始樹?

假設初始敗者樹是Ti,假設葉節點是X[h,h+1,...,h+k-1],其中X[g](h<=g<=j)是勝者,用A[1]替換X[g]並且調整後得到Ti1,Ti1的敗者就不可以是A[1],否則插入A[2]時,A[1]就被替換,最後形成的敗者樹就不是我們需要的敗者樹。一個更一般的邏輯就是:假設A[1...m](m<k)已經被替換得到一個新的敗者樹,那麼這個新的敗者樹的勝者一定不能是A[1...m]中的某個值,必須是Ti原先的葉節點。如何來構造這樣的一個初始敗者樹Ti?如果我讓Ti原先的葉節點都小於(或者都大於)A中所有的值,那麼可以滿足上述要求。選用一個特例來構造Ti:Ti所有的節點都是最小值,這個最小值是比較的關鍵字類型的最小值。如果選用A中最小的葉節點值來構造Ti,也應該是可以的,但是那樣多了一步:找到A中最小的值。

 

爲什麼不構造一個初始樹,然後逐個替換葉節點?

例如,假設初始敗者樹是Ti,假設葉節點是X[h,h+1,...h+k-1]。A[1…k]是我們要構建的敗者樹的葉節點。用A[1]替換X[h],然後調整;再用A[2]替換x[h+1],然後調整;依次類推,把Ti所有的葉節點替換爲A中的值,從而得到我們需要的敗者樹。

如果替換任意一個節點,調整的過程要複雜。


假如葉節點N1是任意一個葉節點,用一個新的值替換N1原先的值。比較N1和N2,可以得到N3的值,同時得到一個勝者值,假設爲Victor。節點N4的值就需要重新確定。用Victor與子樹S1的勝者進行比較。因爲N1不是原先敗者樹的勝者,所以原先N4保存的值就不一定是子樹S1的勝者。S1的勝者可能是N4的父節點的值,或者是N4某個層次的祖父節點的值。但如果節點N1原先的值是原先敗者樹的勝者,那麼N4的值就是子樹S1的勝者。構造原先敗者樹時,N4的值是子樹S1的勝者和子樹S2的勝者比較後的敗者,既然N1的值是原先敗者樹的勝者,子樹S2的勝者就是N1的值,那麼子樹S1的勝者與子樹S2的勝者(也就是N1的值)比較就是敗者,這個敗者被填入節點N4,所以N4的值就是子樹S1的勝者。

 

/*

  葉子數組leaves中第q個節點是原先的勝者,現在已經取得了一個新的值。調整新的樹使其又成爲敗者樹。

 numLeaves:葉節點個數

 KeyT leaves[num+1]:節點數組下標從1開始,保存每個葉節點的Key值

  intloserTree[num]:只存儲每個葉節點在數組leaves中的下標。loserTree[0]存儲冠軍,所以節點數組下標從1開始

  q:葉子數組leaves中第q個節點是原先的勝者,現在已經取得了一個新的值。 

*/

template <class KeyT>

void AdjustTree(KeyT * leaves,intnumLeaves,int *loserTree,int q){

   int fInx = (numLeaves+q-1)/2;

   while(fInx>=0){

       if(fInx == 0){

           loserTree[0] = q;

           break;

       }

       if(leaves[q]>leaves[loserTree[fInx]){

           int tmp = loserTree[fInx];

           loserTree[fInx] = q;

           q = tmp;

       }

       fInx = fInx/2;

    }

}

 

/*

  已知葉節點構造敗者樹。

 numLeaves:葉節點個數

 KeyT leaves[num+1]:節點數組下標從1開始,保存每個葉節點的Key值

  intloserTree[num]:只存儲每個葉節點在數組leaves中的下標。loserTree[0]存儲冠軍,所以節點數組下標從1開始

 

*/

template <class KeyT>

void InitTree(KeyT * leaves,intnumLeaves,int *loserTree){

   leaves[0]=MINKEY;

   for(int i=0; i<numLeaves;i++){

       loserTree[i] = 0;

    }

   for(int i=numLeaves; i>0; i--){

       AdjustTree<KeyT>(leaves,numLeaves,loserTree,i);

    }

}

 

 

 

 

 

 

 

 

 

 


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章