笔者对循坏赛日程表的问题进行了一定深究,以下是该问题的五种解决方案(讨论原理时,笔者默认读者对分治法解决循坏赛日程表问题有理论基础)。
一、分治法,n=2^k,初始化为2*2的矩阵。
该方法的思想是利用递归对数组进行拆分,待数据左上角可用时:左下角=左上角+(此时数组长度)/2,右上角=左下角,右下角=左上角。代码如下(由于代码较多,笔者只贴核心代码,需要全部代码的读者可在笔者主页下载):
void Array_copy(int **p_array,int a_x,int a_y,int b_x,int b_y,int length,int add=0)
{//带默认值的拷贝函数 也是分治法中的合
for(int i=0,j;i<length;i++)
for(j=0;j<length;j++)
p_array[b_x+i][b_y+j]=p_array[a_x+i][a_y+j]+add;
}
void Game_Table(int **p_array,int x,int y,int length)//分治法中的分
{
if(length==1)
return;
Game_Table(p_array,x,y,length/2);//填充左上
Array_copy(p_array,x,y,x+length/2,y,length/2,length/2);//左上填充左下
Array_copy(p_array,x,y,x+length/2,y+length/2,length/2);//左上拷贝至右下
Array_copy(p_array,x+length/2,y,x,y+length/2,length/2);//左下拷贝至右上
}
二、分治法,n=2^k,初始化为第0行第0列的值
此方法是笔者在进行方法一的调试时发现的。因为第0行0列的值是确定的,如果该的值存在的话,分治法切分到可治时左上和左下能自行填充,接着:右上角=左下角,右下角=左上角。代码如下:
void Array_copy(int **p_array,int a_x,int a_y,int b_x,int b_y,int length,int add=0)
{
for(int i=0,j;i<length;i++)
for(j=0;j<length;j++)
p_array[b_x+i][b_y+j]=p_array[a_x+i][a_y+j]+add;
}
void Game_Table(int **p_array,int x,int y,int length)//分治法中的分
{
if(length==1)
return;
Game_Table(p_array,x,y,length/2);//填充左上
Game_Table(p_array,x+length/2,y,length/2);//填充左下
Array_copy(p_array,x,y,x+length/2,y+length/2,length/2);//左上拷贝至右下
Array_copy(p_array,x+length/2,y,x,y+length/2,length/2);//左下拷贝至右上
}
三、分治法,n为任意数,奇数+1得偶数。
首先我们确定解是存在的:当n为奇数时,需要比n天;当n为偶数时需要比n-1天。这里笔者简单证明一下:根据排列组合,n个人需要比C(2,n)场,每天可安排的场数为(int)n/2场,则需要C(2,n)/((int)n/2)天。
具体求解方法笔者先通过举例帮助读者发现规律。
n=3时:n为奇数,n+1=4,转变为n=4的求解,结果将4替换为0,当某选手对手为0时表示今天轮空。
n=4时:如上。
n=5时:n为奇数,n+1=6,分成两组(1 2 3) (4 5 6),各3名选手。将两组分别按n=3时排列。将第二组的结果置于第一组下方,然后把同一天都有空的两组安排在一起比赛。接下来继续填表(同一人数有多个解,笔者只讨论一种),第一组的(1 2 3)和第2组的(4 5 6)分别比赛。 但是由于(1,4), (2, 5), (3 6)已经比赛过了,所以在后面的安排中不能再安排他们比赛。首先,1只能和5或6比赛:若1-5,只能安排:2-6,3-4;若1-6,只能安排:2-4, 3-5 这样安排后前三行的后两列,后三行的后两列由上面的三行来定,如下表所示:
n=9时:n+1=10,分两组(1 2 3 4 5)和(6 7 8 9 10)各5人。根据n=5时的排列将第二组置于第一组下方。接下来的思路和n=5时相同。
按照这个规律,代码如下:
void Array_copy_even(int **p_array,int array_length)
{
if(array_length%2==1) return;
int i,j;
for (j=0;j<array_length;j++)
for (i=0;i<array_length;i++)
p_array[i+array_length][j]=p_array[i][j]+array_length;
for (j=array_length;j<2*array_length;j++)
{
for (i=0;i<array_length;i++)
p_array[i][j]=p_array[i+array_length][j-array_length];
for (i=array_length;i<2*array_length;i++)
p_array[i][j]=p_array[i-array_length][j-array_length];
}
}
void Array_copy_odd(int **p_array,int array_length)
{
int i,j;
for (j=0;j<=array_length;j++)
for (i=0;i<array_length;i++)
{
if (p_array[i][j]!=0)
p_array[i+array_length][j]=p_array[i][j]+array_length;
else
{
p_array[i+array_length][j] = i+1;
p_array[i][j] = i+array_length+1;
}
}
for(i=0,j=array_length+1;j<2*array_length;j++)
{
p_array[i][j]= j+1;
p_array[ (p_array[i][j] -1) ][j] = i+1;
}
for (i=1;i<array_length;i++)
{
for (j=array_length+1;j<2*array_length;j++)
{
if((p_array[i-1][j]+1)%array_length==0)
p_array[i][j] = p_array[i-1][j]+1;
else
p_array[i][j] = array_length + (p_array[i-1][j]+1)%array_length;
p_array[ (p_array[i][j]-1) ][j] = i+1;
}
}
}
void Array_copy(int **p_array,int array_length)
{
if ((array_length/2)%2==1)
Array_copy_odd(p_array,array_length/2);
else
Array_copy_even(p_array,array_length/2);
}
void Game_Table(int **p_array,int array_length)
{
if(array_length==1)
{
p_array[0][0]=1;
return ;
}
else if(array_length%2==1)
{
Game_Table(p_array,array_length+1);
Array_replace(p_array,array_length+1);
return ;
}
else
{
Game_Table(p_array,array_length/2);
Array_copy(p_array,array_length);
}
}
四、多边形轮转法,n为任意数。
有n个选手要进行循环赛,若n为偶数,则m=n-1(为何要转变为奇数求解,读者可画图自己探索),画m边形,并在多边形的中心处的数x置为0(如n为偶数,则x=n),每个点表示一个选手。 在同一水平线上的两名选手进行比赛,单独在一水平线上的选手与中心出的选手比赛(若x=0,则表示该选手今天轮空)。每天的比赛由旋转一次的多边形决定,每次顺时针旋转360/n度。代码如下:
void Game_Table(int n)
{
p_array[n][1]=n;
if(n==1) return;
int m=(n%2==1) ? n : n-1;
int i,j,k,r;
for(i=1;i<=m;++i)
{
p_array[i][1]=i;
b[i]=i+1;
b[m+i]=i+1;
}
for(i=1;i<=m;++i)
{
p_array[1][i+1]=b[i];
p_array[b[i]][i+1]=1;
for(j=1;j<=m/2;++j)
{
k=b[i+j];
r=b[i+m-j];
p_array[k][i+1]=r;
p_array[r][i+1]=k;
}
}
}
五,多边形轮转,n为任意数,笔者优化。
此解法也是笔者在画图是所得,我们用5个人比赛时进行讨论:当n为奇数时,第一天的比赛为(3,4)(2,5)(1,0),旋转后发现第二天的比赛为(2,3)(1,4)(5,0),第三天为(1,2)(5,3)(5,0)........即第i天的赛事安排可用第i-1天减一得来(i循环递减);当n为偶数时,只需在n-1中所得结果将0置为n即可。代码如下(此算法相对简单,笔者大脑已经关机,所以只写一个简单的输出结果供读者参考):
void Game_Table(int amount)
{
int new_amount=amount;
if(amount==1)
return;
if(amount%2==0)
new_amount--;
int a[new_amount][amount],x=0,b[new_amount][amount];
//实现第一天的赛程安排
for(int i=new_amount/2,j=new_amount/2-1;i<=new_amount;i++,j--)
{
a[0][x++]=i+1;
a[0][x++]=j+1;
}
for(int i=1;i<new_amount;i++)
for(int j=0;j<=new_amount;j++)
{
if(a[i-1][j]-1==0)
a[i][j]=new_amount;
else if(a[i-1][j]==0)
a[i][j]=0;
else
a[i][j]=a[i-1][j]-1;
}
for(int i=0;i<new_amount;i++)
{
cout<<"第"<<i+1<<"天的赛程安排:";
for(int j=0;j<=new_amount;j++)
{
if(amount%2==0&&j==new_amount-1)
cout<<"("<<a[i][j++]<<","<<amount<<") ";
else if(amount%2==1&&j==new_amount-1)
cout<<"("<<a[i][j++]<<",0) ";
else
cout<<"("<<a[i][j++]<<","<<a[i][j]<<") ";
}
cout<<endl;
}
return;
}
以上便是笔者这段时间对循坏赛日程表问题的探究,第五种方法是笔者数形结合所得,有种高中解题的既视感,让人怀恋。全部源程序链接https://download.csdn.net/download/m0_37872090/10377520,也可在笔者主页查找下载。