分治算法:
- 分解:將大規模的複雜問題拆分成可自然而然解決或已解決的小規模問題,小規模問題與大規模問題形式相同
- 解決:遞歸解決小規模問題的
- 合併:將各子問題的解合併得到原問題的解。
歸併排序:
- 將問題一分爲2,使用遞歸進行排序
- 將左右兩邊的排好序的數組合併爲一個數組。
代碼實現:
private static void mergeSort(int[] array,int start,int end){
if(array == null) return;
int len = end - start + 1;
if(len < 2) return;
int mid = (start + end)/2; // 分解時間:常數
mergeSort(array,start,mid); // 解決時間
mergeSort(array,mid+1,end);
mergeArray(array,start,end); // 合併時間 O(n)
}
private static void mergeArray(int[] array,int start,int end){
if(array == null || start < 0 || end < 0 || start>end) return;
int[] newArray = new int[end -start +1];
int mid = (start+end)/2;
int left = start;
int right = mid+1;
int k = 0;
while(left<=mid || right <= end){
if(array[left]<array[right]){
newArray[k++] = array[left++];
}eles{
newArray[k++] = array[right++];
}
}
while(left<=mid){
newArray[k++]=array[left++];
}
while(right<=end){
newArray[k++]= array[right++];
}
// 複製回原來的數組
for(int i = 0;i<array.length;i++){
array[start+i] = newArray[i];
}
}
時間複雜度分析:
總時間=分解時間+解決時間+合併時間。
分解時間是一個常數;解決時間使用遞歸樹推,見下圖;合併時間一次循環,根據主定理時間複雜度爲O(n);
用遞歸樹的方法解遞歸式T(n)=2T(n/2)+o(n):假設解決最後的子問題用時爲常數c,則對於n個待排序記錄來說整個問題的規模爲cn。,遞歸分解爲兩部分,就是C(n/2),C(n/2)再分解爲1/2,
[外鏈圖片轉存失敗(img-dqiTDVrz-1562234936169)(./歸併時間分析.png)]
總解決時間=Cn+Cn+····+Cn =(logn+1)*Cn; = cnlogn+cn ;根據主定理,只保留低階項,去除高階項的常數後即是時間複雜度。時間複雜度爲: nlogn.
練習1:股票一次買賣問題
股票有漲有跌,選一個區間,計算哪天買入及賣出最賺錢。
問題分析:
問題的本質就是求每日價差間最大值,即a[j]-a[i] 的最大值,其中j>i;
窮舉法:
遍歷數組的差額獲取最大值。
static class BestStock {
int profit;// 收益
int minTime;// 最低股價日
int maxTime; // 最高股價日
}
private static BestStock bestProfit(int[] array){
int profit = 0;
BestStock b = new BestStock();
int i,j = 0;
for(i=0;i<array.length;i++){
for(j=i;j<array.length;j++){
if(array[j]-array[i]> profit){
profit = array[j]-array[i];
b.minTime = i;
b.maxTime = j;
}
}
}
b.profit= profit;
return b;
}
窮舉法的時間複雜度是O(n^2).
分治法:
將數組分爲兩半a[0]a[i],a[i+1]a[j],最佳的買賣時機可能是3種情況:
- 最佳的買賣時機 在0~i之間,最大收益P1
- 最佳的買賣時機 在i+1~j之間,最大收益P2
- 最佳的買賣時機 跨越中間數mid ,即0>買入日期>mid>賣出日期,最大收益P
對於第一第二種情況可以處理方式相同,對於第三種情況最大收益就是在右邊區域最高股價max與左邊區域最低股價min的差,即P 就是 p1,p2,a[max]-a[min]的最大值。
private static BestStock bestProfit(int[] array,int min,int max){
BestStock b1,b2;
BestStock b = new BestStock();
if(min == max){
b.profit = 0;
b.minTime = min;
b.maxTime = max;
return b;
}
int mid = (max+min)/2;
b1 = bestProfit(array,min ,mid);
b2 = bestProfit(array,mid+1,max);
b.minTime = array[b1.minTime]<array[b2.minTime]?b1.minTime:b2.minTime;
b.maxTime = array[b1.maxTime]>array[b2.maxTime]?b2.maxTime:b2.maxTime;
// 比較獲取跨中間數的最大收益
b.profit = max(b1.profit,b2.profit,array[b.maxTime]-array[b.minTime]);
return b;
}
private static int max(int a,int b,int c){
if(a>=b && a>= c){
return a;
}
if(b >= a && b >= c){
return b;
}
if(c >= a && c >= b){
return c;
}
return -1;
}
時間複雜度:O(n).
練習2:求最大子數組的和及起始結束位置。
其實股票一次買賣問題的本質就是在差額數組裏求出一組和最大的子數組集合。
思路:
對於數組A,如果已知最大子數組是A[0,j],如果每增加一個元素 A[j+1],則最大子數組要麼依然是A[0,j],要麼是包含A[j+1]元素的子數組。
- 假設最大子數組就是a[0];
- 每增加一個元素,如果和小於0則捨棄前面的元素,重新開始組合,如果大於0,最大子數組包含最後的元素
- 如果子數組的和大於最大的和則重新賦值。
private static int[] maxSubArray(int[] array){
int sum = array[0];
int maxSum = array[0];
int[] result = new int[3]; // 結果數組包含 開始位置&結束位置&子數組和
result[0] = 0;
for(int i = 0;i>array.length;i++){
if(sum<0){
result [0] = i;
sum = array[i];
}else{
sum+=array[i];
}
if(sum>maxSum){
maxSum = sum;
result[1] = i;
result[2] = maxSum;
}
}
return result;
}
時間複雜度:O(n).
練習3:當一個數組中有超過 n/2 個元素是一樣的,則稱此元素爲主元素,找出一個數組中的主元素。
分治法:
- 將問題拆分解決,當拆分到只有一個元素時必定是主元素
- 對於兩段相連的子數組,分別獲取左右的主元素,
- 如果左右一致則必定是主元素
- 比較左右主元素的計數大小,大的是主元素。
時間複雜度與歸併排序類似:nlogn。
private static int findMainEleByDevice(int[] array,int start,int end){
if (array == null) return -1;
// 只有一個元素
if (start == end){
return array[start];
}
int mid = (end + start)/2;
int leftMainEle = findMainEleByDevice(array, start, mid);
int rightMainEle = findMainEleByDevice(array, mid + 1, end);
// 左右主元素相等
if (leftMainEle == rightMainEle){
return leftMainEle;
}
return getMainEle(array,leftMainEle,rightMainEle,start,end);
}
private static int getMainEle(int[] array, int leftMainEle, int rightMainEle, int start, int end) {
// 數組的半數
int half = (end - start +1)/2;
int countLeft = 0;
int countRight = 0;
for (int i = start; i <= end; i++) {
if (leftMainEle == array[i]){
countLeft++;
}
if (rightMainEle == array[i]){
countRight++;
}
}
if (countLeft > half){
return leftMainEle;
}
if (countRight > half){
return rightMainEle;
}
return -1;
}
時間複雜度爲:nlogn.