基于分治策略的循环赛日程表问题的思路讲解

一、题目叙述

循环赛日程表问题:

设有n=2k 个运动员要进行网球循环赛。现在要设计一个满足以下要求的比赛日程表:(1)每个选手必须与其他 n-1 个选手各赛一次;(2)每个选手一天只能赛一次;(3)循环赛一共进行 n-1 天。请给出基于分治思想的日程表设计方案,并且编程实现。

二、题目分析

为了分析题目的含义,拿8个运动员举例,对运动员依次编号为1、2、3、......、8,比赛天数为n-1天,依题意,1号运动员要在7天内和其他运动员各比赛一次,且每天只能比赛一场,如果只分析题目的含义,可以简单的用顺次排序的方法制定日程,每个运动员的比赛对象从他的下一位开始依次去比,排满再从头接着排,这样就可以满足题意。具体如下表所示:

现在,我们换一种思路,按照要求,用分治的思想来思考一下这道题的解决思路。我们先拿4个运动员来探寻规律,之后,我们再用8个运动员的规模来推演一遍用分治思想的求解过程。

1、规律探寻

对于4个运动员的场合,按照刚才的设计方法,我们不难得到如下的日程方案:

我们现在换一种方式,我们先把4个人分成2组,每个组里是两名运动员,第一组:1、2;第二组:3、4。我们考虑2个人的日程安排,2个人的时候,比赛是一天,根本不用安排,就让这两名运动员比就行。

第一组:

第二组:

四个人的场合,我们可以把表格看成是一个4 * 4的棋盘,然后将已排好序的2个运动员的日程分别填到棋盘里,我们发现,这样放置后一样是满足题意的。

发现了这个规律,我们就可以考虑运用分治思想了。现在以8个运动员举例,推导一下算法的过程。

思路:

将规模为8的运动员的循环赛日程表问题进行分解,分为2组,第一组:1、2、3、4;第二组:5、6、7、8,得到两个4的运动员的循环赛日程表子问题。分别求解两个子问题,再将得到的子问题的日程安排结果按照上面的摆放方法铺满棋盘,8个运动员的日程表就安排好了。

为了得到规模为4的的运动员的循环赛日程表子问题的答案,对于规模为4的的运动员的循环赛日程表问题,进行分解,分解成2组,说到这里,思路已经明确了,递归分解原问题,每次分为2组,直到长度(运动员数)为1,此为初值,此时就是运动员自己。

我们再来想一个更简单的分治:我们知道,8个运动员的日程表是一个8*8的表格,我们把这个表格看成一个8*8的棋盘,类似于棋盘覆盖,我们直接对它进行切分,分成四块,有了上面的经验,我们知道,左上角和右下角一样,是1到4号运动员的日程,右上角和左下角一样,是5到8号运动员的日程。这样,我们就可以分别对4个子棋盘进行递归求解,直至规模等于1,这是初值。这样分解的好处是省却了合并的操作,所有规模为1的子棋盘填好后,整个问题的解就出来了。

程序清单:

思路一:

cpp 复制代码
#include <stdio.h>
#include <math.h>

// copy函数:把 row, col 开始的 m*m 棋盘复制到 targetRow, targetCol
void copy(int **table, int m, int row, int col, int targetRow, int targetCol)
{
    for(int i = 0; i < m; i++)
    {
        for(int j = 0; j < m; j++)
        {
            table[targetRow + i][targetCol + j] = table[row + i][col + j];
        }
    }
}

// 合并函数:完成两块棋盘拷贝合并
void merge(int **table, int m, int row, int col)
{
    // 右下棋盘 = 左上棋盘
    copy(table, m, row, col, row+m, col+m);
    // 左下棋盘 = 右上棋盘
    copy(table, m, row, col+m, row+m, col);
}

void schedule(int **table, int n, int startNumber,int row, int col){
	// 把日程表看成一个棋盘
	// 递归到初值时,求解,即只有一个运动员,棋盘规模为 n=1 时只有1个格时置入运动员编号
    if(n == 1){
        table[row][col] = startNumber;
        return;
    }
    int m = n / 2;
    // 递归求解左上棋盘
    schedule(table, m, startNumber, row, col);
    // 递归求解右上棋盘
    schedule(table, m, startNumber + m, row, col + m);
	// 合并:填充下半部分
    merge(table, m, row, col);
}

