第一節課(2)

分而治之

分而治之(D&C)是一種重要的算法設計範例。在分治策略中,我們遞歸地求解一個問題,在每層遞歸中應用如下三步驟:

  • 分解(Divide):將問題劃分爲一些子問題,子問題的形式與原問題一樣,只是規模更小
  • 解決(Conquer):遞歸地求解出子問題。如果子問題規模足夠小,則停止遞歸,直接求解
  • 合併(Combine):將子問題的解組合成原問題的解

分治的核心是合併(Combine/Merge)

例子(Examples):

  • Maximum Contiguous Subarray(最大子數組)
  • Counting Inversions(逆序計數)
  • Integer Multiplication(整數乘法)
  • Ploynomial Multiplication(多項式乘法)
  • QuickSort and Partition(快速排序與劃分)
  • Deterministic and Randomized Selection(確定性與隨機化選擇)

 

Maximum Contiguous Subarray(最大子數組)

正式定義:

  • 輸入:一實數數組A[1...n]
  • 子數組A[i...j]的值是:

V(i,j)=\sum_{x=i}^{j}A(x)

找到i <= j,使V(i,j)最大化。

暴力求解

計算每一對i <= j的V(i,j)的值,返回最大值。c++代碼實現:

/***************************************************************
	暴力求解(Brute Force):
	計算每一對i <= j的V(i,j)的值,返回最大值。
	複雜度:O(n^3) 
****************************************************************/
double MCS_BF(vector<double> &A)
{
	int sz = A.size();
	if(sz == 0){
		cout << "輸入數組爲空" << endl;
		return -1;
	}
	double vmax = A[0];
	for(int i= 0; i < sz; ++i){
		for(int j = i; j < sz; ++j){
			double sum = 0;
			for(int x = i; x <= j; ++x){
				sum += A[x];
			}
			if(sum > vmax)
				vmax = sum;
		}
	}
	return vmax;
}

數據再利用

不需要從頭計算每一個V(i,j), V(i,j) = V(i,j-1) + A[j]。c++代碼實現:

/****************************************************************
	數據再利用(Data_Reuse) :
	不需要從頭計算每一個V(i,j), V(i,j) = V(i,j-1) + A[j]
	複雜度:O(n^2)  
*****************************************************************/
double MCS_DR(vector<double> &A)
{
	int sz = A.size();
	if(sz == 0){
		cout << "輸入數組爲空" << endl;
		return -1;
	}
	double vmax = A[0];
	for(int i= 0; i < sz; ++i){
		double sum = 0;
		for(int j = i; j < sz; ++j){
			sum += A[j];
			if(sum > vmax)
				vmax = sum;
		}
	}
	return vmax;
}

分而治之

設 m = \left \lfloor (low+high)/2 \right \rfloor

圖2 子數組可能的位置

數組A[low...high]的最大子數組定是以下三種之一:

  •  S_{1}:位於A[low...m];
  •  S_{2}:位於A[m+1...high];
  • SM:跨越中點

S = max\left \{ S_{1},S_{2},SM \right \}.

c++代碼實現:

/****************************************************************
	分而治之(Divide-and-Conquer) 
	複雜度:O(nlogn) 
*****************************************************************/
double FindMaxCrossingSubarr(vector<double> &A, int low, int mid, int high)
{
	double SM1 = A[mid];
	double sum = A[mid];
	for(int i = mid-1; i >= low; --i){
		sum += A[i];
		if(sum > SM1)
			SM1 = sum;
	}
	double SM2 = A[mid + 1];
	sum = A[mid + 1];
	for(int j = mid + 2; j <= high; ++j){
		sum += A[j];
		if(sum > SM2)
			SM2 = sum;
	}
	return SM1+SM2;
}
double FindMaxSubarr(vector<double> &A, int low, int high)
{
	if(low == high)
		return A[low];
	int m = floor((low + high) / 2);
	double S1 = FindMaxSubarr(A,low,m);
	double S2 = FindMaxSubarr(A,m+1,high);
	double SM = FindMaxCrossingSubarr(A,low,m,high);
	return max({S1,S2,SM});
}
double MCS_DC(vector<double> &A)
{
	if(A.empty()){
		cout << "輸入數組爲空" << endl;
		return -1;
	}
		
	int low = 0;
	int high = A.size() - 1;
	return FindMaxSubarr(A,low,high);
}

運行效果:

圖3 運行效果

 

用O(n)時間複雜度解決:Kadane算法

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章