一、循环赛日程表-设计规则

1.每位选手必须与其他 n - 1 位选手各比赛 1 次

2.每位选手每天只能比赛 1 次
3.循环赛一共进行 n - 1 天
4.选手人数为 n = ^{2^{k}}

假设比赛选手为8(满足n = ^{2^{k}}),则循环赛需要进行7天,如下表1所示:

选手号第一天第二天第三天第四天第五天第六天第七天
1
2
3
4
5
6
7
8

图表1

为了满足每位选手必须与n-1选手比赛1次,并且每位选手每天只能比赛1次的要求,如表2所示:

选手号第一天第二天第三天第四天第五天第六天第七天
12345678
21436587
34127856
43218765
56781234
65872143
78563412
87654321

图表2


二、循环赛日程表-递归设计思路

        如果将所有选手分为两半(故:n/2),那么就将排好的8×8赛程表割分成4个4×4的赛程表,可以发现左上角4×4的赛程表与右下角4×4的赛程表是一样的,左下角4×4的赛程表与右上角4×4的赛程表也是一样的。

        如果再将选手分为两半(故:n/2/2),那么就会将4×4的赛程表割分4个2×2的赛程表,左上角2×2的赛程表与右下角2×2的赛程表是一样的,左下角2×2的赛程表与右上角2×2的赛程表也是一样的。

      那么就可以将每次排好的左上角的赛程表copy到右下角,将每次排好的左下角的赛程表copy到右上角,那么就可以完成整个赛程表的排列。

      以上符合分治策略的思想,将所有的选手分为两半,n个选手的比赛日程就可以通过分成n/2个选手设计的比赛日程表来决定。采用递归的方式对选手进行割分,直至割分到只剩下1位选手时,比赛日程表就不在需要为其排列。

边界条件:n = 1

分解子问题:

1.递归左上角赛程表

2.递归左下角赛程表

3.将左上角赛程表copy到右下角

4.将左下角赛程表copy到右上角


三、循环赛日程表-设计代码(递归)

//C语言编写
#include<stdio.h>

int table[8][8];

//从(行:a~a+n-1,列:b~b+n-1)copy到(行:k~k+n-1,列:h+n-1)
void Copy(int a, int b, int k, int h, int n){    
    int i,j;
    for(i=0;i<n;i++){
        for(j=0;j<n;j++){
            table[k+i][h+j] = table[a+i][b+j];
        }
    }
}

//循环赛日程表-递归方式
void Table(int a, int b, int n){   //a,b为左上角的行号和列号,n比赛选手位数
    if(n == 1){
        return;
    }
    else{
        Table(a,b,n/2);            //将左上表进行割分
        Table(a+n/2,b,n/2);        //将坐下表进行割分
        Copy(a,b,a+n/2,b+n/2,n/2); //将左上表copy到右下表
        Copy(a+n/2,b,a,b+n/2,n/2); //将左下表copy到右上表
    }
}

//初始化二维数组
void InitTable(int table[8][8], int n){    
    int i = 0;
    int j = 1;
    for(i = 0; i < n; i++,j++){
        table[i][0] = j;
    }
}

//打印二维数组
void PrintTable(int table[8][8], int n){   
    int i,j;
    for(i = 0; i < n; i++){
        for(j = 0; j < n; j++){
            printf("%d\t",table[i][j]);
        }
        printf("\n");
    }
}

//主函数
int main(){
    InitTable(table,8);
    PrintTable(table,8);
    printf("\n");
    Table(0,0,8);
    PrintTable(table,8);
    return 0;
}

四、循环赛日程表-递推设计思路

按照上面的要求,通过递推的思想也是很容易的​(如图所示)

图表3

      可以发现想要完成循环赛日程表,那么至少需要执行n-1遍(例如:8个选手,那就需要执行7遍),执行一遍需要将左上表copy到右下表,将左下表copy到右上表。

      那么知道这个规则之后,就需要找出执行一遍的步长(r)比如说图表3上编号1,从左上表的1copy到右下表的1,从左下表的2copy到右上表的2,可以发现从左上表copy到右下表与从左下表copy到右上表两者的步长为1;再比如说图表3上编号5,从左上表的2*2矩阵copy到右下表的2*2矩阵,从左下表的2*2矩阵copy到右上表的2*2矩阵两者的步长为2;再比如说图表3上编号7,从左上表的4*4矩阵copy到右下表的4*4矩阵,从左下表的4*4矩阵copy到右上表的4*4矩阵两者的步长为4

      那么可以发现步长(r)是1,2,4;也就是r=r*2

      如图表3所示,通过数组坐标来分析,如何设计填写从左上表copy到右下表与从左下表copy到右上表:

图表3编号

from

左上表

to

右下表

from

左下表

to

右上表

步长(r)
1[0][0][1][1][1][0][0][1]1
2[2][0][3][1][3][0][2][1]1
3[4][0][5][1][5][0][4][1]1
4[6][0][7][1][7][0][6][1]1
5[0][0][2][2][2][0][0][2]2
6[4][0][6][2][6][0][4][2]2
7[0][0][4][4][4][0][0][4]4

左上表copy到右下表

      1.from左上表[纵坐标][横坐标]中,[纵坐标]都是偶数按照2*r增长[横坐标]都是初始值(0);

     2.to右下表[纵坐标][横坐标]中,[纵坐标]都是奇数按照from左上表[纵坐标]+r并且2*r增长,[横坐标]都是按照from左上表[横坐标]+r;

copy(from左上表[纵坐标],from左上表[横坐标],to右下表[纵坐标],to右下表[横坐标],r)

=

copy(from左上表[纵坐标],初始值(0),from左上表[纵坐标]+r,from左上表[横坐标]+r)

从左下表copy到右上表

      1.from左下表[纵坐标][横坐标]中,[纵坐标]都是奇数按照2*r增长[横坐标]都是初始值(0);

      2.to右上表[纵坐标][横坐标]中,[纵坐标]都是偶数按照from左下表[纵坐标]+r并且2*r增长,[横坐标]都是按照from左下表[横坐标]+r;

copy(from左下表[纵坐标],from左下表[横坐标],to右上表[纵坐标],to右上表[横坐标],r)

=

copy(from左下表[纵坐标],初始值(0),from左下表[纵坐标]+r,from左下表[横坐标]+r)


五、循环赛日程表-设计代码(递推)

//C语言编写
//循环赛日程表-递推方式
void Table(int a, int b, int n){
    int i = 0;
    int r = 1;
    int k = n;
    while(k>1){                         //记录执行次数
        for(i=0;i<n;i=i+2*r){           //i每次增加本次循环的2*步长,因为每次进行两次copy
            Copy(a+i,b,a+i+r,b+r,r);    //左上表copy右下表
            Copy(a+i+r,b,a+i,b+r,r);    //左下表copy右上表
            k--;                        //执行次数减一
        }
        r = r * 2;                      //步长
    }
}

Logo

开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!

更多推荐