理解败者树的初始化

利用败者树进行外部排序时,要根据叶节点构造败者树。仔细理解了这个构造的过程。

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);

    }

}

 

 

 

 

 

 

 

 

 

 


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