面試官:冒泡、插入、選擇這三種常見的排序算法你瞭解?爲什麼插入排序更受歡迎?

排序算法應該是我們每個人剛開始學習時都會接觸的,應該是大部分人學習的第一個算法。常見的排序算法非常多,比如猴子排序、睡眠排序、麪條排序等。這裏我們只學習下最常見、最經典的排序算法。

按照算法的時間複雜度,可以分爲以下三類,我們對着分類去學習,更能加深記憶和對算法的掌握。

如何分析一個“排序算法”?

排序算法的執行效率

  1. 最好情況、最壞情況、平均情況時間複雜度
  2. 時間複雜度的係數、常數 、低階
  3. 比較次數和交換(或移動)次數

排序算法的內存消耗

算法的內存消耗可以通過空間複雜度來衡量,排序算法也不例外。不過,針對排序算法的空間複雜度,我們還引入了一個新的概念,原地排序(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 領取,更多內容陸續奉上

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章