1. 程式人生 > >常用排序算法(五)基數排序、桶排序以及計數排序

常用排序算法(五)基數排序、桶排序以及計數排序

同時 通過 特性 true 線性 大數 收集 只有一個 input

這是三種線性時間復雜度的排序算法,它們是用運算而不是比較來確定排序順序的

一、基數排序

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區間的整數的數組中各個元素個數的方法。

  1. 找出序列中最大值和最小值,開辟Max-Min+1的輔助空間
  2. 最小的數對應下標為0的位置,遇到一個數就給對應下標處的值+1,。
  3. 遍歷一遍輔助空間,就可以得到有序的一組序列

技術分享圖片

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
  }

常用排序算法(五)基數排序、桶排序以及計數排序