據統計,廣東省人口已經突破一億人,是中國人口最多的省份,那么我們如何快速給廣東省人口按照年齡來排序呢?大家都知道快速排序的時間復雜度是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 大很多,就不適合用計數排序了。而且,計數排序只能給非負整數排序,如果要排序的數據是其他類型的,要將其在不改變相對大小的情況下,轉化為非負整數。
計數排序你學會了嗎?歡迎留言和我一起討論,更多有趣內容,請關注公眾號。