循环赛算法实现-支持奇偶数 Round Robin Tournament Scheduling algorithm

 

问题:
设有n个运动员要进行网球循环赛。用分治法设计一个满足以下要求的比赛日程表
(1)每个选手必须与其他n-1个选手各赛一次;
(2)每个选手一天只能赛一次;
(3)当n是偶数时,循环赛进行n-1天,当n是奇数时,循环赛进行n天
循环赛轮转算法分析,以下内容是其于一篇英文算法分析写的的,出错请指教.

 首次适应轮转算法的程序实现的正确性需要一些数学理论基础,这个文档就是总结我对方面的一些分析。当然,针对循环赛日程还有一些更加简单的基于轮转的算法

定义:

循环赛日程是对nn为偶数)个选手比赛进行安排的过程,比赛安排成n-1轮,n个选手组成n/2个唯一的(注:单循环)组,使得每一轮中由若二不同组组成。

总选手数:n

总轮数:n-1;

总组数:m=n/2

一次单循环赛的比赛总数:np=n(n-1)/2=nm

以整数1-n标识每一场比赛,以Pij标识选手ij的比赛,0<i<j,Pij的值表示比赛的安排序列1<=Pij<=np,P组成以下标识组:

  

Pij=E(i,j)是一个枚举映射函数,要注意的是由于E是任意np组比赛的映射,所以映射的可能数为np!

定义这样一个迭代函数Eu,他表示下图矩阵右上部分的元素,迭代顺序为从左到右,从上到下:

定义Sx,它代表包含有np个比赛日程安排的矩阵,如下图:

 

 

Sx表示所有比赛安排,当然也包括不符合单循环赛规则的

Sij=E(i', j')表示S i' j'能从E中找到,

考虑任意一个Sx,n=6

S3,5=10=E(4,5), i=3;j=5;i'=4;j'=5

首次适应算法的目标不断检验比赛日程对战安排Sij,直到有符合单循环规则日程安排产生。对战安排Sij通过枚举E(i' j')产生.

问题:刚开始时,首次适应算法以哪场比赛作为轮转的开始?

S*=所有的Sx, S*中总共有np!种可能的日程矩阵.np!E中排列总数

定义

  Gi={Si1,Si2…Sim}

表示第i轮比赛的对战安排

定义

  等价划分 为所有Sx=Sy的比赛安排

也就是说对两个日程矩阵,如果它们二者通过在Gi中重排Sij并在Sx中重排Gi最终一模一样的话,那么说这两个日程矩阵是等价的。

问题:总共有多少个等价划分?每个等价划分里有多少种对战安排?

我们知道SxGi组成,i=1,2…n-1

考虑轮与轮之间的次序和每一轮内的对战顺序,则总共m!(n-1) 种可能的对战日程安排

分析:

假如轮次之间次序固定:第一轮对战有m!种排列方式,总共有n-1.所以按照排列组合的原理,共有m!(n-1) 种可能的对战日程安排

假如轮次之间次序不固定:第一轮对战有m!种排列方式,总共有n-1轮,轮次安排有(n-1)!.所以按照排列组合的原理,共有m!(n-1)(n-1)种可能的对战日程安排

由此可以推测任何Sx所处的等价分类总共有m!(n-1)(n-1)种日程矩阵,更进一步可以推测

等价分类的总数为np!/[ m!(n-1)(n-1)]=[m(n-1)]!/ [ m!(n-1)(n-1)].

从分析中可以得到的是,如果一个循环比赛日程存在的话,那么它肯定属于[m(n-1)]!/ [ m!(n-1)(n-1)]个等价分类中的一个。可以肯定的是对n个选手的循环比赛日程至少存在一个解决方案。

结论:只有由Si1…Sim对每一个i,i=1…n-1是唯一的,Sx才是一个循环比赛日程矩阵

问题:这个算法是怎么实现的?

假设有一个n位的长整数i,每一位对应一个选手,设选手号码是d,i的值就是2<<d-1

 

Group为一个n位整数,用来存放某轮比赛参与的选手。如果Group所有的位都为1则所有的选手都已经参与了这轮比赛。

请看用整数枚举对战的一个向量表。向量表中每一个元素都有两个字段onetwo,这此字段表示对战参与的选手,向量表是表来获取对战组和用来检查对战组是否可以放入当前对战轮次的。

          .选手数为6时所有可能的对战分组

一轮对战日程安排Group的建立就是通过不断地从此向量表中选择对战组和检验实现的。如果当前向量合适当前轮次,则就奖其加入到对战日程中,否则就检查下一个向量. If the enumeration vector is exhausted

without a pair being added to the plan vector, then the last pair in the plan vector is

‘unplanned’ and the hunt continues using the next pair of the enumeration vector.

 当检查到当前向量v可以放入当前轮次时,则将vGroup作或运算赋值给Group即达到加入Group的目的。

Group|=v;

同时,从Group中移出某个对战组用与运算,如:

   Group&=~v

 

一个产生Pij序列值的例子:

2.c/c++代码实现

 

 #include <stdio.h>
#include <stdlib.h>

#define FALSE 0
#define TRUE  1

#define Maxplayers 20
#define MaxCombinations (Maxplayers/2)*(Maxplayers-1)

struct game { int one, two; };

