今天上午准备学习一道算法,拿到一本《计算机算法设计、分析与实现》书,翻开一道循环赛赛程安排的题目。要求大致是这样的:
要求有n=2^k名网球运动员进行轮流赛,每个人都要与其他n-1名队员打一场比赛;
比赛在n-1天内必须结束;
每个球队每天只能打一场比赛;
试给出安排方案。
这里我们用一个最熟悉的思想来解决----递归。这样理解,假设有n个球员,我们先不管别的,一分两半只关心n/2名球员的安排,以此类推直到最后只剩下两名队员
1 | 2 |
2 | 1 |
发现结果是上面(图一)这样的,如果看不出有什么规律,那我们就拿4名球员来往外扩展:
1 | 2 | 3 | 4 |
2 | 1 | 4 | 3 |
3 | 4 | 1 | 2 |
4 | 3 | 2 | 1 |
这样,我们能发现,如果我们以图二左上角的四个数为参照矩阵,右上角分别是在左上角矩阵的基础上为每个元素增加了矩阵宽度的一半,左下角与右上角保持一致,只是位置发生了位移,右下角的元素是在左上角元素位移得来的。得到这样的规律后,我们就发现问题变得异常简单了。按照正常的思路,我们不用关心矩阵的大小,也就是说不管有多少参赛队员,尽管放马过来,我来随便的为你们分组,一刀少一半,在一刀又少一半,最后只剩下两个人了。好了,基本不用安排了,比赛的第一天就是你打他(他打你)。
基于这种把问题由大化小,由小化简单的思想,同时我们发现了矩阵的规律。那我们就先创建出左上角的子矩阵,然后基于左上角的子矩阵得到其他子矩阵。
代码分析实现:
#include<iostream>
using namespace std;
#define TABLESIZE 8
int table[TABLESIZE][TABLESIZE];
void CreateTable(int r1,int c1,int r2,int c2,int size){//r1c1代表当前块的左上角横纵坐标,r2c2代表当前块右下角的横纵坐标
int halfsize=size/2;//分治思想,一刀两半,刀刀刀……
if(size>1){
CreateTable(0,0,halfsize,halfsize,halfsize);
}
else{
table[0][0]=1;
}
for(int i=0;i<size;i++){
for(int j=0;j<size;j++){//当前在右上角
if(i<halfsize&&(j>=halfsize&&j<size)){
table[i][j]=table[i][j-halfsize]+halfsize;
}
if((i>=halfsize&&i<size)&&j<halfsize){//当前在左下角
table[i][j]=table[i-halfsize][j+halfsize];
}
if((i>=halfsize&&i<size)&&(j>=halfsize&&j<size)){当前在右下角
table[i][j]=table[i-halfsize][j-halfsize];
}
}
}
}
int main(){
CreateTable(0,0,TABLESIZE,TABLESIZE,TABLESIZE);
for(int i=0;i<TABLESIZE;i++){
cout<<"运动员"<<table[i][0]<<"的每日赛程: ";
for(int j=0;j<TABLESIZE;j++){
cout<<table[i][j]<<"";
}
cout<<endl;
}
return 0;
}
反思:这个方法虽然巧妙但却不是从问题的原点分析出来的,而是总结矩阵的规律获得的方法。不过好在代码中实现了递归的思想,使问题变得简单了。在存储矩阵的时候,我在本网站的其他高手写的博客有人使用的是一维数组,动态开辟存储空间,方法很好也很巧妙;绝大多数人使用的还是二维数组存储。当然,这不是关键,最主要的是我们在处理子矩阵复制的时候,代码注释很少,小弟头脑简单,觉得这种方法亦能解决问题,就ok了。