【演算法】快速排序及優化
- 2020 年 3 月 28 日
- 筆記
一、荷蘭國旗問題
在講快速排序前,我們先來看看荷蘭國旗問題。題目如下:
給定一個數組arr,和一個數num,請把 小於num的數放在數組的左邊, 等於num的數放在數組的中間, 大於num的數放在數組的 右邊。 要求額外空間複雜度O(1),時間複雜度O(N), 例如: 對於數組3,5,4,5,8,1,9,以5進行分割 輸出:3 4 1 5 5 9 8
其實,這就是快排的partition過程,通過三個指針,index,less,more進行的,初始less=左邊界-1,more=右邊界+1,說明一開始不存在less域和more域: 1)若arr[index] < num,less域增加,即less++,然後index和less位置的數交換後,index++繼續指向下一個數 2)若arr[index] > num,more域增加,即more++,然後index和more的數交換後,繼續判斷交換後arr[index]和num的關係 3)若arr[index] == num,index++繼續指向下一個數,不坐任何處理 4)重複以上過程,直至index==more
大致過程圖:

程式碼實現
public static int[] partition(int[] arr, int l, int r, int num) { int less = l - 1; int more = r + 1; int index = l; // 循環至index到more相等 while (index < more) { if (arr[index] < num) { // 擴大less域 swap(arr, ++less, index++); }else if (arr[index] > num){ // 擴大more域,且交換後,繼續判斷當前index的數和num的關係 swap(arr, --more, index); }else { index++; } } int[] result = {less + 1, more - 1}; // 返回相等區間開始和結束的索引值 return result; } public static void swap(int[] arr, int i, int j) { int tmp = arr[i]; arr[i] = arr[j]; arr[j] = tmp; }
二、經典快速排序
快排其實是冒泡排序的改進,其演算法思路如下: 1)在數組中選取一個數作為基準元素 2)分區,把小於基準的數放左邊,大於基準的數放右邊 3)遞歸,對左右兩個分區重複以上步驟
在網上找了一張圖,如下所示(侵刪):

實現程式碼
public static void quickSort(int[] arr) { if (arr == null || arr.length < 2) { return; } quickSort(arr, 0, arr.length - 1); } public static void quickSort(int[] arr, int l, int r) { if (l >= r) { return; } int p = partition(arr, l, r); quickSort(arr, l, p - 1); quickSort(arr, p + 1, r); } public static int partition(int[] arr, int l, int r) { // 取左邊第一個數為基準 int pivot = arr[l]; int i = l; int j = r; while(i != j) { // 從右邊找第一個小於基準的數 while(arr[j] >= pivot && i < j) { j--; } // 從左邊找第一個大於基準的數 while(arr[i] <= pivot && i < j) { i++; } if (i < j) { swap(arr, i, j); } } // 將基準歸位 swap(arr, l, j); // 返回基準的位置 return j; }
三、改進的快速排序
回顧經典的快速排序,可以優化的地方有兩個:
1)基準的選取,若每次取數組的第一個作為基準,則可能出現基準是數組中最小的數,造成了多餘的計算。因此,基準的選取可以進行隨機選擇 2)分區返回的邊界,回顧荷蘭國旗問題,分區時,可以發現基準可以是中間一片區域,而不是一個數,因此,分區時,返回的不在是基準的位置,而是基準的左邊界和右邊界,那麼下次劃分子問題時,就可以減少交換的次數了。
程式碼實現
public static void quickSort(int[] arr) { if (arr == null || arr.length < 2) { return; } // quickSort2(arr, 0, arr.length - 1); quickSort(arr, 0, arr.length - 1); } public static void quickSort(int[] arr, int l, int r) { if (l < r) { // 從l~r中選一個數,放在r作為基準 swap(arr, l + (int) (Math.random() * (r - l + 1)), r); int[] p = partition(arr, l, r); quickSort(arr, l, p[0] - 1); quickSort(arr, p[1] + 1, r); } } // 分割 public static int[] partition(int[] arr, int l, int r) { int less = l - 1; int more = r; int index = l; // 循環至index到more相等 while (index < more) { if (arr[index] < arr[r]) { // 擴大less域 swap(arr, ++less, index++); }else if (arr[index] > arr[r]){ // 擴大more域,且交換後,繼續判斷當前index的數和num的關係 swap(arr, --more, index); }else { index++; } } // 將基準歸位,基準取的是最右邊的數 swap(arr, more, r); int[] result = {less + 1, more}; // 返回相等區間開始和結束的索引值 return result; }