十大基礎排序算法[java源碼+動靜雙圖解析+性能分析]

1、概述

做爲一個合格的程序員,算法是必備技能,特此總結十大基礎排序算法。java版源碼實現,強烈推薦《算法第四版》很是適合入手,全部算法網上能夠找到源碼下載java

PS:本文講解算法分三步:1.思想2.圖示3.源碼4.性能分析git

1.1 時間複雜度

算法的運行時間,在這裏主要考量:比較和交換的成本。程序員

常見的時間複雜度排序: 常數階O(1)<對數階O(
log2n )<線性階O(n)<線性對數階O(nlog2n)<平方階O(n^2)<立方階O(n^3)<指數階O(2^n)

1.2 空間複雜度

使用的內存,是否須要額外的存儲空間。算法

1.3 穩定性

相等元素排序先後相對位置保持不變,就認爲是穩定的。shell

穩定性什麼時候有意義?數組

1)排序對象包含多個屬性緩存

2) 原始序列其它屬性有序less

3)指望最終排序後原來有序的屬性還有序,原來沒序的字段有序了。dom

2、常見排序算法

通用函數封裝

因爲排序算法都用到一些相同源碼,因此提煉出來做爲一個父類,算法只須要繼承這個類便可使用通用方法:函數

1.less()比較元素大小

2.exch()交換元素

3.show()打印排序後數組

4.isSorted()校驗排序是否正確

 1 package algorithm;
 2 
 3 /**
 4  * 
 5  * @ClassName:MySort
 6  * @Description:自定義排序基類,封裝經常使用方法
 7  * @author denny.zhang
 8  * @date 2018年2月5日下午3:12:03
 9  */
10 public class MySort {
11     protected static String[] a = new String[]{"a","d","c","b"};
12     
13     /**
14      * 
15      * @Description 打印排序後結果
16      * @param a
17      * @author denny.zhang
18      * @date 2018年2月5日下午3:11:46
19      * @since JDK1.8
20      */
21     protected static void show(Comparable[] a){
22         for(int i=0;i<a.length;i++){
23             System.out.print(a[i]+" ");
24         }
25         System.out.println();
26     }
27     
28     /**
29      * 
30      * @Description 比較是否v小於w
31      * @param v
32      * @param w
33      * @return
34      * @author denny.zhang
35      * @date 2018年2月5日下午3:11:14
36      * @since JDK1.8
37      */
38     protected static boolean less(Comparable v,Comparable w){
39         return v.compareTo(w)<0;
40     }
41     
42     /**
43      * 
44      * @Description 對於數組a,交換a[i]和a[j]。
45      * @param a
46      * @param i
47      * @param j
48      * @author denny.zhang
49      * @date 2018年2月5日下午3:09:41
50      * @since JDK1.8
51      */
52     protected static void exch(Comparable[] a,int i,int j){
53         Comparable temp= a[i];
54         a[i]=a[j];
55         a[j]=temp;
56         //System.out.println("交換a["+i+"]"+",a["+j+"]");
57     }
58     
59     /**
60      * 
61      * @Description 用於校驗是否升序
62      * @param a
63      * @return
64      * @author denny.zhang
65      * @date 2018年2月5日下午3:10:57
66      * @since JDK1.8
67      */
68     protected static boolean isSorted(Comparable[] a){
69         //只要有一個後數<前數,返回失敗
70         for(int i=0;i<a.length;i++){
71             if(less(a[i], a[i-1])) return false;
72         }
73         //都沒問題,返回成功
74         return true;
75     }
76     
77     /**
78      * 
79      * @Description 用於校驗是否升序
80      * @param a
81      * @param lo 起始下標
82      * @param hi 結束下標
83      * @return
84      * @author denny.zhang
85      * @date 2018年2月6日下午5:19:43
86      * @since JDK1.8
87      */
88     protected static boolean isSorted(Comparable[] a, int lo, int hi) {
89         for (int i = lo + 1; i <= hi; i++)
90             if (less(a[i], a[i-1])) return false;
91         return true;
92     }
93 }

 

2.1 冒泡排序

思想:

最簡單的排序,內外兩遍循環。外循環簡單遍歷a[n-1]~a[1],內循環一遍肯定一個最大值,相似"冒泡"。

1.外循環遍歷a[n-1]~a[1]。例如選定a[n-1]

2.內循環,從a[0]-a[n-1] 從左往右,比較相鄰的元素對。若是第一個比第二個大,就交換它們兩個,一直到最後一對,這樣在最後的元素a[n-1]是最大的數;

2.重複以上的步驟,一直到外循環遍歷到a[1],內循環會比較a[0],a[1],至此排序完畢。

優化點:添加一個標記,當內循環一遍不交換,排序完畢。

動態圖:

