在博文各個排序算法的實現與優化(含動畫演示)已經將各種排序算法的實現進行了講解,本文將重點針對其適用場景進行介紹,在介紹各排序算法的使用場景之前,先來溫習一下跟時間復雜度有關的一些名詞概念:
逆序對:設 A 為一個有 n 個數字的有序集 (n>1),其中所有數字各不相同。如果存在正整數 i, j 使得 1 ≤ i < j ≤ n 而且 A[i] > A[j],則 <A[i], A[j]> 這個有序對稱為 A 的一個逆序對,也稱作逆序數。在未知的狀態下,每兩個元素為逆序對的概率為50%,所以一個長度為n的未知數組的逆序對為\((n-1)(n-2)/2*50\%\)
交換操作:交換操作分為三次賦值操作
排序算法的使用場景
下面博主便從是否為原地排序
,是否為穩定排序
,平均時間復雜度
,何時時間復雜度最低(何時最優)
,何時時間復雜度最高(何時不適合)
五個方面進行分析,這幾種排序算法到底適用與否。
冒泡排序
- 是否為原地排序:冒泡操作是在原數組之上進行的,只需要常量級的臨時空間用於交換,空間復雜度為\(O(1)\),是原地排序算法。
- 是否為穩定排序:當有相鄰的兩個元素大小相等的時候,不做交換,即可以保證其為穩定的排序算法
- 平均時間復雜度:冒泡排序交換數組的逆序對個數一樣,而比較操作為k*n次,總得時間復雜度為:\(O(n) = 3*(n-1)(n-2)/2*50\% + k*n = n^2\)
- 何時時間復雜度最低:所需排序的數組是有序的,只需一次冒泡操作,其時間復雜度為\(O(n)\)。
- 何時時間復雜度最高:所需排序的數組是倒序排列的,需要n次冒泡操作,其時間復雜度為\(O(n^2)\)。
插入排序
- 是否為原地排序:插入操作是在原數組之上進行的,只需要常量級的臨時空間用於交換,空間復雜度為\(O(1)\),是原地排序算法。
- 是否為穩定排序:將大小相等的兩個元素按原來的順序合理執行插入操作,即可以保證其為穩定的排序算法
- 平均時間復雜度:由於在數組中插入元素的平均時間復雜度為:\(O((n-1)(n-2)/2/n)=O(n)\),所以n個元素進行插入操作的時間復雜度為:\(n*O(n)=O(n^2)\)
- 何時時間復雜度最低:所需排序的數組是有序的,只需執行一次從頭到尾的插入操作,而每次插入操作都這是進行一次比較,實際上是\(n\)次比較操作,其時間復雜度為\(O(n)\)。
- 何時時間復雜度最高:所需排序的數組是倒序排列的,第n個元素需要n-1次比較操作和n次移位操作,操作次數共有:\((n-1)(n-2)/2 + n(n-1)/2 = n^2 -2n+1\)次,所以其時間復雜度為\(O(n^2)\)。
選擇排序
- 是否為原地排序:選擇排序的交換操作是在原數組之上進行的,只需要常量級的臨時空間用於交換,空間復雜度為\(O(1)\),是原地排序算法。
- 是否為穩定排序:因為每次都是選擇未排序部分的最小值與未排序部分的第一個值進行交換,這樣的交換操作無法使用大小維持原來的順序,所以不是穩定的排序算法
- 平均時間復雜度:無論數組組成如何,都需要將未排序區域全部遍歷比較,然后進行交換,所以當未排序區域長度為n時,需要n-1次比較操作和(1 或 0)次交換操作,所以時間復雜度為\(O((n-1)(n-2)/2+3*k)=O(n^2)\)
- 何時時間復雜度最低:與平均時間復雜度一樣
- 何時時間復雜度最高:與平均時間復雜度一樣
歸並排序
- 是否為原地排序:由於歸並操作需要創建n個內存空間用於存儲歸並的數組,所以不是原地排序算法,同時其為遞歸操作產生遞歸棧使用的空間復雜度為O(logn)
- 是否為穩定排序:在歸並操作中保持相同元素的原排列順序,即可以保證其為穩定的排序算法
- 平均時間復雜度:平均復雜度需要根據推導得出,由於歸並排序采取分而治之的思想,所以時間復雜度也可以進行分解為兩倍的子排序時間復雜度T(n/2)加本歸並過程的時間復雜度n即\(O(n) = 2*O(n/2)+n = 2^k * O(n/2^k) + 3 * k * n\),當子問題分解為一個元素時,\(n/2^k = 1\),即\(k=\log n\),進一步可以得出\(O(n) = n + 3 * n\log n = n \log n\)
- 何時時間復雜度最低:通過上述平均復雜度的分析可以得出,執行效率與要排序的原始數組的有序程度無關,所以其時間復雜度是非常穩定的,不管是最好情況、最壞情況,還是平均情況,時間復雜度都是O(nlogn).
- 何時時間復雜度最高:與平均時間復雜度一樣
快速排序
- 是否為原地排序:每一段的快速排序都是通過交換操作是在原數組之上進行的,只需要常量級的臨時空間用於交換,空間復雜度為\(O(1)\),是原地排序算法,但是由於快排本身為遞歸調用,所以產生的遞歸棧最好和平均情況下的空間復雜度為O(logn),最壞情況下的空間復雜度為O(n)。
- 是否為穩定排序:因為每次都要根據所選的分段值將不同的兩兩元素進行交換,這樣的交換操作無法使用大小維持原來的順序,所以不是穩定的排序算法
- 平均時間復雜度:由於快速排序的分段與分段值有關,當數組未知時,所選擇的元素與其他元素的大小關系也未知,應當是對半分段,如歸並排序一樣,同時與歸並排序不同,多了分段操作(需要n次比較操作),所以其平均時間復雜度跟歸並排序一樣為\(O(n) = n + m * n \log n = n \log n\)
- 何時時間復雜度最低:根據樹的分布特點如果不是完全二叉樹,其深度一定更高,所以對半分是最佳的狀態,最終的子問題必為1個元素,所以分解子問題本身操作時間復雜度不變為\(O(n)\),而對於分段操作中的比較操作仍然是n次,但是交換操作在有序的狀態可以省略,所以對於有序數組且每次取中位值,時間消耗最小,但時間復雜度仍然是\(O(n) = n + n\log n = n \log n\),因為m作為常值趨近於1與否不影響。
- 何時時間復雜度最高:根據樹的分布特點,當其退化為鏈表時深度最高,相應的快排需要執行n次的分段操作即\(k=n\),但是不需要交換操作(不太嚴謹,可能非常值次數的交換操作更合適),所以時間復雜度為\(O(n) = n + n * n = n^2\)
- 讀到這里,相信大部分讀者會有疑問為什么常用快速排序而不用歸並排序,這是因為可以采取很多操作避免快排中的極端情況,同時歸並排序需要O(n)的空間復雜度,快排只需要O(1)的空間復雜度用於臨時交換。值得一提的是,使歸並排序用於鏈表排序時可以避免O(n)時間復雜度這個缺點。
表格總結
排序算法 | 最壞時間復雜度 | 最好時間復雜度 | 平均時間復雜度 | 空間復雜度 | 是否穩定 |
---|---|---|---|---|---|
插入排序 | O(n2) | O(n) | O(n2) | O(1) | ✔ |
冒泡排序 | O(n2) | O(n) | O(n2) | O(1) | ✔ |
選擇排序 | O(n2) | O(n2) | O(n2) | O(1) | ✘ |
歸並排序 | O(nlogn) | O(nlogn) | O(nlogn) | O(n) | ✔ |
快速排序 | O(n2) | O(nlogn) | O(nlogn) | O(logn) | ✘ |