任意参赛人数的循环赛日程问题(非分治方法,找规律解决)

算法课的作业,问题如下:
在这里插入图片描述
和书上的问题有所不同,书上人数只能是2^k人,而作业中的题目要求为n人。书上的分治法很好理解,矩阵左上移到右下,左下移到右上就可以解决问题,但扩展到奇数人时就不适用了,查询网上的代码也很难理解,在查询资料过程中看到了一篇研究该问题的论文,是采用找规律的方法,不含任何算法上的技巧,比较朴实无华,但是我觉得很有意思,就按照他的思路自己编写了代码完成此次作业。具体如下思路:
假设有N名选手参赛,我们首先构造一个N×N的矩阵。在矩阵第一行填充1,2,…,N,第一列填充1,2,…,N。这样做的意义取决于我们对这个矩阵的定义。我把矩阵的第一列作为参赛选手的编号,第2~N列作为某一参赛选手的对手,显然任意的人数的选手其第一行都是一行选手,对手为2,3,…,N号选手,第一列是所有选手编号1,2,…,N。但是以上我们的构造存在一个问题,就是这种假设默认了参赛选手人数是偶数,因为只有人数是偶数的情况下才只会需要N-1天(N-1列参赛选手)就完成比赛。所以我们不妨先考虑是选手是偶数的情况。
在构造了以上矩阵后,我们先观察一下最终答案的矩阵:
在这里插入图片描述
在这里插入图片描述
可以发现以下规律:
1.对角线上的元素全部为1。
2.考虑所有与对角线平行的斜线,对于上半矩阵,将对角线作为第一条斜线,之后的第2,3,…,N-1条斜线上的取值全部依次为2,3,…,N-1。如果斜线中的某一行原本的要取的值与所在行数相同,则取最大值N(如图1,第二行第三列位于第二条斜线,原本要取2,当因为他也在第二行,故取最大值6)。对于下半矩阵,规律也是类似的,将对角线作为第一条斜线,之后的第2,3,…,N-1条斜线取值依次为N-1,N-2,N-3……。同样的如果要取的值与行数相同则取最大值N。
3.上述规律仅对N-1行适用,即通过上述规律得到的结果中第N行是错误的结果。
通过以上规律我们的思路就非常明确了,在N是偶数的情况下,进行以下操作:
1.按照上述规律对上半矩阵赋值。
2.对下半矩阵赋值。
3.对于得到矩阵,由于其每一列的和必定为sum=1+2+⋯+N,故用sum减去前N-1列的值,得到该列最后一行的值。
具体代码如下:

if(N % 2 == 0){
        // 上半区对角线方向赋值
        for (i = 2; i <= N; i ++){
            for (j = 1; j <= N; j ++){
                if (j + 1 == i) {// 如果要赋的值与所在行数相同
                    a[i][i+j] = N;// 赋予最大值
                    continue;
                }
                a[i][i+j] = j + 1;
            }
        }
        // 下半区对角线方向赋值
        for (i = 3; i <= N; i ++){
            for (j = i - 1, k = N - 1; j > 1; j --){
                if (k == i){
                    a[i][j] = N;
                    k --;
                    continue;
                }
                a[i][j] = k --;
            }
        }
        int sum = 0, ss = 0;
        for(i = 1; i <= N; i ++){
            sum += i;
        }
        for (i = 2; i <= N; i ++) {
            for (j = 1; j <= N - 1; j ++){
                ss += a[j][i];
            }
            a[j][i] = sum - ss;
            ss = 0;
        }
    }

接下来我们接着考虑奇数的情况:
首先还是观察一下最终答案的矩阵:
在这里插入图片描述
在这里插入图片描述
规律似乎有点难找,那么再观察下面两个矩阵呢?
在这里插入图片描述
在这里插入图片描述
很容易发现,对于奇数情况,我们可以先为他添加1使其变为偶数人的情况,不同的是,当斜线上存在相同值时我们不是添加最大值,而是添加0(0表示轮空)。得到的结果矩阵删去最后一行就是我们所需要的结果了。所以对于偶数情况,我们的代码要改的地方就是将传入的N+1,然后相同值由添加N改为添加0即可。此外,由于删去了最后一行,我们不需要再考虑偶数时最后一行得到的值不正确的情况了,故不需要再对每一列求和。
具体代码如下:

