算法设计与分析 第二章 递归与分治策略

70
算算算算算算算 算算算 算算算算算算算 算算算

description

算法设计与分析 第二章 递归与分治策略. 杨圣洪. 学习要点 : 理解递归的概念。 掌握设计有效算法的分治策略。 通过下面的范例学习分治策略设计技巧。 ( 1 )二分搜索技术; ( 2 )大整数乘法; ( 3 ) Strassen 矩阵乘法; ( 4 )棋盘覆盖; ( 5 )合并排序和快速排序; ( 6 )线性时间选择; ( 7 )最接近点对问题; ( 8 )循环赛日程表。. 第2章 递归与分治策略. 本章主要知识点: 2.1 递归的概念 2.2 分治法的基本思想 2.3 二分搜索技术 2.4 大整数的乘法 2.5 Strassen 矩阵乘法 - PowerPoint PPT Presentation

Transcript of 算法设计与分析 第二章 递归与分治策略

Page 1: 算法设计与分析 第二章 递归与分治策略

算法设计与分析

第二章 递归与分治策略

杨圣洪

Page 2: 算法设计与分析 第二章 递归与分治策略

22

学习要点 :

理解递归的概念。掌握设计有效算法的分治策略。通过下面的范例学习分治策略设计技巧。( 1 )二分搜索技术; ( 2 )大整数乘法;( 3) Strassen 矩阵乘法;( 4 )棋盘覆盖;( 5 )合并排序和快速排序;( 6 )线性时间选择;( 7 )最接近点对问题;( 8 )循环赛日程表。

Page 3: 算法设计与分析 第二章 递归与分治策略

33

第 2 章 递归与分治策略 本章主要知识点:

2.1 递归的概念 2.2 分治法的基本思想 2.3 二分搜索技术 2.4 大整数的乘法 2.5 Strassen 矩阵乘法 2.6 棋盘覆盖 2.7 合并排序 2.8 快速排序 2.9 线性时间选择 2.10 最接近点对问题 2.11 循环赛日程表

计划授课时间: 6 ~ 8 课时

Page 4: 算法设计与分析 第二章 递归与分治策略

44

2.1 递归的概念直接或间接地调用自身的算法称为递归算法。

用函数自身给出定义的函数称为递归函数。在计算机算法设计与分析中,使用递归技术

往往使函数的定义和算法的描述简洁且易于理解。

下面来看几个实例。

Page 5: 算法设计与分析 第二章 递归与分治策略

55

2.1 递归的概念例 1 阶乘函数可递归地定义为:高低其中:n=0 时, n!=1 为边界条件n>0 时, n!=n(n-1)! 为递归方程边界条件与递归方程是递归函数的二个要素,

递归函数只有具备了这两个要素,才能在有限次计算后得出结果。

T=1;for (i=2;i<n+1;i++) {T=T*i;} 循环低高

0)!1(

01!

nnn

nn

Page 6: 算法设计与分析 第二章 递归与分治策略

66

2.1 递归的概念 例 2 Fibonacci 数列 无穷数列 1 , 1 , 2 , 3 , 5 , 8 , 13 ,

21 , 34 , 55 ,…,被称为 Fibonacci数列。递归地定义:

第 n个 Fibonacci 数可递归地计算如下:public static int fibonacci(int n) { if (n <= 1) return 1; return fibonacci(n-1)+fibonacci(n-2); }pubic static int fibonacciLoop(int n){ f0=1;f1=1; for (i=2;i<=n;i++){f2=f0+f1;f0=f1;f1=f2;} return f1;}

121

11

01

nnFnF

n

n

nF

Page 7: 算法设计与分析 第二章 递归与分治策略

77

2.1 递归的概念 例 3 Ackerman 函数 当一个函数及它的一

个变量是由函数自身定义时,称这个函数是双递归函数。 Ackerman 函数A(n, m) 定义如下:

前 2 例中的函数都可以找到相应的非递归方式定义。

但本例中的 Ackerman函数却无法找到非递归的定义。

11

2

51

2

51

5

1

1321!

1,1,,1,

220,

01,0

20,1

nn

nF

nnn

mnmmnAAmnA

nnnA

mmA

A

Page 8: 算法设计与分析 第二章 递归与分治策略

88

2.1 递归的概念A(n, m) 的自变量 m 的每一个值都定义了一

个单变量函数: M=0 时, A(n,0)=n+2 M=1 时, A(n,1)=A(A(n-1,1),0)=A(n-1,1)+2 ,和

A(1,1)=2故 A(n,1)=2*n M=2 时, A(n,2)=A(A(n-1,2),1)=2A(n-1,2) ,和

A(1,2)=A(A(0,2),1)=A(1,1)=2 ,故 A(n,2)= 2n 。 M=3 时,类似的可以推出 M=4 时, A(n,4) 的增长速度非常快,以至于没有

适当的数学式子来表示这一函数。无法找到循环函数 - 非递归方式

n

2...22

Page 9: 算法设计与分析 第二章 递归与分治策略

99

2.1 递归的概念 例 4 排列问题

递归算法生成 n 个元素 {r1,r2,…,rn} 的全排列。 设 R={r1,r2,…,rn} 是要进行排列的 n 个元

素, Ri=R-{ri} 。去掉元素 ri 。 集合 X 中元素的全排列记为 perm(X)。 (ri)perm(X) 表示在全排列 perm(X) 的每一个排列

前加上前缀得到的排列。 R 的全排列可归纳定义如下:

• 当 n=1 时, perm(R)=(r), r 是集合 R 中唯一的元素;• 当 n>1 时, perm(R) 由 (r1)perm(R1), (r2)perm(R2),

…, (rn)perm(Rn) 构成。

Page 10: 算法设计与分析 第二章 递归与分治策略

1010

2.1 递归的概念 例 5 整数划分问题

将正整数 n 表示成一系列正整数之和: n=n1+n2+…+nk, 其中 n1≥n2≥…≥nk≥1, k≥1 。降序 正整数 n 的这种表示称为正整数 n 的划分。 求正整数 n 的不同划分个数。 例如正整数 6 有如下 11 种不同的划分:

• 6 ;• 5+1 ;• 4+2 , 4+1+1 ;• 3+3 , 3+2+1 , 3+1+1+1 ;• 2+2+2 , 2+2+1+1 , 2+1+1+1+1 ;• 1+1+1+1+1+1 。

Page 11: 算法设计与分析 第二章 递归与分治策略

1111

2.1 递归的概念如果设 p(n) 为正整数 n 的划分数则难以找到递归关系换函数 q(n,m) :最大加数 n1 m 时 n 的划分个数。q(n,m) 的如下递归关系:

q(n,1)=1, n≥1 ;最大加数 1 时,任何正整数 n 只有一种划分形式: n=1+1+...+1(共 n 个 ) 。

q(n,m)=q(n,n), m≥n ;最大加数 n

q(1,m)=1。q(n,n)=1+q(n,n-1); n 的划分 ={ 最大加数 =n 的划分 }{ 最大

加数≤ n-1 的划分 } 。q(n,m)=q(n,m-1)+q(n-m,m),n>m>1 ;最大加数 m 的划分 =

{ 最大加数≤ m-1 的划分 }{ 最大加数 n1=m 的划分 } = ={ 最大加数≤ m-1 的划分 }{ 最大加数 n1+(n-n1)即 (n-

m)<m 的划分 } 。

Page 12: 算法设计与分析 第二章 递归与分治策略

1212

2.1 递归的概念

正整数 n 的划分数 p(n)=q(n,n)。

1,1,

1,1

,

1,11

,

mnmmnqmnq

mnnnq

mnnnq

mn

mnq

Page 13: 算法设计与分析 第二章 递归与分治策略

1313

2.1 递归的概念 例 6 Hanoi 塔问题

设 a,b,c 是 3 个塔座。开始时,在塔座 a 上有一叠共 n 个圆盘,这些圆盘自下而上,由大到小地叠在一起。各圆盘从小到大编号为 1,2,…,n, 现要求将塔座 a 上的这一叠圆盘移到塔座 b 上,并仍按同样顺序叠置。在移动圆盘时应遵守以下移动规则:1) 每次只能移动 1 个圆盘;2) 任何时刻都不允许将较大的圆盘压在较小的圆盘之上;3) 在满足移动规则 1 和 2 的前提下,可将圆盘移至 a,b,c 中任一塔座上。

