c语言网球循环赛,网球循环赛比赛日程表n为奇数问题

初衷

在教材上看到这个问题的时候,对于奇数的处理百思不得其解,然而网上的答案要么就是n=2k的状况,要么就是本身根本都没有理解,给你讲了一大堆,各类状况,很麻烦,甚至有些是错的误人子弟。因此写下这篇思路,分享给各位。其实这个问题的核心就是分治的治该怎么去构造的问题。ios

问题

设有N个运动员要进行网球循环赛,设计一个知足如下要求的比赛日程表算法

(1)每一个选手必须与其余n-1个选手各赛一次数组

(2)每一个选手一天只能赛一次bash

(3)当n 是偶数,循环赛进行n-1天,当n是奇数,循环赛进行n天。ui

算法分析

咱们采用分治法,先算出n/2的状况,而后进行合并,构造出n的状况。难点就在于构造过程,设第i个选手第j天比赛的队员为A[i][j]。

若m = n/2为偶数,这时候咱们知道偶数人数已经算出了前m个队员的前passed_days天(对于偶数而言是m-1,对于奇数是m)的比赛状况,咱们怎么构造呢?

先横向构造,也就是构造出后m个队员在前m-1天的比赛状况,那么为了保证不重复咱们采用递增的构造方式,让i+m号选手与比A[i][j]大m的选手比赛,也是是说

A[i + m][j] = A[i][j] + m; (1≤i≤m,1≤j≤passed_days,i表明队员,j表明当前比赛的天数)

能够看出来必定不会重复,由于前m个队员以前从未与后m个队员比胜过,而后A[i][j]彼此又是互不相同的,因此A[i+m]彼此也必定不相同

再纵向构造,设n个队员比赛所需总天数为days(对于偶数而言是n-1,对于奇数是n),也就是说构造n个队员在后(days - passed_days)天的比赛状况,一样为了保证不重复咱们也采用增量构造,

passed_days +1≤j≤days,

rvalue = (count + i-1)%m + m+1;//保证i队员与后面的队员(rvalue必然大于m)比赛,这样就与前面passed_days天的比赛不重复

count为增量初始值为0,j每加1,count++

A[i][j] = rvalue;

A[rvalue][j] = i; //由于是两两比赛,后面m对用中的与之对应个队员直接构造出来

须要注意的是纵向构造的时候咱们先构造A[1][j]也就是说先保证第一个队员在(days - passed_days)比赛的队员确定与以前(passed_days)是不一样的,那么因为i也是递增的因此,A[i][j]彼此之间一定也是互不相同的!

举个栗子:假设n=4.

先计算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为奇数的话,咱们须要特殊处理下

因为咱们是按照偶数的边界构造的,也就是奇数的时候实际上扩充为了m+1列,那么咱们须要把与m+1比赛的队员置0,删掉多余的m+1列

其次因为置0,那么横向构造的时候若是A[i][j]=0,说明i队员在j天没有比赛,咱们直接让他与i+m号选手比赛(保证与以前的横向构造的增量一致)

A[i + m][j] = i;

A[i][j] = i + m;

复制代码

可是上面也引发了问题就是,咱们在纵向构造的时候A[1][passed_days]可能会重复(由于在为0的位置可能填入了m+1的元素,因此咱们须要标记一下,若是是奇数的话纵向构造的其实增量+1),只要保证了A[1][j]不重复,后面由于都是增量构造确定不会重复 r_value = (count + (i - 1) + 1) % m + m + 1;

仍是举两个例子:

n=3的构造状况

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

复制代码

n=6的构造状况

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

复制代码

C++代码

#include

#include

#include

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)<

cout << endl;

cout.flags(ios::left);

for (int j = 1; j <= days; j++)

{

cout << "第"<

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)

  • 3
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值