前言
我們在之前的排序算法文章中,介紹了六種基於比較的排序算法:選擇排序、冒泡排序、插入排序、歸並排序、快速排序和堆排序,現在我們來總結一下每個算法對應的時間復雜度、空間復雜度以及穩定性
排序比較圖
1. 穩定性
穩定性的意思就是對於兩個值相等的元素,在排完序后,它們的相對位置沒有發生改變,這樣的算法就是穩定的
我們先說說為什么要研究算法的穩定性:對於簡單類型的比較,譬如比較數組中的數,那么每個數值都是等效的,穩定性就沒有什么用;不過對於復雜類型,穩定性就很有用了。舉個栗子:比如一群學生,他們有兩個屬性,一個是成績,一個是班級,我想先按照他們的成績進行降序排序,之后再根據班級進行升序排序;那么排完成績后,我們再根據班級進行排序時,如果排序算法是不穩定的,會導致之前排好的成績可能會因為班級排序而打亂;而對於穩定的排序算法,同一個班級的數據的相對位置就不會發生改變,也就不會影響到之前已經排好的成績順序
講完了穩定性的用處后,我們再來逐個分析一下上述六大排序的穩定性
-
對於選擇排序,它是不穩定的排序,舉例如下:
當找到數組中的最小值 2,並與數組第一個元素進行交換后,兩個數字 3 的相對順序就發生了改變,因此選擇排序是不穩定的排序
-
對於冒泡排序,它是穩定的排序,因為當冒泡的元素只有遇到比它小的元素時,它才交換位置;遇到相同元素時並不交換位置,所以不會改變相同元素之間的相對位置
-
對於插入排序,它是穩定的排序,因為插入的元素只有遇到比它大的元素時,它才會繼續往前比較,直到找到合適的插入位置;遇到相同元素時,它就停止了比較,所以不會改變相同元素之間的相對位置
-
對於歸並排序,它是穩定的排序,我們回想一下歸並排序里的歸並過程,當左右指針所指的元素相同時,左指針所指的數先放入數組中,所以不會改變它們的相對位置,所以歸並排序也是穩定的排序(之前我們講歸並排序的應用時提到了小和問題,小和問題中當左右指針所指的元素相同時,是右指針所指的數先放入數組,所以小和問題中的排序並不是穩定的排序)
-
對於快速排序,它是不穩定的排序,舉例如下:
如下所示的排序過程中,若 5 為基准元素,左邊為小於 5 的元素,右邊為大於 5 的元素,那么當指針 i 指向第三個元素時,因為 6 比基准值 5 大,因此 6 與 5 要交換位置,兩個數字 6 的相對位置就發生了改變,因此快速排序是不穩定的排序
-
對於堆排序,它是不穩定的排序,因為堆排序使用了堆這個數據結構,它在比較的時候只和自己的父結點或孩子結點進行比較,壓根不關心穩定性的問題,不過為了理解,我們還是舉個栗子,沿用之前介紹堆排序穩定性使用的栗子
對於這樣的大根堆,如果我們使堆上浮,去掉里面最大的元素,那么最后一個元素 3 就會跑到根結點的位置,於是兩個 3 的相對位置就發生了變化,因此堆排序是不穩定的排序
根據以上的分析,我們得出一個結論:
不具備穩定性的排序有選擇排序、快速排序和堆排序;它們的特點是元素交換時會進行跳躍,這種跳躍交換就很可能導致相對位置發生變化
有穩定性的排序有冒泡排序、插入排序、歸並排序(tips: 計數排序和基數排序這兩個不基於比較的排序也是穩定的排序)
2. 時間復雜度和空間復雜度
我們現在來分析一下他們的時間復雜度和空間復雜度
-
選擇排序算是里面最 low 的一個了,它的時間復雜度為 O(N²)這樣高的級別,卻還不能保證穩定性
-
現在我們來看看下面這三個排序
- 對於歸並排序、快排以及堆排序,它們的時間復雜度均為 O(NlogN),但是經實驗表明,從常數時間來講,快排的速度是最快的,所以我們優先選擇快排(面試和筆試中快排應該也是最常考的)
- 如果你遇到空間不夠用,或者不能使用額外空間的情況,那就可以用堆排序;如果你需要穩定的排序算法,那就可以使用歸並排序
-
目前還沒有找到時間復雜度小於 O(NlogN) 的排序算法
-
目前還沒有發現時間復雜度為 O(NlogN),空間復雜度在 O(N) 以下,同時又有穩定性的算法
-
所以我們選擇排序算法時,不可能時間復雜度、空間復雜度、穩定性三者好處都占完,目前還沒找到這樣的算法,我們只能根據我們的實際情況選擇要么最快、要么額外空間使用最少、要么穩定的算法
-
工程上對排序的改進:我們可能在某些快排的代碼中,看到這一段代碼
if(l > r - 60) { // 在 arr[l...r] 插入排序 // O(N²) 小樣本量的時候跑得快 return; }
這正是一種綜合排序,充分利用了 O(NlogN) 和 O(N²) 各自的優勢,對於小樣本而言,雖然時間復雜度是 O(N²),但是它此時是常數時間,瓶頸並不明顯;這樣它就能在大樣本中運用快排的時間復雜度優勢,小樣本中利用插入排序常數時間低的優勢
歡迎來逛逛我的博客 mmimo技術小棧