在教材上看到这个问题的时候,对于奇数的处理百思不得其解,然而网上的答案要么就是n=2k的状况,要么就是本身根本都没有理解,给你讲了一大堆,各类状况,很麻烦,甚至有些是错的误人子弟。因此写下这篇思路,分享给各位。其实这个问题的核心就是分治的治该怎么去构造的问题。ios
设有N个运动员要进行网球循环赛,设计一个知足如下要求的比赛日程表算法
(1)每一个选手必须与其余n-1个选手各赛一次数组
(2)每一个选手一天只能赛一次bash
(3)当n 是偶数,循环赛进行n-1天,当n是奇数,循环赛进行n天。ui
先计算n/2 = 2,咱们知道A[1][1] = 2,A[2][1] = 1(偶数比赛只有一天)spa
1 2(队员编号)
2 1(第一天)
复制代码
接下来咱们构造,n=4,此时days = 3,passed_days = 1,m=2设计
横向构造:code
1 2 3 4
2 1 4 3
复制代码
纵向构造A[1][j]:ip
1 2 3 4
2 1 4 3
3 1
4 1
复制代码
接着纵向构造A[2][j]:ci
1 2 3 4
2 1 4 3
3 4 1 2
4 3 2 1
复制代码
若m = n/2为奇数的话,咱们须要特殊处理下
A[i + m][j] = i;
A[i][j] = i + m;
复制代码
仍是举两个例子:
1.先算n=2
1 2
2 1
复制代码
2.m=2,passed_days=1,days = 3横向构造
1 2 3 4
2 1 4 3
复制代码
3.纵向构造A[1][j],j=2,3
1 2 3 4
2 1 4 3
3 1
4 1
复制代码
4.纵向构造A[2][j],j=2,3
1 2 3 4
2 1 4 3
3 4 1 2
4 3 2 1
复制代码
5.把扩充的置0,同时删掉多余的第4列
1 2 3
2 1 0
3 0 1
0 3 2
复制代码
1.首先n/2=3已经算出
2.m=3,passed_days=3,days = 5横向构造A[i][1],即第一天的
1 2 3 4 5 6
2 1 6 5 4 3
3 0 1
0 3 2
复制代码
3.接着横向构造完全部passed_days天的
1 2 3 4 5 6
2 1 6 5 4 3
3 5 1 6 2 4
4 3 2 1 6 5
复制代码
4.纵向构造A[1][4],A[1[5],因为m是奇数因此,构造增量加了1即A[1][4] = (0 + (1 - 1) + 1) % 3 + 3 + 1 = 5;
1 2 3 4 5 6
2 1 6 5 4 3
3 5 1 6 2 4
4 3 2 1 6 5
5 1
6 1
复制代码
5.纵向构造完(因为n是偶数,不须要再进行置0操做)
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
复制代码
#include <iostream>
#include <iomanip>
#include <cmath>
using namespace std;
const int MAX_NUM = 100;
int A[MAX_NUM+2][MAX_NUM+2];
/* 合并子问题 */
void merge(int n)
{
/*
* n 为偶数时,比赛 n - 1 天
* n 为奇数时,比赛 n 天
*/
int days = n&1 ? n : n-1;
/*
* 中间值,若n为奇数,则使 m = (n / 2) + 1,
* 即,前半部分不小于后半部分
*/
int m = (int)ceil(n / 2.0);
int passed_days = m& 1? m : m - 1; /* 已经安排的天数 */
/*
* 经过前 n/2 的比赛安排,构造后n/2的比赛安排
* 若是 n 为奇数,则会产生一个虚拟选手
*/
for (int i = 1; i <= m; i++)
{
for (int j = 1; j <= passed_days; j++)
{
if (A[i][j] != 0) /* 若是 i 号在第 j 天有对手 */
{
/*
* 那么,(i + m) 号在第 j 天的对手为 i号的
* 对手日后数 m 号
*/
A[i + m][j] = A[i][j] + m;
}
else /* 若是 i 号在第 j 天没有对手*/
{
/*
* 那么就让 i 号和 (i + m)号互为对手
*/
A[i + m][j] = i;
A[i][j] = i + m;
}
}
}
int add_one = 0; /*标志子问题是不是奇数,若是是的话构造增量加1 */
if (A[1][passed_days] == m + 1)
add_one = 1;
for (int i = 1; i <= m; i++)
{
for (int j = passed_days + 1, count = 0; j <= days; j++, count++)
{
/*
* 构造i 号在第 j 天的对手
*/
int r_value = (count + (i - 1) + add_one) % m + m + 1;
A[i][j] = r_value;
A[r_value][j] = i;
}
}
if ( n & 1 ) /* 若是 n 为奇数,消除虚拟的选手 */
{
for (int i = 1; i <= 2 * m; i++)
{
for (int j = 1; j <= days; j++)
if (A[i][j] == n + 1)
A[i][j] = 0; /* A[i][j] = 0 ,表示 i 号选手在第 j 天没有比赛 */
}
}
}
/* 分治求解循环赛问题 */
void tournament(int n)
{
if (n <= 1)
return;
else if (n == 2) /* 2 个选手 */
{
A[1][1] = 2;
A[2][1] = 1;
}
else
{
tournament((int)ceil(n / 2.0));
merge(n);
}
}
/* 打印循环赛日程表 */
void show_result(int n)
{
cout << " " << n << "人循环赛" << endl;
int days = n&1 ?n : n-1;
cout.flags(ios::left);
cout << setw(8) << "";
for (int i = 1; i <= n; i++)
cout << setw(2)<<i << "号";
cout << endl;
cout.flags(ios::left);
for (int j = 1; j <= days; j++)
{
cout << "第"<<setw(2)<<j<< "天 ";
for (int i = 1; i <= n; i++)
{
cout << setw(4) << A[i][j];
}
cout << endl;
}
cout << endl;
}
int main()
{
int num;
while(true){
cout << "请输入参赛人数(小于100):(0结束程序)";
cin >> num;
if(num == 0) break;
tournament(num);
show_result(num);
}
return 1;
}
复制代码
设n=2k,第i次循环须要计算(2k/2i)2,2≤i≤k,总共的计算次数粗略的表示为 12+22 +...+ 22j + ... + 22(k-1) 等比数列求和为(22k-1)/3,粗略等于22k=n^2。 因此算法时间复杂度为O(n^2).
因为只须要一个数组,因此空间代价为:O(n^2)