源碼:

 1 /**
 2  * @author denny
 3  * @Description 冒泡排序
 4  * @date 2019/7/2 下午6:19
 5  */
 6 public class Bubble extends MySort {
 7 
 8     public static void sort(Comparable[] a) {
 9         int n = a.length;
10         // 標記內層遍歷是否交換過
11         int flag = 0;
12         // 外層遍歷n-1次,每次肯定一個最大值 a[i]
13         for (int i = n - 1; i > 0; i--) {
14             // 內層遍歷:比較a[0]a[1]...a[n-1-1]a[n-1] 
15             for (int j = 0; j < i; j++) {
16                 // 相鄰元素兩兩對比,若是後<前,交換
17                 if (less(a[j + 1], a[j])) {
18                     // 元素交換
19                     exch(a, j + 1, j);
20                     flag = 1;
21                 }
22             }
23             //  flag=0,說明數列已有序不須要再交換了!!!
24             if(flag==0){
25                 break;
26             }
27         }
28     }
29 
30     public static void main(String[] args) {
31         Bubble.sort(a);
32         show(a);
33     }
34 }

分析:

時間複雜度:外部循環O(n) * 內部循環O(n) -->O(n²)

空間複雜度:不須要藉助外部空間-->O(1)

是否穩定:相等元素不交換-->是。

2.2 選擇排序

思想:

最簡單的排序,內外兩遍循環。外循環簡單遍歷a[0]~a[n-1],內循環每次肯定一個最小值

1.外循環假設第一個數是最小值

2.內循環拿後面全部元素和第一個元素比較,找到最小值,放在第一位(若是第一位不是最小值就交換),第一小的元素肯定。

3.外循環從第一個元素遍歷到最後,重複1,2操做,排序完畢。

圖示:

動態圖:

源碼:

 1 package algorithm;
 2 
 3 /**
 4  * 
 5  * @ClassName:Selection
 6  * @Description:選擇排序:對於數組長度爲N的數組<br>
 7  * <ul>
 8  * <li>比較次數:(n-1)+(n-2)+...2+1=n(n-1)/2,大約N²/2次</li>
 9  * <li>交換次數:N次</li>
10  * </ul>
11  * @author denny.zhang
12  * @date 2018年2月5日上午11:13:26
13  */
14 public class Selection extends MySort{
15 
16     @SuppressWarnings("rawtypes")
17     public static void sort(Comparable[] a){
18         int n = a.length;
19         //遍歷n次,每次肯定一個最小值
20         for(int i=0 ; i<n ; i++){
21             int min=i;//初始化最小值下標爲i 
22             //把i以後的每一項都和a[min]比較,求最小項
23             for(int j=i+1;j<n;j++){
24                 if (less(a[j], a[min])) min = j;
25             }
26             //a[i]和a[min]交換,第i位排序完畢
27             exch(a, i, min);
28         }
29     }
30     
31     public static void main(String[] args) {
32         Selection.sort(a);
33         show(a);
34     }
35 }

分析:

時間複雜度:外部循環O(n) * 內部循環O(n) = O(n²)

空間複雜度:O(1)不須要藉助外部空間

是否穩定:否。只要遇到小的就交換,被交換對象若是有等值元素存在且在交換元素以前,打破相對順序了。列子:58529-》第一個5和2交換,2個5的相對順序變了。

2.3 插入排序

思想:

相似撲克牌抓牌同樣,一次插入一張牌保證當前牌有序。

從第二張牌開始,插入第一張牌;第三張牌插入前兩張牌...一直到最後一張牌。插入牌時,兩兩比較,遇到逆序交換。

外循環i++,內循環a[j]和a[j-1]比較,逆序交換。j--往左移動,兩兩比較。

圖示:

動態圖:

源碼:

 1 package algorithm;
 2 
 3 /**
 4  * 
 5  * @ClassName:Insertion
 6  * @Description:插入排序:對於數組長度爲N的數組<br>就像撲克牌插紙牌同樣
 7  * <ul>
 8  * <li>比較次數:N-1~N²/2  平均N²/4 </li>
 9  * <li>交換次數:0~N²/2 平均N²/4</li>
10  * </ul>
11  * @author denny.zhang
12  * @date 2018年2月5日上午11:13:26
13  */
14 public class Insertion extends MySort{
15 
16     @SuppressWarnings("rawtypes")
17     public static void sort(Comparable[] a){
18         int n = a.length;
19         //遍歷n-1次,a[i]插入前i個數
20         for(int i=1 ; i<n ; i++){
21             //System.out.println("i="+i);
22             //i以前的每兩項比較,出現逆序,當即交換
23             for(int j=i;j>0 && less(a[j], a[j-1]);j--){
24                 exch(a, j, j-1);
25             }
26         }
27     }
28     
29     public static void main(String[] args) {
30         Insertion.sort(a);
31         show(a);
32     }
33 }

分析:

時間複雜度:O(n~n²)若是元素有序就是n, 元素逆序就是n²

空間複雜度:O(1)

是否穩定:是

 

2.4 希爾排序

思想:

希爾排序又稱縮小增量排序,是插入排序的升級版。核心思想是使數組中任意間隔爲h的元素有序,針對每一個h,取出間隔h的子數組並插入排序,h愈來愈小一直到1,即排序完成。這個間隔h的選擇有考究。

 

圖示:

動態圖:

步長:3-2-1

源碼:

遞增序列有不少,只要知足最後一個數爲1便可。即最少有間隔爲1的插入排序便可保證排序。至於這個數列具體最優不在本文討論範圍內。

下圖展現了2種序列,sort()方法是複雜序列,sort2()是簡單序列。若是隻是理解算法的話sort2()足矣

 1 package algorithm;
 2 
 3 /**
 4  * 
 5  * @ClassName:Shell
 6  * @Description:希爾排序:改進自插入排序,交換不相鄰元素以對數組的局部進行排序,並最終用插入排序將局部有序的數組排序<br>
 7  * 
 8  * @author denny.zhang
 9  * @date 2018年2月5日上午11:13:26
10  */
11 public class Shell extends MySort{
12 
13     /**
14      * 
15      * @Description 更加符合要求的算法
16      * @param a
17      * @author denny.zhang
18      * @date 2018年3月7日下午5:35:07
19      * @since JDK1.8
20      */
21     @SuppressWarnings("rawtypes")
22     public static void sort(Comparable[] a){
23         int n = a.length;
24 
25         // 3x+1 increment sequence:  1, 4, 13, 40, 121, 364, 1093, ... 
26         int h = 1;
27         while (h < n/3) {//這裏確保了每一個子數組有>=3個元素,h間隔過大,每一個子數組元素太少就沒有排序的意義了 28             h = 3*h + 1; //1.若是小於n/3,h變大,直到h>=n/3爲止
29         }
30         while (h >= 1) {
31             // h-sort the array 2.遍歷從h->n
32             for (int i = h; i < n; i++) {
33                 //3.從j開始往左每h間隔比較一次,逆序就交換
34                 for (int j = i; j >= h && less(a[j], a[j-h]); j -= h) {
35                     exch(a, j, j-h);
36                 }
37             }
38             assert isHsorted(a, h); 
39             h /= 3;//排完序再把h下降回來
40         }
41         assert isSorted(a);
42     }
43     
44     /**
45      * 
46      * @Description d=n/2序列(向上取整)簡單算法
47      * @param a
48      * @author denny.zhang
49      * @date 2018年3月7日下午5:33:38
50      * @since JDK1.8
51      */
52     @SuppressWarnings("rawtypes")
53     public static void sort2(Comparable[] a){
54         int n = a.length;
55         int h = n; 
56         //1.第一層循環 是h值計算
57         while (h >= 1) {
58             h=(int) Math.ceil(h/2);//向上取整
59             //2.第二層循環i++ 從h->n-1
60             for (int i = h; i < n; i++) {
61                 //3.第三層循環 從j開始往左每h間隔比較一次,逆序就交換,作到局部有序
62                 for (int j = i; j >= h && less(a[j], a[j-h]); j -= h) {
63                     exch(a, j, j-h);
64                 }
65             }
66             assert isHsorted(a, h); 
67         }
68         assert isSorted(a);
69     }
70 
71     // is the array h-sorted?
72     private static boolean isHsorted(Comparable[] a, int h) {
73         for (int i = h; i < a.length; i++)
74             if (less(a[i], a[i-h])) return false;
75         return true;
76     }
77     
78     public static void main(String[] args) {
79         Shell.sort(a);
80         show(a);
81     }
82 }

 

分析:

時間複雜度:O(n的1~2次方,暫沒法證實最優遞增數列)

空間複雜度:O(1)

是否穩定:否

2.5 歸併排序

思想:

將數組拆分爲兩部分(遞歸)分別排序,再將結果歸併排序。

圖示:

動態圖:

源碼:

 1 package algorithm;
 2 
 3 /**
 4  * 
 5  * @ClassName:Merge
 6  * @Description:歸併排序:自頂向下+自底向上。性能差很少:比較次數:1/2NlgN-NlgN 訪問次數6NlgN
 7  * @author denny.zhang
 8  * @date 2018年2月6日下午5:09:57
 9  */
