這是三種線性時間復雜度的排序算法,它們是用運算而不是比較來確定排序順序的
一、基數排序
1.簡介
它一種與其他排序算法完全不同的排序方法,其他的排序算法都是通過關鍵字之間的比較和移動來完成的,而它是采用一種多關鍵字的思想。
多關鍵字的思想:給定一組數據,我可以先按個位的大小對所有數進行排序,然后再按十位進行排序,一直到最高位,這樣就可以使整組數據變得有效,這樣從最低位開始的方法稱為最低位優先
2.圖解過程
經過一次放置和回收,得到的序列已經按個位有序了,接下來按照次低位再次進行放置和回收。
由此可以看出,如果一組序列中最大的數為兩位數,則需要兩次的分配和收集,整個分配收集的次數與最大數的位數有關
基數排序需要兩個輔助空間,一個是0~9號桶,另一個是計算定位的數組,定位數組是干什么的呢?就是記錄每個桶中的數據待會要放回原數組的哪個位置。
def radixSort(number: Array[Int], d: Int): Unit = { //d表示最大的數有多少位 var k = 0 var n, m = 1 //控制鍵值排序依據在哪一位 val temp = Array.ofDim[Int](10, number.length) //數組的第一維表示可能的余數0-9 val order = new Array[Int](10) //數組orderp[i]用來表示該位是i的數的個數 while (m <= d) { for (i <- number.indices) { val lsd = (number(i) / n) % 10 temp(lsd)(order(lsd)) = number(i) order(lsd) += 1 } var i = 0 while (i < 10) { if (order(i) != 0) { var j = 0 while (j < order(i)) { number(k) = temp(i)(j) k += 1 j += 1 } } order(i) = 0 i += 1 } n *= 10 k = 0 m += 1 } }
基數排序的時間復雜度可以理解為O(d*n),d為序列中最大的位數,適用於n值很大,但是關鍵字較小的序列。
二、桶排序
1.概念
桶排序將[0,1)區間划分為n個相同的大小的子區間,這些子區間被稱為桶。然后將n個輸入元素分別放入各自的桶中。因為輸入時均勻獨立的,所以一般不會有很多數同時落在一個桶中的情況。這樣,我們想對各個桶中的數據進行排序,然后遍歷每個桶,按照次序把各個桶中的元素列出來即可
2.特性說明
1》. 桶排序的時間復雜度通常是O(N+N*logM),其中,N表示桶的個數,M表示桶內元素的個數(這里,M取的是一個大概的平均數,這也說明,為何桶內的元素盡量不要出現有的很多,有的很少這種分布不均的事情,分布不均的話,算法的性能優勢就不能最大發揮)。
2》. 桶排序是穩定的(是可以做到平衡排序的)。
3》. 桶排序,在內存方面消耗是比較大的,可以說其時間性能優勢是由犧牲空間換來的。
桶排序,在大數據量的情況下排序,比快速排序還要快。若待排序的數據元素個數比較少,桶排序的優勢就不是那么明顯了,因為桶排序就是基於分而治之的策略,可以將數據進行分布式排序,充分發揮並行計算的優勢。
3.步驟
1》.找出待排序數組中的最大值max、最小值min
2》.我們使用 動態數組ArrayList 作為桶,桶里放的元素也用 ArrayList 存儲。桶的數量為(max-min)/arr.length+1
3》.遍歷數組 arr,計算每個元素 arr[i] 放的桶
4》.每個桶各自排序
5》.遍歷桶數組,把排序好的元素放進輸出數組
4.實現
def bucketsort(inputData: ArrayBuffer[Int], max: Int): ArrayBuffer[Int] = { var buckets = new Array[Int](max) for (i <- inputData.indices) //計數 buckets(inputData(i)) = buckets(inputData(i)) + 1 var j = 0 for (i <- 0 until max) while (buckets(i) > 0) { inputData(j) = i j = j + 1 buckets(i) = buckets(i) - 1 } buckets = null inputData }
三、計數排序
1.優勢
計數排序是桶排序的一種特殊情況,可以把計數排序當成每個桶里只有一個元素的情況,它是一個非基於比較的排序算法,它的優勢在於在對一定范圍內的整數排序時,它的復雜度為Ο(n+k)(其中k是整數的范圍),快於任何比較排序算法。
2.思想
對於一個輸入數組中的一個元素x,如果這個數組中比x小的元素有n個,那么我們就可以直接把x放到(n+1)的位置上。這就是計數排序的基本思想,類似於哈希表中的直接定址法,在給定的一組序列中,先找出該序列中的最大值和最小值,從而確定需要開辟多大的輔助空間,每一個數在對應的輔助空間中都有唯一的下標。
基於這個思想,計數排序的一個主要問題就是如何統計數組中元素的個數。再加上輸入數組中的元素都是0-k區間的一個整數這個條件,那么就可以通過另外一個數組的地址表示輸入元素的值,數組的值表示元素個數的方法來進行統計。
下面給出統計數組元素都是0-k區間的整數的數組中各個元素個數的方法。
- 找出序列中最大值和最小值,開辟Max-Min+1的輔助空間
- 最小的數對應下標為0的位置,遇到一個數就給對應下標處的值+1,。
- 遍歷一遍輔助空間,就可以得到有序的一組序列
3.算法分析:
計數排序是一種以空間換時間的排序算法,並且只適用於待排序列中所有的數較為集中時,比如一組序列中的數據為0 1 2 3 4 999;就得開辟1000個輔助空間。
時間復雜度
計數排序的時間度理論為O(n+k),其中k為序列中數的范圍。
不過當O(k)>O(n*log(n))的時候其效率反而不如基於比較的排序(基於比較的排序的時間復雜度在理論上的下限是O(n*log(n)), 如歸並排序,堆排序)
4.代碼實現
def Countingsort(inputData: ArrayBuffer[Int], k: Int): Array[Int] = { //k表示有所輸入數字都介於0到k之間 val temp = new Array[Int](k) // 臨時存儲區 val outdata = new Array[Int](inputData.length) val len = temp.length for (i <- 0 until len) { // 初始化 temp(i) = 0 } for (i <- inputData.indices) { temp(inputData(i)) = temp(inputData(i)) + 1 } for (i <- 1 until len) { temp(i) = temp(i) + temp(i - 1) } // 把輸入數組中的元素放在輸出數組中對應的位置上 var n = inputData.length - 1 while (n >= 0) { // 從后往前遍歷 outdata(temp(inputData(n)) - 1) = inputData(n) temp(inputData(n)) = temp(inputData(n)) - 1 n = n - 1 } outdata }