public static void hanoi(int n, int a, int b, int c){ if (n > 0) { hanoi(n-1, a, c, b); move(a,b); hanoi(n-1, c, b, a); }}

思考:如果塔的个数变为 a,b,c,d四个,现要将 n 个圆盘从 a 全部移动到d ,移动规则不变,求移动步数最小的方案。

Page 14: 算法设计与分析 第二章 递归与分治策略

1414

2.1 递归的概念 5 个盘子循环1 号顺移 B :2345 1 _2 号到 C: 345 1 2 1 号顺移 C :345 _ 123 号到 B: 45 3 121 号顺移 A : 145 3 2 2 号到 B: 145 23 _1 号顺移 B : 45 123 _ 4 号到 C: 5 123 4 1 号顺移 C : 5 23 142到 A:25 3 141 号顺移 A:125 3 4 3 号到 C:125 _ 341 号顺移 B:25 1 34

2 号到 C: 5 1 234 1 号顺移 C:5 _ 1234到 B: _ 5 12341 号顺移 A:1 5 2342到 B: 1 25 341 号顺移 B:_ 125 343到 A: 3 125 41 号顺移 C:3 25 14 2到 A:23 5 141 号顺移 A:123 5 44到 B:123 45 __1 号顺移 B:23 145 _2到 C:3 145 21 号顺移 C:3 45 123到 B :_ 345 12

Page 15: 算法设计与分析 第二章 递归与分治策略

1515

2.1 递归的概念 递归小结

优点:结构清晰,可读性强,且用数学归纳法证明正确性,因此它为设计算法、调试程序带来很大方便。

缺点:递归运行效率较低,时间 , 空间比非递归多。 解决方法:消除递归调用,使其转化为非递归算法。

1. 采用一个用户定义的栈来模拟系统的递归调用工作栈。该方法通用性强,但本质上还是递归,只不过人工做了本来由编译器做的事情,优化效果不明显。自顶向下保存现场

2. 用递推来实现递归函数。3. 通过 Cooper 变换、反演变换能将一些递归转化为尾递归,从

而迭代求出结果。后两种方法在时空复杂度上均有较大改善,但其适用范围有限。讨论题:递归的执行过程如何?

Page 16: 算法设计与分析 第二章 递归与分治策略

1616

2.2 分治法的基本思想 分治法的基本思想

将一个规模为 n 的问题分解为 k 个规模较小的子问题,这些子问题互相独立且与原问题相同。

对这 k 个子问题分别求解。如果子问题的规模仍然不够小,则再划分为 k 个子问题,如此递归的进行下去,直到问题规模足够小,很容易求出其解为止。

将求出的小规模的问题的解合并为一个更大规模的问题的解,自底向上逐步求出原来问题的解。

将一个难以直接解决的大问题,分割成一些规模较小的相同问题,以便各个击破,分而治之。

凡治众如治寡,分数是也。——孙子兵法

Page 17: 算法设计与分析 第二章 递归与分治策略

1717

练习1, 算法分析的基本原则有哪些?

2,试设计一算法,实现有 31 个网球运动员参加比赛的循环赛日程表。且简要分析你设计算法的时间和空间复杂性。

31*31 矩阵 , 主对角线为 0, 每行中 1-31 只能出现一次 , 每列也是如此 .

讨论题:什么时候可使用递归?程序中递归的执行过程如何?

Page 18: 算法设计与分析 第二章 递归与分治策略

1818

分治法所能解决的问题一般具有以下几个特征:分治法所能解决的问题一般具有以下几个特征: 1, 该问题的规模缩小到一定的程度就可以容易地解决; 2, 该问题可以分解为若干个规模较小的相同问题,即该问题

具有最优子结构性质 3, 利用该问题分解出的子问题的解可以合并为该问题的解; 4, 该问题所分解出的各个子问题是相互独立的,即子问题之

间不包含公共的子问题。

因为问题的计算复杂性一般是随着问题规模的增加而增加,因此大部分问题满足这个特征。这条特征是应用分治法的前提,它也是大多数问题可以满足的,此特征反映了递归思想的应用能否利用分治法完全取决于问题是否具有这条特征,如果具备了前两条特征,而不具备第三条特征,则可以考虑贪心算法或动态规划。

这条特征涉及到分治法的效率,如果各子问题是不独立的,则分治法要做许多不必要的工作,重复地解公共的子问题,此时虽然也可用分治法,一般用动态规划 .

2.2 分治法的基本思想 分治法的适用条件

Page 19: 算法设计与分析 第二章 递归与分治策略

1919

2.2 分治法的基本思想 分治法的基本步骤