10 public class Merge extends MySort{
11 
12 
13     /**
14      * 
15      * @Description 歸併
16      * @param a
17      * @param aux
18      * @param lo
19      * @param mid
20      * @param hi
21      * @author denny.zhang
22      * @date 2018年2月6日下午4:55:25
23      * @since JDK1.8
24      */
25     private static void merge(Comparable[] a, Comparable[] aux, int lo, int mid, int hi) {
26 
27         // 複製數組a->aux
28         for (int k = lo; k <= hi; k++) {
29             aux[k] = a[k]; 
30         }
31 
32         // 歸併進a數組
33         int i = lo, j = mid+1;
34         //從aux數組找到小值並賦值給數組a
35         for (int k = lo; k <= hi; k++) {
36             if      (i > mid)              a[k] = aux[j++];//左半邊用盡,取右半邊元素aux[j]->賦值給a[k],並j++
37             else if (j > hi)               a[k] = aux[i++];//右半邊用盡,取左半邊元素aux[i]->賦值給a[k],並i++
38             else if (less(aux[j], aux[i])) a[k] = aux[j++];//aux[j]小->賦值給a[k],並j++
39             else                           a[k] = aux[i++];//aux[i]小->賦值給a[k],並i++
40         }
41     }
42 
43     // 自頂向下歸併排序 a[lo..hi] 使用輔助數組 aux[lo..hi]
44     private static void sort(Comparable[] a, Comparable[] aux, int lo, int hi) {
45         if (hi <= lo) return;
46         int mid = lo + (hi - lo) / 2;
47         sort(a, aux, lo, mid);//左半邊排序
48         sort(a, aux, mid + 1, hi);//右半邊排序
49         merge(a, aux, lo, mid, hi);//歸併結果
50     }
51 
52     /**
53      * 
54      * @Description 自底向上歸併排序
55      * @param a
56      * @author denny.zhang
57      * @date 2018年2月6日下午6:46:01
58      * @since JDK1.8
59      */
60     public static void sortBU(Comparable[] a,Comparable[] aux) {
61         int n = a.length;
62         for (int len = 1; len < n; len *= 2) {//len子數組大小,有多少個子數組遍歷多少回
63             //子數組從2個元素開始本身歸併:22歸併->44歸併->88歸併...最後一個大歸併
64             for (int lo = 0; lo < n-len; lo += len+len) {//lo子數組起始下標
65                 int mid  = lo+len-1;
66                 int hi = Math.min(lo+len+len-1, n-1);//最後一個數組可能小於len,即不能按照2的倍數分小數組,最後一個取最小值
67                 merge(a, aux, lo, mid, hi);
68             }
69         }
70     }
71     
72     /**
73      * 
74      * @Description 歸併排序
75      * @param a
76      * @author denny.zhang
77      * @date 2018年2月6日下午4:52:45
78      * @since JDK1.8
79      */
80     public static void sort(Comparable[] a) {
81         //定義一個輔助數組
82         Comparable[] aux = new Comparable[a.length];
83         //自頂向下歸併
84         sort(a, aux, 0, a.length-1);
85         //自底向上歸併
86         //sortBU(a,aux);
87         //校驗是否排序成功
88         assert isSorted(a);
89     }
90     
91     public static void main(String[] args) {
92         Merge.sort(a);
93         show(a);
94     }
95 }

分析:

時間複雜度:O(NlogN)簡單理解:看上圖源碼44行sort(),把數組拆分2個有序數組,而後再歸併2個小數組。長度爲N的數組遞歸「折半拆分紅2個有序數組」須要logN步(二叉排序樹的樹高),每一步的歸併耗時N,相乘獲得NlogN便是時間複雜度。

空間複雜度:O(N)不用多說使用了額外的輔助數組aux[N]

是否穩定:是

2.6 快速排序

思想:

兩向切分:取數組第一個元素做爲切分元素,左邊子數組所有小於等於切分元素,右邊子數組所有大於等於切分元素,這樣每切分一次就能夠肯定一個元素的位置。左右子數組再分別遞歸排序。

三向切分:對於數組中重複元素多的數組,更適合三向切分。也是第一個元素做爲切分元素值=v,三向切分:第一段:lo~lt元素<v,第二段:lt~gt元素=v,第三段:gt~hi元素>v。遞歸給第一段和第三段排序。

注意:對於小數組(<=15),插入排序更適合

圖示:

下圖是一個兩向切分

 動態圖

