分析方法
執行效率
對於排序演算法執行效率的分析,不僅僅只是簡簡單單的一個時間複雜度。
還需要從以下方面進行分析:
- 最好情況、最壞情況、平均情況時間複雜度。對於排序演算法來說,有序度不同的資料,對於排序的執行時間有一定的影響,從多個方面分析時間複雜度會更加準確
- 時間複雜度的係數、常數、低階。在實際開發中,大多是對一些規模較小的資料進行排序,實際執行速度是非常快的,這時候也可以把係數、常數、低階考慮進來
- 比較次數或交換(移動)次數。常見的排序演算法都是基於比較的,這時候會涉及到元素比較大小和元素交換或移動,這時候比較次數和交換次數也會影響到執行效率
記憶體消耗
在演算法中,記憶體消耗基本上通過空間複雜度來衡量。
但是,在排序演算法中,會有一個新的概念用來衡量記憶體消耗,即原地排序。原地排序演算法特指不需要另外空間儲存的排序演算法,空間複雜度能達到 \(O(1)\)。
穩定性
針對排序演算法,還有穩定性這樣一個重要的度量指標。
這個概念是指,如果待排序的序列中存在值相等的元素,經過排序之後,相等元素之間原有的先後順序不變。
常見排序演算法
常見的排序演算法有很多,這裡列出 10 種排序演算法作比較。而這 10 種常見的排序演算法會根據是否進行比較分為兩種:
- 比較類排序:氣泡排序、選擇排序、插入排序、希爾排序、堆排序、快速排序、歸併排序
- 非比較排序:計數排序、桶排序、基數排序
排序演算法 | 平均時間複雜度 | 最壞時間複雜度 | 最好時間複雜度 | 空間複雜度 | 穩定性 |
---|---|---|---|---|---|
氣泡排序 | \(O(n^2)\) | \(O(n^2)\) | \(O(n)\) | \(O(1)\) | 穩定 |
選擇排序 | \(O(n^2)\) | \(O(n^2)\) | \(O(n^2)\) | \(O(1)\) | 不穩定 |
插入排序 | \(O(n^2)\) | \(O(n^2)\) | \(O(n)\) | \(O(1)\) | 穩定 |
希爾排序 | \(O(n^{1.3 \sim 2})\) | \(O(n^2)\) | \(O(n)\) | \(O(1)\) | 不穩定 |
堆排序 | \(O(n\log_2n)\) | \(O(n\log_2n)\) | \(O(n\log_2n)\) | \(O(1)\) | 不穩定 |
快速排序 | \(O(n\log_2n)\) | \(O(n^2)\) | \(O(n\log_2n)\) | \(O(n\log_2n)\) | 不穩定 |
歸併排序 | \(O(n\log_2n)\) | \(O(n\log_2n)\) | \(O(n\log_2n)\) | \(O(n)\) | 穩定 |
計數排序 | \(O(n+k)\) | \(O(n+k)\) | \(O(n+k)\) | \(O(n+k)\) | 穩定 |
桶排序 | \(O(n+k)\) | \(O(n^2)\) | \(O(n)\) | \(O(n+k)\) | 穩定 |
基數排序 | \(O(n \times k)\) | \(O(n \times k)\) | \(O(n \times k)\) | \(O(n+k)\) | 穩定 |
如何選擇合適的排序演算法?
選擇依據
在實際開發的時候,並不是時間複雜度低的排序演算法就能適用於任何場景。
比如說,計數排序演算法適用於較集中的小範圍整數序列,桶排序演算法適用於容易劃分為桶的均勻整數序列,計數排序適用於可劃分為具有遞進關係的“位”的整數序列。
一般來說,對於小規模的資料進行排序時,可以選擇時間複雜度是 \(O(n^2)\) 的排序演算法;對於大規模的資料進行排序時,需要選擇時間複雜度是 \(O(n \log n)\) 的排序演算法;對於非比較類排序演算法,主要應用於特定的場景。
這樣選擇的原因是,時間複雜度為 \(O(n^2)\) 的排序演算法會比 \(O(n \log n)\) 的排序演算法的效率低,一般指的都是時間複雜度在沒有係數、常數、低階介入比較的情況,當真正使用的時候,這些是不可避免的。
因此,在實際使用時,有些時候 \(O(n^2)\) 的排序演算法也會比 \(O(n \log n)\) 的排序演算法的效率高。
通常,為了兼顧任意規模資料的排序,在一個方法中會使用到多種排序演算法。
排序實現 - Glibc
例如 Glibc 的 qsort()
函式,資料量較小時會優先使用歸併排序演算法來對輸入資料排序,當資料量比較大時,qsort()
會改用快速排序演算法來排序。
在歸併排序中,每個元素小於 32 時,會直接進行歸併排序;當有元素大於 32 時,則先將元素的指標拷貝到臨時空間,再使用歸併排序對指標進行排序。
在快排過程中,元素個數小於等於 4 個時候,會使用插入排序代替快速排序。
排序實現 - Java
例如 JDK 8 中 Arrays.sort()
的底層實現,也根據不同的情況使用到多種排序演算法。
對於元素個數小於 47 的序列,使用的是插入排序演算法;對於元素個數大於 47 而小於 286 的序列,使用的則是快速排序演算法。
而對於超過 286 個元素的序列,還會判斷這個序列是否結構化(資料是否時升時降),結構化的序列會使用歸併排序演算法,而非結構化的序列仍然會使用快速排序演算法。