顧名思義,快速排序是實踐中的一種快速排序算法,在C++或對Java基礎類型的排序中特別有用。它的平均運行時間是O(NlogN);但最壞情形性能爲O(N2)。我會先介紹快速排序過程,再討論如何優化。java
採用分治法,將數組分爲兩部分,並遞歸調用。將數組S排序的快排過程算法
將樞紐元與數組最後一個元素調換,使樞紐元離開要被分割的數據段;數組
初始化兩個索引left和right,分別指向數組第一個與倒數第二個元素;安全
若是left索引指向的元素小於樞紐元,則left++;不然,left中止。right索引指向的元素大於樞紐元,right--;不然,right中止。markdown
若是left<right,則交換兩個元素,循環繼續3,4步驟;不然跳出循環,將left對應的元素與樞紐元交換(這時候完成了分割)。遞歸調用這兩個子序列。app
假設全部元素互異(即都不相等)。下面會說重複元素怎麼處理。post
接下來要作的就是將小於樞紐元的元素移到數組左邊,大於樞紐元的元素移到數組右邊。性能
當left在right的左邊時,咱們將left右移,移過那些小於樞紐元的元素,並將right左移,移過那些大於樞紐元的元素。當left和right中止時,left指向一個大於樞紐元的元素,right指向一個小於樞紐元的元素,若是left<right,則將這兩個元素交換。這樣是將一個大於樞紐元的元素推向右邊而把小於樞紐元的元素推向左邊。咱們來圖示過程:left不動,而right左移一個位置,以下圖:優化
咱們交換left與right指向的元素,重複這個過程,直到left>right。ui
至此,咱們能夠看到,left左邊的元素都小於樞紐元,右邊的元素都大於樞紐元。咱們繼續遞歸左右序列,最終可完成排序。
上面咱們假設的是元素互異,下面咱們討論重複元素的處理狀況。
在大規模輸入量中,重複元素仍是挺多的。考慮能將這些重複元素進行有效排序,仍是很重要。
快速排序真的快嗎?其實也不必定,對於小數組(N<=20)的輸入序列,快速排序不如插入排序;而且在咱們上面的優化中,採用三數中值分割時,遞歸獲得的結果能夠是隻有一個,或者兩個元素,這時會有錯誤。因此,繼續優化是將小的序列用插入排序代替,這會減小大約15%的運行時間。較好的截止範圍是10(其實5-20產生的效果差很少)。
對於三數中值分割還能夠進行優化:假設輸入序列爲a,則選擇a[left],a[center],a[right],選擇出樞紐值,並將最小,與最大值分別放到a[left],a[right],將樞紐值放到a[right-1]處,這樣放置也是正確的位置,而且能夠防止right向右進行比較時不會越界;這樣左右起始位置就是left+1,right-2。
public class Quicksort {
/**
* 截止範圍
*/
private static final int CUTOFF = 10;
public static void main(String[] args) {
Integer[] a = {8, 1, 4, 9, 6, 3, 5, 2, 7, 0};
System.out.println("快速排序前:" + Arrays.toString(a));
quicksort(a);
System.out.println("快速排序後:" + Arrays.toString(a));
}
public static <T extends Comparable<? super T>> void quicksort(T[] a) {
quicksort(a, 0, a.length - 1);
}
private static <T extends Comparable<? super T>> void quicksort(T[] a, int left, int right) {
if (left + CUTOFF <= right) {
//三數中值分割法獲取樞紐元
T pivot = median3(a, left, right);
// 開始分割序列
int i = left, j = right - 1;
for (; ; ) {
while (a[++i].compareTo(pivot) < 0) {
}
while (a[--j].compareTo(pivot) > 0) {
}
if (i < j) {
swapReferences(a, i, j);
} else {
break;
}
}
//將樞紐元與位置i的元素交換位置
swapReferences(a, i, right - 1);
//排序小於樞紐元的序列
quicksort(a, left, i - 1);
//排序大於樞紐元的序列
quicksort(a, i + 1, right);
} else {
//插入排序
insertionSort(a, left, right);
}
}
private static <T extends Comparable<? super T>> T median3(T[] a, int left, int right) {
int center = (left + right) / 2;
if (a[center].compareTo(a[left]) < 0) {
swapReferences(a, left, center);
}
if (a[right].compareTo(a[left]) < 0) {
swapReferences(a, left, right);
}
if (a[right].compareTo(a[center]) < 0) {
swapReferences(a, center, right);
}
// 將樞紐元放置到right-1位置
swapReferences(a, center, right - 1);
return a[right - 1];
}
public static <T> void swapReferences(T[] a, int index1, int index2) {
T tmp = a[index1];
a[index1] = a[index2];
a[index2] = tmp;
}
private static <T extends Comparable<? super T>> void insertionSort(T[] a, int left, int right) {
for (int p = left + 1; p <= right; p++) {
T tmp = a[p];
int j;
for (j = p; j > left && tmp.compareTo(a[j - 1]) < 0; j--) {
a[j] = a[j - 1];
}
a[j] = tmp;
}
}
}
//輸出結果
//快速排序前:[8, 1, 4, 9, 6, 3, 5, 2, 7, 0]
//快速排序後:[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
複製代碼
本篇從如何較好選擇樞紐元,分析重複元素的處理及遞歸分紅小數組時更換爲插入排序三個方面進行快速排序的優化,系統全面詳述了快速排序原理、過程及其優化。快速排序以平均時間O(NlogN)進行,是java中基礎類型使用的排序算法。能夠去看一下Arrays.sort方法。到這裏,我就要回過頭去完善求解topK問題了,能夠利用快速排序的思想,達到平均O(N)求解topK。
以爲能夠的小夥伴們點個推薦或小贊支持啊。