divide-and-conquer(P){  if ( | P | <= n0) adhoc(P); // 解决小规模的问题 自治  divide P into smaller subinstances P1,P2,...,Pk; // 分解问题  for (i=1,i<=k,i++)  yi=divide-and-conquer(Pi); // 递归的解各子问题  return merge(y1,...,yk); // 将各子问题的解合并为原问题的解}

人们从大量实践中发现,在用分治法设计算法时,最好使子问题的规模大致相同。即将一个问题分成大小相等的 k 个子问题的处理方法是行之有效的。这种使子问题规模大致相等的做法是出自一种平衡(balancing) 子问题的思想,它几乎总是比子问题规模不等的做法要好。云计算 mapreduce

Page 20: 算法设计与分析 第二章 递归与分治策略

2020

2.2 分治法的基本思想 分治法的复杂性分析

一个分治法将规模为 n 的问题分成 k个规模为 n/m 的子问题去解。

分解阈值 n0=1, adhoc 解耗费 1 个单位时间。

将原问题分解为 k 个子问题以及用merge将 k 个子问题的解合并为原问题的解需用 f(n) 个单位时间。

分治法解规模为 |P|=n 的问题所需的计算时间,则有(右上)。

通过迭代法求得其规模为右下 。注意:递归方程及其解只给出 n 等于

m 的方幂时 T(n) 的值,但是如果认为 T(n) 足够平滑,那么由 n 等于m 的方幂时 T(n) 的值可以估计T(n) 的增长速度。通常假定 T(n)是单调上升的,从而当min<mi+1 时,

T(mi) T(n)<T(mi+1)。

1

1

)()/(

)1()(

n

n

nfmnkT

OnT

1log

0

log)/()(

nm

j

jjkm mnfknnT

Page 21: 算法设计与分析 第二章 递归与分治策略

2121

2.3 二分搜索技术给定已按升序排好序的 n 个元素 a[0:n-1] ,现

要在这 n 个元素中找出一特定元素 x。适用分治法求解问题的基本特征:分形术

问题规模缩小到一定的程度就可以容易地解决; 该问题可以分解为若干个规模较小的相同问题 ; 分解出的子问题的解可以合并为原问题的解; 分解出的各个子问题是相互独立的。

二分法:在 a[0:i]与 a[i+1:n-1] 各自查找 x ,满足分治法的四个适用条件。

Page 22: 算法设计与分析 第二章 递归与分治策略

2222

算法及其复杂性 据此容易设计出二分搜索算法:

public static int binarySearch(int [] a, int x, int n){

// 在 a[0] <= a[1] <= ... <= a[n-1] 中搜索 x// 找到 x 时返回其在数组中的位置,否则返回 -1int left = 0; int right = n - 1;while (left <= right) {

int middle = (left + right)/2; // 注意要取整!!!if (x == a[middle]) return middle;if (x > a[middle]) left = middle + 1;else right = middle - 1;

}return -1; // 未找到 x

} 算法复杂度分析:每执行一次算法的 while 循环, 待搜索数组的大小减少一半。

最坏情况任意二个相邻元素都要分划到,即 2m=n,log2n=m因此,, while 循环被执行了 O(logn) 次。循环体内运算需要 O(1) 时间,因此整个算法在最坏情况下的计算时间复杂性为 O(logn) 。

nOnm

TTnT

TT

TT

T

nn

T

nnT

mm

log1log1

122

3124

2112

11

112

11

1

Page 23: 算法设计与分析 第二章 递归与分治策略

2323

Template (class Type)

Int BinarySearch1(Type a[],const Type& x,int n) {

Int left=0;int right=n-1;

While (left<=right) {

int middle=(left+right)/2;

if (x==a[middle]) return middle;

if (x>a[middle]) left = middle;

else right=middle;

}

return -1; }

Template (class Type)

Int BinarySearch2(Type a[],const Type& x,int n) {

Int left=0;int right=n-1;

While (left<right-1) {

int middle=(left+right)/2;

if (x<a[middle]) right = middle;

else left=middle;

}

if (x==a[left]) return left;

else return -1; }

Template (class Type)

Int BinarySearch3(Type a[],const Type& x,int n) {

Int left=0;int right=n-1;

While (left+1!=right) {

int middle=(left+right)/2;

if (x>=a[middle]) left = middle;

else right=middle;

}

if (x==a[left]) return left;

else return -1;}

Template (class Type)

Int BinarySearch4(Type a[],const Type& x,int n) {

If (n>0 && x>=a[0]) {

Int left=0;int right=n-1;

While (left<right) {

int middle=(left+right)/2;

if (x<a[middle]) right = middle-1;

else left=middle;

}

if (x==a[left]) return left; }

return -1;}

习题 2-2/p35

1. 与主教材中的算法 BinarySearch 相比,数组段左、右游标 left和 right 的调整不正确,导致陷入死循环。

2. 与主教材中的算法 BinarySearch 相比,数组段左、右游标 left和 right 的调整不正确,导致当 x=a[n-1] 时返回错误。 3. 与正确算法 BinarySearch5 相比,数

组段左、右游标 left和 right 的调整不正确,导致当 x=a[n-1] 时返回错误。

4. 与正确算法 BinarySearch5 相比,数组段左、右游标 left和 right 的调整不正确,导致陷入死循环。

Page 24: 算法设计与分析 第二章 递归与分治策略

2424

2.4 大整数的乘法 设计一个有效的算法,可以进行两个 n位大整数的

乘法运算。分成二段再相乘,即分治也 小学的方法: O(n2) 效率太低 分治法 :

X=a2n/2+b Y=c2n/2+d XY=ac2n+(ad+bc)2n/2+bd

复杂度分析

T(n)=O(n2) 没有改进

    n/2 位 n/2位    n/2 位 n/2位X=         Y=

A B C D

12/4

11

nnOnT

nOnT

Page 25: 算法设计与分析 第二章 递归与分治策略

2525

算法改进 为了降低时间复杂度,必须减少乘法的次数。为此,我们把 XY写成另外的形式: XY = ac 2n + ((a-b)(d-c)+ac+bd) 2n/2 + bd 或 XY = ac 2n + ((a+c)(b+d)-ac-bd) 2n/2 + bd

复杂性: 这两个算式看起来更复杂一些,但它们仅需要 3次 n/2位

乘法 [ac、 bd 和 (a±c)(b±d)] ,于是

T(n)=O(nlog3) =O(n1.59) 较大的改进细节问题:两个 XY 的复杂度都是 O(nlog3) ,但考虑到 a+c,b+d 可能

得到 m+1位的结果,使问题的规模变大,故不选择第 2 种方案。

12/3

11

nnOnT

nOnT

Page 26: 算法设计与分析 第二章 递归与分治策略

2626

