败者树是计算机科学学科里的一种数据结构。可用于外部排序中提高效率。败者树实际上是一棵完全二叉树,可以看做是胜者树的一种变体。败者树简化了重构。败者树的重构只是与该结点的父结点的记录有关,而胜者树的重构还与该结点的兄弟结点有关。败者树中每个叶子结点存放各归并段在归并过程中当前参加比较的记录,每个非叶子结点记忆其两个子女结点中记录排序码小的结点(即败者)。
注意,败者树的重构跟胜者树是不一样的,败者树的重构只需要与其父结点比较。
b3与结点ls[4]的原值比较,ls[4]中存放的原值是结点4,即b3与b4比较,b3负b4胜,则修改ls[4]的值为结点3。同理,以此类推,沿着根结点不断比赛,直至结束。
败者树重构过程如下:
将新进入选择树的结点与其父结点进行比赛:将败者存放在父结点中;而胜者再与上一级的父结点比较。
比赛沿着到根结点的路径不断进行,直到ls[1]处。把败者存放在结点ls[1]中,胜者存放在ls[0]中。
/**
* 实验题目:
* 创建一颗败者树(数值小的胜出)
* 实验目的:
* 领会外排序中败者树的创建过程和算法设计
* 实验内容:
* 编写程序,给定关键字序列(17, 5, 10, 29, 15),采用5路归并,
* 创建对应的一颗败者树,并输出构建过程。
* 备注:
* 败者树ls的结点类型loser_tree即为关键字类型key_type,对于k路归并,
* 败者树有一个冠军结点ls[0](存放最小的数值)和ls[1]~ls[K-1]的分支结点,
* 另外加上b[0]~b[K-1]的叶子结点,b数组设置为全局变量。
*/
#include <stdio.h>
#define K 5 // 5路平衡归并
#define MIN_KEY -32768 // 最小关键字值-∞
typedef int key_type;
typedef key_type loser_tree;
key_type b[K]; // b中存放所有的记录(关键字)
int cnt = 1; // 全局变量
/*-------------沿从叶子结点b[s]到根结点ls[0]的路径调整败者树---------------*/
static void adjust(loser_tree ls[K], int s)
{
int i, t;
t = (s + K) / 2; // ls[t]是b[s]的双亲结点
/*
* ls[4] = 5, b[4] = 15, b[ls[4]] = -∞
* ls[4] = 5, b[3] = 29, b[ls[4]] = -∞
* ls[3] = 5, b[2] = 10, b[ls[4]] = -∞
* ls[3] = 5, b[1] = 5, b[ls[4]] = -∞
* ls[2] = 5, b[0] = 17, b[ls[4]] = -∞
*/
while(t > 0)
{
if(b[s] > b[ls[t]]) // 若双亲结点ls[t]比较小(胜者)
{
i = s; // i指向败者(大者)
s = ls[t]; // s指向新的胜者
ls[t] = i; // 将败者(大者)放在双亲结点中
}
t = t / 2; // 继续向上调整
}
ls[0] = s; // 冠军结点存放最小者
}
/*---------------------输出败者树----------------------*/
static void disp_loser_tree(loser_tree ls[K])
{
int i;
printf("败者树:");
for(i = 0; i < K; i++)
{
if(b[ls[i]] == MIN_KEY)
printf("%d(-∞) ", ls[i]);
else
printf("%d(%d) ", ls[i], b[ls[i]]);
}
printf("\n");
}
/*---------------------创建败者树----------------------*/
static void create_loser_tree(loser_tree ls[K])
{
int i;
b[K] = MIN_KEY; // b[K]置为最小关键字
for(i = 0; i < K; i++) // ls[0]~ls[4]设置为5
ls[i] = K;
for(i = K - 1; i >= 0; --i) // 依次从b[K-1]=15,b[K-2]=29,...,b[0]=17出发调整败者
{
printf("第%d步,从b[%d]=%2d调整->", cnt++, i, b[i]);
adjust(ls, i);// 15, 29, 10, 5, 17
disp_loser_tree(ls);
}
}
int main(void)
{
int n = 5;
loser_tree ls[K]; // 定义败者树数组
key_type a[] = {17, 5, 10, 29, 15};
printf("%d路归并的关键字序列:", K);
for(int i = 0; i < n; i++)
{
b[i] = a[i];
printf("%d ", b[i]);
}
printf("\n");
create_loser_tree(ls);
printf("最终结果\n");
disp_loser_tree(ls);
return 0;
}
测试结果:
5路归并的关键字序列:17 5 10 29 15
第1步,从b[4]=15调整->败者树:5(-∞) 5(-∞) 5(-∞) 5(-∞) 4(15)
第2步,从b[3]=29调整->败者树:5(-∞) 5(-∞) 4(15) 5(-∞) 3(29)
第3步,从b[2]=10调整->败者树:5(-∞) 5(-∞) 4(15) 2(10) 3(29)
第4步,从b[1]= 5调整->败者树:5(-∞) 1(5) 4(15) 2(10) 3(29)
第5步,从b[0]=17调整->败者树:1(5) 4(15) 0(17) 2(10) 3(29)
最终结果
败者树:1(5) 4(15) 0(17) 2(10) 3(29)