第3章 分治算法
②分治法 基本思想: 首先,将原问题分解成大小基本相同的2个子问题; 然后,再分别对这2个子问题继续进行分解,直到分 解成的子问题中只有2个元素,此时可以直接求出最 大元、最小元; 最后,再进行两两合并,取2个最小元最小者,2个 最大元最大者。
寻找最大最小元素的求解过程:
8 1,8 10 8 3,8 7 8 3,8 4 8 3 3 5 6 3 2 6 6,6 6 8 2 1 3 6 1 2 1 1,2 9 4,9 13 9 4 12 14 5 11 9 4 5 3 6 2 1 9 4 5 7 4,9 16 7 5,7 15 7 1,9
T(n)= 2x *1 + 2x +2x-1… + 22 + 21
= 2x *1 +(2- 2x*2 )/(1-2) = 2x + 2x+1 - 2 =3n/2 - 2 故时间复杂度记为:O(n)
补充2: 棋盘覆盖问题 1、问题提出
3.1.2 分治算法基本步骤:
1)分解 将一个难以直接解决的大问题,分割成一些规模 较小的相同子问题,以便各个击破,分而治之。
T(n)
=
n
T(n/2)
T(n/2)
对这k个子问题分别求解。如果子问题的规模仍然不 足够小,则再划分为k个子问题,如此递归的进行下 去,直到问题规模足够小,很容易求出其解为止。
③算法描述(递归形式) int BinarySearch (int s[n], int x, int low, int high) { if (low>high) return -1; int middle=(low+high)/2; if(x==s[middle]) return middle; else if(x>s[middle]) //继续在右边查找 return BinarySearch (s, x, middle+1, high); else //继续在左边查找 return BinarySearch (s, x, low, middle-1); }
④ 算法分析
设给定的有序序列中具有n个元素。
当n=1时,查找一个元素需要常量时间,因而T(n)=O(1)。 当n>1时,T(n)= T(n/2) + O(1) = T(n/4) + 2O(1) =…… = T(n/2x) + xO(1) 分解到最后就是1个元素,则n=2x,则x=logn。
由此,T(n)=T(1)+logn=O(1)+O(logn)。
3.2 二分法(二分查找)
在算法设计中每次一个问题分解成的子问题个数一般是
固定的,每个子问题的规模也是平均分配的。当每次都
将问题分解为原问题规模的一半时,称为二分法。 二分法是分治法较常用的分解策略,数据结构课程中的 二分查找、归并排序等算法都是采用此策略实现的.
二分查找
①基本思想 如果low<high; Ⅰ、首先确定中点位置:mid=(low+high)/2; Ⅱ、然后将待检查的数据X与R[mid]比较: a、若X<R[mid],则在左边区间R[1…mid-1] 查找; b、若X>R[mid],则在右边区间R[mid+1…n] 查找; c、若X=R[mid],查找成功;
②举例 例: 查找33 08, 23, 29, 31, 37, 65, 70, 79, 80, 88,100 low=1;
在左边找 08, 23, 29, 31, 37 low=1; mid=3;
mid=(1+11)/2=6; 33<65;
high=11;
high=5 33>29 31, 37 low=4 high=5 33>31 mid=4 37
③算法描述(非递归形式) int binsearch( int a[n], int x ) //x待查数据 {int mid, low=1; int high=n; while(low<=high) {mid=(low+high)/2; if(a[mid]=x) return mid; if(a[mid]>x) high=mid-1; //继续在左边查找 else // (a[mid]<x) low=mid+1; //继续在右边查找 } return 0;//low大于high查找区间为空,查找失败 }
void max_min(int A[n], int &max,int &min) { int i; max =min = A[0]; for (i=2;i<=n; i++) { if (A[ i]<min) min = A[ i]; if (A[ i]>max) max = A[ i]; } } 时间复杂度:O(n)
分治算法基本框架:
divide-and-conquer(P) { if ( | P | <= n0) adhoc(P); //解决小规模的问题 else divide P into smaller subinstances P1,P2,...,Pk; //分解问题 for (i=1,i<=k,i++) yi=divide-and-conquer(Pi); //递归的解各子问题 return merge(y1,...,yk); //将各子问题的解合并为原问题的解 }
算法2 递归求取最大和最小元素
maxmin (int i, int j ,float &fmax, float &fmin) {int mid; float lmax, lmin, rmax, rmin; if (i=j) {fmax= a[i]; fmin=a[i];} //只有1个元素 else if (i=j-1) //只有2个元素 if(a[i]<a[j]) { fmax=a[j];fmin=a[i];} else {fmax=a[i]; fmin=a[j];} else //多于2个元素 {mid=(i+j)/2; maxmin (i,mid,lmax,lmin);//递归调用算法求最大最小 maxmin (mid+1,j,rmax,rmin);//递归调用算法求最大最小 if(lmax>rmax) fmax=lmax; else fmax=rmax; if(lmin>rmin) fmin=rmin; else fmin=lmin; }
适合用分治算法策略的问题,具有以下几个特征: 1)该问题的规模缩小到一定的程度就可以容易解决; 2)该问题可以分解为若干个规模较小的相同问题,即 该问题具有最优子结构性质; 3)该问题所分解出的各个子问题是相互独立的,即子 问题之间不包含公共的子问题。 4)利用该问题分解出子问题解可以合并为该问题解;
{ max==min=a[1]; for(i=2 i<=n i++ ) if(max < a[i]) max=a[i]; else if(min > a[i])
}
min=a[i];
分治算法设计: 问题可以简化为:在含n(n是2的幂(n>=2))个 元素的集合中寻找极大元和极小元。 用分治法(二分法)可以用较少比较次数地解决上述 问题: 1)将数据等分为两组(两组数据可能差1),目的 是分别选取其中的最大(小)值。 2)递归分解直到每组元素的个数≤2,可简单地找到最 大(小)值. 3)回溯时将分解的两组解大者取大,小者取小,合 并为当前问题的解。
分析该算法时间复杂度: 令T(n)为元素个数为n时所需比较次数(时间): 当n=2时,查找查找最大最小元只需要1次比较,T(2)=1; 时间复杂度记为O(1)。 当n>2时,T(n)=2T(n/2) + 2 T(2) =4T(n/4) + 4 T(2) + 2 T(2) =8T(n/8) + 8 + 4 + 2 =…… =2x T(n/2x) + 2x +2x-1+…+8+4+2 分解到最后只有2个元素可以求解,n/2x=2, T(n)= 2x *1 + 2x +2x-1… + 22 + 21
low=5,high=5-1 j i
在右边找 在右边找
在左边找
33<37 low=5,high=5,mid=5
跳出循环,查找失败
补充1:求最大元和最小元问题
1、问题提出
在含有n个不同元素的集合中找出最大元素和最小 元素。
2、解决问题方法
①传统方法 (逐个比较最终找到最大最小) ②分治法
①传统方法
②分治法:算法描述
void max_min( int a[ n], int i, int j, int &max, int &min) { int min1,max1,max2,min2; if(i=j) { max=min=a[ i]; } //只有1个元素 else if( j - i=1) //只有2个元素 { if(a[ i]<a[ j]) { max=a[ j]; min=a[ i]; } else { max=a[ i]; min=a[ j]; } } else //多于2个元素 {int k=(i+j)/2; //以中点k为界线分成2部分 max_min (int a, i, k, max1, min1); //在i到k部分求最大最小 max_min (int a, k+1, j, max2, min2); //在k+1到j求最大最小 if(max1<max2) max=max2; //合并取大 else max=max1; if(min1>min2) min=min2; //合并取小 else min=min1; } }
②举例 例 查找31 08, 23, 29, 31, 37, 65, 70, 79, 80, 88,100 low=1; mid=(1+11)/2=6; 31<65; high=11;