更快的方法小学的方法: O(n2)—— 效率太低分治法 : O(n1.59)——较大的改进更快的方法?

如果将大整数分成更多段,用更复杂的方式把它们组合起来,将有可能得到更优的算法。

最终的,这个思想导致了快速傅利叶变换 (Fast Fourier Transform) 的产生。该方法也可以看作是一个复杂的分治算法,对于大整数乘法,它能在O(nlogn) 时间内解决。

是否能找到线性时间的算法?目前为止还没有结果。

FFT?

Page 27: 算法设计与分析 第二章 递归与分治策略

2727

课堂练习习题 2-4 大整数乘法的 O(nmlog3/2)

给定 2 个大整数 u和 v,他们分别有 m位和 n位数字,且 m<=n 。

1, 用通常的乘法求 uv 的值需要 O(nm) 时间;2, 用教材的分治法,可以将 u和 v 都看作是有 n位数字大整数,在 O(nlog3) 时间内计算 uv 的值 ;当 m比 n 小得多的时候,这样分治法就不那么

有效,试设计一算法,使得可以在 O(nmlog3/2) 时间内求出 uv 的值。

Page 28: 算法设计与分析 第二章 递归与分治策略

2828

2.5 Strassen 矩阵乘法n×n 矩阵 A和 B 的乘积矩阵 C 中的元素 C[i,j]

定义为:

若依此定义来计算 A和 B 的乘积矩阵 C ,则每计算 C 的一个元素 C[i][j] ,需要做 n 次乘法和 n-1 次加法。因此,算出矩阵 C 的所有元素所需的计算时间为 O(n3)

n

kkjikij bac

1

Page 29: 算法设计与分析 第二章 递归与分治策略

2929

简单分治法求矩阵乘 首先假定 n 是 2 的幂。使用与上例类似的技术,将矩阵 A, B和 C 中每

一矩阵都分块成 4 个大小相等的子矩阵。由此可将方程 C=AB重写为:

由此可得:

复杂度分析

T(n)=O(n3) 没有改进 n2=n*n个元素值加法得出

2221

1211

2221

1211

2221

1211

BB

BB

AA

AA

CC

CC

22221221222122112121

22121211122112111111

BABACBABAC

BABACBABAC

22/8

212 nnOnT

nOnT

Page 30: 算法设计与分析 第二章 递归与分治策略

3030

改进算法 为了降低时间复杂度,必须减少乘法的次数。而其关键在于计算 2 个 2 阶

方阵的乘积时所用乘法次数能否少于 8 次。为此, Strassen提出了一种只用 7 次乘法运算计算 2 阶方阵乘积的方法(但增加了加 /减法次数):M1=A11(B12-B22) M2=(A11+A12)B22

M3=(A21+A22)B11 M4=A22(B21-B11)

M5=(A11+A22)(B11+B22) M6=(A12-A22)(B21+B22)

M7=(A11-A21)(B11+B12) 提取公因式,先加减后乘,还有其他方法吗? 做了这 7 次乘法后,在做若干次加 /减法就可以得到:

C11=M5+M4-M2+M6 C12=M1+M2

C21=M3+M4 C22=M5+M1-M3-M7

复杂度分析

T(n)=O(nlog7) =O(n2.81) 较大的改进

22/7

212 nnOnT

nOnT

Page 31: 算法设计与分析 第二章 递归与分治策略

3131

更快的方法 Hopcroft和 Kerr已经证明 (1971) ,计算 2 个2 ×2

矩阵的乘积, 7 次乘法是必要的。因此,要想进一步改进矩阵乘法的时间复杂性,就不能再基于计算2×2 矩阵的 7 次乘法这样的方法了。或许应当研究 3×3 或 5×5 矩阵的更好算法。

在 Strassen 之后又有许多算法改进了矩阵乘法的计算时间复杂性。

目前最好的计算时间上界是 O(n2.376) 是否能找到 O(n2) 的算法?目前为止还没有结果。

Page 32: 算法设计与分析 第二章 递归与分治策略

3333

2.6 特殊棋盘覆盖 在一个 2k×2k 个方格组成的棋盘中,恰有 1 个方格与其他方格不

同,称该方格为一特殊方格,且称该棋盘为一特殊棋盘。 在特殊棋盘覆盖问题中,要用图示的 4 种不同形态的 L型骨牌

覆盖给定的特殊棋盘上除特殊方格以外的所有方格,且任何 2个 L型骨牌不得重叠覆盖。

覆盖任意一个 2k×2k 的特殊棋盘,骨牌数恰好为 (4K-1)/3 。总格数为 (2k×2k)=2k+k=22k=(22)k=4k),去 1特殊格 , L占 3格 .

4k+1-1=4*4k-1=(3+1)4k-1=3*4k+(4k-1) 数归可证 4K-1为 3 的倍数

Page 33: 算法设计与分析 第二章 递归与分治策略

3434

2.6 特殊棋盘覆盖1 个格直接用特殊色盖2*2 时,无论特殊格在何处,只要 4个 L中 1

个4*4 时,分解成 4块,特殊格位置可东西南北

不同小块中,连接处用不同 L ,推广 2k*2k.

Page 34: 算法设计与分析 第二章 递归与分治策略

3535

分治策略求解 当 k>0 时,将 2k×2k 棋盘分割为 4 个 2k-1×2k-1 子棋盘 (a)所示。 特殊方格必位于 4 个较小子棋盘之一,其余 3 个无特殊方格。 为了将这 3 个无特殊方格的子棋盘转化为特殊棋盘,可以用一个 L型骨牌覆盖这 3 个较小棋盘的会合处, 如 (b)!!关键。 从而将原问题转化为 4 个较小规模的特殊棋盘覆盖问题即 4个同样的小问题 , 直至棋盘简化为棋盘 1×1 。

其他三种情况,画在黑板上

Page 35: 算法设计与分析 第二章 递归与分治策略

3636

分治策略求解CB(tr,tc,dr,dc,size):tr,tc 是四小块中某块左上角在原始图中行号列号dr,dc 是该小块中不能用 L型砖覆盖的地方size:该小块宽目标除特殊块以外,其他位置用 L型砖覆盖。

Page 36: 算法设计与分析 第二章 递归与分治策略

3737

