算法:循环赛日程表_一般化(n可以为奇数,也可以为偶数)
算法思路:
算法思路(N可能为奇数,也可能是偶数)
总体思路:按分治策略,将所有分为两半,n个选手可以通过n/2个选手设计的比赛日程表来决定。递归地用一分为二的略对选手进行分割,直到只剩下两个选手。
对于N为奇数的情况可以虚拟多一个选手,使其编程N+1个选手的日程表,最然后忽略虚拟运动员参与的比赛。对于分割时候N/2的情况也做特殊处理, 前n/2轮比赛空选手与下一个未参赛的选手进行比赛
存储结构:
数组 a[i][j], a[i][j]表示运动员i在第j天所遇到的选手
详细思路:
详细思路:
(1)分治法,
Tourna(n):
If n==1
a[1][1]=1;return;
tourna(n/2);//递归分割
copy(n); //填表
(2)n为偶数的情况Copy()函数:A.将左上角递归计算出的小块的所有数字按其相对位置抄到右下角,B.将右上角的小块的所有数字加n/2后按其相对位置抄到左下角,将左下角的小块中的所有数字按其相对位置抄到右上角
Viod copy(int n){
Int m=n/2;
For i=1àm
For j=1--->m{
a[i][j+m]=a[i][j]+m;// 小块的数值抄到右下角
a[i+m][j]=a[i][j+m];// 右上抄到左下
a[i+m][j+m]=a[i][j];// 左下抄到右上
}
}
----------------------------------------------------------------------------------------------------------------------
(3)一般性描述:n为偶数; n是奇数时增加一个虚拟选手n+1,将问题转换为n是偶数的情形。
tournament(n):
if n==1 : a[1][1]=1;return;//分割到最后
if n为奇数 :tournament(n+1);return;//奇数的情况加上虚拟选手
tournament(n/2);//分割
makecopy(n);//这个函数copy分n为偶数很n为奇数的情况
(4) 判断奇偶
odd(n):
Return n&1;
(5)makecopy()与copy相似,并区分奇偶情况
makecopy(n):
if n/2>1 &&add(n/2) copyodd(n);//对n/2为奇数的情况的处理
else copy(n);//偶数的情况
(6)copyodd(n)实现n/2为奇数的时候的复制
n/2奇数的一种处理方法:前n/2轮比赛空选手与下一个未参赛的选手进行比赛
Copyodd(n):
Int m=n/2
For i=1→m
b[i]=m+i;b[m+i]=b[i];
for i=1→m
for j=1→m+1{
if a[i][j]>m:
a[i][j]=b[i];a[m+i][j]=(b[i]+m)%n;
else
a[m+i][j]=a[i][j]+m;
for j=2→m
a[i][m+j]=b[i+j-1];
a[b[i+j-1]][m+j]=i
}
三、时间效率:
T(n)=T(n/2)+f(n)
f(n)为copy的时间
f(n)=(n/2)^2
推出:T(n)=T(n/2)+(n/2)^2
N规模的问题做logN次f(n)
T(n)属于O(∑O((n/(2^k))^2)) 1<=k<log(n) 也就是T(n)∈O(n^2)
如有不懂参考这位博主的详解:
点击进入
C++代码实现如下:
#include<bits/stdc++.h>
using namespace std;
const int size = 50;
//数组 a[i][j], a[i][j]表示运动员i在第j天所遇到的选手
int a[size][size];
//运动员个数
int n;
//判断奇偶
int odd(int n)//奇数时返回1
{
if(n%2==1)
return 1;
else
return 0;
}
//打印结果
void print()
{
if(odd(n)) // n为奇数和偶数输出情况不同,要分别考虑
{
for(int i = 1; i<=n; i++)
{
for(int j = 1; j<=n+1; j++)
if(a[i][j] == n+1)
cout << "0 ";
else
cout << a[i][j] << " " ;
cout << endl;
}
}
else
{
for(int i = 1; i<=n; i++)
{
for(int j = 1; j<=n; j++)
cout << a[i][j] << " " ;
cout << endl;
}
}
}
// 实现n/2为奇数时的复制
void copyodd(int n)
{
int b[size];
int m = n/2;
/*
因为2m+1已经超出n的范围,所以我们将值为m+1和2m+1的队组成一组,即1—m+1,2—m+2,3—m+3,……,m—n.
因为它们之间都相差m,所以可以用一个数组b的索引和值的对应关系来表示:
*/
for(int i = 1; i<=m; i++)
{
b[i] = m+i;
b[m+i] = b[i];
}
/*1--m行+m得到m+1--n行的值*/
for(int i = 1; i<=m; i++)
{
for(int j=1; j<=m+1; j++)
{
if(a[i][j] > m)
{
a[i][j] = b[i];
a[m+i][j] = (b[i] + m)%n;
}
else
a[m+i][j] = a[i][j] + m;
}
for(int j = 2; j<=m; j++)
{//在m+1到n之间循环取值,且每次取m-1个
a[i][m+j] = b[i+j-1];
a[b[i+j-1]][m+j] = i;//剩下未分配的随之解决
}
}
}
//n为偶数的情况copy()函数
void copy(int n)
{
int m = n/2;
for(int i = 1; i<=m; i++)
for(int j = 1; j<=m; j++)
{
a[i][j+m] = a[i][j] + m;//相应位置加2放到右上角
a[i+m][j] = a[i][j+m];//右上抄到左下
a[i+m][j+m] = a[i][j];//左上复制到右下
}
}
//makecopy 与copy算法类似,但是要区分n/2为奇数或偶数的情形
void makecopy(int n)
{
if(n/2 > 1 && odd(n/2))
copyodd(n);//对n/2为奇数时处理
else
copy(n);//偶数时处理
}
//分治法
void tournament(int n)
{
if(n == 1)
{
a[1][1] = 1;
return;
}
if(odd(n))
{
tournament(n+1);
return;
}
tournament(n/2);
makecopy(n);
}
int main()
{
int i,j;
cin >> n;
tournament(n);
print();
}
结果:

