循环赛日程表,分治法(n为任意数,n=2^k),多边形轮转法(n为任意数),递归和指针,共五种解决方案。

    笔者对循坏赛日程表的问题进行了一定深究,以下是该问题的五种解决方案(讨论原理时,笔者默认读者对分治法解决循坏赛日程表问题有理论基础)。

    一、分治法,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,也可在笔者主页查找下载。

评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值