源碼:

  1 package algorithm;
  2 
  3 import edu.princeton.cs.algs4.StdRandom;
  4 
  5 /**
  6  * 
  7  * @ClassName:Quick
  8  * @Description:快速排序
  9  * <ul>
 10  * <li>兩向切分</li>
 11  * <li>三向切分:重複元素多的時候</li>
 12  * </ul>
 13  * @author denny.zhang
 14  * @date 2018年2月7日下午5:50:56
 15  */
 16 @SuppressWarnings("rawtypes")
 17 public class Quick extends MySort{
 18 
 19     
 20     public static void sort(Comparable[] a) {
 21         StdRandom.shuffle(a);//隨機排列,避免數組有序出現最差排序
 22         show(a);
 23         sort(a, 0, a.length - 1);//兩向切分
 24         //sort3Way(a, 0, a.length - 1);//三向切分
 25         assert isSorted(a);
 26     }
 27 
 28     /**
 29      * 
 30      * @Description 兩向切分快排
 31      * @param a
 32      * @param lo
 33      * @param hi
 34      * @author denny.zhang
 35      * @date 2018年2月7日下午4:36:48
 36      * @since JDK1.8
 37      */
 38     private static void sort(Comparable[] a, int lo, int hi) { 
 39         if (hi <= lo) return;//
 40         int j = partition(a, lo, hi);//拆分,找到拆分下標
 41         System.out.println("j="+j);
 42         show(a);
 43         sort(a, lo, j-1);//左邊排序,遞歸
 44         sort(a, j+1, hi);//右邊排序,遞歸
 45         assert isSorted(a, lo, hi);
 46     }
 47 
 48     // 兩向切分數組,找到拆分下標並返回,保證a[lo..j-1] <= a[j] <= a[j+1..hi]
 49     private static int partition(Comparable[] a, int lo, int hi) {
 50         System.out.println("lo="+lo+",hi="+hi);
 51         int i = lo;//左指針
 52         int j = hi + 1;//右指針
 53         Comparable v = a[lo];//初始化一個切分元素
 54         //一個大的自循環
 55         while (true) {
 56             
 57             // 分支1:若是左指針元素小於v,++i,一直到右邊界退出,或者左邊有不小於v的元素停下,執行分支2
 58             while (less(a[++i], v))
 59                 if (i == hi) break;
 60 
 61             // 分支2:若是右指針元素大於v,j--,一直到到左邊界退出,或者右邊有不大於v的元素爲止,執行分支3
 62             while (less(v, a[--j]))
 63                 if (j == lo) break;      
 64 
 65             // 分支3:若是左右指針碰撞,甚至左指針大於右指針,退出
 66             if (i >= j) break;
 67             // 交換須要交換的a[i]>=v>=a[j],使得a[i]<a[j]
 68             System.out.println("交換 a[i] a[j] i="+i+",a[i]="+a[i]+",j="+j+",a[j]="+a[j]);
 69             exch(a, i, j);
 70             
 71         }
 72         System.out.println("i="+i+",j="+j+",設置 a[j]="+a[j]+",替換爲a[lo]="+a[lo]);
 73         // a[j]=a[lo],即v
 74         exch(a, lo, j);
 75         
 76         // 返回下標j,a[j]就是中軸
 77         return j;
 78     }
 79     
 80     //三向切分快排
 81     @SuppressWarnings("unchecked")
 82     private static void sort3Way(Comparable[] a, int lo, int hi) { 
 83         if (hi <= lo) return;
 84         int lt = lo, gt = hi;
 85         Comparable v = a[lo];
 86         int i = lo + 1;
 87         while (i <= gt) {
 88             int cmp = a[i].compareTo(v);
 89             if      (cmp < 0) exch(a, lt++, i++);
 90             else if (cmp > 0) exch(a, i, gt--);
 91             else              i++;
 92         }
 93 
 94         // a[lo..lt-1] < v = a[lt..gt] < a[gt+1..hi]. 
 95         sort(a, lo, lt-1);
 96         sort(a, gt+1, hi);
 97         assert isSorted(a, lo, hi);
 98     }
 99 
100     public static void main(String[] args) {
101         Quick.sort(a);
102         show(a);
103     }
104 }

分析:

時間複雜度:O(NlogN):長度與爲N的數組,一顆二叉排序樹左節點<根節點<右節點,一層一層拆分下去,層數=logN+1,每一層的時間複雜度都是o(N)因此NlogN+N,N>2時logN>1,因此時間複雜度爲O(NlogN)

空間複雜度:O(1)

是否穩定:否

2.7 堆排序

思想:

1.構造一個有序堆(每一個節點都大於等於2個子節點),2.下沉排序銷燬堆:交換a[1]和a[n],即每次確認一個最大元素,後移除a[n],這樣數組愈來愈小一直到元素爲0.

圖示:

動態圖

 

源碼:

 1 package algorithm;
 2 
 3 /**
 4  * 
 5  * @ClassName:Heap
 6  * @Description:堆排序:對於數組長度爲N的數組<br>
 7  * <ul>
 8  * <li>比較次數:2NlgN+2N</li>
 9  * <li>交換次數:NlgN+N</li>
10  * </ul>
11  * @author denny.zhang
12  * @date 2018年2月5日上午11:13:26
13  */
14 public class Heap extends MySort{
15 
16     /**
17      * 
18      * @Description 排序
19      * @param pq
20      * @author denny.zhang
21      * @date 2018年3月2日下午2:28:38
22      * @since JDK1.8
23      */
24     @SuppressWarnings("rawtypes")
25     public static void sort(Comparable[] pq){
26         int n = pq.length-1;//這樣數組下標好算一點。原來下標0~6==>1~6
27         //1.構造有序堆(父節點大於等於子節點),從k=n/2--》1
28         for (int k = n/2; k >= 1; k--)
29             sink(pq, k, n);
30         //2.下沉排序,升序排序
31         while (n > 1) {
32             exch(pq, 1, n--);//交換根節點和首節點,後n--交換完一次就剔除最後那個已排序的元素
33             sink(pq, 1, n);//修復有序堆
34         }
35     }
36     
37     /**
38      * 
39      * @Description 從上至下的堆有序化的實現
40      * @param pq 
41      * @param k 根節點
42      * @param n 堆長度
43      * @author denny.zhang
44      * @date 2018年3月2日上午10:28:36
45      * @since JDK1.8
46      */
47     @SuppressWarnings("rawtypes")
48     private static void sink(Comparable[] pq, int k, int n) {
49         while (2*k <= n) {
50             int j = 2*k;//左子節點下標
51             if (j < n && less(pq[j], pq[j+1])) j++;//找到子節點大值(pq[j]若是小於pq[j+1]就j++一直找,一直到大於等於的j),即pq[j]爲左右子節點最大值
52             if (!less(pq[k], pq[j])) break;//若是父節點大於等於子節點大值,則堆有序,退出當前循環
53             exch(pq, k, j);//不然交換根節點和子節點大值
54             k = j;//跟節點下標變爲j,即下沉到j
55         }
56     }
57     
58     public static void main(String[] args) {
59         String[] a = new String[]{"0","d","c","b","e","a"};//第一個元素空着不用排序,這樣數組下標好算一點。排序結果:0 a b c d e 
60         Heap.sort(a);
61         show(a);
62     }
63 }

