分治法——快速排序,歸併排序
分治法
分治法是一種很重要的演算法,也就是“分而治之”的意思,就是把一個複雜的問題分解成兩個或者多個相似的子問題,直到最後子問題可以簡單的直接求解,原問題的解即子問題的解的合併。
比如二分搜尋演算法,排序演算法中的快速排序和歸併排序都屬於分治法的一種。下面我們來看看歸併排序和快速排序演算法的實現。
歸併排序
簡介

維基百科
歸併排序(Merge sort),是建立在歸併操作上的一種有效的排序演算法,效率為O(n log n)。最優時間複雜度,O(n),平均時間複雜度為O(n log n)。由上圖我們可以瞭解到歸併排序的過程。
例項分析
以陣列6 5 3 1 8 7 2 4為例。首先遞迴的將陣列一分為2,並對子陣列排序
[6, 5, 3, 1][8, 7, 2, 4] [6, 5][3, 1][8, 7][2, 4] [6], [5][4], [3][7], [8][2], [4]
然後向上回溯,將兩個數組合併成有序陣列
[6], [5][4], [3][7], [8][2], [4] [5, 6][3, 4][7, 8][2, 4] [3, 4, 5, 6] [2, 4, 7, 8] [1, 2, 3, 4, 5, 6, 7, 8]
動圖如下所示

維基百科
兩個有序陣列排序
/** * * @param a 有序陣列a * @param b 有序陣列b * @param result 結果陣列 */ public static void merge2(int[] a,int [] b, int[] result){ int i = 0 , j = 0 , k = 0 ; while (i < a.length && j < b.length){ if (a[i] < b[j]){ result[k++] = a[i++]; }else { result[k++] = b[j++]; } } while (i < a.length){ result[k++] = a[i++]; } while (j < b.length){ result[k++] = b[j++]; } print(result); }
看會了兩個有序陣列的排序,則知道了如何實現歸併排序
Java程式碼實現
private static void merge(int[] arr, int[] result, int start, int end) { if (start >= end) return; int center = (start + end) / 2; int start1 = start, end1 = center; int start2 = center + 1, end2 = end; merge(arr, result, start1, end1); merge(arr, result, start2, end2); int k = start1; while (start1 <= end1 && start2 <= end2) { if (arr[start1] < arr[start2]) { result[k++] = arr[start1++]; } else { result[k++] = arr[start2++]; } } while (start1 <= end1) { result[k++] = arr[start1++]; } while (start2 <= end2) { result[k++] = arr[start2++]; } for (k = start; k <= end; k++) { arr[k] = result[k]; } print(arr); }
快速排序
簡介

使用快速排序法對一列數字進行排序的過程——維基百科
快速排序(Quicksort),是一種排序演算法,最壞情況複雜度:Ο(n2),最優時間複雜度:Ο(n log n),平均時間複雜度:Ο(n log n)。快速排序的基本思想也是用了分治法的思想:找出一個元素X,在一趟排序中,使X左邊的數都比X小,X右邊的數都比X要大。然後再分別對X左邊的陣列和X右邊的陣列進行排序,直到陣列不能分割為止。
具體操作
ok,我們來看一下具體操作:
1.設定一個長度為n的陣列A,定義兩個變數i = 0,j = n - 1;
2.從陣列中挑選出一個元素作為基準元素,複製給key;
3.從j開始從後向前搜尋,j--,找到比key小的值,將A[j]與A[i]互換;
4.從i 開始向後搜尋,i++,找到比key大的值,將A[i]與A[j]互換;
5.遞迴的,重複2,3,4步,直到i == j ;
舉個栗子:
- 存在一個數組A:6 2 7 3 8 9 ,建立i = 0 ; j = 5,選擇一個基準元素 k = 6
- j 從右向左查詢比k小的元素,發現,當 j = 3 時,發現元素3比k小,則另A[i] 與 A[j]交換,得到3 2 7 6 8 9;
- i 從左向右進行查詢,當 i = 2時,發現元素 7 比k大,則另A[i] 與A[j]進行交換,得到 3 2 6 7 8 9;
- 接著,再減小j,重複上面的迴圈。
- 但是我們發現,在本例中,一次迴圈後j與i就相等了,他們的下標同時指向了2.這時候,我們就進行分組,將3 2分為一組,7 8 9分為一組繼續上述的比較,最終得到排序好的陣列。
Java程式碼實現
public static void quickSort(int[] arr, int start, int end) { if (start >= end) return; int mid = arr[end]; int left = start; int right = end - 1; while (left < right) { while (arr[left] <= mid && left < right) { left++; } while (arr[right] >= mid && left < right) { right--; } swap(arr, left, right); } if (arr[left] >= arr[end]) { swap(arr, left, end); } else { left++; } quickSort(arr, start, left - 1); quickSort(arr, left + 1, end); print(arr); } private static void swap(int[] arr, int x, int y) { int temp = arr[x]; arr[x] = arr[y]; arr[y] = temp; }
總結
可以看出分治法的策略還是遞迴的去解決問題,基本分為三個步驟:
分解:將原問題分解為若干個規模較小,相互獨立,與原問題形式相同的子問題;
解決:若子問題規模較小而容易被解決則直接解,否則遞迴地解各個子問題;
合併:將各個子問題的解合併為原問題的解。