對於排序演演算法執行效率的分析,不僅僅只是簡簡單單的一個時間複雜度。
還需要從以下方面進行分析:
在演演算法中,記憶體消耗基本上通過空間複雜度來衡量。
但是,在排序演演算法中,會有一個新的概念用來衡量記憶體消耗,即原地排序。原地排序演演算法特指不需要另外空間儲存的排序演演算法,空間複雜度能達到 \(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 的 qsort()
函數,資料量較小時會優先使用歸併排序演演算法來對輸入資料排序,當資料量比較大時,qsort()
會改用快速排序演演算法來排序。
在歸併排序中,每個元素小於 32 時,會直接進行歸併排序;當有元素大於 32 時,則先將元素的指標拷貝到臨時空間,再使用歸併排序對指標進行排序。
在快排過程中,元素個數小於等於 4 個時候,會使用插入排序代替快速排序。
例如 JDK 8 中 Arrays.sort()
的底層實現,也根據不同的情況使用到多種排序演演算法。
對於元素個數小於 47 的序列,使用的是插入排序演演算法;對於元素個數大於 47 而小於 286 的序列,使用的則是快速排序演演算法。
而對於超過 286 個元素的序列,還會判斷這個序列是否結構化(資料是否時升時降),結構化的序列會使用歸併排序演演算法,而非結構化的序列仍然會使用快速排序演演算法。