分析:

時間複雜度:O(NlogN):1.恢復堆:因爲每次從新恢復堆的時間複雜度爲O(logN),共N - 1次從新恢復堆操做;2.建堆操做:從len/2到0處一直調用調整堆的過程,至關於o(h1)+o(h2)…+o(hlen/2) 其中h表示節點的深度,len/2表示節點的個數,這是一個求和的過程(具體須要公式推導這裏再也不拓展),結果是O(n)。二次操做時間相加=(N-1)logN+N=O(NlogN)。

空間複雜度:O(1)

是否穩定:否

 

2.8 計數排序

思想:

利用數組下標有序,新構造一個bucket[max-min+1],計數後,把新數組的下標(與min的差值)反向賦值給原數組。
爲何要求min? 避免沒必要要的空間浪費。好比90~99,若是開闢99+1=100,浪費太多空間了。如今只須要99-90+1=10的空間

注:升級版能夠保證穩定性(相同元素值排序先後相對位置保持不變),這裏就再也不多寫。

動態圖:

源碼:

 1 package study.algorithm.sorting;
 2 
 3 import study.algorithm.sorting.base.MySort;
 4 
 5 /**
 6  * @author denny
 7  * @Description 計數排序,利用數組下標有序,新構造一個bucket[max-min+1],計數後,把新數組的下標反向賦值給原數組。
 8  * 爲何要求min? 避免沒必要要的空間浪費。好比90~99,若是開闢99+1=100,浪費太多空間了。如今只須要99-90+1=10的空間
 9  * 時間複雜度:O[n+k],k = max-min+1
10  * 空間複雜度:O[n+k]
11  * @date 2019/7/8 上午10:21
12  */
13 public class CountingSort extends MySort {
14 
15     public static void sort(Integer[] b) {
16         // 1.遍歷一遍,求最大值,最小值
17         int max = b[0], min = b[0];
18         for (int i : b) {
19             if (i > max) {
20                 max = i;
21             }
22             if (i < min) {
23                 min = i;
24             }
25         }
26         int k = max - min + 1;
27         // 構造長度爲max+1的數組bucket
28         int[] bucket = new int[k];
29 
30         // 2.遍歷一遍b[], 以b元素-min值做爲下標,統計出現次數賦值給bucket[]
31         for (int value : b) {
32             bucket[value-min]++;
33         }
34 
35         int sortedIndex = 0;
36         // 3.遍歷一遍bucket[],反過來給b賦值,j=元素與最小值差值
37         for (int j = 0; j < k; j++) {
38             // 重複元素賦值,bucket[j]元素重複出現次數,bucket的下標是b的元素值
39             while (bucket[j] > 0) {
40                 // bucket的下標是b的元素值,j+min=元素值
41                 b[sortedIndex++] = j+min;
42                 bucket[j]--;
43             }
44         }
45 
46     }
47 
48     public static void main(String[] args) {
49         CountingSort.sort(b);
50         show(b);
51     }
52 }

分析:

計數排序是一種犧牲空間換取時間的排序算法。當輸入的元素是 n 個 0到 k 之間的整數時,其排序速度快於任何比較排序算法。當k不是很大而且序列比較集中時,計數排序表現良好。

時間複雜度:1.遍歷一遍b[n],求最大值最小值=O(n) 2.遍歷一遍b[n],給新數組賦值=O(n) 3.遍歷一遍bucket(k=max-min+1)=O(k)    O(2n+k),去掉係數=O(n+k)

空間複雜度:構造了額外空間int[] bucket = new int[k], 因此爲O(k)。其中k=max-min+1

2.9 桶排序

思想:

