提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档
竞赛树:
tournament tree, 也是以可完全二叉树,所以使用数组描述效率最好,竞赛树的基本操作是替换最大(小)元素
赢者树和输者树:
为了便于实现,需要将赢者树限制为完全二叉树。n个参与者的赢者树,具有n个外部节点,n-1个内部节点。每个内部节点记录的是在该节点比赛的赢者。
最大赢者树和最小赢者树:
为了确定输赢者,给每个选手一个分数,分数最大挥着最小的获胜,称为最大最小赢者树:
赢者树的初始化:
采用二叉树的后序遍历方法。每遍历一个节点,就相当于进行一场比赛。
排序:
内部排序法: 例如堆排序,插入排序,需要将排序的元素全部放入计算机内存进行排序,但是当待排序的元素数量超出内存的大小时,就需要使用外部排序。
外部排序步骤:
-
生成一些初始的归并段(有序的部分待排序元素)
-
合并这些归并段(利用赢者树)
C++实现赢者树:
假设用完全二叉树的数组表示赢者树,一棵赢者树有n个选手,则需要n-1个内部节点tree[1:n-1],选手用数组player[1:n]表示。因此tree[i]是数组player的一个索引。
因此,必须确定外外部节点player[i]的父节点tree[P]。
选手个数n,内部节点个数n-1,最底层最左端的内部节点编号为2^s,其中s = 【log2(n-1)】, 最底层内部节点的个数n-s.最底层外部节点的个数lowExt = 2*(n-s),取offset = 2*s-1.则对于任何一个外部节点player[i], 其父节点tree[P]由以下公式给出:
赢者树的初始化:
为了初始化赢者树,需要从右孩子选手开始,进行他们所参加的比赛,而且逐层往上,只要是右孩子上升到比赛节点,就可以进行比赛。如在上图中,先进性p[2]的比赛,在进行p[3]的比赛得到结果t[2],但是t[2]的比赛不能进行,因为它是左孩子,所以需要进行p[5]的比赛,得到t[3],t[3]是右孩子,进行t[3]的比赛,得到t[1];
重新组织比赛:
当选手theplayer的值发生变化时,从外部节点player[theplayer]到根tree[1]的路径上,一部分或全部比赛都要重赛。
二、竞赛树的实现代码:
1.引入库
代码如下(示例):
#ifndef TOURNAMENT_TREE_H
#define TOURNAMENT_TREE_H
#include <iostream>
#include <cmath>
using namespace std;
template<class T>
class completeWinnerTree
{
public:
completeWinnerTree(T *thePlayer, int theNumberOfPlayers)
{
tree = NULL;
initialize(thePlayer, theNumberOfPlayers);
}
~completeWinnerTree() {delete [] tree;}
void initialize(T*, int);
int winner() const//返回赢者树的根节点。
{
return tree[1];
}
int winner(int i) const//返回第i个节点的胜者
{
return (i < numberOfPlayers) ? tree[i] : 0;
}
// return winner of match at node i
void rePlay(int);
void output() const;
private:
int lowExt; // lowest-level external nodes
int offset; // 2^log(n-1) - 1
int *tree; // array for winner tree
int numberOfPlayers;
T *player; // array of players
void play(int, int, int);
};
template<class T>
void completeWinnerTree<T>::play(int p, int leftChild, int rightChild)
{
// play matches beginning at tree[p]
// leftChild is left child of p
// rightChild is right child of p
//如果左节点的数据较小则左节点晋级。
tree[p] = (player[leftChild] <= player[rightChild]) ?
leftChild : rightChild;
// more matches possible if at right child
while (p % 2 == 1 && p > 1)//如果当前节点是右节点,且不是根节点,那么继续进行比赛。
{// at a right child
tree[p / 2] = (player[tree[p - 1]] <= player[tree[p]]) ?tree[p - 1] : tree[p];
p /= 2; // go to parent
}
}
template<class T>
void completeWinnerTree<T>::initialize(T *thePlayer, int theNumberOfPlayers)
{
// Create winner tree for thePlayer[1:numberOfPlayers].
//比赛参与者最少2个人否则报错
int n = theNumberOfPlayers;
if (n < 2)
{
//throw illegalParameterValue("must have at least 2 players");
cout << "The players must > 2" << endl;
exit(0);
}
player = thePlayer;//获取外节点数组。
numberOfPlayers = n;
delete [] tree;//初始化树节点,因为第0位置不放数据,所以实际大小为n-1.
tree = new int [n];//
// compute s = 2^log (n-1)
int i, s;//计算内部节点最左边的节点编号,下面采取寻访方式计算s,非常巧妙
for (s = 1; 2 * s <= n - 1; s += s);
lowExt = 2 * (n - s);//计算最低层外部节点的个数
offset = 2 * s - 1;
// play matches for lowest-level external nodes
for (i = 2; i <= lowExt; i += 2)//首先进行最底层外部节点的比赛,i从2开始且每次+2是为了每次都是从右子节点开始进行比赛
play((offset + i) / 2, i - 1, i);
// handle remaining external nodes
if (n % 2 == 1)//如果倒数第二层最左边的元素为某个父节点的右节点,则进行如下处理,即比赛。
{// special case for odd n, play internal and exteral node
play(n/2, tree[n - 1], lowExt + 1);
i = lowExt + 3;//然后从lowExt+3即倒数第二层外部节点的右儿子开始比赛
}
else i = lowExt + 2;//否则右儿子为lowExt+2
// i is left-most remaining external node
for (; i <= n; i += 2)//然后开始处理倒数第二层其他外部节点。
play((i - lowExt + n - 1) / 2, i - 1, i);//play每次都会处理到上层的左节点为止,然后再去处理下一个右节点。
}
template<class T>
void completeWinnerTree<T>::rePlay(int thePlayer)
{// Replay matches for player thePlayer.
int n = numberOfPlayers;
if (thePlayer <= 0 || thePlayer > n)//改变的选手必须要在范围内。
{
cout << "Invalid index" << endl;
exit(0);
}
//throw illegalParameterValue("Player index is illegal");
int matchNode, // node where next match is to be played需要重赛的父节点
leftChild, // left child of matchNode该父节点的左孩子
rightChild; // right child of matchNode右孩子
// find first match node and its children
if (thePlayer <= lowExt)//如果改变的参数在最低层
{
// begin at lowest level
matchNode = (offset + thePlayer) / 2;//要求重赛的父节点
leftChild = 2 * matchNode - offset;//及其子节点
rightChild = leftChild + 1;
}
else
{
matchNode = (thePlayer - lowExt + n - 1) / 2;//如果要求重赛的选手在倒数第二层的外部节点。
if (2 * matchNode == n - 1)//如果要求重赛的选手在倒数第二层与倒数第一层的交界点处
{
leftChild = tree[2 * matchNode];
rightChild = thePlayer;
}
else//否则在倒数第二层的外部节点处。
{
leftChild = 2 * matchNode - n + 1 + lowExt;
rightChild = leftChild + 1;
}
}
tree[matchNode] = (player[leftChild] <= player[rightChild])//重赛
? leftChild : rightChild;
// special case for second match
if (matchNode == n - 1 && n % 2 == 1)//这里没弄明白。。。
{
matchNode /= 2; // move to parent
tree[matchNode] = (player[tree[n - 1]] <=
player[lowExt + 1]) ?
tree[n - 1] : lowExt + 1;
}
// play remaining matches
matchNode /= 2; // move to parent
for (; matchNode >= 1; matchNode /= 2)//依次往上进行重赛直到主节点。
tree[matchNode] = (player[tree[2 * matchNode]] <=player[tree[2 * matchNode + 1]])?tree[2 * matchNode] : tree[2 * matchNode + 1];
}
template<typename T>
void completeWinnerTree<T>::output() const
{
cout << "number of players: " << numberOfPlayers << endl;
cout << "lowExt = " << lowExt << endl;
cout << "offset = " << offset << endl;
for(int i=1; i<numberOfPlayers; i++)
{
cout << tree[i] << " ";
}
cout << endl;
}
#endif
代码来源:https://www.cise.ufl.edu/~sahni/dsaac