设有n个运动员要进行网球循环赛。设计一个知足下列条件的比赛日程表:ios
编译运行源代码,提示输入参赛者的数目,以9个参赛者为例,显示以下:算法
Please input the number of contestants:
9
File "result.dat" created.
复制代码
表示程序运行完毕,输出结果在可执行文件相同目录下的result.dat
文件中数组
此时,result.dat
文件中输出结果以下:bash
The tournament shall be arranged as follow:
选手编号: 1 2 3 4 5 6 7 8 9
第1 天: 2 1 8 5 4 7 6 3 0
第2 天: 3 5 1 9 2 8 0 6 4
第3 天: 4 3 2 1 0 9 8 7 6
第4 天: 5 7 4 3 1 0 2 9 8
第5 天: 6 4 5 2 3 1 9 0 7
第6 天: 7 8 9 0 6 5 1 2 3
第7 天: 8 9 0 6 7 4 5 1 2
第8 天: 9 0 6 7 8 3 4 5 1
第9 天: 0 6 7 8 9 2 3 4 5
复制代码
其中,其中一行表明一天,每一列表明一个选手,即第i行j列表示第i天第j个选手的对手编号,行号列号从1开始计(第零行为选手编号,不算在表格中,从第一天开始为第一行)架构
若参赛者为奇数个,则会出现数字0,表明该参赛者当天没有比赛函数
本算法核心参考连接在此,但在原做者的算法上作了必定修改post
在参考连接中做者提供的算法之中有一些本人以前没见过或者没用过的写法和巧妙的运算方法、函数等,在此先加以罗列ui
iomanip
头文件中,为固定设置字符宽度的函数,默认控制右对齐,若想要进行左对齐须要加入left控制符号,以下:outfile << left << setw(17) << "选手编号: ";
复制代码
cmath
头文件中,是一个向上取整的函数,即ceil(n)
返回不小于n的最小整数。须要特别注意的是,这个函数在头文件中定义时返回值类型为double型,故实际使用时,若涉及到整数的传参须要把返回类型强制转换成int型,以下:tournament((int)ceil(n / 2.0),table);
复制代码
还须要注意的一点是,ceil的输入也是double型的,这意味着对于整数n,上面函数中若写为ceil(n/2)
,则括号内部为整数除法,返回的值是整数除法的结果,天然也是整数,而不是double,此时会引发错误。故应写为ceil(n/2.0)
if(n & 1){
for (int i = 1; i <= total_days;i++){
for (int j = 1; j <= n;j++){
if (table[i][j]==n+1)
table[i][j] = 0;
}
}
}
复制代码
符号"&"单个使用时表示按位与,在上面的例子(是程序源码的一部分)中的含义即,将n转变为二进制,以后再和1按位与。显然,当n为奇数的时候表达式为1,不然为0,故这个语句能够做为判断n是不是奇数的判断语句,很是简洁int **table = new int *[n + 2];
for (int i = 0; i < n + 2;i++)
table[i] = new int[n + 2];
tournament(n, table);
复制代码
其中tournament函数定义为:void tournament(int n,int **table) 复制代码
在这种状况下必须使用二重指针动态分配内存的方式来初始化table,而不能直接创建一个二维数组table[n+2][n+2]
先贴上完整代码spa
#include<fstream>
#include<iostream>
#include<iomanip>
#include<cmath>
using namespace std;
void Show_Result(int n,int **table)//将结果按行(即按天)输出到文件中 {
ofstream outfile;
outfile.open("result.dat");
outfile << "The tournament shall be arranged as follow:" << endl;
int total_days = n & 1 ? n : n - 1;
outfile << left << setw(17) << "选手编号: ";
for (int i = 0; i <= total_days;i++){
if(i)
outfile << "第" << left << setw(3) << i << left << setw(9) << "天:";
for (int j = 1; j <= n;j++){
if (!i)
table[0][j] = j;
// cout << table[i][j] << "\t";
outfile << setw(5) << table[i][j];
}
// cout << endl;
outfile << endl;
}
outfile.close();
}
void merge(int n,int **table) {
int m = (int)ceil(n / 2.0);//m中记录n/2向上取整的值,注意必定是2.0而不能是2,由于若是是2则括号内部为整数除法
int total_days = n & 1 ? n : n - 1;
int merged_days = m & 1 ? m : m - 1;//已经排好平常表的天数,注意当m为奇数和偶数时状况的不一样
for (int i = 1; i <= merged_days;i++){//先列后行地横向构造出n个选手前merged_days天的日程表
for (int j = m+1; j <= n; j++){//从第m+1列开始构造,直到n列结束
if (table[i][j-m]) //在第i天时,先检查某个选手的对应构造选手(即第j-m个选手)当天对手是否为0号(即当天没有比赛),若不是,正常赋值
table[i][j] = table[i][j - m] + m;
else{//如果,则这一天让他俩比赛
table[i][j] = j - m;
table[i][j - m] = j;
}
}
}
//继续构造从第merged_days+1到第total_days的状况
for (int i = merged_days + 1; i <= total_days;i++){//先构造第1列,数据按行依次加1
table[i][1] = table[i - 1][1] + 1;
table[i][table[i][1]] = 1;//必定要注意构造第一行的时候把后面的对应比赛对手的数据也改了!!!
}
for (int i = merged_days + 1; i <= total_days;i++){//紧接着修改从第2列到第merged_days的数据,同时修改对应对手的数据
for (int j = 2; j <= m;j++){
//先计算对手,这里对手的序号的计算公式不太好想
//应该是以m+1为基础,依赖于m、这一行的第一个数、该运动员的编号,三者所共同决定
int rival = m + 1 + (table[i][1] - (m + 1) + (j - 1)) % m;
//修改本身的值为对手,并修改对手的值为本身
table[i][j] = rival;
table[i][rival] = j;
}
}
//若是总人数是奇数,则会出现虚拟的一我的,要把全部和这个虚拟的对手比赛的运动员,对应位置置成0,即没有比赛
if(n & 1){//符号"&"是按位与的意思,即将n转变为二进制,以后再和1按位与,显然当n为奇数的时候表达式为1,不然为0
for (int i = 1; i <= total_days;i++){
for (int j = 1; j <= n;j++){
if (table[i][j]==n+1)
table[i][j] = 0;
}
}
}
}
void tournament(int n,int **table) {
if (n==2){//当问题划分为2个运动员时,使两人相互比赛
table[1][1] = 2;
table[1][2] = 1;
// cout << table[1][2];
}
else{//若剩余待排日程表人数超过两人,则先解决(n/2向上取整)规模的子问题,以后再将子问题合并
tournament((int)ceil(n / 2.0),table);
// Show_Result(n, table);
merge(n, table);
// Show_Result(n, table);
}
}
int main() {
int n;
cout << "Please input the number of contestants:" << endl;
cin >> n;
//创建一个存储日程表的二维数组table,其中一行表明一天,从第一列开始每一列表明一个选手
//即第i行j列表示第i天第j个选手的对手编号
int **table = new int *[n + 2];
for (int i = 0; i < n + 2;i++)
table[i] = new int[n + 2];
tournament(n, table);
Show_Result(n, table);//生成的循环赛表格输出到文件中
cout << "File \"result.dat\" created." << endl;
}
复制代码
实际上算法的设计思路已经都写在注释中了,若是仔细看很快就能看懂算法的架构,在此再简要叙述一下:设计
m = ceil(n/2.0)
的问题,并解决这个子问题,有两种状况
其中,“正常构造”是如何操做的还须要详细说明一下: 以6个选手为问题规模举例说明:
选手编号: 1 2 3 4 5 6
第1 天: 2 1 6 5 4 3
第2 天: 3 5 1 6 2 4
第3 天: 4 3 2 1 6 5
第4 天: 5 6 4 3 1 2
第5 天: 6 4 5 2 3 1
复制代码
在这个问题中,实际上算法里的数组长成这个样子:
2 1 6 5 4 3
3 5 1 6 2 4
4 3 2 1 6 5
5 6 4 3 1 2
6 4 5 2 3 1
复制代码
其中,最上面一行为算法table二维数组的第1行,同理最左边一列为table中的第一列 在计算这个问题的时候,先完成规模为3的问题的计算,为了完成规模为3的问题,须要先完成规模为2的问题,此时让两我的相互比赛,即:
2 1
复制代码
当试图解决规模为3的问题的时候,咱们发现它是奇数,故扩充一个虚拟对手,变为构造一个四个选手的日程表问题,因此接下来咱们看一下如何从2个选手构造4个选手: 咱们先横向构造,将多的两我的第一天的赛程和前两人对称,比赛对手的编号加2,即:
2 1 4 3
复制代码
以后再纵向构造,首先,将第一列即第一号选手的对手按照天天增长1构造出来,同时修改对手的信息,即:
2 1 4 3
3 X 1 X
4 X X 1
复制代码
以后,构造前2列(对于规模为n的问题应该是前m列,m定义见上),从还未安排好平常表的那行开始,按照先行再列依次填写前两列中上表中标注为"X"的位置,填写的规则为: 最小的数是m+1,最大的数是n,从这一行第一列的数开始依次循环填写,同时将对应对手的值做以修改 在本例中,即最小的数为3,最大数为选手个数4,第二行第一列为3,则第二行第二列填4,同时第二行第四列填2,表示次日2号和4号相互对战;以后填写第三行,第三行第一列为4,则填写第三行第二列为3,同时修改第三行第3列为2,以下:
2 1 4 3
3 4 1 2
4 3 2 1
复制代码
因为实际上咱们须要解决的是3个选手的问题,故咱们把多余的去掉,相应位置置0:
2 1 0
3 0 1
0 3 2
复制代码
继续从3个选手构造6个选手 先横向构造,此时咱们能够看到和以前的不一样在于有数字0的出现,这时先无论0,其他按照以前法则正常横向构造:
2 1 0 5 4 0
3 0 1 6 0 4
0 3 2 0 6 5
复制代码
其实其中的0表明当天这个选手没有比赛,故咱们直接让这一天都没有比赛的两我的互相比赛便可
2 1 6 5 4 3
3 5 1 6 2 4
4 3 2 1 6 5
复制代码
接着按照上面的法则,先构造完第一列
2 1 6 5 4 3
3 5 1 6 2 4
4 3 2 1 X X
5 X X X 1 X
6 X X X X 1
复制代码
按照以前说的法则依次构造每一行
2 1 6 5 4 3
3 5 1 6 2 4
4 3 2 1 6 5
5 6 4 3 1 2
6 X X X X 1
复制代码
2 1 6 5 4 3
3 5 1 6 2 4
4 3 2 1 6 5
5 6 4 3 1 2
6 4 5 2 3 1
复制代码
最终完成了整个日程表的构造 其他规模的日程表构造同理