前言:快速排序是一種分治的排序算法,他將一個數組分成兩個數組,將兩部分獨立的排序。快速排序和歸並排序是互補的:歸並排序是將數組分成兩個子數組分別排序,並將有序的子數組歸並以將整個數組排序,而快速排序將數組排序的方式是當兩個子數組都有序時整個數組也就有序了。
大致過程如下:
其中左邊的不大於K,右邊的部分不小於K。
代碼實現

1 public static void sort(Comparable[] a,int start,int end) 2 { 3 if(end<=start) return; 4 int j=partition(a,start,end); 5 sort(a,start,j-1); 6 sort(a,j+1,end); 7 }
快速排序遞歸的將子數組a[start……end]排序,然后調用partition()方法將a[j]放到一個合適的位置,然后再遞歸調用將其他位置的元素排序。該方法的關鍵在於切分,
(1)對於某個J,a[j]已經確定,
(2)a[start……j-1]中的元素都不大於a[j];
(3) a[j+1……end]中的元素都不小於a[j];
切分的過程總能正確排序一個元素,用歸納法可以證明通過遞歸調用能將數組排序。
要實現這個方法,一般策略是先隨意取一個a[start]作為切分元素,這個元素就是那個將要被排序的元素,然后從數組的左端向右掃描直到找到一個大於等於它的元素,從數組右端向左掃描直到找到一個小於等於它的元素這兩個元素我們交換位置,依此類推
我們就可以保證指針i左邊的元素不大於a[start](圖上為a[lo]),指針j右邊的元素不小於a[start].當兩指針相遇時,我們只需要將a[start]和左子數組最右邊的元素a[j]交換位置即可
切分代碼實現:

1 private static int partition(Comparable[] a,int start,int end) 2 { 3 Comparable v=a[start]; 4 int i=start;; 5 int j=end+1; 6 while(true) 7 { 8 while(less(a[++i],v)) 9 if(i==end) break; 10 11 12 while(less(v,a[--j])) 13 if(j==start) break; 14 if(i>=j) break; 15 exch(a,i,j); 16 } 17 exch(a,start,j); 18 return j; 19 }
算法軌跡:
分析這段軌跡:
首先執行while(less(a[++i],v)),掃描到R,發現R大於K,此時這個循環跳出,執行while(less(v,a[--j])),掃描到S,滿足,繼續掃描,直到發現C小於K。這樣就找到了兩個元素,此時兩個掃描指針沒有相遇,交換兩個元素位置。繼續掃描,交換T和I兩個元素,繼續掃描,交換L和E兩個元素,此時左掃描指針i=5,右掃描指針j=6,繼續掃描,++i為6,--j為5,此時執行if(i>=j) break;跳出循環。接着執行 exch(a,start,j);交換切分元素的位置。
算法分析:
事實上,再算法中,j==start的條件是可以去掉的,因為切分元素就是a[start],他不可能比自己小,當j=start+1時,在執行到while(less(v,a[--j])),此時判斷條件不成立,直接跳出循環。
左側掃描我們最好要選擇遇到大於等於切分元素的時候停下,右側掃描最好是遇到小於等於切分元素的時候停下,盡管可能會交換一些不必要的值,但是再某些應用下他能避免算法的運行時間變為平方級別。例如如果不停下來,當我們用這種算法處理只有若干元素值的數組的運行時間為平方級別。
快速排序在切分方法的內循環中用一個遞增或遞減的索引將數組元素和一個定值比較,而歸並排序比它慢(通常情況下)的一個原因就是在內循環中要移動數據。
但是這種算法也有一個缺點,如果我們的切分不平衡這個程序可能極為低效,例如第一次從最小的元素切分,第二次從第二小的元素切分。每次調用只會移除一個元素。解決的一個方法是在排序前將數組隨機打亂。
算法改進:
(一)切換到插入排序。
原因:
1、對於小數組,插入排序更快。
2、因為遞歸,sort()方法就算在小數組中也會調用自己。
if(end<=start) return;改為if(end<=start+M){ Innertion.sort(a,start,end);} ,M一般取5~15,
(二)三取樣切分
使用子數組中的一小部分元素的中位數來切分數組,這樣做效果更好,但是代價是需要計算中位數。人們發現將取樣大小設為3並取大小居中的元素效果最好
(三)熵最優的排序。
實際應用中在應用程序中經常出現具有大量重復元素的數組。我們的算法會使元素全部重復的子數組經常出現,在這些數組中,有可能將從線性對數級別的性能提高到到線性級別
一個想法是將數組切分為三部分,分別對應於小於,等於和大於切分元素的數組元素(有興趣的可以百度Dijkstra提出的荷蘭國旗問題),如圖:
它從左到右遍歷數組一次,維護一個指針lt使得a[lo……lt-1]中的元素都小於V,一個指針gt使得a[gt+1……hi]中的元素都大於V,一個指針i使得a[lt……i-1]中的元素都等於V,而a[i……gt]中的元素未確定。一開始i和lo相等,我們使用Comparable接口(非less())對a[i]進行三向比較來處理一下情況。
(1)、a[i]小於V,將a[i]與a[lt]交換,i++,lt++;
(2)、 a[i]大於V,將a[i]與a[gt]交換,gt--;i不變
(3)、a[i]等於V,i++;
代碼實現

1 private static void sort0(Comparable[] a,int lo,int hi) 2 { 3 if(hi>=lo) return; 4 int lt=lo,i=lo+1,gt=hi; 5 Comparable v=a[lo]; 6 while(i<=gt) 7 { 8 int cmp=a[i].compareTo(v); 9 if(cmp<0) exch(a,lt++,i++); 10 else if(cmp>0) exch(a,i,gt--); 11 else i++; 12 13 } 14 sort0(a,lo,lt-1); 15 sort0(a,gt+1,hi); 16 }
算法軌跡
完整代碼:

1 public class Quick { 2 3 private static boolean less(Comparable<Object> a,Comparable<Object> b) 4 { 5 return a.compareTo(b)<=0; 6 } 7 8 9 private static void exch(Comparable<Object> [] ary,int i,int j) 10 { 11 Comparable<Object> temp=ary[i]; 12 ary[i]=ary[j]; 13 ary[j]=temp; 14 15 } 16 17 18 19 private static int partition(Comparable[] a,int start,int end) 20 { 21 Comparable v=a[start]; 22 int i=start;; 23 int j=end+1; 24 while(true) 25 { 26 while(less(a[++i],v)) 27 if(i==end) break; 28 29 30 while(less(v,a[--j])) 31 if(j==start) break; 32 if(i>=j) break; 33 exch(a,i,j); 34 } 35 exch(a,start,j); 36 return j; 37 } 38 39 public static void sort(Comparable[] a,int start,int end) 40 { 41 if(end<=start) return; 42 int j=partition(a,start,end); 43 sort(a,start,j-1); 44 sort(a,j+1,end); 45 } 46 47 private static void sort0(Comparable[] a,int lo,int hi) 48 { 49 if(hi>=lo) return; 50 int lt=lo,i=lo+1,gt=hi; 51 Comparable v=a[lo]; 52 while(i<=gt) 53 { 54 int cmp=a[i].compareTo(v); 55 if(cmp<0) exch(a,lt++,i++); 56 else if(cmp>0) exch(a,i,gt--); 57 else i++; 58 59 } 60 sort0(a,lo,lt-1); 61 sort0(a,gt+1,hi); 62 } 63 }