基本思想:將一組要排序的數列分成兩部分,其中一部分的值都比另一部分的小;然后按照這個方法分別對兩部分數據進行快速排序,整個過程可以用遞歸進行,從而實現整個數列的排序。
快速排序方法是基於分值策略的,排序在原地排序,不需要輔助的數組,但是分解困難。
快速排序分為三個過程:分解、治理、合並。
分解:先從數列num[low,high]中取出一個基准元素,將所有比基准元素小的數據存放在基准元素左側,將所有比基准元素大的數據存放在基准元素右側;基准元素此時已經位於正確的位置了,這個位置記為mid;
治理:基准元素左右兩個子數列num[low,mid-1]和num[mid+1,high]進行分解的操作,即進行快速排序的操作,這一步通過遞歸的方式實現;
合並:由於快速排序是在原地進行排序,因此不需要進行合並的操作。
快速排序中一個注意點的是基准元素該如何選擇?如果基准元素選取不當,有可能分解成規模為0和n-1的兩個子數列,快速排序會退化成冒泡排序。
一般來說,基准元素有以下幾種取法:
- 取第一個元素
- 取最后一個元素
- 取中間元素
- 取第一個、最后一個、中間位置元素三者的中位數
- 取第一個和最后一個之間位置的隨機數k(low<=k<=high),取R[k]作為基准元素
快速排序的算法中,第二步是遞歸,關鍵的第一步分解如何實現呢?也就是說,我們選擇好了基准元素后,如何將數列中比基准元素小的數都移到基准元素左邊,而把數列中比基准元素大的數都移動到基准元素右邊呢?
下面選擇基准元素為第一個元素,說明如何實現這一步。
假設當前待排序的數列為num[low,high],其中low<=high。
步驟1:首先取數組的第一個元素作為基准元素pivot = num[low], 設置兩個指針i和j,i = low, j = high;
步驟2:從右向左掃描(即 j不斷向左移動),找到小於 pivot 的數,如果找到的話,num[i] 和 num[j] 交換, i++;(這一步其實就是將比基准元素小的數移動到基准元素左邊)
步驟3:從左向右掃描(即 i 不斷向右移動),找到大於 pivot 的數,如果找到的話,num[i] 和 num[j] 交換,j--;(這一步其實就是將比基准元素大的數移動到基准元素左右邊)
步驟4:重復步驟2~步驟3,直到指針 i 和 j 重合,返回該位置 mid = i,該位置的數正好是pivot
以數組{30,24,5,58,18,36,12,42,39}為例,上述步驟的演示過程如圖


實現代碼如下所示:
1 public static void main(String[] args){ 2 int[] num = {30,24,5,58,18,36,12,42,39}; 3 QuickSort(num,0,num.length-1); 4 for(int k:num) 5 System.out.print(k+","); 6 } 7 public static void QuickSort(int[] num,int low,int high) { 8 int mid; 9 if(low<high) { 10 mid = Partition(num,low,high); 11 QuickSort(num,low,mid-1); 12 QuickSort(num,mid+1,high); 13 } 14 } 15 public static int Partition(int[] num, int low, int high) { 16 int i = low; 17 int j = high; 18 int pivot = num[low]; 19 while(i < j) { 20 while(i < j && num[j] > pivot) 21 j--; 22 if(i<j) { 23 swap(num,i,j); 24 i++;//注意,這一行不能在花括號外面 25 } 26 while(i<j && num[i]<pivot) 27 i++; 28 if(i<j) { 29 swap(num,i,j); 30 j--;//注意,這一行不能在花括號外面 31 } 32 } 33 return i; 34 } 35 public static void swap(int[] num,int i,int j) { 36 int tmp = num[j]; 37 num[j] = num[i]; 38 num[i] = tmp; 39 }
需要注意的是,以上Partition()函數的實現只適用於基准元素是第一個元素的時候,當基准元素取最后一個元素時,需要將步驟2和步驟3調換。我們仔細看上述過程,會發現,指針i和j總有一個是指向基准元素,在做交換過程的時候,是基准元素和其它元素在做交換。如果我們將基准元素取最后一個元素是,可能會發生指針i和j指向的不是基准元素了。
在代碼中,我有兩行加了注釋,就是指針i++和指針j--這一過程必須在花括號里。這是因為只有在i<j的情況下,才能執行 i++和j--的操作。否則的話,當i==j了,此時需要換回i,如果i++不在i<j的約束下,肯定是要執行的,返回的就不是基准元素的位置,而是基准元素后一位了。