算法描述void CB(int tr,tc,dr,dc,size){ if (size == 1) return;

int t = tile++; // L 型骨牌号s = size/2; // 分割棋盘// 覆盖左上角子棋盘if (dr < tr + s && dc < tc + s)

// 此块左上角为 (tr,tc)CB(tr, tc, dr, dc, s);

else {// 无特殊方格// 用 t 号 L 型骨牌覆盖右下角board[tr + s - 1][tc + s - 1] = t;// 再处理CB(tr, tc, tr+s-1, tc+s-1, s);}

// 覆盖右上角子棋盘if (dr < tr + s && dc >= tc + s)

// 此块左上角为 (tr,tc+s)CB(tr, tc+s, dr, dc, s);

else {// 无特殊方格// 用 t 号 L 型骨牌覆盖左下角board[tr + s - 1][tc + s] = t;// 再处理CB(tr,tc+s,tr+s-1,tc+s, s);}

// 覆盖左下角子棋盘if (dr >= tr + s && dc < tc + s)

//此块左上角为 (tr+s,tc)CB(tr+s, tc, dr, dc, s);

else {// 用 t 号 L 型骨牌覆盖右上角

board[tr + s][tc + s - 1] = t;// 再处理CB(tr+s, tc, tr+s, tc+s-1, s);}

// 覆盖右下角子棋盘if (dr >= tr + s && dc >= tc + s)

//此块左上角为 (tr+s,tc+s) CB(tr+s, tc+s, dr, dc, s);

else {// 用 t 号 L 型骨牌覆盖左上角

board[tr + s][tc + s] = t;// 再处理CB(tr+s, tc+s, tr+s, tc+s, s);}

}

Page 37: 算法设计与分析 第二章 递归与分治策略

3838

复杂度分析说明:

整形二维数组 Board 表示棋盘, Borad[0][0] 是棋盘的左上角方格。

tile 是全局整形变量,表示 L 形骨牌的编号,初始值为 0 。 tr: 棋盘左上角方格的行号; tc: 棋盘左上角方格的列号; dr:特殊方格所在的行号; dc:特殊方格所在的列号; size: size=2k ,棋盘规格为 2k×2k。

复杂度分析:

T(k)=4k-1=O(4k) 渐进意义下的最优算法

1114

01

kOkT

kOkT

Page 38: 算法设计与分析 第二章 递归与分治策略

3939

非递归求解 - 分治策略求解CB2(dr,dc,size):dr,dc 是该小块中不能用 L型砖覆盖的地方size:该小块宽基本思想:以 8*8 为例在黑板上演示 (1) 分成 4块,当每块格子数 >4 时,将每个 4个格子的连接处用 L型条子覆盖。 (2) 每块又分成 4块。当每块格子数 >4 时,将每个 4 个格子的连接处用 L型条子覆盖。 …… (3) 当每块格子数为 4 后,不再细分,针对每 4个格子用 L型覆盖。

Page 39: 算法设计与分析 第二章 递归与分治策略

算法描述void CB2(dr,dc,size){var t=1; if (size<4) {return ;} var sizesub=size/2; // 子块的大小 var sumTmp=0; while (sizesub>=2) {for (i=0;i<size;i=i+sizesub*2) {for (j=0;j<size;j=j+sizesub*2) { // 判断左上角是否变脏了 t++; sumTmp=0; for (dr1=0;dr1<sizesub;dr1++){ for (dc1=0;dc1<sizesub;dc1++)

{sumTmp+=Board[dr1+i][dc1+j];} } if (sumTmp==0) { Board[i+sizesub-1][j+sizesub-1]=t; } // 判断右上角是否脏了 sumTmp=0; for (dr1=0;dr1<sizesub;dr1++) { for (dc1=0;dc1<sizesub;dc1++)

{sumTmp+=Board[dr1+i][dc1+sizesub+j];}}

if (sumTmp==0) { Board[i+sizesub-1][j+sizesub]=t;}

// 判断左下角是否脏了 sumTmp=0; for (dr1=0;dr1<sizesub;dr1++) { for (dc1=0;dc1<sizesub;dc1++) {sumTmp+=Board[dr1+sizesub+i][dc1+j];} } if (sumTmp==0) {Board[i+sizesub][j+sizesub-1]=t; } // 判断右下角是否脏了 sumTmp=0; for (dr1=0;dr1<sizesub;dr1++) { for (dc1=0;dc1<sizesub;dc1++) {sumTmp+=Board[dr1+sizesub+i][dc1+sizesub+j];} } if (sumTmp==0) {Board[i+sizesub][j+sizesub]=t; }} } sizesub=sizesub/2; }

Page 40: 算法设计与分析 第二章 递归与分治策略

4141

算法描述// 最后都是 2 行 2 列的未处理完 for (i=0;i<size;i=i+2) { for (j=0;j<size;j=j+2) { // 处理一个小四块 for (dr1=0;dr1<2;dr1++) { for (dc1=0;dc1<2;dc1++) {

if (Board[dr1+i][dc1+j]==0) {Board[dr1+i][dc1+j]=t; } } } t++; } } }

非递归算法也可以解决,关键在于细心的观察!

Page 41: 算法设计与分析 第二章 递归与分治策略

4242

课堂思考为什么必须是 2k×2k 的方格?

Page 42: 算法设计与分析 第二章 递归与分治策略

4343

2.7 排序最原始的排序方法?给出代码冒泡法:从后往前两两比较,小的往前,一轮又一轮 给出代码

插入排序 给出代码合并排序自然排序快速排序线性时间选择

Page 43: 算法设计与分析 第二章 递归与分治策略

4444

2.7 合并排序 基本思想:将待排序元素分成大小大致相同的 2 个子集,分别对 2 个子

集合进行排序,将排好序的子集合合并成为所要求的排好序的集合。 直到子集中只有 1 个元素为止,然后再两两合并。。

递归算法描述:public static void mergeSort(Comparable a[], int left, int right){

if (left<right) {// 至少有 2 个元素int i=(left+right)/2; // 取中点 , 一定要取整mergeSort(a, left, i);

mergeSort(a, i+1, right);

merge(a, b, left, i, right); // 合并到数组 b

copy(a, b, left, right); // 复制回数组 a

}

} 复杂度分析

T(n)=O(nlogn) 渐进意义下的最优算法

12/2

11

nnOnT

nOnT

Page 44: 算法设计与分析 第二章 递归与分治策略

4545

算法改进算法 mergeSort 的递归过程可以消去。

初始序列 [49] [38] [65] [97] [76] [13] [27]

第一步  [38 49] [65 97] [13 76] [27]

第二步  [38 49 65 97] [13 27 76]

第三步  [13 27 38 49 65 76 97]

Page 45: 算法设计与分析 第二章 递归与分治策略

4646

非递归算法描述及其复杂性算法描述:直接在板上演示

MergeSort

MergePass

Merge

Page 46: 算法设计与分析 第二章 递归与分治策略

4747

Conti…复杂性分析:

