利用敗者樹進行外部排序時,要根據葉節點構造敗者樹。仔細理解了這個構造的過程。
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);
}
}