基于分治法的循环日程表算法
一.题目
设有 n=2^k 个运动员要进行网球循环赛。现要设计一个满足以下要求的比赛日程表:
(1) 每个选手必须与其他 n-1 个选手各赛一次。
(2) 每个选手一天只能赛一次。
(3) 循环赛一共进行 n-1 天。 按此要求可将比赛日程表设计成有 n 行和 n-1 列的日程表。在表中的第 i 行和第 j 列处填入 第 i 个选手在第 j 天所遇到的选手。
二.思路
看到问题的第一反应是在求全排列的满足条件的子集,但是全排列较多且选择适当的子集也比较困难,该问题有很多解,但求出其中的一种即可,那么就可以找最简单的一种进行求解。
n 为偶数,该问题可被分割为偶数个与原问题相似的子问题且最简单的子问题可轻易被求解,满足分治法的条件。即将大问题拆解为相似的可解决的小问题进行解决, 最后再将小问题的解进行合并,最终得到大问题的解。 可采用递归和非递归两种方法实现分治法思想。
最简单的子问题为 k=1 时,即两个运动员位置交错即可,由此合并,k=2 时是交错的两个的组合再交错,…由此推出 k=n 时解法 可将整个部分分为四个大块,求解出左上角,进而求解出其他三个部分,而左上角又由进一 步的递归求解…如此往复。
可采用递归和非递归两种方法实现分治法思想。
三.伪代码
1.递归
CIRCULAR_CALENDER(A,n)//A[1..n][1..n]
IF(n==1)
RETURN A[1][1]=1
ELSE
CIRCULAR_CALENDER(A,n/2)
COPY_CALENDER(A,n)
COPY(A,n)
m=n/2
FOR i = 1 TO m
FOR j = 1 TO m
A[i+m][j+m]=A[i][j] //左上复制到右下
A[i][j+m]=A[i][j]+2^(m-1) //计算左下 (A[i][j+m]=A[i][j]+m也行 )
A[i+m][j]= A[i][j+m]//左下复制到右上
2.非递归
CIRCULAR_CALENDER(A,n)
A[1][1]=1
k=logn
FOR j = 1 TO k
m = POWER(2, j) //从1开始边长每次扩大两倍
COPY_CALENDER(A, m)//复制
四.代码实现
这里采用递归法实现
#include <iostream>
using namespace std;
void copy_calender(int n,int **A){
int m=n/2;
for(int i=1;i<=m;i++){
for(int j=1;j<=m;j++){
A[i+m][j+m]=A[i][j];
A[i+m][j]=A[i][j]+m;
A[i][j+m]=A[i+m][j];
}
}
}
int** circular_calender(int n,int **A)
{
if(n==1)
{
A[1][1]=1;
return A;
}
else
{
circular_calender(n/2,A);
copy_calender(n,A);
}
return A;
}
int power(int k)
{
int n=1;
while(k--)
{
n*=2;
}
return n;
}
void print(int **A,int n){
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++){
printf("%5d",A[i][j]);
}
cout<<endl;
}
}
int main()
{
int k=4;
//cin>>k;
int n=power(k);
int **A=new int*[n+1];
for(int i=0;i<=n;i++){
A[i]=new int[n+1];
}
for(int i=0;i<n;i++){
for(int j=0;j<n;j++){
A[i][j]=0;
}
}
circular_calender(n,A);
print(A,n);
return 0;
}