目录
一、问题描述
循环比赛日程安排问题:设有n(n=2^k)支队伍參加循环赛,循环赛共进行n-1天,每支队伍要与其它n-1支队伍比赛一场,且每支队伍每天必须比赛一场,不能轮空。试按此要求为比赛安排日程。运用分治递归法解决该问题。
二、解题思路
首先安排奇数下标位置与偶数下标位置之间的比赛,就有n/2场,team[2k]=2k,全部奇数号组成一个序列[1,3...n-1],然后循环移动n/2-1次(比方第2个序列就是[3,5...n-1,1]),然后将该序列填充在team的奇数位置上。
接下来将队伍一分为二,奇数为一组,偶数为一组,分配安排其内部比赛(由于奇偶数之间前面已经安排过了啦)。以奇数组[1,3,5,7]为例(以n=8为例说明),我们仍然先安排奇数下标位置与偶数下标位置之间的比赛,也就是[15]与[37]之间的比赛,共同拥有2场(n/4)。
接下来,再将队伍一分为二,得到[15],[37],[04],[26],对每一部分,仍然是先安排奇数下标位置与偶数下标位置之间的比赛,共1场(n/8)。此时已不可再分出子队伍,计算结束。
三、代码
#include <iostream>
#include <stdio.h>
#include <math.h>
using namespace std;
// team: 比赛安排结构,team[2j] vs team[2j+1]
// n: team的总数,n = 2^k
// id: 第id轮的安排,id的范围[1, n-1]
void game(int *team, int n, int id){
int base = 2;
while (id > n/base){
id -= n/base;
base <<= 1;
}
for (int i=0; i<base/2; ++i){
int start = i+base/2+(id-1)*base;
for (int j=0; j<n/base; ++j){
team[i*2*n/base+2*j] = base*j+i;
team[i*2*n/base+2*j+1] = (start+base*j)%n;
}
}
}
void dump(int *arr, int n){//输出每天比赛安排
for (int i=0; i<n; i+=2)
printf("%02d-%02d ", arr[i], arr[i+1]);
printf("\n");
}
int main(){
int k;
printf("请输入k的值:")
scanf("%d",&k);
const int n = pow(2, k);
int team[n];
for (int i=1; i<n; ++i){
game(team, n, i);
printf("[%02d] ", i);
dump(team, n);
}
return 0;
}
四、结果
结果说明:第一行,输入k的值为4,意味着队伍总数n=2k;接下来的部分为每天的赛程安排,以第一行为例,[01]表示第一天,其后边的形如“00-01”的表示00号队伍与01号队伍比赛,以此类推,最终结果正确。
五、总结
1、分治的全称称为“分而治之”,分治即是将大问题划分为若干个规模较小、可以直接解决的子问题,然后解决这些子问题,最后将这些子问题的解合并起来,即是原问题的解。
2、分治的步骤
分治法的目的就是为了把无法解决的大问题分解成若干个能够解决小问题。通常来说,分治法可以归纳为三个步骤:
(1)分解,将原问题分解成若干个与原问题结构相同但规模较小的子问题;
(2)解决,解决这些子问题。如果子问题规模足够小,直接求解,否则递归地求解每个子问题;
(3)合并,将这些子问题的解合并起来,形成原问题的解。
3、对于本次实验问题:设有n=2^k个选手参加循环赛,要求设计一个满足以下要求比赛日程表:
(1)每个选手必须与其它n-1个选手各赛一次;
(2)每个选手一天只能赛一次。
按照上面的要求,可以将比赛表设计成一个n行n-1列的二维表,其中第i行第j列的元素表示和第i个选手在第j天比赛的选手号。
采用分治策略,可将所有参加比赛的选手分成两部分,n=2^k个选手的比赛日程表就可以通过n=2^(k-1)个选手的的比赛日程表来决定。递归的执行这样的分割,直到只剩下两个选手,比赛日程表的就可以通过这样的分治策略逐步构建。