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

一、题目叙述

循环赛日程表问题:

设有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;
}
相关推荐
布莱克6053 小时前
C++ 中 map 的 insert 与 emplace:key 已存在时会覆盖吗?
开发语言·数据结构·c++·算法·map
D e re k ZH5 小时前
C++ 系统学习手记:从语法细节到工程实践
开发语言·数据结构·c++·算法
石小石Orz6 小时前
AI 正在蚕食我们的编码能力
前端·后端·算法
民乐团扒谱机8 小时前
【微实验】NSGA-II(非支配排序遗传算法)——解决多目标优化问题的“自然选择模拟器”
linux·人工智能·算法
青少儿编程课堂8 小时前
蓝桥杯·全国青少年信息素养大赛·电子学会Python/C++考级:中缀表达式求值解析
c++·python·算法·bfs·信息学竞赛
武子康10 小时前
Qwen-Drive 到底是什么:4B 级 VLM 如何成为驾驶策略底座
人工智能·算法·机器学习
zh_xuan11 小时前
LeeCode 496. 下一个更大元素I
数据结构·算法·栈·单调栈
机 _ 长12 小时前
05 智算运维|8 张卡怎么才算「捆成一张」:NCCL 实测与拓扑
运维·人工智能·算法·智算运维
早起CaiCai12 小时前
【2023-01 OM】利用深度学习算法融合异质观测实现海洋场降尺度
人工智能·深度学习·算法