快速排序也是一種分治算法。主要思想是選取一個切分點,將大於切分點的元素都放置到數組右側,小於切分點的元素都放置到數組左側;然后遞歸,再對切分點左側和右側分別排序。
歸並排序時遞歸在前,歸並在后,快速排序是切分在前,排序在后。
快速排序的運行時間在1.39nlogn的某個常數因子范圍之內,歸並排序也能做到這一點,但是快速排序更快,因為它的移動次數更少。
快速排序的關鍵點在於切分點的選取,對於正好逆序的情況,它的復雜度達到了n2,而且與歸並排序相比不穩定。
為了防止最壞情況的出現,一般在排序前先將元素隨機打亂。
1 package 排序; 2 3 import edu.princeton.cs.algs4.In; 4 import edu.princeton.cs.algs4.StdOut; 5 import edu.princeton.cs.algs4.StdRandom; 6 /** 7 * @author evasean www.cnblogs.com/evasean/ 8 */ 9 @SuppressWarnings("rawtypes") 10 public class Quick快速排序 { 11 private static int partition(Comparable[] a, int lo, int hi){ 12 int i = lo; 13 int j = hi+1; 14 Comparable v = a[lo];//切分元素 15 while(true){ 16 while(less(a[++i],v)) 17 if(i==hi) break; 18 while(less(v,a[--j])) 19 if(j==lo) break; 20 if(i>=j) break; 21 exch(a,i,j); 22 } 23 exch(a,lo,j);//此時j<=i,且v > a[j],將切分元素v放入正確位置 24 return j; 25 } 26 public static void sort(Comparable[] a){ 27 StdRandom.shuffle(a); //消除最壞的情況 28 sort(a,0,a.length-1); 29 } 30 private static void sort(Comparable[] a, int lo, int hi){ 31 if(hi <= lo) return; 32 int j = partition(a,lo,hi); 33 sort(a,lo,j-1); 34 sort(a,j+1,hi); 35 } 36 37 @SuppressWarnings("unchecked") 38 private static boolean less(Comparable v, Comparable w){ 39 return v.compareTo(w) < 0; 40 } 41 private static void exch(Comparable[] a, int i, int j){ 42 Comparable t = a[i]; 43 a[i] = a[j]; 44 a[j] = t; 45 } 46 private static void show(Comparable[] a){ 47 for(int i=0; i<a.length; i++) StdOut.print(a[i] + " "); 48 StdOut.println(); 49 } 50 public static boolean isSorted(Comparable[] a){ 51 for(int i = 1; i < a.length; i++){ 52 if(less(a[i],a[i-1])) return false; 53 } 54 return true; 55 } 56 public static void main(String[] args){ 57 String[] a = new In().readAllStrings(); 58 sort(a); 59 assert isSorted(a); 60 show(a); 61 } 62 }
實際應用中經常會出現大量重復元素的排序情況,而快速排序在面對重復元素時排序復雜度並沒有降低。Dijkstra提出的三向切分快速排序方法可以迅速降低這種情況下的復雜度,甚至有可能達到線性級別n,如荷蘭國旗問題(見我的另一篇文章)。其基本思想就是選取切分點v,從左到右遍歷,維護一個從前往后遍歷的位置點lt,使得a[0]~a[lt-1]都小於v,維護一個從前往后遍歷的位置點i,使得a[lt]~a[i-1]都等於v,維護一個從后往前的位置點,使得a[i]~a[gt]都大於v。
1 /* 2 * 三向切分的快速排序 3 * a[lo...lt-1]中的元素都小於v 4 * a[gt+1....hi]中的元素都大於v 5 * a[lt...i-1]中的元素都等於v 6 * a[i...gt]中的元素都還未確定,通過下面處理 7 * 1. a[i]小於v,將a[lt]和a[i]交換,將lt和i加1 8 * 2. a[i]大於v,將a[gt]和a[i]交換,將gt減1 9 * 3. a[i]等於v,將i加1 10 * 這些操作都會保證數組元素不變且縮小gt-i的值,這樣循環才會結束 11 */ 12 private static void sort3way(Comparable[] a, int lo, int hi){ 13 if(hi <= lo) return; 14 int lt = lo; 15 int i = lo+1; 16 int gt = hi; 17 Comparable v = a[lo]; 18 while(i <= gt){ 19 int cmp = a[i].compareTo(v); 20 if(cmp<0) exch(a,lt++,i++); 21 else if(cmp>0) exch(a,i,gt--); 22 else i++; 23 }//現在a[lo...lt-1] < v=a[lt...gt]<a[gt+1...hi] 24 sort3way(a,lo,lt-1); 25 sort3way(a,gt+1,hi); 26 }
用這里的sort3way方法替代快速排序中的sort(Comparable[] a, int lo, int hi)即可。