在前面的章節裡,我們知道歸併排序是一種高效的排序演算法,在任何情況下時間複雜度都為
它需要用額外的記憶體空間來暫時儲存歸併過程中的元素,因此我們可以認為歸併排序是以犧牲一部分記憶體空間為代價來獲得時間的高效性。相反,如果記憶體空間有限,我們就必須以犧牲時間為代價來保證空間不被過度使用。像上面所說的那樣,在設計乙個演算法的過程中同時考慮時間複雜度和空間複雜度,並且在這兩者中找到乙個平衡點的過程我們把它稱作時空權衡(time and space trade-off)。
在通常情況下,我們都認為時間更寶貴,而空間相對廉價。因此在大多數情況下,我們都是以犧牲空間的方式來減少執行時間。
計數排序(counting sort)就是一種犧牲記憶體空間來換取低時間複雜度的排序演算法,同時它也是一種不基於比較的演算法。這裡的不基於比較指的是陣列元素之間不存在比較大小的排序演算法,我們知道,用分治法來解決排序問題最快也只能使演算法的時間複雜度接近
基於比較的時間複雜度存在下界
下面我們就來介紹一種不基於比較的排序演算法:計數排序。我們用一種通俗方式來理解這個過程過程。如下圖所示,假設我們用不同大小的小球來表示每乙個陣列元素的值,我們的目標是使小球從小到大以次排列。
首先我們需要知道最大的球和最小的球分別對應的元素,這裡最大的對應
,最小的對應
然後我們需要用
個計數器來分別統計
到
之間每種小球的個數
(假設
到 之間的小球對應的元素均為整數),下圖中我們用帶有標記的桶來表示。
接下來,我們遍歷所有的小球,將每個值為
的小球放入到第
個桶中,比方說第乙個小球的值為
,那麼我們就把它放到標號為
,也就是從左往右第
個桶中,放入後計數器加
最後,我們從左往右依次將每個桶裡的小球取出,每取出乙個小球,對應桶的計數器減
,直到計數器為,將所有桶內的小球都取出後,小球就是從小到大排列了。
我們用乙個動畫來完整地看一看這個過程。
**實現:
def counting_sort(array):
largest = max(array); smallest = min(array) # 獲取最大,最小值
counter = [0 for i in range(largest-smallest+1)] # 用於統計個數的空陣列
idx = 0 # 桶內索引值
for i in range(len(array)):
counter[array[i]-smallest] += 1 # 統計每個元素出現的次數
for j in range(len(counter)):
while counter[j] > 0:
array[idx] = j + smallest # 取出元素
idx += 1
counter[j] -= 1
return array
整個過程需要遍歷兩次陣列,一次是遍歷長度為的陣列,另一次是從計數器(假設有個計數器)中遍歷
,因此時間複雜度為
。而在計數排序的過程中
用到了長度為的額外陣列
,故空間複雜度為
。我們可以看到計數排序突破了基於比較的排序演算法效率的下界,達到了線性的複雜度,是到目前為止我們接觸到的最快的演算法。我們還是可以通過執行時間來間接反應這一結論,大家可以將下面的結果和前面複雜度為
的排序演算法進行對比。
>>> python "counting sort.py"→本節全部**←>>> 平均比較次數:0
← 2-3 樹 | 演算法與複雜度zhuanlan.zhihu.com
→ 字串匹配(tst)| 演算法與複雜度zhuanlan.zhihu.com
四 歸併排序 非比較排序 歸併排序 計數排序
基本思想 歸併排序 merge sort 是建立在歸併操作上的一種有效的排序演算法,該演算法是採用分治法 divide and conquer 的乙個非常典型的應用。將已有序的子串行合併,得到完全有序的序列 即先使每個子串行有 序,再使子串行段間有序。若將兩個有序表合併成乙個有序表,稱為二路歸併。歸...
排序演算法 歸併排序 計數排序
1.歸併排序 將待排序序列分成兩個長度相同的子串行,對每個子串行進行排序,直至子串行剩餘乙個數,在將其合併成乙個序列 具體步驟 分組 將待排序序列一分為2,在將子串行進行劃分,直至子串行只有乙個元素 歸併 將每個子串行進行排序,將排好序的兩個子串行進行合併 演算法分析 void merge data...
非比較排序 歸併排序
一 歸併排序的思想 歸併排序利用了分冶的思想,將一塊待排序的區間,取中間位置,分成兩段區間 然後通過不斷的遞迴,直到每段區間只有乙個數字,這時我們就認為該段區間有序,然後將這相鄰的兩段有序區間合併為乙個有序區間 這樣通過遞迴先 陣列,然後再合併陣列,就完成了歸併排序 二 實現 include inc...