n位(正整数)比赛日程的安排

本文介绍了一种使用分治法解决n个运动员网球循环赛日程安排问题的算法,详细解释了偶数和奇数参赛者数量情况下的处理方法,包括递归分解、轮空处理和对手配对策略。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

分治法

题目描述:

		设有n个运动员要进行网球循环比赛。设计一个满足要求比赛的算法
		(1)每个选手必须和其他n-1位选手进行比赛
		(2)每个选手一天只能参加一场比赛(也可以不参加)
		(3)当n为偶数时要进行n-1天的比赛;当n为奇数时要进行n天的比赛

分治法代码:

#include <iostream>

using namespace std;
int b[100];//随便设置的空间根据问题需要设置
int a[20][20];
void copy1(int n){//对偶数的处理
    int m = n/2;
    for(int i=1;i<=m;i++){
        for(int j=1;j<=m;j++){
            a[i][j+m] = a[i][j]+m;
            a[i+m][j] = a[i][j+m];
            a[i+m][j+m] = a[i][j];
        }
    }
}
void copyodd(int n){//对奇数的处理
    int m = n/2;
    for(int i=1;i<=m;i++){
        b[i] = m+i;//设置辅助数组
        b[m+i] = b[i];
    }
    for(int i =1;i<=m;i++){
        for(int j=1;j<=m+1;j++){
        //针对左上角和左下角的操作
        if(a[i][j]>m){//此处存在是为了将轮空的置为相应的值
            /*输出的控制台中的数据 input=10
            a[i][j]的变化
            3 1 4 :4-->4
            3 2 3 :4-->5
            3 3 2 :4-->6
            5 1 6 :6-->6
            5 2 5 :6-->7
            5 3 2 :6-->8
            5 4 3 :6-->9
            5 5 4 :6-->10
            */
            //1:cout<<m<<" "<<i<<" "<<j<<" "<<":"<<a[i][j];
            a[i][j] = b[i];
            //cout<<"-->"<<a[i][j]<<endl;
            //cout<<m<<" "<<m+i<<" "<<j<<" "<<":"<<a[m+i][j];
            a[m+i][j] = (b[i]+m)%n;
            //cout<<"-->"<<a[m+i][j]<<endl;
            /*输出的控制台中的数据 input=10
            a[m+i][j]的前后变化
            3 4 4 :1-->1
            3 5 3 :0-->2
            3 6 2 :0-->3
            5 6 6 :1-->1
            5 7 5 :0-->2
            5 8 2 :0-->3
            5 9 3 :0-->4
            5 10 4 :0-->5
            */
        }
        else
            a[m+i][j] = a[i][j]+m;//针对左下角进行处理
            //if else 语句的设置主要是为了 a[i][j]+m 不超过n 
        }
        for(int j=2;j<=m;j++){
            a[i][m+j] = b[i+j-1];//针对右上角操作
            a[b[i+j-1]][m+j] = i;//针对右下角操作
        }
    }
}
int odd(int n){
    return n&1;
}
void makecopy(int n){
    if(n/2>1&&odd(n/2))
        copyodd(n);
    else
        copy1(n);
}
void tournament(int n){
    if(n==1){
        a[1][1] = 1;
        return ;
    }
    if(odd(n)){
        tournament(n+1);//无法将奇数分成相等的两份引入虚拟的n+1
        return;
    }
    tournament(n/2);
    makecopy(n);
}
int main()
{
    int n;
    cin>>n;
    tournament(n);
    if(!odd(n)){
    for(int i=1;i<=n;i++){
        for(int j=1;j<=n;j++)
            cout<<a[i][j]<<'\t';
        cout<<endl;
    }}
    else{
        for(int i=1;i<=n;i++){
            for(int j=1;j<=n+1;j++)
                cout<<a[i][j]<<'\t';
            cout<<endl;
        }
    }

    return 0;
}
/*测试n=10
1       2       3       4       5       6       7       8       9       10
2       1       5       3       7       4       8       9       10      6
3       8       1       2       4       5       9       10      6       7
4       5       9       1       3       2       10      6       7       8
5       4       2       10      1       3       6       7       8       9
6       7       8       9       10      1       5       4       3       2
7       6       10      8       2       9       1       5       4       3
8       3       6       7       9       10      2       1       5       4
9       10      4       6       8       7       3       2       1       5
10      9       7       5       6       8       4       3       2       1
*/

注:当选手轮空时,将会显示数字n+1

算法结构设想:

首先我们的想法肯定是分治法,但是奇数的情况下无法平等均分,
会存在轮空的情况,所以我们需要进行加一操作,加入n+1,实际代表的就是一个轮空标记
首先我们要利用递归,递归的尽头a[1][1] =1
在此基础上我们想到递归到1过程中
我们会自然而然的想到所加入的机器人怎么处理呢
我们需要根据n/2的奇偶性,来判断一下是否是引入了机器人
这也就是makecopy的作用,然后我们再调用偶数算法或者奇数算法
偶数算法就是copy1,即与计算机算法设计与分析课本一致
奇数算法就是copyodd
由此的框架函数基本
int odd(int n){
}
void makecopy(int n){
}
void copyodd(int n){
}
void copy1(int n){
}
void tournament(int n){
}

核心的copyodd

其中copyodd难以理解所以补充一下
按照我们正常的想法来想这个问题,我们最希望在原来的基础上进行粘贴
例如
我们的6名队员是如何在四名队员的基础上拓展的
因为tournament(3) 再分治时需要加一个虚拟机器人所以是四位
copyodd(6)
四名队员的日程安排
1 2 3 4
2 1 4 3
3 4 1 2
4 3 2 1
我们最希望是在不改动原有数组的基础上,进行日程的安排
所以对于左下角来说我们直接m+a[i][j]的方式进行填充
但是对于我们之前设置的四号机器人我们需要进行一下改动
所以每当a[i][j]>m时即是上一次机器人(也就是轮空的意思所以需要进行填充)
这个代码简单的表述实际是对轮空的处理,处理要改变的结果是一对队员
a[i][j] = b[i]=m+i;
a[m+i][j] = (b[i]+m)%n=i;
按照这逻辑填充完的结果是:
1 2 3 4
2 1 5 3
3 6 1 2
4 5 6 1
5 4 2 6
6 3 4 5
剩余的还有两列没有显示,我们所要实现的不过是安排一组又一组的对手
这时候就体现了我们辅助数组b[]的作用了
对于二维数组我们需要两次遍历进行操作,然后j=2设置,是因为我们从m+j到2m
因为m+1列已经处理过了
我们需要通过这个辅助数组来获取右上角对手的编号,然后将当前i为右下角的值
b数组的值是大于m的,其次通过改变i,j的值使得这两列上下不会重复左右也不会重复
因为b[i+j-1]!=b[i]
i+j-1!=i&&i+j-1!=i+m
故可以获得剩余的对手,还不致使重复

 for(int i=1;i<=m;i++){
  for(int j=2;j<=m;j++){
            a[i][m+j] = b[i+j-1];//针对右上角操作
            a[b[i+j-1]][m+j] = i;//针对右下角操作
        }
  }

至于设置b[m+i] = b[i]的原因是正好错开
使得右上角的三行为
5 6
6 4
4 5
实际类似于4 5 6的循环左移这样可以使得安排妥当
补充右下角即可得到
1 2 3 4 5 6
2 1 5 3 6 4
3 6 1 2 4 5
4 5 6 1 3 2
5 4 2 6 1 3
6 3 4 5 2 1

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值