一、分治法基本思想
1.求解问题算法的复杂性一般都与问题规模相关,问题规模越小越容易处理。
2.分治法的基本思想是,将一个难以直接解决的大问题,分解为规模较小的相同子问题,直至这些子问题容易直接求解,并且可以利用这些子问题的解求出原问题的解。各个击破,分而治之。
3.分治法产生的子问题一般是原问题的较小模式,这就为使用递归技术提供了方便。递归是分治法中最常用的技术。
二、分治法解决问题的先决条件
1.该问题的规模缩小到一定的程度就可以容易地解决;
2.该问题可以分解为若干个规模较小的相同问题,即该问题具有最优子结构性质;
3.利用该问题分解出的子问题的解可以合并为该问题的解;
4.该问题所分解出的各个子问题是相互独立的,即子问题之间不包含公共的子问题。 这条特征涉及到分治法的效率,如果各子问题是不独立的,则分治法要做许多不必要的工作,重复地解公共的子问题,此时虽然也可用分治法,但一般用动态规划较好。
三、分治法解决问题的基本步骤
一般来说,分治法的求解过程由以下三个阶段组成:
1.划分:既然是分治,当然需要把规模为n的原问题划分为k个规模较小的子问题,并尽量使这k个子问题的规模大致相同。
2.求解子问题:各子问题的解法与原问题的解法通常是相同的,可以用递归的方法求解各个子问题,有时递归处理也可以用循环来实现。
3.合并:把各个子问题的解合并起来,合并的代价因情况不同有很大差异,分治算法的有效性很大程度上依赖于合并的实现。
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); //将各子问题的解合并为原问题的解
}
四、分治法复杂性分析
1.分治法的复杂性分析依据—递推方程
2.两类递推方程:
3.求解方法:
迭代法,递归树,Master定理
典型的递推方程
迭代法可求得
四、分治法求解问题典型案例
排列问题
问题:R是由n个元素构成的序列集合,R={r1, r2, … ,rn},求R的全排列perm(R)。
理解问题
(1) 若R中只有1个元素{r},则perm(R)=(r)
(2) 若R中只有2个元素{r1, r2},则
perm(R)=(r1)perm(R1)∪(r2)perm(R2)
其中,Ri=R-{ri}
(3) 若R中有3个元素{ r1, r2, r3},则
perm(R)=(r1)perm(R1)∪(r2)perm(R2)∪(r3)perm(R3)
思想:分治法
依次将待排列的数组的后n-1个元素与第一个元素交换,则每次递归处理的都是后n-1个元素的全排列。当数组元素仅有一个时为此递归算法的出口。
算法设计 伪代码
算法 perm(Type list[], int k, int m)
//生成列表list的全排列
//输入:一个全排列元素列表list[0..n-1]
//输出:list的全排列集合
流程图/伪代码
if k == m
for i←0 to m do
输出list[i]
else
for i←k to m do
swap list[k] and list[i]
perm(list, k+1, m)
swap list[k] and list[i]
效率分析
T(n) = O(1) n=1
T(n) = nT(n-1)+O(1) n > 1
二分查找
问题:给定已按升序排好序的n个元素a[0:n-1],现要在这n个元素中找出一特定元素x。
算法设计 伪代码
template<class Type>
int BinarySearch(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 + 1;
else right = middle - 1;
}
return -1; //x未找到
}
算法实现
/*
* Find the first occurrence of x in sorted array a[first..max].
* @param x value to find
* @param a array sorted in increasing order
* (a[0] <= a[1] <= ... <= a[n-1])
* @param first low end of range.
* Requires 0 <= first <= a.length-1.
* @param max high end of range.
* Requires 0 <= max <= a.length-1.
* @return lowest i such that first<=i<=max and a[i]==x,
* or -1 if there's no such i.
*/
public static int binarySearch(int[] a, int x, int left, int right){
int mid = (left+right)/2;
if( left >= right ){
return -1 ;
}
if( x < a[mid]){
return binarySearch(a,x,left,mid-1);
}
if( x > a[mid]){
return binarySearch(a,x,mid+1,right);
}
if( x == a[mid]){
if( x == a[mid-1] && mid > 0 ){
return binarySearch( a , x , left , mid-1);
}else{
return mid ;
}
}
return -1 ;
}
效率 T(n) = T(n/2)+O(1) = O(logn)
归并排序
问题:给定一个可排序的n个元素序列(数字、字符或字符串),对它们按照非降序方式重新排列。
基本思想:将待排序元素分成大小大致相同的2个子集合,分别对2个子集合进行排序,最终将排好序的子集合合并成为所要求的排好序的集合。
算法设计:伪代码
小规模案例
算法实现
private static void mergeSort(int[] a, int left, int right){
int[] b = new int[a.length];
mSort(a, b, left, right);
}
private static void mSort(int[] a, int[] b, int left, int right){
if(left < right){
int i = (left + right)/2;
mSort(a, b, left, i);
mSort(a, b, i+1, right);
merge(a, b, left, i, right);//合并到数组b
copy(a, b, left, right);//复制回数组a
}
}
private static void merge(int[] a, int[] b, int l, int m, int r){//合并a[l:m]和a[m+1:r]到b[l:r]
int i = l , j = m + 1 , k = l ;
while( i <=m && j <= r){
if( a[i] <= a[j] ) b[k++] = a[i++];
else b[k++] = a[j++];
}
if( i > m ){
for( int q = j ; q <= r ; q++ ){
b[k++]=a[q];
}
}else{
for( int q = i ; q <= m ; q++ ){
b[k++]=a[q];
}
}
}
private static void copy(int[] a, int[] b, int left, int right){//拷贝数组b中元素到数组a
for(int i = left; i <= right; i++)
a[i] = b[i];
}
快速排序
问题:给定一个可排序的n个元素序列(数字、字符或字符串),对它们按照非降序方式重新排列。
基本思想:对待排序数组不断拆分,拆分的同时不断交换元素,归位分裂点元素。记录的比较和交换是从两端向中间进行,记录每次移动的距离较大,因而总的比较和移动次数较少。
两次扫描法确定分区的算法
Partition(A[L ... R])
{ p←A[L] //选择中轴
i←L+1, j←R //左右扫描位置指针。左扫描没找到时,i 停在L+1位置
while (true)
{ while (A[ i ] < p) and ( i ≤ R ) do i←i+1 //左扫描(带限位)
while (A[ j ] > p) and ( j ≥ L ) do j←j-1 //右扫描(带限位)
if ( i≥j ) then break //左右扫描已交叉,退出循环
swap(A[ i ], A[ j ]) //左右扫描未交叉
}
swap(A[ L], A[ j ])
return ( j ) //返回分裂点 j
}
最坏时间复杂度:O(n2)
平均时间复杂度:O(n)
总结:进一步提高分治法效率的基本措施
1.代数变换,减少子问题个数
2.利用中间结果
3.数据预处理