比赛日程问题
问题描述:
1.设有n(n为任意值)个选手进行循环赛,手工设计一个满足以下要求的比赛日程表:
(1) 每个选手必须与其他n-1个选手各赛一次;
(2) 每个选手一天只能赛一次;
(3) 循环赛一共进行n-1天。
算法设计:
假设有N名选手参赛,不妨构造一个N×N的矩阵。在矩阵第一行填充1,2,…,N,第一列填充1,2,…,N。矩阵定义:把矩阵的第一列作为参赛选手的编号,第2~N列作为某一参赛选手的对手,显然任意的人数的选手其第一行都是一行选手,对手为2,3,…,N号选手,第一列是所有选手编号1,2,…,N。但是以上构造存在一个问题,就是这种假设默认了参赛选手人数是偶数,因为只有人数是偶数的情况下才只会需要N-1天(N-1列参赛选手)就完成比赛。所以我们不妨先考虑是选手是偶数的情况。
按照以上方法构造出来的答案如下(以N=6为例,取其它偶数情况亦同):
从以上结果寻找规律:
- 对角线上的元素全部为1。
- 考虑所有与对角线平行的斜线,对于上半矩阵,将对角线作为第一条斜线,之后的第2,3,…,N-1条斜线上的取值全部依次为2,3,…,N-1。如果斜线中的某一行原本的要取的值与所在行数相同,则取最大值N(如图1,第二行第三列位于第二条斜线,原本要取2,当因为他也在第二行,故取最大值6)。对于下半矩阵,规律也是类似的,将对角线作为第一条斜线,之后的第2,3,…,N-1条斜线取值依次为N-1,N-2,N-3……。同样的如果要取的值与行数相同则取最大值N。
根据以上规律,我们得到当N等于偶数时算法设计的思路:
- 按照上述规律对上半矩阵赋值。
- 对下半矩阵赋值。
- 对于得到矩阵,由于其每一列的和必定为sum=1+2+⋯+N,故用sum减去前N-1列的值,得到该列最后一行的值。
然后考虑N为奇数的情况(以N=7为例,其它奇数情况亦同),构造矩阵情况如下:
由构造的结果发现,对于奇数情况,我们可以先为他添加1使其变为偶数人的情况,不同的是,当斜线上存在相同值时我们不是添加最大值,而是添加0(0表示轮空)。得到的结果矩阵删去最后一行就是我们所需要的结果了。所以对于偶数情况,我们的代码要改的地方就是将传入的N+1,然后相同值由添加N改为添加0即可。此外,由于删去了最后一行,我们不需要再考虑偶数时最后一行得到的值不正确的情况了,故不需要再对每一列求和。
算法分析:
时间复杂度:O(N^2),因为有两个嵌套的for循环,每个循环的次数都是N。
空间复杂度:O(N^2),因为创建了一个N*N的二维数组。
程序设计:
1 #include <iostream> 2 3 using namespace std; 4 5 int a[101][101] = {0}; 6 7 int main(int argc, const char * argv[]) { 8 9 int N, i, j, k; 10 11 cout << "请输入参赛人数:"; 12 13 cin >> N; // 比赛人数 14 15 for (i = 1; i <= N; i ++){ 16 17 a[1][i] = a[i][1] = i; 18 19 a[i][i] = 1; 20 21 } 22 23 bool flag = true; // N为偶数 24 25 // 偶数人 26 27 if(N % 2 == 0){ 28 29 // 上半区对角线方向赋值 30 31 for (i = 2; i <= N; i ++){ 32 33 for (j = 1; j <= N-i; j ++){ 34 35 if (j + 1 == i) {// 如果要赋的值与所在行数相同 36 37 a[i][i+j] = N;// 赋予最大值 38 39 continue; 40 41 } 42 43 a[i][i+j] = j + 1; 44 45 } 46 47 } 48 49 // 下半区对角线方向赋值 50 51 for (i = 3; i <= N; i ++){ 52 53 for (j = i - 1, k = N - 1; j > 1; j --){ 54 55 if (k == i){ 56 57 a[i][j] = N; 58 59 k --; 60 61 continue; 62 63 } 64 65 a[i][j] = k --; 66 67 } 68 69 } 70 71 int sum = 0, ss = 0; 72 73 for(i = 1; i <= N; i ++){ 74 75 sum += i; 76 77 } 78 79 for (i = 2; i <= N; i ++) { 80 81 for (j = 1; j <= N - 1; j ++){ 82 83 ss += a[j][i]; 84 85 } 86 87 a[j][i] = sum - ss; 88 89 ss = 0; 90 91 } 92 93 } 94 95 // 奇数人 96 97 else{ 98 99 flag = false; 100 101 N += 1; 102 103 for (i = 2; i <= N; i ++){ 104 105 for (j = 1; j <= N; j ++){ 106 107 if (j + 1 == i) {// 如果要赋的值与所在行数相同 108 109 a[i][i+j] = 0;// 赋予0 110 111 continue; 112 113 } 114 115 a[i][i+j] = j + 1; 116 117 } 118 119 } 120 121 // 下半区对角线方向赋值 122 123 for (i = 3; i <= N; i ++){ 124 125 for (j = i - 1, k = N - 1; j > 1; j --){ 126 127 if (k == i){ 128 129 a[i][j] = 0; 130 131 k --; 132 133 continue; 134 135 } 136 137 a[i][j] = k --; 138 139 } 140 141 } 142 143 } 144 145 146 147 if(flag){ 148 149 for (i = 1; i <= N; i ++) { 150 151 for (j = 1; j <= N; j ++){ 152 153 cout << a[i][j] << "\t"; 154 155 if (j == 1){ 156 157 cout << "|\t"; 158 159 } 160 161 } 162 163 cout << endl; 164 165 } 166 167 } 168 169 else{ 170 171 for (i = 1; i <= N - 1; i ++) { 172 173 for (j = 1; j <= N; j ++){ 174 175 cout << a[i][j] << "\t"; 176 177 if (j == 1){ 178 179 cout << "|\t"; 180 181 } 182 183 } 184 185 cout << endl; 186 187 } 188 189 } 190 191 return 0; 192 193 }
结果输出: