竞赛树之赢者树的实现

赢者树:有n个选手的一颗赢者树是一颗完全二叉树,它由n个外部节点和n-1个内部节点,每个内部节点记录的是在该节点比赛的赢者。
简单的说就是有一颗完全二叉树,从叶节点开始,左右两个子节点谁大(小),就把谁的值赋值给他们的父节点,然后父节点在和其兄弟节点进行比较将较大(小)者赋值给其父节点直到根节点,那么根节点的数据就是冠军。
注意:现实的竞赛所对应的树不一定都是完全二叉树,但是用完全二叉树能使比赛的场次最少。

赢者树实现原理:
假如用完全二叉树的数组表示来表示赢者树。一颗赢者树有n名选手,需要n-1个内部节点tree[1:n-1]。选手(或外部节点)用数组palyer[1:n]表示,因此tree[i]是数组player的一个索引,类型为int。在赢者树的节点i对应比赛中,tree[i]代表赢者,为实现这种对应关系,我们必须能够确定外部节点player[i]的父节点tree[p]。当外部节点的个数为n时,内部节点的个数为n-1。最底层最左端的内部节点,其编号为s,且s=2^[log2(n-1)] (这里中括号代表向下取整)。因此,最底层内部节点的个数是n-s,最底层外部节点个数lowExt是这个数的2倍。倒数第二层最左端的外部节点号为lowExt+1。令offset=2*s-1。对于任何一个外部节点player[i],其父节点tree[p]由以下公式给出:

      (i+offset)/2    i<=lowExt.
p=
      (i-lowExt+n-1)/2   i>lowExt

赢者树的初始化:
我们计算比赛时是从左往右,所以当当前计算的节点为某个父节点的右子节点时,我们就能组织一场比赛,因为左节点的胜者一定产生了,否则不会轮到右节点,我们是从左往右开始计算的。

// node type used by winner tree

#ifndef player_
#define player_

struct player
{
   int id, key;

   operator int () const {return key;}
};

#endif
// abstract class winner tree
// all methods are pure virtual functions

#ifndef winnerTree_
#define winnerTree_

using namespace std;

template<class T>
class winnerTree 
{
   public:
      virtual ~winnerTree() {}
      virtual void initialize(T *thePlayer, int theNumberOfPlayers) = 0;
         // create winner tree with thePlayer[1:numberOfPlayers]
      virtual int winner() const = 0;
         // return index of winner
      virtual void rePlay(int thePLayer) = 0;
         // replay matches following a change in thePLayer
};
#endif

以上是winnertree的虚类。

#ifndef completeWinnerTree_
#define completeWinnerTree_

#include <iostream>
#include "winnerTree.h"
#include "myExceptions.h"

template<class T>
class completeWinnerTree : public winnerTree<T>
{
   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");

   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)//改变的选手必须要在范围内。
      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];
}

以上为重赛代码,其中有一句一直没弄明白。

代码来源于:https://www.cise.ufl.edu/~sahni/dsaac

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值