分治法的基本思想與例子解析
阿新 • • 發佈:2019-02-11
分治法的設計思想:將一個難以直接解決的大問題,分割成一些規模較小的相同問題,以便各個擊破,分而治之。
參考資料:
凡治眾如治寡,分數是也。——孫子兵法
1.基本思想
(1) 將求解的較大規模的問題分割成k個更小規模的子問題。
(2) 對這k個子問題分別求解。如果子問題的規模仍然不夠小,則再劃分為k個子問題,如此遞迴的進行下去,直到問題規模足夠小,很容易求出其解為止。
(3) 將求出的小規模的問題的解合併為一個更大規模的問題的解,自底向上逐步求出原來問題的解。
2.適用條件
分治法所能解決的問題一般具有以下幾個特徵:
I. 該問題的規模縮小到一定的程度就可以容易地解決; II. 該問題可以分解為若干個規模較小的相同問題,即該問題具有最優子結構性質III. 利用該問題分解出的子問題的解可以合併為該問題的解; IV. 該問題所分解出的各個子問題是相互獨立的,即子問題之間不包含公共的子問題。
注意:
如果各子問題是不獨立的,則分治法要做許多不必要的工作,重複地解公共的子問題,此時雖然也可用分治法,但一般用動態規劃較好。
3. 分治法的應用例子
(a) 快速排序
I. 分解(divide):以a[p]為基準元素將a[p:r]劃分為3段a[p:q-1],a[q]和a[q+1,r],使得a[p:q-1]中任何元素小於等於a[q],a[q+1,r]中任何元素大於等於a[q]。下標q在劃分過程中確定。
II. 遞迴求解(conquer):通過遞迴呼叫快速排序演算法,分別對a[p:q-1]和a[q+1,r]進行排序。
III. 合併(merge):由於對a[p:q-1]和a[q+1,r]的排序時就地進行的,所以在a[p:q-1]和a[q+1,r]都已排好的序後不需要執行任何計算,a[p:r]就已排好序。
package Sort; /** * @author LIn * 演算法名稱:快速排序 * 演算法描述: * 1.通過一趟排序將要排序的資料分割成獨立的兩部分,其中一部分的所有資料都比另外一部分的所有資料都要小 * 2.重複步驟1對這兩部分資料分別進行快速排序,整個排序過程可以遞迴進行,以此達到整個資料變成有序序列。 * * 複雜度分析: * 1.平均時間複雜度:O(nlogn) * 2.空間複雜度:O(logn)(最值元素儲存空間) */ public class QuickSort { public static void quickSort(int[] a){ quickSort(a, 0, a.length - 1); } private static void quickSort(int[] a, int left, int right){ int pivotpos; //劃分後基準的位置 if(left < right){ pivotpos = Partition(a, left ,right); quickSort(a, left, pivotpos-1); quickSort(a, pivotpos+1, right); } } /** * 普通選擇基準 */ private static int Partition(int[] a, int p, int r){ //呼叫Partition(a,left,right)時,對a[left...right]做劃分 //並返回基準記錄的位置 int i = p, j = r + 1; int pivot = a[p]; //用區間的第一個記錄作為基準 while(true){ while(a[++i] < pivot){} while(a[--j] > pivot){} if(i < j){ swap(a, i, j); } else{ break; } } swap(a, j, p); return j; } private static void swap(int[] a, int x, int y){ int temp = a[x]; a[x] = a[y]; a[y] = temp; } }
(b) 二分查詢
合併排序演算法是用分治策略實現對n個元素進行排序的演算法。其基本思想是:將待排序元素分成大小大致相同的2個子集合,分別對2個子集合進行排序,最終將排好序的子集合合併為所要求的排好序的集合。
package Sort;
/**
* @author LIn
* 演算法名稱:歸併排序
* 演算法描述:
* 1.將陣列分為n等份(演算法中為2),對各子陣列遞迴呼叫歸併排序
* 2.等分為2份時為2路歸併,最後子陣列排序結束後,將元素合併起來,複製回原陣列
*
* 複雜度分析:
* 1.平均時間複雜度:O(nlogn)
* 2.空間複雜度:O(n)(臨時資料儲存空間)
*/
public class MergeSort {
/*public型的mergeSort是private型遞迴方法mergeSort的驅動程式*/
public static void mergeSort(int[] a){
int[] tempArray = new int[a.length]; //若陣列元素為物件型別,需建立Comparable類的陣列,再強轉為該物件型別
mergeSort(a, tempArray, 0, a.length - 1);
}
/**
* 遞迴呼叫歸併排序
*/
private static void mergeSort(int[] a, int[] tempArray, int left, int right){
if(left < right){
int center = (left + right) / 2;
mergeSort(a, tempArray, left, center);
mergeSort(a, tempArray, center + 1, right);
merge(a, tempArray, left, center + 1, right); //子陣列排序結束後,將子數組合並
}
}
/**
* 合併左右的半分子陣列
* @param a 需排序陣列
* @param tempArray 臨時儲存陣列
* @param leftPos 左半子陣列開始的下標
* @param rightPos 右半子陣列開始的下標
* @param rightEnd 右半子陣列結束的下標
*/
private static void merge(int[] a, int[] tempArray, int leftPos, int rightPos, int rightEnd) {
int leftEnd = rightPos - 1;
int tempPos = leftPos;
int num = rightEnd - leftPos + 1;
//主迴圈
while(leftPos <= leftEnd && rightPos <= rightEnd){
if(a[leftPos] <= a[rightPos]){
tempArray[tempPos++] = a[leftPos++];
}else{
tempArray[tempPos++] = a[rightPos++];
}
}
/*比較結束後,只會有一個子陣列元素未完全被合併*/
while(leftPos <= leftEnd){ //複製左半子陣列剩餘的元素
tempArray[tempPos++] = a[leftPos++];
}
while(rightPos <= rightEnd){ //複製右半子陣列剩餘的元素
tempArray[tempPos++] = a[rightPos++];
}
//將元素從臨時陣列賦值回原陣列
for(int i = 0; i < num; i++, rightEnd--){
a[rightEnd] = tempArray[rightEnd];
}
}
}
參考資料:
1. 《演算法設計與分析》
2. 《演算法》