桶排序是計數排序的升級版。它利用了函數的映射關係,高效與否的關鍵就在於這個映射函數的肯定。

  • 1.求原數組max、min。
  • 2.把原數組元素分發到有限數量個桶中。(桶個數可自定義,映射關係例如:Math.floor(arr[i] - min)/ bucketSize
  • 3.桶內元素排序(可自選排序算法)。
  • 4.遍歷全部桶,把桶內元素順序賦值給原數組。

圖示:

源碼:

 1 package study.algorithm.sorting.intSort;
 2 
 3 import study.algorithm.sorting.base.MyIntSort;
 4 
 5 import java.util.Arrays;
 6 
 7 /**
 8  * @Description 桶排序
 9  * @author denny
10  * @date 2019/7/9 下午4:03
11  */
12 public class BucketSort extends MyIntSort {
13 
14     public static void sort(int[] arr) {
15         // 每一個桶能存儲5個元素
16         bucketSort(arr, 5);
17     }
18 
19     /**
20      * 桶排序
21      * @param arr 須要排序的數組
22      * @param bucketSize 每一個桶的容量
23      */
24     public static void bucketSort(int[] arr, Integer bucketSize){
25         // 1.遍歷一遍,求最大值,最小值
26         int max = arr[0], min = arr[0];
27         for (int i : arr) {
28             if (i > max) {
29                 max = i;
30             }
31             if (i < min) {
32                 min = i;
33             }
34         }
35         // 桶個數
36         int bucketCount = (int) Math.floor((max - min) / bucketSize) + 1;
37         // 構造一個二維數組,行=桶個數,列=桶容量,初始爲0
38         int[][] buckets = new int[bucketCount][0];
39 
40         // 2.利用映射函數將數據分配到各個桶中
41         for (int i = 0; i < arr.length; i++) {
42             // arr[i]在哪一個桶中
43             int index = (int) Math.floor((arr[i] - min) / bucketSize);
44             // 把arr[i]追加進桶 buckets[index]
45             buckets[index] = arrAppend(buckets[index], arr[i]);
46         }
47 
48         int arrIndex = 0;
49         // 3.遍歷全部桶,賦值
50         for (int[] bucket : buckets) {
51             if (bucket.length <= 0) {
52                 continue;
53             }
54             // 對每一個桶內部元素排序,這裏使用了插入排序
55             InsertionIntSort.sort(bucket);
56             // 遍歷每一個桶中的元素,賦值給原始數組
57             for (int value : bucket) {
58                 arr[arrIndex++] = value;
59             }
60         }
61 
62 
63     }
64 
65     /**
66      * 自動擴容1位,並保存數據
67      *
68      * @param arr
69      * @param value
70      */
71     public static int[] arrAppend(int[] arr, int value) {
72         // 擴容1
73         arr = Arrays.copyOf(arr, arr.length + 1);
74         // 最後一位賦值
75         arr[arr.length - 1] = value;
76         return arr;
77     }
78 
79     public static void main(String[] args) {
80         // 桶排序
81         BucketSort.sort(a);
82         // 查看排序結果
83         show(a);
84     }
85 
86 }

分析:

時間複雜度:1.桶數量足夠大時,一次分配完事,不須要桶內部排序O(n)。2.桶數量只有一個,都在一個桶內部排序,排序算法最差是O(n^2)

空間複雜度:使用了N個桶,桶初始容量固定。每一個桶容量自動擴容,因此空間沒浪費,可是有可能存在空桶。假設空桶有k個:O(n+k)

 

2.10 基數排序

思想:

1.求最高位

2.從低位開始排序,一直到高位。每一次排序都是穩定的。

動態圖:

源碼:

 1 package study.algorithm.sorting.intSort;
 2 
 3 import study.algorithm.sorting.base.MyIntSort;
 4 
 5 /**
 6  * @Description 基數排序:從低位到高位,一遍一遍排序。
 7  * @author denny
 8  * @date 2019/7/10 上午11:40
 9  */
10 public class RadixIntSort extends MyIntSort {
11 
12     public static void sort(int[] arr) {
13         // 求最高位
14         int maxDigit = getMaxDigit(arr);
15         // 基數排序
16         radixSort(arr,maxDigit);
17 
18     }
19 
20     /**
21      * 基數排序核心方法
22      * @param arr
23      * @param maxDigit
24      */
25     private static void radixSort(int[] arr, int maxDigit) {
26         // 取模數
27         int mod = 10;
28         // 除數
29         int dev = 1;
30         // 依次是:個位有序、十位有序(十位相同的個位有序)、百位有序(百位相同的個位、十位前後有序)...
31         for (int i = 0; i < maxDigit; i++, dev *= 10, mod *= 10) {
32             // 考慮負數的狀況,這裏擴展一倍隊列數,其中 [0~(mod-1)]對應負數,[mod~(2mod-1)]對應正數 (bucket + 10),int[行][列] 行=桶 ,列=元素
33             int[][] buckets = new int[mod * 2][0];
34             // 遍歷數組arr
35             for (int j = 0; j < arr.length; j++) {
36                 // 求arr[j]在counter中的index,這裏+mod,就是爲了處理負數,例如mod=10,-1+10=9,在負數裏面是最大的。
37                 int index = ((arr[j] % mod) / dev) + mod;
38                 // 把arr[j]追加進桶buckets[index]
39                 buckets[index] = arrAppend(buckets[index], arr[j]);
40             }
41             int pos = 0;
42             // 遍歷全部桶
43             for (int[] bucket : buckets) {
44                 // 每一個桶元素賦值給arr
45                 for (int value : bucket) {
46                     arr[pos++] = value;
47                 }
48             }
49         }
50     }
51 
52     /**
53      * 求最高位數
54      * @param arr
55      * @return
56      */
57     protected static int getMaxDigit(int[] arr) {
58         // 最大值
59         int maxValue = getMaxValue(arr);
60         // 求長
61         return getNumLenght(maxValue);
62     }
63 
64     /**
65      * 求數長度
66      * @param num
67      * @return
68      */
69     protected static int getNumLenght(long num) {
70         if (num == 0) {
71             return 1;
72         }
73         int lenght = 0;
74         for (long temp = num; temp != 0; temp /= 10) {
75             lenght++;
76         }
77         return lenght;
78     }
79 
80     public static void main(String[] args) {
81         int[] a = new int[] {321,60,1,-1,21,577,-123,11,10,743,127};
82         RadixIntSort.sort(a);
83         show(a);
84     }
85 }

分析:

時間複雜度:假設d是最高位,每一位排序都須要O(n),賦值給數組要O(n),因此總=O(d*2n),固然通常來講d<<n,因此總體來講也是O(n)線性階。

空間複雜度:1.每次遍歷都構造了一個int[k][0]的二維數組,k表明桶的個數 2.每一個元素都會所有插入桶中,即O(n), 因此空間複雜度=O(n+k)。

 

3、總結

  要想吃透這些算法,特別是時間空間的複雜度。這些算法不少都有改進版,可是是否真實有效,是否值得改進,又是另外一說(例如現代計算機的cache命中機制、算法優化後難以理解且提高可能也只是適合特定的特徵入參)。看完了算法,有一個例子能夠來看看前輩們是如何使用算法的:jdk源碼Arrays.sort(),後續會寫一篇。整體就一句話,沒有最優的算法,只有最適合特定場景的算法。 最後一張表來總結下:

 

各類經常使用排序算法

類別

排序方法

時間複雜度

空間複雜度

穩定性

特色

最好

平均

最壞

輔助存儲

 

 

插入

排序

直接插入

O(N)

O(N2)

O(N2)

O(1)

穩定

 

希爾排序

O(N)

O(Ns)

O(N2)

O(1)

不穩定

 希爾排序的平均時間複雜度並未證實,和遞增序列有關

選擇

排序

直接選擇

O(N)

O(N2)

O(N2)

O(1)

不穩定

 

堆排序

O(N*log2N)

O(N*log2N)

O(N*log2N)

O(1)

不穩定

 實際效果並不如快排好!!!

交換

排序

冒泡排序

O(N)

O(N2)

O(N2)

O(1)

穩定

一、冒泡排序是一種用時間換空間的排序方法,n小時好
二、最壞狀況是把順序的排列變成逆序,或者把逆序的數列變成順序,最差時間複雜度O(N^2)只是表示其操做次數的數量級
三、最好的狀況是數據原本就有序,複雜度爲O(n)

快速排序

O(N*log2N)

O(N*log2N) 

O(N2)

O(log2n)~O(n) 

不穩定

一、n大時好,快速排序比較佔用內存,內存隨n的增大而增大,但倒是效率高不穩定的排序算法。
二、劃分以後一邊是一個,一邊是n-1個,
這種極端狀況的時間複雜度就是O(N^2),在排序前手動隨機排序便可避免。
三、最好的狀況是每次都能均勻的劃分序列,O(N*log2N)

比較排序中實際最快的排序了,緣由:

1.內循環中指令不多,且能利用緩存(具備更好的引用局部性:下一個要訪問的對象一般與您剛剛查看的對象在內存中很接近。)

2.有優化方案:小數組切換爲插入排序效果更好;大量重複元素時使用三向排序可將排序時間從現行對數級別下降到線性級別。

歸併排序

O(N*log2N) 

O(N*log2N) 

O(N*log2N) 

O(n)

穩定

一、n大時好,歸併比較佔用內存,內存隨n的增大而增大,但倒是效率高且穩定的排序算法。

基數排序

O(2n)

O(d*2n)

O(d*2n)

O(n+k)

穩定

 當d=1,即都是個位數,最快O(2n)

計數排序

O(n+k)

O(n+k)

O(n+k)

O(k)

穩定

k=max-min+1

桶排序

O(n)

O(n+k)

O(n^2)

O(n+k)

穩定

時間複雜度:

1.桶數量足夠大時,一次分配完事,不須要桶內部排序O(n)。

2.桶數量只有一個,都在一個桶內部排序,排序算法最差是O(n^2)

 

=============

《算法第四版》

相關文章
相關標籤/搜索