就像歸併之於歸併排序,劃分是快速排序的核心java
劃分數據即將數據項分爲兩組,使所有值小於特定值的數據項在一組,使所有值大於特定值的數據項在另外一組算法
劃分算法由兩個指針開始,兩個指針分別指向數組2端,相向移動,當左側指針遇到大於特定值的數據項時,中止移動,當右側指針遇到小於特定值的數據項時,中止移動,此時交換兩個指針所指數據項的值,交換完成後,兩個指針再次相向移動,直到兩個指針之間沒有其它數據項或兩個指針之間只有一個數據項數組
public class Partition { private int[] data; public Partition(int[] data){ this.data = data; } //返回右側數組中第一個數據項在整個數組中的位置 public int part(int left, int right, int keyNum){ while(true){ while(left < right && data[left] < keyNum) left++; while(right > left && data[right] >= keyNum) right--; if(right <= left) break; swap(left, right); } return right; } private void swap(int left, int right){ int temp = data[right]; data[right] = data[left]; data[left] = temp; } public void display(){ for(int i = 0 ; i < data.length ; i++){ System.out.print(data[i] + " "); } System.out.print("\n"); } public static void main(String[] args){ int[] ints = {17, 53, 44, 9, 12, 86, 144, 72, 22, 30}; //int[] ints = {1,2,3}; int keyNum = ints[ints.length-1]; Partition p = new Partition(ints); System.out.println(p.part(0, ints.length-1, keyNum)); p.display(); } }
4 17 22 12 9 44 86 144 72 53 30
劃分算法在數組長度很短(好比數組長度爲一、2或3)的時候也能夠正常工做大數據
劃分算法的效率:ui
數據比較次數:左右指針相遇時比較結束,故需比較N次this
數據交換次數:交換次數小於比較次數,但確切的交換次數取決於數組的排列及keyNum的大小編碼
故總的時間複雜度爲O(N)spa
快速排序:把一個數組劃分爲兩個子數組,而後遞歸地調用自身爲每個子數組進行快速排序指針
取每一個待劃分數組右端數據項的值做爲本次劃分的keyNumcode
public class QuickSort { private int[] data; public QuickSort(int[] data){ this.data = data; } public void sort(){ this.reSort(0, data.length - 1); } private void reSort(int left, int right){ if(right <= left) return; int n = this.part(left, right); //顯示局部排序結果 this.display(); //遞歸調用當前方法爲劃分出來的子數組進行排序 //排序過程最後會將keyNum移動至右端數組的最左端 //故在當前排序結束後keyNum是有序的 //不須要再對keyNum進行排序 reSort(left, n - 1); reSort(n + 1, right); } private int part(int left, int right){ int tail = right; int keyNum = data[right]; //當前數組最右端的數據已被選爲keyNum //不須要參與比較過程 right--; while(true){ while(left < right && data[left] < keyNum) left++; while(right > left && data[right] > keyNum) right--; if(right == left) break; swap(left,right); } //整個當前數組都小於等於keyNum //這時候不須要移動keyNum的位置 //由於整個右端數組只包含keyNum這一個數據項 if(data[right] > keyNum) swap(right,tail); return right; } private void swap(int left, int right){ int temp = data[right]; data[right] = data[left]; data[left] = temp; } public void display(){ for(int i = 0 ; i < data.length ; i++){ System.out.print(data[i] + " "); } System.out.print("\n"); } public static void main(String[] args) { int[] data = {42, 89, 63, 12, 94, 27, 78, 3, 50, 36}; //int[] data = {1}; QuickSort qs = new QuickSort(data); qs.sort(); System.out.println("result:"); qs.display(); } }
排序過程以下圖所示:
3 27 12 36 94 89 78 42 50 63 3 12 27 36 94 89 78 42 50 63 3 12 27 36 50 42 63 89 94 78 3 12 27 36 42 50 63 89 94 78 3 12 27 36 42 50 63 78 94 89 3 12 27 36 42 50 63 78 89 94 result: 3 12 27 36 42 50 63 78 89 94
當每次劃分都能將數組分爲兩個大小相等的子數組時,快速排序的效率是最高的,爲O(N*logN),由於此時完成排序所需的總劃分次數是最少的
考慮以下狀況:若是數組爲逆序,則選擇待劃分數組右端數據項做爲keyNum將致使快速排序退化爲冒泡排序,此時快速排序的效率將變爲O(N*N)
因此keyNum的選擇對於快速排序來講是很是重要的,keyNum應儘可能接近待劃分數組數據項的平均值,避免出現keyNum是待劃分數組最大數據項或最小數據項的狀況
三數據項取中:
public class QuickSort { private int[] data; public QuickSort(int[] data){ this.data = data; } public void sort(){ this.reSort(0, data.length - 1); } private void reSort(int left, int right){ //對於長度少於3的待劃分數組,使用冒牌排序 if(right - left + 1 <= 3){ shortPart(left, right); // System.out.print("normal:"); // display(); }else{ //經過三數據項取中的方式獲得keyNum int p = getMiddleNum(left, right); int keyNum = data[p]; //普通劃分過程 int n = part(left, right, keyNum); display(); reSort(left, n - 1); reSort(n + 1, right); } } private void shortPart(int left, int right){ //待劃分數組長度爲1 if(right - left == 0) return; //待劃分數組長度爲2 else if(right - left == 1){ if(data[right] < data[left]) swap(right, left); }else{ int middle = left + 1; if(data[left] > data[middle]) swap(left, middle); if(data[middle] > data[right]) swap(middle, right); if(data[left] > data[middle]) swap(left, middle); } } //將3個數據項中的中間值做爲keyNum //並將keyNum移動至3個數據項的中間位置 //避免劃分出的2個子數組一個過大一個太小 //若是不將keyNum移動至中間位置,在有些狀況下 //會致使劃分次數變多,排序效率下降 private int getMiddleNum(int left, int right){ int middle = (left + right)/2; if(data[left] > data[middle]) swap(left, middle); if(data[middle] > data[right]) swap(middle, right); if(data[left] > data[middle]) swap(left, middle); return middle; } private int part(int left, int right, int keyNum){ while(true){ while(left < right && data[left] < keyNum) left++; while(right > left && data[right] >= keyNum) right--; if(right == left) break; swap(left,right); } return right; } private void swap(int left, int right){ int temp = data[right]; data[right] = data[left]; data[left] = temp; } public void display(){ for(int i = 0 ; i < data.length ; i++){ System.out.print(data[i] + " "); } System.out.print("\n"); } public static void main(String[] args) { int[] data = {42, 89, 63, 12, 94, 27, 78, 3, 50, 36}; QuickSort qs = new QuickSort(data); qs.sort(); System.out.println("result:"); qs.display(); } }
排序過程以下圖所示:
36 3 27 12 42 63 78 89 50 94 3 12 27 36 42 63 78 89 50 94 3 12 27 36 42 63 78 50 89 94 result: 3 12 27 36 42 50 63 78 89 94
能夠看到確實比使用待劃分數組右端數據項的值做爲keyNum所需的劃分次數要少
PS:
寫這個算法費了很多力氣,主要是考慮的太過複雜,對於一個長度很長的數據,多1-2次的比較對效率是沒什麼影響的,反而會增長編碼的複雜度,就像遞歸,遞歸的效率確定沒有循環的效率高,可是遞歸能夠下降問題的複雜性