一、問題描述
設有 n = 2k 個運動員要進行網球循環賽。現在要設計一個滿足以下要求的比賽日程表
- 每個選手必須與其他 n-1 個選手各賽一場
- 每個選手一天只能比賽一場
- 循環賽一共進行 n-1 天
二、算法分析
按此要求可將比賽日程表設計成 n 行 n-1 列的表,在表中第 i 行和第 j 列處填入第 i 個選手在第 j 天所遇到的對手。
按分治策略,可以將所有的選手分為兩半,則 n 個選手的比賽日程表可以通過 n/2 個選手的比賽日程表來決定。遞歸地用一分為二的策略對選手進行划分,直到只剩下兩個選手時,比賽日程表的制定就變得很簡單,這時只要讓這兩個選手進行比賽就可以了。
通過 k 增長來看算法實現步驟:
當 k = 1 時,n = 21 = 2 人,循環表為
1 2
2 1
當 k = 2 時,n = 22 = 4 人,循環表為
1 2 3 4
2 1 4 3
3 4 1 2
4 3 2 1
當 k = 3 時,n = 23 = 8 人,循環表為
1 2 3 4 5 6 7 8
2 1 4 3 6 5 8 7
3 4 1 2 7 8 5 6
4 3 2 1 8 7 6 5
5 6 7 8 1 2 3 4
6 5 8 7 2 1 4 3
7 8 5 6 3 4 1 2
8 7 6 5 4 3 2 1
以此類推,可以用分治的方法實現,先自頂向下分解,直到分解到最簡單的情況,即人數為 2 人,這時就可以兩兩比賽,表的填充為對角填充的方式,然后再自底向上填充表格,具體的看上面的 k = 1、k = 2、k = 3 時形成的循環表就很好理解了。
三、代碼實現
#include <stdio.h>
#define N 64
void GameTable(int k, int a[][N])
{
int n = 2; // 選手數
// 求解兩個選手比賽日,得到左上角元素
a[0][0] = 1; a[0][1] = 2;
a[1][0] = 2; a[1][1] = 1;
int i, j, half;
// 循環處理,依次處理 2^2 ... 2^k 個選手比賽日程
for (int t = 1; t < k; t++) {
half = n; // 當前選手數的 1 / 2
n *= 2; // 當前選手數
// 左下角
for (i = half; i < n; i++) // 行
for (j = 0; j < half; j++) // 列
a[i][j] = a[i - half][j] + half;
// 右上角
for (i = 0; i < half; i++)
for (j = half; j < n; j++)
a[i][j] = a[i + half][j - half];
// 右下角
for (i = half; i < n; i++)
for (j = half; j < n; j++)
a[i][j] = a[i - half][j - half];
}
printf("運動員編號\t");
for (i = 1; i < n; i++) {
printf("第 %d 天\t", i);
}
printf("\n\n");
for (i = 0; i < n; i++) {
printf(" %d 號 \t", i + 1);
for (j = 1; j < n; j++)
printf(" %d", a[i][j]);
printf("\n");
}
}
int main()
{
int a[N][N] = { 0 };
int k = 3;
printf("******************************************\n");
printf("\t\t**\t\t循環賽日程表\t\t**\n");
printf("******************************************\n\n");
GameTable(k, a);
return 0;
}
******************************************
** 循環賽日程表 **
******************************************
運動員編號 第 1 天 第 2 天 第 3 天 第 4 天 第 5 天 第 6 天 第 7 天
1 號 2 3 4 5 6 7 8
2 號 1 4 3 6 5 8 7
3 號 4 1 2 7 8 5 6
4 號 3 2 1 8 7 6 5
5 號 6 7 8 1 2 3 4
6 號 5 8 7 2 1 4 3
7 號 8 5 6 3 4 1 2
8 號 7 6 5 4 3 2 1