分治法的設計思想是,將一個難以直接解決的大問題,分割成一些規模較小的相同問題,以便各個擊破,分而治之。分治策略是:對於一個規模爲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" );
}