最坏时间复杂度: O(nlogn) 平均时间复杂度: O(nlogn) 辅助空间: O(n)

思考题:给定有序表 a[1:n] ,修改合并排序算法,求出该有序表的逆序对数。

自然合并排序 ?

Page 47: 算法设计与分析 第二章 递归与分治策略

4848

Conti…在一种极端情况:序列已排好序

自然合并排序: O(n) 合并排序: O(nlogn)

一般情况下,自然合并排序所需的合并次数较少。

Page 48: 算法设计与分析 第二章 递归与分治策略

4949

课堂练习习题 2-13: n1/2段合并排序算法

如果在合并排序算法的分段步骤中,将数组a[0,n-1] 划分为 [n1/2] 个子数组,每个子数组有O(n1/2) 个元素。然后递归地对分段后的子数组进行排序,最后将所得到的 [n1/2] 个排好序的子数组合并成所要求的排好序的 a[0,n-1] 。设计一个实现上述策略的合并排序算法,并分析算法的计算复杂性。

习题 2-14 :自然合并排序算法 对所给元素存储于数组中和存储于链表中 2 种情

形,写出自然合并排序算法。

Page 49: 算法设计与分析 第二章 递归与分治策略

5050

2.8 快速排序 快速排序是基于分治策略的另一个排序算法,其基本思想是:

分解——以 ap 为基准元素将 ap: r 划分成 3段 ap: q-1、 aq和 aq+1: r ,使得 ap:

q-1 中任何元素小于 aq , aq+1: r 中任何元素大于 aq ;下标 q 在划分过程中确定;

递归求解——通过递归调用快速排序算法分别对 ap: q-1和 aq+1: r 进行排序; 合并——由于对 ap: q-1和 aq+1: r 的排序是就地进行的,所以在 ap: q-1和 aq+1: r

都已排好序后不需要执行任何计算 ap: r就已排好序。 在快速排序中,记录的比较和交换是从两端向中间进行的,关键字较大的记录

一次就能交换到后面单元,关键字较小的记录一次就能交换到前面单元,记录每次移动的距离较大,因而总的比较和移动次数较少。

左边比首元小右移,直到大于首元,右边比首元大左移直到小于首元 快速算法描述:

template<class Type>void QuickSort (Type a[], int p, int r){

if (p<r) {int q=Partition(a,p,r);QuickSort (a,p,q-1); // 对左半段排序QuickSort (a,q+1,r); // 对右半段排序}

}

Page 50: 算法设计与分析 第二章 递归与分治策略

5151

分解 / 划分算法描述 分解 / 划分算法描述:

template<class Type>int Partition (Type a[], int p, int r){

int i = p, j = r + 1; Type x=a[p];// 将 < x 的元素交换到左边区域// 将 > x 的元素交换到右边区域while (true) {

while (a[++i] <x);while (a[- -j] >x);if (i >= j) break; Swap(a[i], a[j]);

}a[p] = a[j];a[j] = x;return j;

}

{6, 7, 51, 2, 5, 8} 初始序列{6, 7, 51, 2, 5, 8} j--;↑i ↑ j

{5, 7, 51, 2, 6, 8} i++; ↑ i ↑ j

{5, 6, 51, 2, 7, 8} j--; ↑ i ↑ j

{5, 2, 51, 6, 7, 8} i++; ↑ i ↑j

{5, 2, 51}6{7, 8} 完成

快速排序具有不稳定性!

Page 51: 算法设计与分析 第二章 递归与分治策略

5252

复杂性分析及随机化的快速排序算法 算法复杂性分析:

最坏时间复杂度: O(n2) 平均时间复杂度: O(nlogn) 辅助空间: O(n)或 O(logn)

Page 52: 算法设计与分析 第二章 递归与分治策略

5353

Conti… 快速排序算法的性能取决于划分的对称性。通过修改算法

partition ,可以设计出采用随机选择策略的快速排序算法。在快速排序算法的每一步中,当数组还没有被划分时,可以在 a[p:r] 中随机选出一个元素作为划分基准,这样可以使划分基准的选择是随机的,从而可以期望划分是较对称的。

算法描述: template<class Type> int RandomizedPartition (Type a[], int p, int r) {

• int i = Random(p,r);• Swap(a[i], a[p]);• return Partition (a, p, r);

}

Page 53: 算法设计与分析 第二章 递归与分治策略

5454

2.9 线性时间选择 元素选择问题:给定线性序集中 n 个元素和一个整数

k, 1≤k≤n ,要求找出这 n 个元素中第 k 小的元素。 RandomizedSelect 算法:模仿快速排序算法,首先对输入数组进行划分,然后对划分出的子数组之一进行递归处理。算法描述如下:template<class Type>Type RandomizedSelect(Type a[],int p,int r,int k){

if (p==r) return a[p];int i=RandomizedPartition(a,p,r),j=i-p+1;if (k<=j) return RandomizedSelect(a,p,i,k);else return RandomizedSelect(a,i+1,r,k-j);

}

Page 54: 算法设计与分析 第二章 递归与分治策略

5555

时间复杂性分析

RandomizedSelect

RandomizedPartition

Partition

快速排序算法中的随机化划分,T(n)?

快速排序算法中的划分, O(nlogn)

最坏情况下, Omega(n2)比如要找最小元素,但总是在最大元素处划分

算法复杂性:在最坏情况下,算法 randomizedSelect需要 O(n2)计算时间。但可以证明,算法 RandomizedSelect 可以在 O(n)平均时间内找出 n个输入元素中的第 k小元素。

Page 55: 算法设计与分析 第二章 递归与分治策略

5656

改进算法 基本思路:如果能在线性时间内找到一个划分基准,

使得按这个基准所划分出的 2 个子数组的长度都至少为原数组长度的 ε倍 (0<ε<1 是某个正常数 ) ,那么就可以在最坏情况下用 O(n) 时间完成选择任务。

例如,若 ε=9/10 ,算法递归调用所产生的子数组的长度至少缩短 1/10 。所以,在最坏情况下,算法所需的计算时间 T(n)满足递归式 T(n)≤T(9n/10)+O(n) 。由此可得 T(n)=O(n)。

T(εn)+O(n)

Page 56: 算法设计与分析 第二章 递归与分治策略

5757

一个较好的基准划分步骤 步骤(如图所示):

将 n 个输入元素划分成 n/5 个组,每组 5 个元素,只可能有一个组不是 5 个元素。用任意一种排序算法,将每组中的元素排好序,并取出每组的中位数,共 n/5 个。

递归调用 select 来找出这 n/5 个元素的中位数。如果 n/5 是偶数,就找它的 2 个中位数中较大的一个。以这个元素作为划分基准。