// 输出打印函数
void printTable(int **table, int n)
{
    for(int i=0;i<n;i++){
        for(int j=0;j<n;j++){
            printf("%2d\t",table[i][j]);
        }
        printf("\n");
    }
}

int main()
{
    int k, n;
    printf("输入k:");
    scanf("%d", &k);
    printf("\n");
    n = pow(2, k);	// 运动员数量
    // 根据运动员数量生成 n 行 n 列的二维数组
    int **table = (int**)malloc(n*sizeof(int*));	// 分配 n 个行指针,拿一个整形指针的值(地址)的大小乘以 n,申请空间
    for(int i=0;i<n;i++){
    	table[i] = (int*)malloc(n*sizeof(int));		// 每行 n 个整形元素,拿一个整形的大小乘以 n,申请空间
	}
    schedule(table,n,1,0,0);

    printTable(table, n);  // 调用输出函数输出结果

    for(int i=0;i<n;i++){
    	free(table[i]);	
	}
    free(table);
    return 0;
}

思路二:

cpp 复制代码
#include <stdio.h>
#include <math.h>

// row:子表行开始位置,col:子表列开始位置
void schedule(int **table, int n, int startNumber, int row, int col)
{
	// 把日程表看成一个棋盘
	// 递归到初值时,即只有一个运动员,棋盘规模为 n=1 时只有1个格时置入运动员编号
    if(n == 1)
    {
        table[row][col] = startNumber;
        return;
    }else{
    	int m = n / 2;		// 问题规模减半,均分为四个子问题,对应四个子棋盘
    	// 左上角子棋盘:开始于(row, col),起始运动员编号 startNumber
    	schedule(table, m, startNumber, row, col);
    	// 右上子块:列偏移 + m,起始运动员编号 startNumber + m(startNumber 改变)
    	schedule(table, m, startNumber + m, row, col + m);
    	// 左下角子棋盘:行偏移 + m,起始运动员编号 startNumber + m(startNumber 改变)
    	schedule(table, m, startNumber + m, row + m, col);
    	// 右下角子棋盘:行列都偏移 + m,起始运动员编号 startNumber
    	schedule(table, m, startNumber, row + m, col + m);
	}
}

// 输出打印函数
void printTable(int **table, int n)
{
    for(int i=0;i<n;i++){
        for(int j=0;j<n;j++){
            printf("%2d\t",table[i][j]);
        }
        printf("\n");
    }
}

int main()
{
    int k, n;
    printf("输入k:");
    scanf("%d", &k);
    printf("\n");
    n = pow(2, k);	// 运动员数量
    // 根据运动员数量生成 n 行 n 列的二维数组
    int **table = (int**)malloc(n*sizeof(int*));	// 分配 n 个行指针,拿一个整形指针的值(地址)的大小乘以 n,申请空间
    for(int i=0;i<n;i++){
    	table[i] = (int*)malloc(n*sizeof(int));		// 每行 n 个整形元素,拿一个整形的大小乘以 n,申请空间
	}
    schedule(table,n,1,0,0);

    printTable(table, n);  // 调用输出函数输出结果

    for(int i=0;i<n;i++){
    	free(table[i]);	
	}
    free(table);
    return 0;
}
相关推荐
起个名字费劲死了2 小时前
VisionMaster集成深度学习算法(基础狗)
人工智能·深度学习·算法
lengxuenong2 小时前
枚举例题分析
c++·算法·青少年编程
Navigator_Z2 小时前
LeetCode //C - 1269. Number of Ways to Stay in the Same Place After Some Steps
c语言·算法·leetcode
2601_965742222 小时前
短视频脚本创作方法分享:本地生活类账号的起步思路
大数据·人工智能·算法·ai·新媒体运营·生活
55873生态系统手记2 小时前
读完 55873 资产钱包的技术栈,我重新理解了 "合规钱包" 应该长什么样
算法·架构
旋生万物2 小时前
素数螺旋映射 $z_n=n^{1+i}$ 的角分布统计检验与零模型对比
大数据·前端·人工智能·算法·云原生·螺旋生成论·螺旋相位
HRTOS2 小时前
HRTOS Shell继续完善:优化printf缓冲区与字符串越界保护,国庆后随新版本更新
运维·单片机·嵌入式硬件·算法·51单片机
邵奈一3 小时前
当沉香之乡遇上 Seed-2.1-pro:我用 AI 给村子做了一个数字门面
算法·架构
海天一色y3 小时前
图像分割全解析:从经典算法到深度学习的原理与实战(Python + MATLAB)
python·深度学习·算法