程序員面試---分治法的應用

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


2、求第K小的數

從一堆亂序的數組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" );
}



發佈了12 篇原創文章 · 獲贊 12 · 訪問量 4萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章