本文根據《大話數據結構》一書,實現了Java版的快速排序。html
更多:數據結構與算法合集java
基本思想:在每輪排序中,選取一個基準元素,其餘元素中比基準元素小的排到數列的一邊,大的排到數列的另外一邊;以後對兩邊的數列繼續進行這種排序,最終達到總體有序。程序員
圖片來自公衆號:程序員小灰算法
根據上述基本思想,能夠先寫出快速排序最核心的代碼:對於數組a中從下標爲low至下標爲high的元素,選取一個基準元素(記爲pivotKey),根據與基準比較的大小,將這些元素排到基準元素的兩端。數組
注意點:1.兩端向中間掃描時,必定要先從高段往低端掃描(low<high && a[high]<pivotKey),這樣才能實現pivotKey一直會交換到中間。!!數據結構
2.比較大小時不要忘記low<high還要一直成立,即(low<high && a[high]<pivotKey)。!! 例如,數組全爲同一個數字時,不加這個判斷有可能致使越界ide
/** * 對數組a中下標從low到high的元素,選取基準元素pivotKey, * 根據與基準比較的大小,將各個元素排到基準元素的兩端。 * 返回值爲最後基準元素的位置 */ public int partition(int[] a, int low, int high) { int pivotKey = a[low]; //用第一個元素做爲基準元素 while (low < high) { //兩側交替向中間掃描 while (low < high && a[high] >= pivotKey) high--; swap(a, low, high); //比基準小的元素放到低端 while (low < high && a[low] <= pivotKey) low++; swap(a, low, high); //比基準大的元素放到高端 } return low; //返回基準元素所在位置 }
將元素分爲兩部分後,必須對兩個子部分繼續進行上面的排序,因此要用到遞歸。代碼以下:post
/** * 遞歸調用 */ public void qSort(int[] a, int low, int high) { int pivot; if (low >= high) return; pivot = partition(a, low, high); //將數列一分爲二 qSort(a, low, pivot - 1); //對低子表排序 qSort(a, pivot + 1, high); //對高子表排序 }
(含測試代碼)性能
import java.util.Arrays; /** * * @Description 快速排序 * * @author yongh * @date 2018年9月14日 下午2:39:00 */ public class QuickSort { public void quickSort(int[] a) { if (a == null) return; qSort(a, 0, a.length - 1); } /** * 遞歸調用 */ public void qSort(int[] a, int low, int high) { int pivot; if (low >= high) return; pivot = partition(a, low, high); //將數列一分爲二 qSort(a, low, pivot - 1); //對低子表排序 qSort(a, pivot + 1, high); //對高子表排序 } /** * 對數組a中下標從low到high的元素,選取基準元素pivotKey, * 根據與基準比較的大小,將各個元素排到基準元素的兩端。 * 返回值爲最後基準元素的位置 */ public int partition(int[] a, int low, int high) { int pivotKey = a[low]; //用第一個元素做爲基準元素 while (low < high) { //兩側交替向中間掃描 while (low < high && a[high] >= pivotKey) high--; swap(a, low, high); //比基準小的元素放到低端 while (low < high && a[low] <= pivotKey) low++; swap(a, low, high); //比基準大的元素放到高端 } return low; //返回基準元素所在位置 } public void swap(int[] a, int i, int j) { int temp; temp = a[j]; a[j] = a[i]; a[i] = temp; } // =========測試代碼======= public void test1() { int[] a = null; quickSort(a); System.out.println(Arrays.toString(a)); } public void test2() { int[] a = {}; quickSort(a); System.out.println(Arrays.toString(a)); } public void test3() { int[] a = { 1 }; quickSort(a); System.out.println(Arrays.toString(a)); } public void test4() { int[] a = { 3, 3, 3, 3, 3 }; quickSort(a); System.out.println(Arrays.toString(a)); } public void test5() { int[] a = { -3, 6, 3, 1, 3, 7, 5, 6, 2 }; quickSort(a); System.out.println(Arrays.toString(a)); } public static void main(String[] args) { QuickSort demo = new QuickSort(); demo.test1(); demo.test2(); demo.test3(); demo.test4(); demo.test5(); } }
null [] [1] [3, 3, 3, 3, 3] [-3, 1, 2, 3, 3, 5, 6, 6, 7]
1.優化選取樞紐測試
基準應儘可能處於序列中間位置,能夠採起「三數取中」的方法,在partition()方法開頭加如下代碼,使得a[low]爲三數的中間值:
// 三數取中,將中間元素放在第一個位置 if (a[low] > a[high]) swap(a, low, high); if (a[(low + high) / 2] > a[high]) swap(a, (low + high) / 2, high); if (a[low] < a[(low + high) / 2]) swap(a, (low + high) / 2, low);
2.優化沒必要要的交換
兩側向中間掃描時,能夠將交換數據變爲替換:
while (low < high) { // 兩側交替向中間掃描 while (low < high && a[high] >= pivotKey) high--; a[low] = a[high]; // swap(a, low, high); //比基準小的元素放到低端 while (low < high && a[low] <= pivotKey) low++; a[high] = a[low]; // swap(a, low, high); //比基準大的元素放到高端 } a[low]=pivotKey; //在中間位置放回基準值
3.優化小數組時的排序方案
當數組很是小時,採用直接插入排序(簡單排序中性能最好的方法)
4.優化遞歸操做
qSort()方法中,有兩次遞歸操做,遞歸對性能有較大影響。所以,使用while循環,在第一次遞歸後,變量low就沒有用處了,可將pivot+1賦值給low,下次循環中,partition(a, low, high)的效果等同於qSort(a, pivot + 1, high),從而能夠減少堆棧的深度,提升性能。
// pivot = partition(a, low, high); // 將數列一分爲二 // qSort(a, low, pivot - 1); // 對低子表排序 // qSort(a, pivot + 1, high); // 對高子表排序 //優化遞歸操做 while (low < high) { pivot = partition(a, low, high); // 將數列一分爲二 qSort(a, low, pivot - 1); // 對低子表排序 low = pivot + 1; }
快速排序時間性能取決於遞歸深度,而空間複雜度是由遞歸形成的棧空間的使用。遞歸的深度能夠用遞歸樹來描述,如{50,10,90,30,70,40,80,60,20}的遞歸樹以下:
最優狀況:
最優狀況下,每次選取的基準元素都是元素中間值,partition()方法劃分均勻,此時根據二叉樹的性質4能夠知道,排序n個元素,其遞歸樹的深度爲[log2n]+1,因此僅須要遞歸log2n次。
將排序n個元素的時間記爲T(n),則有如下推斷:
因此最優狀況下的時間複雜度爲:O(nlogn);一樣根據遞歸樹的深度,最優空間複雜度爲O(logn)。
最壞狀況:
遞歸樹爲一棵斜樹,須要n-1次調用,因此最壞空間複雜度爲O(logn)。在第i次調用中須要n-1次的關鍵字比較,因此比較次數爲:Σ(n-i)=(n-1)+……+2+1=n(n-1)/2,因此最壞時間複雜度爲O(n^2)。
平均狀況:
平均時間複雜度:O(nlogn),平均空間複雜度O(logn)。
更多:數據結構與算法合集