int   players;        /* 选手总数 */
long  combinations; /* 比赛场数,一场有两人参加,故起名combinations */
int   a, b, c, i, m,
      startC,
      matchCount,
      roundCount,
      index;

long  round_set;    /*轮次选择的标志,如果某位被置1则相应的选手已经被加入当前轮次 */
long  totalChecks;

struct game tourn[1+MaxCombinations];   /* 单循环比赛tournament */
int    mList[1+Maxplayers/2];             /* matches */
struct game cList[1+MaxCombinations];   /* 所有比赛的对战组(一组两人) */
int    cUsed[1+MaxCombinations];        /* 已经使用的对战组 */


void ShowSchedule(int event)//显示日程表
{
  int index, r, m ;

  fprintf( stdout, "共有/n%d 个选手", players-event );
  fprintf( stdout, "/n        ");
  for (r=1; r <= players/2; r++) fprintf( stdout, " 好戏%d", r);
  fprintf( stdout, "/n" );
  fprintf( stdout, "        +-");
  for (r=1; r <= (players/2)*6-2; r++) fprintf( stdout, "-" );
  fprintf( stdout, "/n" );

  index = 1;
  for (r=1; r <= players-1; r++) {
    fprintf( stdout, "第%2d轮 |", r);
    for (m=1; m <= players/2; m++) {
      fprintf( stdout, "%2d&%2d ", tourn[index].one, tourn[index].two );
      index++;
    }
    fprintf( stdout, "/n" );
  }
  fprintf( stdout, "/n%d 次尝试对战分组/n/n", totalChecks );
}


void ClearArrays()//清除以前的标记
{
  int i;

  for (i=0; i <= MaxCombinations; i++) { tourn[i].one = 0; tourn[i].two = 0; }
  for (i=0; i <= Maxplayers/2; i++) mList[i] = 0;
  for (i=0; i <= MaxCombinations; i++) { cList[i].one = 0; cList[i].two = 0; }
  for (i=0; i <= MaxCombinations; i++) cUsed[i] = 0;
}


void doSchedule(int flag)//安排比赛日程
{
  players = 4;

  while (players <= Maxplayers) {

    combinations = players/2 * (players-1);
    totalChecks = 0;

    ClearArrays();

    /* 初始化所有比赛对战图 */  /*       a       */
    m = 1;                                 /* b 1 2 3 4 5   */
    for (a=1;   a < players; a++)            /* 1             */
    for (b=a+1; b <=players; b++) {          /* 2 .           */
      cList[m].one = a;                    /* 3 . .         */
      cList[m].two = b;                    /* 4 . . .       */
      m++;                                 /* 5 . . . .     */
    }

    roundCount = 1;
    index = 1;

    while (roundCount <= players-1) {

      matchCount = 1;
      round_set = 0;
      for (i=0; i <= Maxplayers/2; i++) mList[i] = 0;
      startC = roundCount;

      /* 开始查找,找到合适的对战组加入当前的比赛轮次*/
   /*
     注:因为算法已经被验证对任何一个选手数目,总会有一个解决方案,所以这里不怕会有死循环
   */

      while (matchCount <= players/2) {

            c = combinations + 1;
            while (c > combinations) {

              c = startC;

              /* 查找下一个可以加入当前轮次的对战组 */
              while ((c <= combinations) &&
                     ((round_set & (1 << cList[c].one)) ||
                      (round_set & (1 << cList[c].two)) ||
                      (cUsed[c])
                     )
                    )
                c++;

              if (c > combinations) {
             /* 没有找到合适的对战组,故回 */
                do {
                 mList[matchCount] = 0;

                 matchCount--;
                 index--;

                 round_set &= ~(1 << cList[mList[matchCount]].one);
                 round_set &= ~(1 << cList[mList[matchCount]].two);

                 cUsed[mList[matchCount]] = FALSE;

                 tourn[index].one = 0;
                 tourn[index].two = 0;
            /*cList:已经使用的组,mList:合适的对战组*/
                } while (cList[mList[matchCount]  ].one !=
                         cList[mList[matchCount]+1].one);

                startC = mList[matchCount] + 1;
              }
            }

           /* 找到一个合适的对战组,并放到当前比赛轮次中
            */

            tourn[index] = cList[c];
            totalChecks++;
           /*动态显示进度*/
            if ((totalChecks % 1000) == 0) fprintf( stdout, "%d/033A/n", totalChecks );

            cUsed[c] = TRUE;
            mList[matchCount] = c;

            startC = 1;

            round_set |= (1 << cList[c].one);
            round_set |= (1 << cList[c].two);

            index++;
            matchCount++;
      }

      /* 进入下一轮比赛的安排 */
      roundCount++;
    }


    fprintf( stdout, "          " );
    ShowSchedule(flag);
 if(flag==0)
 printf("按回车自动演示下一个选手数目");
 else printf("奇数情况,遇到虚拟选手%d作为轮空处理!/n按回车自动演示下一个选手数目",players);
 getchar();
    players += 2;
  }
}
main()
{
  char c;
  while(true)
  {
  printf("按e演示偶数,q退出,其它键演示奇数");
  scanf("%c",&c);
  if(c=='e' || c=='E')
  doSchedule(0);
  else if (c=='y'||c=='Y')break;
else  doSchedule(1);
  }

}

 

运行效果:


 

3.参考文章 http://www.devenezia.com/downloads/round-robin/
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值