標籤: algorithmshtml
[TOC]java
本文介紹幾種常見排序算法(選擇排序,插入排序,希爾排序,歸併排序,快速排序,堆排序),對算法的思路、性質、特色、具體步驟、java實現以及trace圖解進行了全面的說明。最後對幾種排序算法進行了比較和總結。git
git clone git@github.com:brianway/algorithms-learning.git
java:
Interface Comparable<T>
github
Java中不少類已經實現了Comparable<T>
接口,用戶也可自定義類型實現該接口算法
total order:api
注意: The <=
operator for double is not a total order,violates totality: (Double.NaN <=
Double.NaN) is false數組
通用代碼:oracle
// Less. Is item v less than w ? private static boolean less(Comparable v, Comparable w) { return v.compareTo(w) < 0; } //Exchange. Swap item in array a[] at index i with the one at index j private static void exch(Comparable[] a,, int i, int j) { Comparable swap = a[i]; a[i] = a[j]; a[j] = swap; }
思路:less
- 在第i次迭代中,在剩下的(即未排序的)元素中找到最小的元素
- 將第i個元素與最小的元素交換位置
現象:dom
步驟:
java實現:
public static void sort(Comparable[] a) { int N = a.length; for (int i = 0; i < N; i++) { int min = i; for (int j = i+1; j < N; j++) { if (less(a[j], a[min])) min = j; } exch(a, i, min); } }
特色:
思路:
- 在第i次迭代中,將第i個元素與每個它左邊且比它大的的元素交換位置
現象:
步驟:
a[i]
with each larger entry to its left.java實現:
public static void sort(Comparable[] a) { int N = a.length; for (int i = 0; i < N; i++) { for (int j = i; j > 0 && less(a[j], a[j-1]); j--) { exch(a, j, j-1); } } }
inversion(倒置):An inversion is a pair of keys that are out of order
部分有序:An array is partially sorted if the number of inversions is ≤ c N.
特色:
希爾排序是基於插入排序的。
思路:
- Move entries more than one position at a time by h-sorting the array
- 按照h的步長進行插入排序
現象:
性質:
- 遞增數列通常採用3x+1:1,4,13,40,121,364.....,使用這種遞增數列的希爾排序所需的比較次數不會超過N的若干倍乘以遞增數列的長度。
- 最壞狀況下,使用3x+1遞增數列的希爾排序的比較次數是O(N^(3/2))
java實現:
public static void sort(Comparable[] a) { int N = a.length; // 3x+1 increment sequence: 1, 4, 13, 40, 121, 364, 1093, ... int h = 1; while (h < N/3) h = 3*h + 1; while (h >= 1) { // h-sort the array for (int i = h; i < N; i++) { for (int j = i; j >= h && less(a[j], a[j-h]); j -= h) { exch(a, j, j-h); } } h /= 3; } }
目標:Rearrange array so that result is a uniformly random permutation
shuffle sort思路
- 爲數組的每個位置生成一個隨機實數
- 排序這個生成的數組
Knuth shuffle demo
- In iteration i, pick integer r between 0 and i uniformly at random.
- Swap
a[i]
anda[r]
.
correct variant: between i and N – 1
下面看看這兩種排序算法
思路:
- Divide array into two halves.
- Recursively sort each half.
- Merge two halves.
Given two sorted subarrays a[lo] to a[mid] and a[mid+1] to a[hi],replace with sorted subarray a[lo] to a[hi]
步驟:
aux[]
中,再歸併回a[]
中。merging java實現:
// stably merge a[lo .. mid] with a[mid+1 ..hi] using aux[lo .. hi] private static void merge(Comparable[] a, Comparable[] aux, int lo, int mid, int hi) { // precondition: a[lo .. mid] and a[mid+1 .. hi] are sorted subarrays // copy to aux[] for (int k = lo; k <= hi; k++) { aux[k] = a[k]; } // merge back to a[] int i = lo, j = mid+1; for (int k = lo; k <= hi; k++) { if (i > mid) a[k] = aux[j++]; else if (j > hi) a[k] = aux[i++]; else if (less(aux[j], aux[i])) a[k] = aux[j++]; else a[k] = aux[i++]; } }
mergesort java實現:
// mergesort a[lo..hi] using auxiliary array aux[lo..hi] private static void sort(Comparable[] a, Comparable[] aux, int lo, int hi) { if (hi <= lo) return; int mid = lo + (hi - lo) / 2; sort(a, aux, lo, mid); //將左邊排序 sort(a, aux, mid + 1, hi); //將右邊排序 merge(a, aux, lo, mid, hi); //歸併結果 }
自頂向下的歸併排序的軌跡圖
由圖可知,原地歸併排序的大體趨勢是,先局部排序,再擴大規模;先左邊排序,再右邊排序;每次都是左邊一半局部排完且merge了,右邊一半纔開始從最局部的地方開始排序。
改進
思路:
- 先歸併微型數組,從兩兩歸併開始(每一個元素理解爲大小爲1的數組)
- 重複上述步驟,逐步擴大歸併的規模,2,4,8.....
java實現:
public class MergeBU{ private static void merge(...){ /* as before */ } public static void sort(Comparable[] a){ int N = a.length; Comparable[] aux = new Comparable[N]; for (int sz = 1; sz < N; sz = sz+sz) for (int lo = 0; lo < N-sz; lo += sz+sz) merge(a, aux, lo, lo+sz-1, Math.min(lo+sz+sz-1, N-1)); } }
自底向上的歸併排序的軌跡圖
由圖可知,自底向上歸併排序的大體趨勢是,先局部排序,逐步擴大到全局排序;步調均勻,穩步擴大
思路:
- Shuffle the array.
- Partition(切分) so that, for some j - entry a[j] is in place - no larger entry to the left of j - no smaller entry to the right of j
- Sort each piece recursively.
其中很重要的一步就是Partition(切分),這個過程使得知足如下三個條件:
partition java實現
// partition the subarray a[lo..hi] so that a[lo..j-1] <= a[j] <= a[j+1..hi] // and return the index j. private static int partition(Comparable[] a, int lo, int hi) { int i = lo; int j = hi + 1; Comparable v = a[lo]; while (true) { // find item on lo to swap while (less(a[++i], v)) if (i == hi) break; // find item on hi to swap while (less(v, a[--j])) if (j == lo) break; // redundant since a[lo] acts as sentinel // check if pointers cross if (i >= j) break; exch(a, i, j); } // put partitioning item v at a[j] exch(a, lo, j); // now, a[lo .. j-1] <= a[j] <= a[j+1 .. hi] return j; }
快排java實現:
public static void sort(Comparable[] a) { StdRandom.shuffle(a); sort(a, 0, a.length - 1); } // quicksort the subarray from a[lo] to a[hi] private static void sort(Comparable[] a, int lo, int hi) { if (hi <= lo) return; int j = partition(a, lo, hi); sort(a, lo, j-1); sort(a, j+1, hi); assert isSorted(a, lo, hi); }
快排的軌跡圖
由圖可知,和歸併排序不一樣,快排的大體趨勢是,先全局大致有個走勢——左邊比右邊小,逐步細化到局部;也是先左後右;局部完成時所有排序也就完成了。
一些實現的細節:
性質:
改進
思路:
- Let v be partitioning item a[lo].
- Scan i from left to right.
主要是經過增長一個指針來實現的。普通的快拍只有lo和high兩個指針,故只能記錄大於
(high右邊)和小於
(lo左邊)兩個區間,等於
只能併入其中一個;這裏增長了使用了lt,i,gt三個指針,從而達到記錄大於
(gt右邊)、小於
(lt左邊)和等於
(lt和i之間)三個區間。
三切分的示意圖
三向切分的java實現:
// quicksort the subarray a[lo .. hi] using 3-way partitioning private static void sort(Comparable[] a, int lo, int hi) { if (hi <= lo) return; int lt = lo, gt = hi; Comparable v = a[lo]; int i = lo; while (i <= gt) { int cmp = a[i].compareTo(v); if (cmp < 0) exch(a, lt++, i++); else if (cmp > 0) exch(a, i, gt--); else i++; } // a[lo..lt-1] < v = a[lt..gt] < a[gt+1..hi]. sort(a, lo, lt-1); sort(a, gt+1, hi); }
思路:
- Create max-heap with all N keys.
- Repeatedly remove the maximum key.
堆排序主要分爲兩個階段:
java實現以下:
public static void sort(Comparable[] pq) { int N = pq.length; //堆的構造 for (int k = N/2; k >= 1; k--) sink(pq, k, N); //下沉排序 while (N > 1) { exch(pq, 1, N--); sink(pq, 1, N); } }
堆排序的軌跡圖
由圖看出,堆排序的趨勢是,堆構造階段,大體是降序的走勢,到了下沉階段,從右到左(或者說從後往前)逐步有序
Significance: In-place sorting algorithm with N log N worst-case.
缺點
排序算法總結表
最好狀況和最壞狀況:參見上面的表格
關於穩定性:
關於額外空間:除了歸併排序須要線性的額外空間,其餘都是in-place的
aux[]
needs to be of size N for the last merge.)