说明: 设所有元素互不相同。在这种情况

下,找出的基准 x 至少比 3(n-5)/10个元素大,因为在每一组中有 2 个元素小于本组的中位数,而 n/5 个中位数中又有 (n-5)/10 个小于基准 x(如图)。同理,基准 x也至少比3(n-5)/10 个元素小。而当 n≥75 时,3(n-5)/10≥n/4所以按此基准划分所得的 2 个子数组的长度都至少缩短1/4 。

图 2-7 选择划分基准其中, n个元素用小圆点表示,   空心圆点为每组元素的中位数;    x 为中位数的中位数;   箭头由较大元素指向较小元素。只要等于基准的元素不太多,利用这个基准来划分的两个数组的大小就不会相差太远。

ai<xn/5+(n/5-1)/2

>3(n-5)/10

ai>xn/5+(n/5-1)/2

>3(n-5)/10

Page 57: 算法设计与分析 第二章 递归与分治策略

5858

算法描述及复杂性分析private static Comparable select (int p, int r, int k){

// 用某个简单排序算法对数组 a[p:r] 排序 ; if (r-p<75) {

bubbleSort(p,r);return a[p+k-1];

}//将 ap+5*i至 ap+5*i+4 的第 3 小元素与 ap+i交

换 ;// 找中位数的中位数, r-p-4即前述 n-5;for ( int i = 0; i<=(r-p-4)/5; i++ ){

int s=p+5*i,t=s+4;for (int j=0;j<3;j++) bubble(s,t-j);MyMath.swap(a, p+i, s+2);

}Comparable x = select(p, p+(r-p-4)/5, (r-p+6)/10);int i=partition(p,r,x),j=i-p+1;if (k<=j) return select(p,i,k);else return select(i+1,r,k-j);

}

复杂度分析

C1 为直接简单排序时间 C2n 为执行 for 循环的时间 解递归方程得 T(n)=O(n)

说明: 上述算法将每一组的大小定为

5,并选取 75作为是否作递归调用的分界点。这 2点保证了T(n)的递归式中 2个自变量之和 n/5+3n/4=19n/20=εn , 0<ε<1。这是使T(n)=O(n)的关键之处。当然,除了 5和 75之外,还有其他选择。

上述算法中我们假设元素互不相等已保证划分后子数组不超过 3n/4。当元素可能相等时,设有 m个(将他们集中起来),若 j≤k≤j+m-1时返回 ai;否则调用 select(i+m+1, r, k-j-m) 。

754/35/

75

2

1

nnTnTnC

nCnT

Page 58: 算法设计与分析 第二章 递归与分治策略

5959

课堂练习习题 2-11 O(1)空间子数组换位算法

设 a[0,n-1] 是一个有 n 个元素的数组, k 是一个非负整数。试设计一个算法将子数组 a[0,k-1]与a[k,n-1] 换位。要求算法在最坏情况下耗时 O(n) ,且只用到 O(1) 的辅助空间。

习题 2-12 O(1)空间合并算法 设 a[0,k-1]和 a[k,n-1)已排好序。试设计一个算法

将这两个子数组合并为排好序的数组 a[0,n-1) 。要求算法在最坏情况下所用的计算时间为 O(n) ,且只用到 O(1) 的辅助空间。

Page 59: 算法设计与分析 第二章 递归与分治策略

6060

2.10 最接近点对问题问题描述:给定平面上 n 个点,找其中的一

对点,使得在 n 个点所组成的所有点对中,该点对间的距离最小。

说明: 严格来讲,最接近点对可能多于一对,为简便起见,我们只找其中的一对作为问题的解。

一个简单的做法是将每一个点与其他 n-1 个点的距离算出,找出最小距离的点对即可。该方法的时间复杂性是 T(n)=n(n-1)/2+n=O(n2) ,效率较低。

已经证明,该算法的计算时间下界是 Ω(nlogn)。

Page 60: 算法设计与分析 第二章 递归与分治策略

6161

一维空间中的情形 为了使问题易于理解和分析,先来考虑一维的情形。此时, S 中的 n 个

点退化为 x轴上的 n 个实数 x1,x2,…,xn 。最接近点对即为这 n 个实数中相差最小的 2 个实数。

一个简单的办法是先把 x1,x2,…,xn 排好序,再进行一次线性扫描就可以找出最接近点对, T(n)=O(nlogn) 。然而这种方法无法推广到二维情形。

假设我们用 x轴上某个点 m将 S 划分为 2 个子集 S1和 S2 ,基于平衡子问题的思想,用 S 中各点坐标的中位数来作分割点。

递归地在 S1和 S2 上找出其最接近点对 {p1,p2} 和 {q1,q2} ,并设d=min{|p1-p2|,|q1-q2|}, S 中的最接近点对或者是 {p1,p2} ,或者是{q1,q2} ,或者是某个 {p3,q3} ,其中 p3 S1∈ 且 q3 S2∈ 。

能否在线性时间内找到 p3,q3?

Page 61: 算法设计与分析 第二章 递归与分治策略

6262

算法描述及复杂性 如果 S 的最接近点对是 {p3,q3} ,即

|p3-q3|<d ,则 p3和 q3 两者与 m 的距离不超过 d ,即 p3 (m-∈d,m], q3 (m,m+d]∈ 。

由于在 S1 中,每个长度为 d 的半闭区间至多包含一个点(否则必有两点距离小于 d),并且 m是 S1和 S2的分割点,因此 (m-d,m] 中至多包含S 中的一个点。由图可以看出,如果(m-d,m] 中有 S 中的点,则此点就是S1 中最大点。

因此,我们用线性时间就能找到区间(m-d,m] 和 (m,m+d] 中所有点,即 p3和 q3 。从而我们用线性时间就可以将 S1 的解和 S2 的解合并成为 S 的解。

分割点 m 的选取不当,会造成 |Si|=1, |Sj|=n-1(i+j=1) 的情形,使得T(n) =T(n-1)+O(n)=O(n2) 。这种情形可以通过“平衡子问题”方法加以解决:选取各点坐标的中位数作分割点。

算法描述:bool CPair1(S, d){

n=|S|;if (n<2){d=∞; return false;}m=Blum(S); //S各点坐标中位数S=>S1+S2;//S1={x|x<=m} S2={x|

x>m}CPair1(S1, d1);CPair1(S2, d2);p=max(S1);q=min(S2);d=min(d1, d2, q-p);return ture;

} 复杂性分析:

T(n)=O(nlogn) 该算法可推广到二维的情形中去。

42/2

41

nnOnT

nOnT

