问题描述:
- 设有n=2^k个运动员,要进行网球循环赛。
- 每个选手必须与其他n-1个选手各赛一次。
- 每个选手一天只能赛一次。
- 循环赛一共进行n-1天。
算法策略:
1、将所有的选手分为两半,n个选手的比赛日程表可以通过为n/2个选手设计的比赛日程表来决定。
2、递归地用对选手进行分割,直到只剩下2个选手时,只要让这2个选手进行比赛就可以了。
(1)当k=1时,即人数为2人,此时表为
1 2
2 1
解释:如果只有两个选手,那么第0列看作选手编号(从0开始对列编号,第0列可以看作每个选手第0天在和自己打),第1列就是在第一天,每个选手的对手编号。
(2)当k=2时,人数为4人,循环表为
1 2 3 4
2 1 4 3
3 4 1 2
4 3 2 1
解释:如果有4个选手,分别设计4/2=2个比赛日程表,1-2选手前一天的比赛日程表如上图左上角的部分,3-4选手前一天的比赛日程表如上图左下角的部分。据此,后两天的日程表可以将左上角的子表按其对应位置抄到右下角的子表,左下角的子表可以按其对应位置抄到右上角的子表。
表的行列长度均为参赛选手数2^2 = 4,在用分治法求行、列长度均为(2^2)/2=2的子表时,首先确定左上角的子表,左下角的子表可以由左上角的子表加(2^2)/2=2得到。
(3)当k=3时,人数为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
解释:选手人数为8时,左上角的子表是选手1至选手4的前三天的比赛日程,左下角的子表是选手5至选手8前三天的比赛日程。据此后四天的比赛日程,就是分别将左上角子表按其对应位置抄到右下角的子表,将左下角的子表按其对应位置抄到右上角的子表。这样就完成了比赛日程的安排。
小结:在每次迭代求解的过程中,可以看作4部分:
1)求左上角子表:左上角子表是前个选手的比赛前半程的比赛日程。
2)求左下角子表:左下角子表是剩余的个选手的比赛前半程比赛日程。这个子表和左上角子表的对应关系式为:对应元素等于左上角子表对应元素加 。
3)求右上角子表:等于左下角子表的对应元素。
4)求右下角子表:等于左上角子表的对应元素。
具体的实现代码(java实现):
import java.util.Scanner;
public class Main {
public static int[][] table(int n) {
int[][] a = new int[n][n];
//构造赛程表第一行数据
for (int i = 0; i < n; i++)
a[0][i] = i + 1;
//采用分治算法,构造整个赛程表
for (int r = 1; r < n; r <<= 1) {
for (int i = 0; i < n; i += 2 * r) {
copy(a, r, r + i, 0, i, r);
copy(a, r, i, 0, r + i, r);
}
}
return a;
}
private static void copy(int[][] a, int tox, int toy, int fromx, int fromy, int r) {
for (int i = 0; i < r; i++) {
for (int j = 0; j < r; j++) {
a[tox + i][toy + j] = a[fromx + i][fromy + j];
}
}
}
public static void main(String[] args) {
System.out.println("请输入参赛人数:");
Scanner scan = new Scanner(System.in);
Integer read = scan.nextInt();
int num = read.intValue();
if (num % 2 != 0) {
System.out.println("输入人数不满足要求");
System.exit(0);
}
int[][] a = table(num);
for (int i = 0; i < a.length; i++) {
for (int j = 0; j < a[0].length; j++) {
System.out.print(a[i][j] + "|");
}
System.out.println();
}
}
}