分治法的设计思想是,将一个难以直接解决的大问题,分割成一些规模较小的相同问题,以便各个击破,分而治之。分治策略是:对于一个规模为n的问题,若该问题可以容易地解决(比如说规模n较小)则直接解决,否则将其分解为k个规模较小的子问题,这些子问题互相独立且与原问题形式相同,递归地解这些子问题,然后将各子问题的解合并得到原问题的解。这种算法设计策略叫做分治法。
分治法所能解决的问题一般具有以下几个特征:1) 该问题的规模缩小到一定的程度就可以容易地解决 2) 该问题可以分解为若干个规模较小的相同问题,即该问题具有最优子结构性质。3) 利用该问题分解出的子问题的解可以合并为该问题的解;4) 该问题所分解出的各个子问题是相互独立的,即子问题之间不包含公共的子子问题。
上述的第一条特征是绝大多数问题都可以满足的,因为问题的计算复杂性一般是随着问题规模的增加而增加;第二条特征是应用分治法的前提它也是大多数问题可以满足的,此特征反映了递归思想的应用;第三条特征是关键,能否利用分治法完全取决于问题是否具有第三条特征,如果具备了第一条和第二条特征,而不具备第三条特征,则可以考虑用贪心法或动态规划法。第四条特征涉及到分治法的效率,如果各子问题是不独立的则分治法要做许多不必要的工作,重复地解公共的子问题,此时虽然可用分治法,但一般用动态规划法较好。把我最近遇到的分治法应用的问题总结了一下,大家多多指正。
1、求Fibonacci数列,要求时间复杂度为O(logN)
求Fibonacci数列一般有递归和动态规划方法,递归最慢,动态规划方法时间复杂度是O(N)。利用分治法求Fibonacci数列需要知道一个前提条件就是
{ F(n), F(n-1), F(n-1), F(n-1) } = { 1, 1, 1, 0 }n-1; 即F(n)是{ 1, 1, 1, 0 }的n-1次方的第一行第一列,通过数学归纳法可以证明得到。
考虑如下乘方性质:
/ an/2*an/2 n为偶数时
an=
\ a(n-1)/2*a(n-1)/2*a n为奇数时
要求得n次方,我们先求得n/2次方,再把n/2的结果平方一下。如果把求n次方的问题看成一个大问题,把求n/2看成一个较小的问题。这种把大问题分解成一个或多个小问题的思路就是分治法。这样求n次方就只需要logN次运算。
实现这种方式时,首先需要定义一个2×2的矩阵,并且定义好矩阵的乘法以及乘方运算。当这些运算定义好了之后,剩下的事情就变得非常简单。完整的实现代码如下所示:
#include <iostream>
#include <cstdlib>
using namespace std;
typedef long long LL;
//注意数列项溢出的情况
LL Fibonacci( int n ){
if( n == 0 )
return 0;
if( n == 1 )
return 1;
LL Front = 1, back = 0;
LL temp;
for( int i = 1; i < n; i++ ){
temp = Front;
Front = Front + back;
back = temp;
}
return Front;
}
//定义一个2*2的矩阵
struct Matrix2By2{
Matrix2By2
(
LL m00 = 0,
LL m01 = 0,
LL m10 = 0,
LL m11 = 0
)
:m00(m00), m01(m01), m10(m10), m11(m11){}
LL m00;
LL m01;
LL m10;
LL m11;
};
//矩阵相乘
Matrix2By2 MatrixMultiply( const Matrix2By2 &M1, const Matrix2By2 &M2){
return Matrix2By2(
M1.m00*M2.m00+M1.m01*M2.m10,
M1.m00*M2.m01+M1.m01*M2.m11,
M1.m10*M2.m00+M1.m11*M2.m10,
M1.m10*M2.m01+M1.m11*M2.m11);
}
Matrix2By2 MatrixPower( unsigned int n ){
if( n <= 0 )
return NULL;
Matrix2By2 matrix;
if( n == 1 ){
matrix = Matrix2By2(1,1,1,0);
}else if( n%2 == 0 ){
matrix = MatrixPower( n/2 );
matrix = MatrixMultiply( matrix, matrix );
}else if( n%2 == 1 ){
matrix = MatrixPower( ( n - 1 ) / 2 );
matrix = MatrixMultiply( matrix, matrix );
matrix = MatrixMultiply( matrix, Matrix2By2(1,1,1,0) );
}
return matrix;
}
LL Fibonacci2( unsigned int n ){
int result[2] = { 0, 1 };
if( n < 2 )
return result[n];
Matrix2By2 Power = MatrixPower( n - 1 );
return Power.m00;
}
int main(){
int number;
cout<<"请输入Fibonacci数列的项数:";
cin>>number;
cout<<Fibonacci( number )<<endl;
cout<<Fibonacci2( number )<<endl;
system( "pause" );
}
从一堆乱序的数组a[1...n]中找出第K小的数,利用基于快速排序的分治算法,分为左右两个部分来解决,然后利用递归左右边界不断靠近K,直到最终找到K
//分治法求第K小的数
#include <iostream>
#include <cstdlib>
using namespace std;
int FindSmall(int *a , int l , int r , int k){
if(l == r)
return a[l] ;
int i = l , j = r , x = a[(l+r)>>1] ;
while(i<=j) {
while(a[i] < x) ++ i ;
while(a[j] > x) -- j ;
if(i <= j){
swap( a[i], a[j] ) ;
++i ; --j ;
}
}
if(k <= j)
return FindSmall(a , l , j , k);
if(k >= i)
return FindSmall(a , i , r , k);
}
int main(){
int a[9] = { 1, 2, 6, 5, 4, 3, 7, 8, 9 };
cout<<FindSmall( a, 0, 8, 5 )<<endl;
system( "pause" );
}
3、归并排序
归并排序是最典型的分治法的应用,将一个数组分成左右两个部分,分别排序,然后归并,具体代码如下:
//归并排序算法
#include <iostream>
#include <cstdlib>
using namespace std;
void Merge( int* a, int low, int mid, int high ){
int k;
int begin1 = low;
int end1 = mid;
int begin2 = mid+1;
int end2 = high;
int *temp = new int [high-low+1];
for( k = 0; begin1 <= end1 && begin2 <= end2; k++ ){
if( a[begin1] < a[begin2] )
temp[k] = a[begin1++];
else
temp[k] = a[begin2++];
}
//剩余部分存入Temp
while( begin1 <= end1 )
temp[k++] = a[begin1++];
while( begin2 <= end2 )
temp[k++] = a[begin2++];
for( int i = 0; i< ( high-low+1); i++ )
a[low+i] = temp[i];
delete [] temp;
}
void MergeSort( int* a, int l, int r ){
int mid = 0;
if( l < r ){
mid = ( l + r )>>1;
MergeSort( a, l, mid );
MergeSort( a, mid+1, r );
Merge( a, l, mid, r );
}
}
int main(){
int a[10] = { 2, 67, 8, 36, 1, 5, 77, 100, 99, 3 };
MergeSort( a, 0, 9 );
for( int i = 0; i < 10; i++ )
cout<<a[i]<<" ";
cout<<endl;
system( "pause" );
}