在教材上看到這個問題的時候,對於奇數的處理百思不得其解,然而網上的答案要麼就是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)