else{
        flag = false;
        N += 1;
        for (i = 2; i <= N; i ++){
            for (j = 1; j <= N; j ++){
                if (j + 1 == i) {// 如果要赋的值与所在行数相同
                    a[i][i+j] = 0;// 赋予0
                    continue;
                }
                a[i][i+j] = j + 1;
            }
        }
        // 下半区对角线方向赋值
        for (i = 3; i <= N; i ++){
            for (j = i - 1, k = N - 1; j > 1; j --){
                if (k == i){
                    a[i][j] = 0;
                    k --;
                    continue;
                }
                a[i][j] = k --;
            }
        }
    }

至此便完美的解决了问题。
完整代码:

#include <bits/stdc++.h>
using namespace std;

int a[101][101] = {0};

int main(int argc, const char * argv[]) {
    // insert code here...
    int N, i, j, k;
    cout << "请输入参赛人数:";
    cin >> N; // 比赛人数
    for (i = 1; i <= N; i ++){
        a[1][i] = a[i][1] = i;
        a[i][i] = 1;
    }
    bool flag = true; // N为偶数
    // 偶数人
    if(N % 2 == 0){
        // 上半区对角线方向赋值
        for (i = 2; i <= N; i ++){
            for (j = 1; j <= N; j ++){
                if (j + 1 == i) {// 如果要赋的值与所在行数相同
                    a[i][i+j] = N;// 赋予最大值
                    continue;
                }
                a[i][i+j] = j + 1;
            }
        }
        // 下半区对角线方向赋值
        for (i = 3; i <= N; i ++){
            for (j = i - 1, k = N - 1; j > 1; j --){
                if (k == i){
                    a[i][j] = N;
                    k --;
                    continue;
                }
                a[i][j] = k --;
            }
        }
        int sum = 0, ss = 0;
        for(i = 1; i <= N; i ++){
            sum += i;
        }
        for (i = 2; i <= N; i ++) {
            for (j = 1; j <= N - 1; j ++){
                ss += a[j][i];
            }
            a[j][i] = sum - ss;
            ss = 0;
        }
    }
    // 奇数人
    else{
        flag = false;
        N += 1;
        for (i = 2; i <= N; i ++){
            for (j = 1; j <= N; j ++){
                if (j + 1 == i) {// 如果要赋的值与所在行数相同
                    a[i][i+j] = 0;// 赋予0
                    continue;
                }
                a[i][i+j] = j + 1;
            }
        }
        // 下半区对角线方向赋值
        for (i = 3; i <= N; i ++){
            for (j = i - 1, k = N - 1; j > 1; j --){
                if (k == i){
                    a[i][j] = 0;
                    k --;
                    continue;
                }
                a[i][j] = k --;
            }
        }
    }
    
    if(flag){
        for (i = 1; i <= N; i ++) {
            for (j = 1; j <= N; j ++){
                cout << a[i][j] << "\t";
                if (j == 1){
                    cout << "|\t";
                }
            }
            cout << endl;
        }
    }
    else{
        for (i = 1; i <= N - 1; i ++) {
            for (j = 1; j <= N; j ++){
                cout << a[i][j] << "\t";
                if (j == 1){
                    cout << "|\t";
                }
            }
            cout << endl;
        }
    }
    
    
    return 0;
}

运行结果:
在这里插入图片描述
参考文献
[1]刘超. 扩展循环赛日程表算法研究[J].辽宁工程大学学报,2004,(23):50-52.
[2]王晓东.计算机算法设计与分析[M].北京:电子工业出版社,2001.

  • 5
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值