分治法在循环日程赛中的运用

任何一个可以用计算机求解的问题所需的计算时间都与其规模有关。问题的规模越小,越容易直接求解,解题所需的计算时间也越少。分治法是计算机科学中经常使用的一种算法。设计思想是,将一个难以直接解决的大问题,分割成一些规模较小的相同问题,以便各个击破,分而治之。但是不是所有问题都适合用分治法解决。当求解一个输入规模为n且取值又相当大的问题时,使用蛮力策略效率一般得不到保证。

1 分治法应用条件及一般步骤

1.1分治法的应用条件

若问题能满足以下所列条件,就可以考虑用分治法来提高解决问题的效率。

1)能将这n个数据分解成k个不同子集合,且得到的k个子集合是可以独立求解的子问题,其中1<k<=n;

2)分解所得到的子问题与原问题具有相似的结构,便于利用递归或循环机制;

3)合并各个子问题的解,就是原问题的解。

1.2 分治法的一般步骤

采用分治算法解决问题的基本步骤为:

(1)分解:将原问题分解为若干个规模较小,相互独立,与原问题形式相同的子问题;

(2)求解子问题:若子问题规模较小而容易被解决则直接解,否则再继续分解为更小的子问题,直到容易解决;

(3)合并:将已求解的各个子问题的解,合并为原问题的解。

需要注意的是,不是所有的分治法都比简单的蛮力法更有效。

2 循环赛分治算法

下面,我们应用分治法解决循环赛日程表的设计问题。

2.1问题描述

有n支球队参加循环赛,设计一个满足下面要求的比赛日程表:

(1)每支球队必须与其他n-1支球队各赛一次;

(2)每支球队一天只能比赛一次;

(3)当n为偶数时,比赛进行n-1天;当n为奇数时,比赛进行n天。

按此要求,可将比赛日程表设计成一个 n 行n列的二维表。

2.2算法分析

当n= (k=1、2、3、4,……,n=2、4、8、16,……)时,此时问题比较简单。按照分治的策略,可将所有参赛的选手分为两部分,n=个选手的比赛日程表就可以通过为n/2=个选手设计的比赛日程表来决定。递归地执行这种分割,直到只剩下2个选手时,比赛日程表的制定就变得很简单:只要让这2个选手进行比赛就可以了。再逐步合并子问题的解即可得到原问题的解。

算法如下:

void tourna(int n)        //基本的分治算法

{

if(n==1){a[0][0]=1;return;}

tourna(n/2);          //分治

copy(n);              //合并

}

void copy(int n)

{

int m=n/2;

for(int i=0;i<m;i++)

for(int j=0;j<m;j++)

{

//由左上角小块的值算出对应的右上角小块的值

a[i][j+m]=a[i][j]+m;

//由右上角小块的值算出对应的左下角小块的值

a[i+m][j]=a[i][j+m];

//由左上角小块的值算出对应的右下角小块的值

a[i+m][j+m]=a[i][j];

}

}

分析算法的时间性能,迭代处理的循环体内部有2个循环语句,基本语句是最内层循环体的赋值语句,即填写比赛日程表中的元素。基本语句的执行次数是:T(n)= 3=O( ),

所以算法的时间复杂度为O( )。

下面推广n为任意整数:

当n小于或等于1时,没有比赛。

当n是偶数时,至少举行n-1轮比赛.

当n是奇数时,至少举行n轮比赛,这时每轮必有一支球队轮空。

为了统一奇数偶数的不一致性,当n为奇数时,可以加入第n+1支球队(虚拟球队,实际上不存在),并按n+1支球队参加比赛的情形安排比赛日程。那么n(n为奇数)支球队时的比赛日程安排和n+1支球队时的比赛日程安排是一样的。只不过每次和n+1队比赛的球队都轮空。所以,我们只需考虑n为偶数时情况(以上问题的详细证明,见《初等数论》—北京大学出版社—潘承洞)。

下面仅对n为偶数进行讨论:

(1)当n/2为偶数时,与n= 情形类似,可用分治法求解。

(2)当n/2为奇数时,递归返回的轮空的比赛要作进一步处理。可以在前n/2轮比赛中让轮空选手与下一个未参赛选手进行比赛。

算法如下:

void tourna(int n)                     //改进的分治赛算法

{

if(n==1){a[0][0]=0;return;}

if(odd(n)){tourna(n+1);return;}   //n为奇数,分治

tourna(n/2);                      //n为偶数,分治

makecopy(n);                      //合并

}

 

bool odd(int n)   //判断n奇偶性

{

return n&1;   //n为奇数,返回1,n为偶数,返回0

}

 

void makecopy(int n)                      //合并算法

{

if((n/2)>1&&odd(n/2)) copyodd(n);    //n/2为奇数时

else copy(n);

}

 

void copyodd(int n)              // n/2为奇数时的合并算法

{

int m=n/2;

for(int i=0;i<m;i++)

{

b[i]=m+i;

b[m+i]=b[i];

}

for(i=0;i<m;i++)

{

//由左上角小块的值算出相应的左下角小块的值

for(int j=0;j<m+1;j++)

{

if(a[i][j]>=m)

{

a[i][j]=b[i];

a[m+i][j]=(b[i]+m)%n;

}

else a[m+i][j]=a[i][j]+m;

}

//由左上角小块的值算出相应的右上角和右下角小块的值

for(j=1;j<m;j++)

{

a[i][m+j]=b[i+j];

a[b[i+j]][m+j]=i;

}

}

}

分析算法的时间性能:

当n/2为奇数时,迭代处理的循环体内部有2个循环结构,基本语句是循环体内的赋值语句,即填写比赛日程表中的元素。基本语句的执行次数是:

T(n)=  + ( + )= O( ),

此时时间复杂度为O( )。

当n/2为偶数时,迭代处理的循环体内部有2个循环语句,基本语句是最内层循环体的赋值语句,即填写比赛日程表中的元素。基本语句的执行次数是:        T(n)= 3=O( ),

此时时间复杂度为O( )。

所以,总有,时间复杂度为O( )。

2.3运行结果及分析

当输入n为2时,

输出:

1

0

在上面n=2时的日程表(二维表)中,左边第一列表示球队编号,第二列表示在某天碰到的对手球队的编号。推广之,对于n行n列的二维日程表,那么,a[0][0],a[1][0],……,a[n-1][0]表示参加循环赛的n支球队的编号;a[0][1],a[0][2],……,a[0][n-1]表示球队a[0][0]在第1,2,……,n-1天碰到的对手球队编号。a[i][j](i,j<n)表示编号为a[i][0]的球队在第j日遇到的对手球队的编号。以下同。

输入n为4时,

输出:

 2  3

 3  2

 0  1

 1  0

输入n为8时,

输出:

 2  3  4  5  6  7

 3  2  5  4  7  6

 0  1  6  7  4  5

 1  0  7  6  5  4

 6  7  0  1  2  3

 7  6  1  0  3  2

 4  5  2  3  0  1

 5  4  3  2  1  0

输入n为7时,

输出:

 2  3  4  5  6  7

 0  3  2  5  4  7  6

 3  0  1  6  7  4  5

 2  1  0  7  6  5  4

 5  6  7  0  1  2  3

 4  7  6  1  0  3  2

 7  4  5  2  3  0  1

 6  5  4  3  2  1  0

此时输出结果和输入n为8时输出结果一样。但意义略有不同。

此时球队共有7支,编号分别为0、1、2、3、4、5、6,增加了编号为7的虚拟球队(实际上该球队不存在),日程表图中有球队7参加的比赛都表示轮空。以下同。

输入为6时(6/2=3,为奇数),

输出:

 2  3  4  5

 4  2  5  3

 0  1  3  4

 5  0  2  1

 1  5  0  2

 3  4  1  0

同理,输入n为5时,

输出结果和输入n为6时输出结果一样。

输入n为10时(10/2=5,为奇数),

输出:

 2  3  4  5  6  7  8  9

 4  2  6  3  7  8  9  5

 0  1  3  4  8  9  5  6

 8  0  2  1  9  5  6  7

 1  9  0  2  5  6  7  8

 7  8  9  0  4  3  2  1

 9  7  1  8  0  4  3  2

 5  6  8  9  1  0  4  3

 3  5  7  6  2  1  0  4

 6  4  5  7  3  2  1  0

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值