Page 62: 算法设计与分析 第二章 递归与分治策略

6363

二维空间的最接近点对问题 下面来考虑二维的情形。

选取一垂直线 l:x=m 来作为分割直线。其中 m为 S 中各点 x坐标的中位数。由此将 S 分割为 S1和 S2。

递归地在 S1和 S2 上找出其最小距离 d1和 d2 ,并设d=min{d1,d2}, S 中的最接近点对或者是 d ,或者是某个 {p,q} ,其中 p P1∈ 且 q P2 ∈ ,如图 2-9所示。

能否在线性时间内找到 p,q? 考虑 P1 中任意一点 p ,它若与 P2 中的点 q 构成最接近点对的候选者,则必有 distance(p, q)< d 。满足这个条件的 P2 中的点一定落在一个 d×2d 的矩形 R 中,如图 2-10所示。

由 d 的意义可知, P2 中任何 2个 S 中的点的距离都不小于d 。由此可以推出矩形 R 中最多只有 6个 S 中的点。

图 2-9距离直线 l 小于 d 的所有点

图 2-10包含 q的 d×2d 矩形 R

Page 63: 算法设计与分析 第二章 递归与分治策略

6464

R 中至多包含 6个 S 中的点的证明 证明:

将矩形 R 的长为 2d 的边 3等分,将它的长为 d 的边 2等分,由此导出 6 个 (d/2)×(2d/3) 的矩形(如图 (a)所示 )。

若矩形 R 中有多于 6个 S 中的点,则由鸽舍原理易知至少有一个 (d/2)×(2d/3) 的小矩形中有 2 个以上 S 中的点。

设 u, v 是位于同一小矩形中的 2 个点,则

因此, distance(u,v)<d 。这与 d 的意义相矛盾。

也就是说,矩形 R 中最多有 6个 S 中的点。

极端情形:图 (b) 是矩形 R 中恰有 6个 S 中的点的极端情形。

222

22

36

253/22/ ddd

vyuyvxux

Page 64: 算法设计与分析 第二章 递归与分治策略

6565

鸽舍原理 ?鸽舍原理,也称“抽屉原理”或利克雷原则,它是一个重要而又基本的数学原理,应用它可以解决各种有趣的问题,并且常常能够得到令人惊奇的结果,许多看起来相当复杂,甚至无从下手的问题,利用它能很容易得到解决。 •原理(一):把多于几个的元素按任一确定的方式分成几个集合,那么一定至少有一个集合中,至少含有两个元素。 •原理(二): 把多于 m×n 个物体放到 n个抽屉里,那么一定有一个抽屉里有m+ 1 个或者m+ 1 个以上的物体。

Page 65: 算法设计与分析 第二章 递归与分治策略

6666

说明因此,在分治法的合并步骤中最多只需要检查 6×n/

2=3n 个候选者。 为了确切地知道要检查哪 6 个点,可以将 p和 P2 中所有 S2 的点投影到垂直线 l 上。由于能与 p 点一起构成最接近点对候选者的 S2 中点一定在矩形 R 中,所以它们在直线 l 上的投影点距 p在 l 上投影点的距离小于 d 。由上面的分析可知,这种投影点最多只有6 个。

因此,若将 P1和 P2 中所有 S 中点按其 y坐标排好序,则对 P1 中所有点,对排好序的点列作一次扫描,就可以找出所有最接近点对的候选者。对 P1 中每一点最多只要检查 P2 中排好序的相继 6 个点。

Page 66: 算法设计与分析 第二章 递归与分治策略

6767

算法描述及复杂性分析 算法描述:

public static double CPair2(S) {

• n=|S|;• if (n < 2) return ;• m=S中各点 x间坐标的中位数 ;• 构造 S1 和 S2 ;• //S1={p S|x(p)<=m}, ∈• S2={p S|x(p)>m}∈• d1=cpair2(S1);• d2=cpair2(S2);• dm=min(d1,d2);• 设 P1 是 S1中距垂直分割线 l的距离在 dm之内的所有点组成的集合;

• P2 是 S2中距分割线 l的距离在dm之内所有点组成的集合;

• 将 P1 和 P2中点依其 y坐标值排序;

• 并设 X 和 Y是相应的已排好序的点列;

• 通过扫描 X以及对于 X中每个点检查 Y中与其距离在 dm之内的所有点 (最多 6个 )可以完成合并;

• 当 X中的扫描指针逐次向上移动时, Y中的扫描指针可在宽为 2dm的区间内移动;

• 设 dl是按这种扫描方式找到的点对间的最小距离;

• d=min(dm,dl);

• return d;

} 复杂度分析:

T(n)=O(nlogn)

算法的具体实现:略。

42/2

41

nnOnT

nOnT

Page 67: 算法设计与分析 第二章 递归与分治策略

6868

2.11 循环赛日程表 分治法不仅可以用来设计算法,而且再其他方面也有广泛应

用:利用分治法设计电路、构造数学证明等。 循环赛日程标问题,设有 n=2k 个选手要进行循环赛,设计一

个满足以下要求的比赛日程表: 每个选手必须与其他 n-1 个选手各赛一次; 每个选手一天只能赛一次; 循环赛一共进行 n-1天。

按此要求,可以将比赛日程表设计成 n行 n-1 列的表格, i行 j 列表示第 i 个选手在第 j天所遇到的选手。

基本思路:按分治策略,将所有的选手分为两组, n 个选手的比赛日程表就可以通过为 n/2 个选手设计的比赛日程表来决定。递归地用对选手进行分割,直到只剩下 2 个选手时,比赛日程表的制定就变得很简单。这时只要让这 2 个选手进行比赛就可以了。

Page 68: 算法设计与分析 第二章 递归与分治策略

6969

算法描述及示例算法描述:略。思考题:

递归形式的算法描述。

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

Page 69: 算法设计与分析 第二章 递归与分治策略

7070

课堂思考为什么必须是 2k 个运动员的循环赛?

设有 n 个运动员要进行网球循环赛。设计一个满足下面要求的比赛日程表: 1 ,每个选手必须和其他 n-1 个选手各赛一次; 2 ,每个选手一天只能赛一次; 3 ,当 n=2k 时,循环赛进行 n-1天;当 n=2k+1

时,循环赛进行 n天。

Page 70: 算法设计与分析 第二章 递归与分治策略

7171

小结递归的概念分治策略的基本思想和相关实例

1. 算法分析题 2-1、 2-3、 2-7、 2-14、 2-18、 2-19、 2-32、 2-33 。

2. 算法实现题 2-2、 2-3、 2-4、 2-9、 2-12、 2-14 。