循环赛日程安排问题
问题描述:设有n=2的k次方个运动员要进行网球循环赛。现要设计一个满足以下要求的比赛日程表:
(1)每个选手必须与其他n-1个选手各赛一次;
(2)每个选手一天只能赛一次;
(3)循环赛一共进行n-1天。
分治法思路:
- 分解:将所有选手分为两组
- 解决:n个选手的比赛日程表可以通过为n/2个选手设计的比赛日程表来决定
- 合并:递归地用一分为二的策略对选手进行分割,直到只剩下2个选手时,制定日程表。这时只要让这2个选手进行比赛即可。
图解求解过程:
如果只有n=2¹
两人参赛,比赛日程表:
左上角拷贝到右下角
左下角拷贝到右上角
如果只有n=2的2次方
两人参赛,比赛日程表:
左上角拷贝到右下角
左下角拷贝到右上角
如果只有n=2的三次方
两人参赛,比赛日程表:
左上角拷贝到右下角
左下角拷贝到右上角
求解过程:
2的k次方个选手的比赛日程是在2的k-1次方个选手的比赛日程的基础上通过迭代的方法求得的。在每次迭代中,将问题划分为4部分:
(1)左上角:左上角为2k-1个选手在前半程的比赛日程(k=1时直接给出,否则,上一轮求出的就是2k-1个选手的比赛日程)。
(2)左下角:左下角为另2k-1个选手在前半程的比赛日程,由左上角加2k-1得到,例如22个选手比赛,左下角由左上角直接加2(2k-1)得到,23个选手比赛,左下角由左上角直接加4(2k-1)得到。
(3)右上角:将左下角直接复制到右上角得到另2k-1个选手在后半程的比赛日程。
(4)右下角:将左上角直接复制到右下角得到2k-1个选手在后半程的比赛日程。
C语言代码及注释:
#include <stdio.h>
#include<stdlib.h>
#include<string.h>
//分治递归,mStart是分治区域的起始的纵坐标,mEnd是分治区域结束的纵坐标
//nStart是分治区域的起始的横坐标,nEnd是分治区域的结束的横坐标
void arrange(int** a, int mStart, int mEnd, int nStart, int nEnd)
{
//只有一个参赛选手,只能自己与自己比
if (mStart == mEnd && nStart == nEnd)
{
a[mStart][nStart] = nStart;
return;
}
//有两个选手,矩阵对角线相等
if (mStart + 1 == mEnd && nStart + 1 == nEnd)
{
//主对角线相等
a[mStart][nStart] = nStart;
a[mEnd][nEnd] = nStart;
//逆对角线相等
a[mStart][nEnd] = nEnd;
a[mEnd][nStart] = nEnd;
return;
}
int mMid = (mStart + mEnd) / 2;
int nMid = (nStart + nEnd) / 2;
arrange(a, mStart, mMid, nStart, nMid);//左上角分块矩阵
arrange(a, mStart, mMid, nMid + 1, nEnd);//右上角分块矩阵
//将左上角分块矩阵复制到右下角
for (int i = mMid + 1; i <= mEnd; i++)
{
for (int j = nMid + 1; j <= nEnd; j++)
{
//我们要做的是将每次递归的矩阵的左上角复制到右下角,而不是这样
//求出右下角纵坐标到上半矩阵对应的纵坐标的距离mMid - mStart + 1
//求出右下角横坐标到上半矩阵对应的横坐标的距离nMid - nStart + 1
//右下角矩阵的横纵坐标实际上就与左上角矩阵差一个从中轴到起点的距离
a[i][j] = a[i - (mMid - mStart + 1)][j - (nMid - nStart + 1)];
}
}
//将右上角分块矩阵复制到左下角
for (int i = mMid + 1; i <= mEnd; i++)
{
for (int j = nStart; j <= nMid; j++)
{
a[i][j] = a[i - (mMid - mStart + 1)][j + (nMid - nStart + 1)];//mMid-mStart+1是左下角纵坐标到上半矩阵对应纵坐标的距离,nMid - nStart+1同理
}
}
}
//打印矩阵
void printMatrix(int** a, int m, int n)
{
printf("参赛\n选手\t");
for (int i = 2; i < n; i++)
{
printf("第%d天\t", i - 1);
}
printf("\n");
for (int i = 1; i < m; i++)
{
for (int j = 1; j < n; j++)
{
printf("%d\t", a[i][j]);
}
printf("\n");
}
}
int main()
{
int m = 0;//参赛选手人数
printf("请输入参赛选手的个数:\n");
scanf("%d", &m);
if ((m & (m - 1)) != 0)//判断2的n次方的经典条件(m & (m-1))的结果为0就是,注意加括号,符号优先级要注意
{
printf("参赛选手必须为2的n次方!\n");
return 0;
}
//初始化(m+1) * (m+1)的二维数组,因为我们整体代码逻辑是按下标从1开始,所以边缘位置空着为0就行
int** matrix = (int**)malloc((m + 1) * sizeof(int*));
for (int i = 0; i < (m + 1); i++)
{
matrix[i] = (int*)malloc((m + 1) * sizeof(int));
memset(matrix[i], 0, (m + 1) * sizeof(int));//将二维数组每一行置为0
}
arrange(matrix, 1, m, 1, m);
printMatrix(matrix, m + 1, m + 1);
}