排序算法應該是我們每個人剛開始學習時都會接觸的,應該是大部分人學習的第一個算法。常見的排序算法非常多,比如猴子排序、睡眠排序、麪條排序等。這裏我們只學習下最常見、最經典的排序算法。
按照算法的時間複雜度,可以分爲以下三類,我們對着分類去學習,更能加深記憶和對算法的掌握。
如何分析一個“排序算法”?
排序算法的執行效率
- 最好情況、最壞情況、平均情況時間複雜度
- 時間複雜度的係數、常數 、低階
- 比較次數和交換(或移動)次數
排序算法的內存消耗
算法的內存消耗可以通過空間複雜度來衡量,排序算法也不例外。不過,針對排序算法的空間複雜度,我們還引入了一個新的概念,原地排序(Sorted in place)。原地排序算法,就是特指空間複雜度是 O(1) 的排序算法。
排序算法的穩定性
僅僅用執行效率和內存消耗來衡量排序算法的好壞是不夠的。針對排序算法,我們還有一個重要的度量指標,穩定性。這個概念是說,如果待排序的序列中存在值相等的元素,經過排序之後,相等元素之間原有的先後順序不變。
我通過一個例子來解釋一下。比如我們有一組數據 2,9,3,4,8,3,按照大小排序之後就是 2,3,3,4,8,9。這組數據裏有兩個 3。經過某種排序算法排序之後,如果兩個 3 的前後順序沒有改變,那我們就把這種排序算法叫作穩定的排序算法;如果前後順序發生變化,那對應的排序算法就叫作不穩定的排序算法。
冒泡排序(Bubble Sort)
冒泡排序只會操作相鄰的兩個數據。每次冒泡操作都會對相鄰的兩個元素進行比較,看是否滿足大小關係要求。如果不滿足就讓它倆互換。一次冒泡會讓至少一個元素移動到它應該在的位置,重複 n 次,就完成了 n 個數據的排序工作。
我用一個例子,帶你看下冒泡排序的整個過程。我們要對一組數據 4,5,6,3,2,1,從小到大進行排序。第一次冒泡操作的詳細過程就是這樣:
可以看出,經過一次冒泡操作之後,6 這個元素已經存儲在正確的位置上。要想完成所有數據的排序,我們只要進行 6 次這樣的冒泡操作就行了。
實際上,剛講的冒泡過程還可以優化。當某次冒泡操作已經沒有數據交換時,說明已經達到完全有序,不用再繼續執行後續的冒泡操作。我這裏還有另外一個例子,這裏面給 6 個元素排序,只需要 4 次冒泡操作就可以了。
冒泡排序算法的原理比較容易理解,具體的代碼我貼到下面,你可以結合着代碼來看我前面講的原理。
插入排序
首先,我們將數組中的數據分爲兩個區間,已排序區間和未排序區間。初始已排序區間只有一個元素,就是數組的第一個元素。插入算法的核心思想是取未排序區間中的元素,在已排序區間中找到合適的插入位置將其插入,並保證已排序區間數據一直有序。重複這個過程,直到未排序區間中元素爲空,算法結束.
如圖所示,要排序的數據是 4,5,6,1,3,2,其中左側爲已排序區間,右側是未排序區間。
插入排序也包含兩種操作,一種是元素的比較,一種是元素的移動。當我們需要將一個數據 a 插入到已排序區間時,需要拿 a 與已排序區間的元素依次比較大小,找到合適的插入位置。找到插入點之後,我們還需要將插入點之後的元素順序往後移動一位,這樣才能騰出位置給元素 a 插入。
代碼部分也不難,如下所示:
注意,這裏是從尾到頭遍歷已經有序的數據。
選擇排序
選擇排序算法的實現思路有點類似插入排序,也分已排序區間和未排序區間。但是選擇排序每次會從未排序區間中找到最小的元素,將其放到已排序區間的末尾。
也比較簡單,直接看代碼:
擴展
冒泡排序和插入排序的時間複雜度都是 O(n2),都是原地排序算法,爲什麼插入排序要比冒泡排序更受歡迎呢?
解答:從代碼實現上來看,冒泡排序的數據交換要比插入排序的數據移動要複雜,冒泡排序需要 3 個賦值操作,而插入排序只需要 1 個。我們來看這段操作:
冒泡排序中數據的交換操作:
if (a[j] > a[j+1]) { // 交換
int tmp = a[j];
a[j] = a[j+1];
a[j+1] = tmp;
flag = true;
}
插入排序中數據的移動操作:
if (a[j] > value) {
a[j+1] = a[j]; // 數據移動
} else {
break;
}
我們把執行一個賦值語句的時間粗略地計爲單位時間(unit_time),然後分別用冒泡排序和插入排序對同一個逆序度是 K 的數組進行排序。用冒泡排序,需要 K 次交換操作,每次需要 3 個賦值語句,所以交換操作總耗時就是 3*K 單位時間。而插入排序中數據移動操作只需要 K 個單位時間。
這個只是我們非常理論的分析,爲了實驗,針對上面的冒泡排序和插入排序的 Java 代碼,我寫了一個性能對比測試程序,隨機生成 10000 個數組,每個數組中包含 200 個數據,然後在我的機器上分別用冒泡和插入排序算法來排序,冒泡排序算法大約 555ms 才能執行完成,而插入排序只需要 115ms 左右就能搞定!
總結
要想分析、評價一個排序算法,需要從執行效率、內存消耗和穩定性三個方面來看。這三種時間複雜度是 O(n2) 的排序算法,冒泡排序、插入排序、選擇排序。
推薦閱讀
最近面試 字節、BAT,整理一份面試資料《Java 面試 BAT 通關手冊》,覆蓋了 Java 核心技術、JVM、Java 併發、SSM、微服務、數據庫、數據結構等等。獲取方式:點“在看”,關注公衆號並回復 666 領取,更多內容陸續奉上