【分治法】循环赛日程表问题 C\C++(附代码、实例)

问题描述

设计一个满足以下要求的比赛日程表:

  1. 每位选手必须与其他n-1个选手各赛一次
  2. 每位选手一天只能赛一次
  3. 循环赛一个进行n-1天
  4. 选手人数 n = 2 k n=2^k n=2k

问题分析

下图是一种日程表的安排方式

在这里插入图片描述

观察上图,我们发现日程表左上角的四行四列和右下角的四行四列完全一致,左下角的四行四列和右上角的四行四列也完全一致

那么我们可以得到一个思路,就是将大日程表一分为二,对两部分分别安排日程,然后再复制到对应的其他位置,获得整个大日程表

下面来看一下该问题是否满足分治法的四个条件:

  1. 大问题可以分成规模小的小问题。对日程表进行划分后,只有问题规模变小了,仍然是相同问题
  2. 问题小到一定规模后可以很容易的解决。当日程表是1x1的大小时,自己不用和自己安排比赛,也就不用再分再填了
  3. 子问题的解可以合成大问题的解。得到子问题的答案后,将子日程表依次复制到对角位置,就得到了大日程表,也就得到了大问题的解
  4. 分解出的各子问题互相独立(不重复)

综上所述,该问题满足分治策略的四个条件,可以用分治法解决

所以这个问题的解决方法就是,将对n位选手安排日程表的问题分成对前n/2位选手和后n/2位选手分别安排日程,再对每部分递归的分割,直到只剩下一个选手

代码

void table(int i, int j, int n) {//i j为表左上角的行号和列号,n为选手个数
	if (n > 1) {
		table(i, j, n / 2);
		table(i + n / 2, j, n / 2);
		copy(i, j, i + n / 2, j + n / 2, n / 2);
		copy(i + n / 2, j, i, j + n / 2, n / 2);
	}
	else
		a[i][j] = i + 1;
}

table函数为a[i][j]a[i+n][j+n]的子表安排日程,如果该表的大小为1x1,就把当前选手编号填进去,如果表的大小大于1x1,就递归的调用自身,为a[i][j]a[i+n/2][j+n/2]a[i+n/2][j]a[i+n][j+n/2]的子表安排日程。再将安排好的日程表copy到对角部分

void copy(int x1, int y1, int x2, int y2, int n) {
	for (int i = 0; i < n; i++)
		for (int j = 0; j < n; j++)
			a[i + x2][j + y2] = a[i + x1][j + y1];
}

copy函数很简单,就是将x1、y1 长度为n的日程表复制到x2、y2

实例

#include<stdio.h>
int a[8][8];
void copy(int x1, int y1, int x2, int y2, int n) {
	for (int i = 0; i < n; i++) {
		for (int j = 0; j < n; j++) {
			a[i + x2][j + y2] = a[i + x1][j + y1];
		}
	}
}
void table(int i, int j, int n) {//i j为表左上角的行号和列号,n为选手个数
	if (n > 1) {
		table(i, j, n / 2);
		table(i + n / 2, j, n / 2);
		copy(i, j, i + n / 2, j + n / 2, n / 2);
		copy(i + n / 2, j, i, j + n / 2, n / 2);
	}
	else
		a[i][j] = i + 1;
}
int main() {
	int n = 8;
	table(0, 0, n);
	for (int i = 0; i < n; i++) {
		for (int j = 0; j < n; j++) {
			printf("%d ", a[i][j]);
		}
		printf("\n");
	}
	return 0;
}

运行结果
在这里插入图片描述

非递归方法

使用非递归的方法解决这个问题,思路是逐步将已得到的日程copy到对角的区域中

对于下图,方法就是先分别将1至8号复制到对角区域,再将橙色区域copy到对角的橙色区域,将黄色区域copy到对角的黄色区域,下半部分同理;再将1-4的大区域copy到对角的浅蓝色区域,5-8copy到对角的深蓝色区域。最后就得到了8x8的日程表
在这里插入图片描述

代码

int main() {
    int n;
    n = 8;

    // 初始化第一天的比赛
    for (int i = 0; i < n; i++) {
        a[i][0] = i + 1;
    }
    int len = 1;
    // 使用迭代方法填充日程表
    for (int j = 0; j < n; j += len) {
        for (int i = 0; i < n; i += len * 2) {
            copy(i, 0, i + len, len, len);
            copy(i + len, 0, i, len, len);
        }
        len *= 2;
    }

    // 打印日程表
    for (int i = 0; i < n; i++) {
        for (int j = 0; j < n; j++) {
            printf("%d ", a[i][j]);
        }
        printf("\n");
    }

    return 0;
}

运行结果
在这里插入图片描述

小结

使用分治法的注意事项

  1. 注意检查四个条件是否满足
  2. 尽量保证划分出的子问题规模大致一致
  3. 如果可以使用递推的方式解决问题,尽量使用递推算法
  4. 用递归算法解决问题时,要分析出其递归边界和递归方程

分治策略相关问题
线性时间选择问题
快速排序中的分治策略
棋盘覆盖问题
快速幂算法

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值