據統計,廣東省人口已經突破一億人,是中國人口最多的省份,那麼我們如何快速給廣東省人口按照年齡來排序呢?大家都知道快速排序的時間複雜度是O(nlogn),那還有比快速排序更快的演算法嗎?那就跟我來一探究竟。
計數排序
計數排序的思想很簡單:當要排序 n 個資料,並且n的取值範圍並不大的時候,比如最大值是k,我們就可以把資料劃分成k個桶,每個桶內的資料值都是相同的。我們來觀察一下年齡這個資料的特徵是啥?我們假設最大的年齡是120歲,最小的年齡是0歲,那麼年齡的資料範圍是0-120歲,這就說明年齡的取值範圍不大,正好符合計數排序的資料要求。所以我們可以分成121個桶,對應年齡0~120。我們根據人的年齡,將所有廣東省的人口劃入
這個121個桶裡。桶內的資料都是年齡相同的人,所以不需要再排序。我們只需要依次掃描每個桶,將桶內的資料輸出,就實現了廣東省人口按年齡來排序,因為只是涉及到了遍歷操作,所以時間複雜度是O(n)。
我們接下來來看一下計數排序的具體實現。我們還拿年齡的例子來看,為了方便說明,我們將資料規模進行了簡化處理,假設只有7個人,年齡的範圍是0~5。這7個人的年齡放在一個陣列A中。他們分別是5,4,3,3,2,4,0。
年齡的範圍是0~5,我們使用大小為6的陣列C表示桶,其中陣列的下標對應的是年齡,C陣列儲存的是對應年齡的人數。我們遍歷一遍人口年齡資料,就可以給陣列C初始化完成。
從圖中可以看出,年齡為3的人數是2,小於3的人數有2個,所以,年齡為3的人在排序之後的有序陣列中,會儲存下標2,3的位置。
那我們如何快速計算出,每個年齡的人在有序陣列中對應的儲存位置呢?這個處理方法非常巧妙,很不容易想到。思路是這樣的:我們對陣列C順序求和,陣列C儲存的資料就變成了下面這樣子。C[k]裡儲存小於等於年齡k的人的個數。
接下來是最核心的部分了。我們從後到前依次遍歷陣列A。比如,當掃描到0時,我們從陣列C中取出下標為0的值1,也就是說,到目前為止,包括自己在內,年齡小於等於 0 的人數有 1 個,也就是說 0 是陣列 R 中的第 1 個元素(也就是陣列 R 中下標為 0 的位置)。當0放入到陣列 R 中後,小於等於 0 的元素就只剩下了 1 個了,所以相應的 C[0]要減 1,變成 0。當我們掃描完整個陣列 A 後,陣列 R 內的資料就是按照分數從小到大有序排列的了。
接下來,我們來看一下程式碼是如何實現的。
def countingSort(a,n): if n<=1: return #求出最大值 max_data=max(a) c=[0 for i in range(max_data+1)] #初始化C for j in a: c[j]+=1 ##依次累加 for i in range(1,max_data+1) : c[i] = c[i-1] + c[i] r=[0 for i in range(n)] for i in range(n-1,-1,-1): index = c[a[i]]-1 r[index] = a[i] c[a[i]]-=1 return r a=[5,4,3,3,2,4,0] r=countingSort(a,len(a)) print(r)
總結一下,計數排序只能用在資料範圍不大的場景中,如果資料範圍 k 比要排序的資料 n 大很多,就不適合用計數排序了。而且,計數排序只能給非負整數排序,如果要排序的資料是其他型別的,要將其在不改變相對大小的情況下,轉化為非負整數。
計數排序你學會了嗎?歡迎留言和我一起討論,更多有趣內容,請關注公眾號。