【基數排序演演算法詳解】Java/Go/Python/JS/C不同語言實現

2023-03-05 21:00:46

說明

基數排序(RadixSort)是一種非比較型整數排序演演算法,其原理是將整數按位元數切割成不同的數位,然後按每個位數分別比較。由於整數也可以表達字串(比如名字或日期)和特定格式的浮點數,所以基數排序也不是隻能使用於整數。基數排序的發明可以追溯到1887年赫爾曼·何樂禮在列表機(Tabulation Machine)上的貢獻。

基數排序的方式可以採用LSD(Least significant digital)或MSD(Most significant digital),LSD的排序方式由鍵值的最右邊開始,而MSD則相反,由鍵值的最左邊開始。LSD使用計數排序或桶排序,MSD可以使用桶排序。由低到高(LSD)比較簡單,按位元重排即可,如果是從高往低(MSD)則不能每次重排,可以通過遞回來逐層遍歷實現。詳細請看各種不同版本的原始碼。

排序演演算法網上有很多實現,但經常有錯誤,也缺乏不同語言的比較。本系列把各種排序演演算法重新整理,用不同語言分別實現。

實現過程

  1. 將待排序數列(正整數)統一為同樣的數位長度,數位較短的補零。
  2. 每個數位單獨排序,從最低位到最高位,或從最高位到最低位。
  3. 這樣從最低到高或從高到低排序完成以後,數列就變成一個有序數列。

 

示意圖

 

 

效能分析

時間複雜度:O(k*N)
空間複雜度:O(k + N)
穩定性:穩定

 

程式碼

 

Java

class RadixSort {

  // 基數排序,基於計數排序,按數位從低到高來排序
  public static int[] countingSort(int arr[], int exponent) {
    // 基數exponent按10進位,range為10
    int range = 10;
    int[] countList = new int[range];
    int[] sortedList = new int[arr.length];

    // 設定最小值以支援負數
    int min = arr[0];
    for (int i = 0; i < arr.length; i++) {
      if (arr[i] < min) {
        min = arr[i];
      }
    }

    // 根據基數求得當前專案對應位置的數值,並給對應計數陣列位置加1
    for (int i = 0; i < arr.length; i++) {
      int item = arr[i] - min;
      // 根據exponent獲得當前位置的數位是幾,存入對應計數陣列
      int idx = (item / exponent) % range;
      countList[idx] += 1;
    }

    // 根據位置計數,後面的位數為前面的累加之和
    for (int i = 1; i < range; i++) {
      countList[i] += countList[i - 1];
    }
    System.out.println("radixSort1 countingSort countList:" + Arrays.toString(countList));

    // 根據計數陣列按順序取出排序內容
    for (int i = arr.length - 1; i >= 0; i--) {
      int item = arr[i] - min;
      int idx = (item / exponent) % range;
      // 根據計數位置得到順序
      sortedList[countList[idx] - 1] = arr[i];
      countList[idx] -= 1;
    }

    // 最後賦值給原資料
    for (int i = 0; i < arr.length; i++) {
      arr[i] = sortedList[i];
    }
    System.out.println("radixSort1 -> sortedList:" + Arrays.toString(sortedList));
    return sortedList;
  }

  // 基數排序1,按數位大小,基於計數排序實現
  public static int[] radixSort1(int arr[]) {
    int max = arr[0];
    for (int i = 0; i < arr.length; i++) {
      if (arr[i] > max) {
        max = arr[i];
      }
    }
    // 根據最大值,逐個按進位(基數)來應用排序,exponent即數位。
    for (int exponent = 1; (max / exponent) > 0; exponent *= 10) {
      countingSort(arr, exponent);
    }
    return arr;
  }
}

 

  

// 基數排序,從高到低逐位排序,遞迴方式,基於桶排序。具體步驟如下:
// 1. 找出陣列中最大的數,確定其位數。
// 2. MSD是從高位開始,依次按照位數的值將數位放入到不同桶中。
// 3. 如果桶裡的長度超過1,則通過遞迴繼續按桶排序。當桶裡的資料只有1位時新增到原列表對應位置。
// 重複步驟2和3,直到按照最高位排序完成。
class RadixSortMSD {
  static int[] radixSort(int[] arr) {
    int len = arr.length;
    // 獲取陣列最大項
    int max = arr[0];
    for (int i = 0; i < len; i++) {
      if (max < arr[i]) {
        max = arr[i];
      }
    }

    // 獲取陣列最小項
    int min = arr[0];
    for (int i = 0; i < len; i++) {
      if (min > arr[i]) {
        min = arr[i];
      }
    }

    // 獲取數位一共有幾位,減去min得到最大值,以支援負數和減少最大值
    int numberOfDigits = (int) (Math.log10(max - min) + 1);
    int exponent = (int) (Math.pow(10, numberOfDigits - 1));
    // 根據陣列最大值,自後向前逐個按數位基數(exponent)比較排序。
    return bucketSort(arr, len, exponent);
  }

  static int[] bucketSort(int[] arr, int len, int exponent) {

    System.out.println("origin arr:" + Arrays.toString(arr) + " len=" + len + " exponent:" + exponent);
    if (len <= 1 || exponent < 1) {
      return arr;
    }

    // 獲取陣列最小項
    int min = arr[0];
    for (int i = 0; i < len; i++) {
      if (min > arr[i]) {
        min = arr[i];
      }
    }

    // 位數按10遞進
    int range = 10;
    // 定義桶二維陣列,長度為10,放入0-9的數位
    int[][] buckets = new int[range][len];
    // 記錄某個桶的最新長度,以便往桶內追加資料。
    int[] bucketsCount = new int[range];
    for (int i = 0; i < len; i++) {
      int item = arr[i] - min;
      // 根據數位上的值,把資料追加到對應的桶裡,減去min是支援負數
      int bucketIdx = (item / exponent) % range;
      // 把資料按下標插入到桶裡
      int numberIndex = bucketsCount[bucketIdx];
      buckets[bucketIdx][numberIndex] = arr[i];
      bucketsCount[bucketIdx] += 1;
    }

    // 將每個桶的資料按順序逐個取出,重新賦值給原陣列
    int sortedIdx = 0;

    for (int i = 0; i < range; i++) {
      int[] bucket = buckets[i];
      int bucketLen = bucketsCount[i];
      // 如果只有一個值,則直接更新到原陣列
      if (bucketsCount[i] == 1) {
        arr[sortedIdx] = bucket[0];
        sortedIdx += 1;
      } else if (bucket.length > 0 && bucketLen > 0) {
        // 如果是陣列且記錄大於1則繼續遞迴呼叫,位數降低1位
        // 遞迴呼叫傳參時需要傳入當前子序列、子序列長度、當前分解的位數基數
        int[] sortedBucket = bucketSort(bucket, bucketLen, (int) (exponent / range));
        // 依照已排序的子序列實際長度,把各個桶裡的值按順序賦給原陣列
        for (int j = 0; j < bucketLen; j++) {
          int num = sortedBucket[j];
          arr[sortedIdx] = num;
          sortedIdx += 1;
        }
      }
    }
    System.out.println("exponent:" + exponent + " sorted arr:" + Arrays.toString(arr));
    return arr;
  }

 

Python

 

"""
基數排序LSD版,本基於桶排序。
1. 找出陣列中最大的數,確定其位數。
2. LSD是低位到高位,依次按照位數的值將數位放入到不同桶中。
3. 按照桶順序重新給陣列排序。
重複步驟2和3,直到排序完成。
"""
def radix_sort(arr):
    max_value = max(arr)  # 找出陣列中最大的數
    min_value = min(arr)  #最小值,為了支援負數
    digit = 1  # 從個位開始排序

    # 每次排序一個數位,從低到高直到排序完成
    while (max_value - min_value) // digit > 0:
        # 建立10個桶,分別對應0-9的數位值
        buckets = [[] for _ in range(10)]
        for num in arr:
            # 找出當前位數的值
            digit_num = (num - min_value) // digit % 10
            # 將數位新增到對應數位的桶中,相當於根據數位排序
            buckets[digit_num].append(num)

        print('buckets:', buckets)

        # 通過exend展開陣列,相當於逐層新增
        arr = []
        for bucket in buckets:
            arr.extend(bucket)
            # 或逐項新增
            # for item in bucket:
            #     arr.append(item)

        # 數位移動到下一位
        digit *= 10

    return arr
"""
基數排序,從高到低逐位排序,遞迴方式,基於桶排序。具體步驟如下:
1. 找出陣列中最大的數,確定其位數。
2. MSD是從高位開始,依次按照位數的值將數位放入到不同桶中。
3. 如果桶裡的長度超過1,則通過遞迴繼續按桶排序。當桶裡的資料只有1位時新增到原列表對應位置。
重複步驟2和3,直到按照最高位排序完成。
"""
# 桶排序,根據數位遞迴呼叫
def bucket_sort(arr, exponent):
    print('origin arr:', arr, 'exponent:', exponent)
    if (len(arr) <= 1 or exponent <= 0):
        return arr

    min_value = min(arr)

    radix = 10
    amount = 10

    print('prepared arr:', arr, 'exponent:', exponent)
    # 構建排序的桶二維列表
    buckets = [None] * radix

    for i in range(len(arr)):
        item = arr[i] - min_value
        # 根據數位上的值,把資料追加到對應的桶裡,減去min是支援負數
        bucketIdx = int(item / exponent) % radix
        # 填充空桶,或者提前填充為列表
        if buckets[bucketIdx] is None:
            buckets[bucketIdx] = []
        buckets[bucketIdx].append(arr[i])

    print('append to buckets:', buckets)

    # 將每個桶的資料按順序逐個取出,重新賦值給原陣列
    sortedIdx = 0
    for i in range(radix):
        bucket = buckets[i]
        if bucket is None or len(bucket) < 1:
            continue
        # 如果是陣列則繼續遞迴呼叫,位數降低1位
        sortedBucket = bucket_sort(bucket, exponent // amount)
        # 把各個桶裡的值按順序賦給原陣列
        for num in sortedBucket:
            print ('sortedIdx::', sortedIdx)
            arr[sortedIdx] = num
            print('bucket:', bucket, 'sortedBucket:', sortedBucket,
                  'sortedIdx:', sortedIdx, 'set arr:', arr)
            sortedIdx += 1

    print('exponent:', exponent, 'sorted arr:', arr)

    return arr

# 基數排序,從高到低逐位排序MSD版,基於桶排序遞迴實現
def radix_sort_msd(arr):
    # 根據最大值,逐個按進位(基數)來應用排序,從高位到低位。
    # 獲取數位的數位,這減去min_value是為了支援負數
    # exponent是最大的數位,由高到低逐位計算
    max_value = max(arr)
    min_value = min(arr)
    numberOfDigits = int(math.log10(max_value - min_value) + 1)
    exponent = math.pow(10, numberOfDigits - 1)
    return bucket_sort(arr, int(exponent))

 

Go

// 2. 基數排序LSD版,計算最小值,基於計數排序實現
func radixSort2(arr []int) []int {
  var arrLen = len(arr)
  // 基數exponent按10進位,amount為10
  var amount = 10
  var sortedList = make([]int, arrLen)
  var max = arr[0]
  for i := 0; i < arrLen; i++ {
    if arr[i] > max {
      max = arr[i]
    }
  }
  var min = arr[0]
  for i := 0; i < arrLen; i++ {
    if arr[i] < min {
      min = arr[i]
    }
  }

  // 根據基數求得當前專案對應位置的數值,並給對應計數陣列位置加1
  // 按最大值補齊數位,基數exponent按10進位
  for exponent := 1; ((max - min) / exponent) > 0; exponent *= amount {

    // 計數陣列,長度為10,0-9一共10個數位
    countList := make([]int, amount)
    // 根據基數得到當前位數,並給計數陣列對應位置加1
    for i := 0; i < arrLen; i++ {
      var item = arr[i] - min
      var idx = (item / exponent) % amount
      countList[idx] += 1
    }

    // 計數排序構建,自前往後,逐個將上一項的值存入當前項
    for i := 1; i < amount; i++ {
      countList[i] += countList[i-1]
    }

    fmt.Println("radixSort2 -> countList:", countList)

    // 根據計數陣列按順序取出排序內容
    for i := arrLen - 1; i >= 0; i-- {
      item := arr[i] - min
      var idx = (item / exponent) % amount
      sortedList[countList[idx]-1] = arr[i]
      countList[idx] -= 1
    }

    // 將新順序賦值給原陣列
    for i := 0; i < arrLen; i++ {
      arr[i] = sortedList[i]
    }
  }

  return arr
}
// 基數排序,從高到低逐位排序,遞迴方式,基於桶排序。具體步驟如下:
// 1. 找出陣列中最大的數,確定其位數。
// 2. MSD是從高位開始,依次按照位數的值將數位放入到不同桶中。
// 3. 如果桶裡的長度超過1,則通過遞迴繼續按桶排序。當桶裡的資料只有1位時新增到原列表對應位置。
// 重複步驟2和3,直到按照最高位排序完成。
func radixSortMSD(arr []int) []int {
  var amount = 10
  maxValue := max(arr)
  exponent := pow(amount, getNumberOfDigits(maxValue)-1)

  bucketSort(arr, exponent)
  return arr
}

func bucketSort(arr []int, exponent int) []int {
  fmt.Println("origin arr:", arr, "exponent: ", exponent)
  if exponent < 1 || len(arr) <= 1 {
    return arr
  }
  var amount = 10
  fmt.Println("prepared arr:", arr, "exponent: ", exponent)

  buckets := [][]int{}
  // 按數位來獲取最小值
  minValue := getMinValue(arr, exponent)

  // 增加偏移以便支援負數
  offset := 0
  if minValue < 0 {
    offset = 0 - minValue
  }

  // 填充桶二維陣列
  for i := 0; i < (amount + offset); i++ {
    buckets = append(buckets, []int{})
  }

  // 獲取陣列項指定數位的值,放入到對應桶中,桶的下標即順序
  for i, num := range arr {
    bucketIdx := getDigit(arr, i, exponent) + offset
    buckets[bucketIdx] = append(buckets[bucketIdx], num)
  }
  fmt.Println("append to buckets: ", buckets)

  sortedIdx := 0
  for _, bucket := range buckets {
    if len(bucket) <= 0 {
      continue
    }
    // 遞迴遍歷所有的桶,由裡而外逐個桶進行排序
    sortedBucket := bucketSort(bucket, exponent/amount)
    // 把各個桶裡的值按順序賦給原陣列
    for _, num := range sortedBucket {
      arr[sortedIdx] = num
      fmt.Println("bucket:", bucket, "sortedBucket: ", sortedBucket, "sortedIdx:", sortedIdx, "set arr: ", arr)
      sortedIdx += 1
    }
  }
  fmt.Println("exponent: ", exponent, "sorted arr: ", arr)

  return arr
}

// 獲取數位位數
func getNumberOfDigits(num int) int {
  numberOfDigits := 0
  for num > 0 {
    numberOfDigits += 1
    num /= 10
  }
  return numberOfDigits
}

// 獲取絕對值
func abs(value int) int {
  if value < 0 {
    return -value
  }
  return value
}

// 獲取陣列最大值
func max(arr []int) int {
  maxValue := arr[0]
  for i := 1; i < len(arr); i++ {
    if arr[i] > maxValue {
      maxValue = arr[i]
    }
  }
  return maxValue
}

// 計算數位次冪
func pow(a int, power int) int {
  result := 1
  for i := 0; i < power; i++ {
    result *= a
  }
  return result
}

// 獲取陣列項指定數位的最小值
func getMinValue(arr []int, exponent int) int {
  minValue := getDigit(arr, 0, exponent)
  for i := 1; i < len(arr); i++ {
    element := getDigit(arr, i, exponent)
    if minValue > element {
      minValue = element
    }
  }
  return minValue
}

// 獲取數位指定數位的值,超出數位補0,負數返回負數
// 如: 1024, 百位: 100 => 返回 0
// 如: -2048, 千位: 1000 => 返回 -2
func getDigit(arr []int, idx int, exponent int) int {
  element := arr[idx]
  digit := abs(element) / exponent % 10
  if element < 0 {
    return -digit
  }
  return digit
}

 

JS

// 基數排序2,從低到高逐個數位對比排序,基於桶排序,利用JS陣列展開來還原陣列
function radixSort2(arr) {

  // 倒數獲取數位指定位置的數
  function getDigit(num, position) {
    const digit = Math.floor(num / Math.pow(10, position - 1)) % 10
    return digit
  }

  // 獲取陣列最大數位的位數
  function getNumberLength(num) {
    let maxLength = 0
    while (num > 0) {
      maxLength++
      num /= 10
    }
    return maxLength
  }

  const max = Math.max.apply(null, arr)
  const min = Math.min.apply(null, arr)
  const maxLength = getNumberLength(max - min)

  for (let i = 0; i < maxLength; i++) {
    // 每個數位準備10個空陣列,用於放數位0-9
    const buckets = Array.from({
      length: 10
    }, () => [])

    // 遍歷陣列將數位上的數放入對應桶裡
    for (let j = 0, l = arr.length; j < l; j++) {
      const item = (arr[j] - min)

      // 從後往前獲取第x位置的數,通過計算的方式
      const num = getDigit(item, i + 1)
      // 當前位數如果不為空則新增到基數桶中
      if (num !== isNaN) {
        buckets[num].push((arr[j]))
      }
    }

    // 將桶逐級展開取出數位,如果支援flat則直接使用陣列的flat()
    if (buckets.flat) {
      arr = buckets.flat()
    } else {
      // 定定義陣列展開函數
      // arr = flat(buckets)
    }
  }

  return arr
}
// 基數排序,從高到低逐位排序,遞迴方式,基於桶排序。具體步驟如下:
// 1. 找出陣列中最大的數,確定其位數。
// 2. MSD是從高位開始,依次按照位數的值將數位放入到不同桶中。
// 3. 如果桶裡的長度超過1,則通過遞迴繼續按桶排序。當桶裡的資料只有1位時新增到原列表對應位置。
// 重複步驟2和3,直到按照最高位排序完成。
function radixSortMSD(arr) {

  function bucketSort(arr, exponent) {
    console.log('origin arr:', arr, 'exponent:', exponent)
    if (!arr || arr.length <= 1 || exponent < 1) {
      return arr
    }
    const min = Math.min.apply(null, arr)
    const range = 10

    // 定義桶二維陣列,長度為10,放入0-9的數位
    const buckets = []
    for (let i = 0; i < range; i++) {
      buckets[i] = []
    }
    for (let i = 0, l = arr.length; i < l; i++) {
      const item = arr[i] - min
      // 根據數位上的值,把資料追加到對應的桶裡,減去min是支援負數
      const bucketIdx = Math.floor(item / exponent % range)
      // 提前填充空桶或使用時再填充
      if (!buckets[bucketIdx]) {
        buckets[bucketIdx] = []
      }
      buckets[bucketIdx].push(arr[i])
    }

    // 將每個桶的資料按順序逐個取出,重新賦值給原陣列
    let sortedIdx = 0

    for (let i = 0; i < range; i++) {
      const bucket = buckets[i]
      if (bucket && bucket.length > 0) {
        // 如果是陣列則繼續遞迴呼叫,位數降低1位
        const sortedBucket = bucketSort(bucket, Math.floor(exponent / range))
        // 把各個桶裡的值按順序賦給原陣列
        sortedBucket.forEach(num => {
          arr[sortedIdx] = num
          sortedIdx += 1
        })
      }
    }

    return arr
  }

  const max = Math.max.apply(null, arr)
  const min = Math.min.apply(null, arr)
  // 獲取數位一共有幾位,減去min得到最大值,以支援負數和減少最大值
  const numberOfDigits = Math.floor(Math.log10(max - min) + 1)
  const exponent = Math.pow(10, numberOfDigits - 1)
  // 根據陣列最大值,自後向前逐個按數位基數(exponent)比較排序。
  return bucketSort(arr, exponent)
}

 

TS

 

class RadixSort {
  // 基數排序,基於計數排序的基礎上,按數位的每個位置來排序
  countingSort(arr: Array<number>, exponent: number) {
    const countList = Array<number>()
    const range = 10
    countList.length = range
    countList.fill(0)
    const min = Math.min.apply(null, arr)
    for (let i = 0, l = arr.length; i < l; i++) {
      const item = arr[i] - min
      // 取得數位的最後一位,並給對應計數陣列加1
      const idx = Math.floor((item / exponent) % range)
      countList[idx] += 1
    }
    console.log('countingSort countList:', countList)

    // 根據位置計數,後面的位數為前面的累加之和
    for (let i = 1; i < range; i++) {
      countList[i] += countList[i - 1]
    }

    const sortedList = Array<number>()
    // 根據計數陣列按順序取出排序內容
    for (let i = arr.length - 1; i >= 0; i--) {
      const item = arr[i] - min
      const idx = Math.floor((item / exponent) % range)
      sortedList[countList[idx] - 1] = arr[i]
      countList[idx] -= 1
    }

    // 最後賦值給原資料
    for (let i = 0; i < arr.length; i++) {
      arr[i] = sortedList[i]
    }
    return sortedList
  }

  // 基數排序LSD版,基於計數排序的基礎,支援負數,按數位的每個位置來排序
  radixSort(arr: Array<number>) {
    let sortedList = Array<number>()
    const max = Math.max.apply(null, arr)
    const min = Math.min.apply(null, arr)
    for (
      let exponent = 1;
      Math.floor((max - min) / exponent) > 0;
      exponent *= 10
    ) {
      sortedList = this.countingSort(arr, exponent)
    }
    return sortedList
  }
}

 

C

// 計數排序,根據基數按位元進行計數
void counting_sort(int arr[], int len, int exponent)
{
  int sorted_list[len];
  int range = 10;
  int count_list[range];
  // 找出最小值
  int min_value = arr[0];
  for (int i = 1; i < len; i++)
  {
    if (arr[i] < min_value)
      min_value = arr[i];
  }
  memset(count_list, 0, range * sizeof(int));
  // 根據數位所在位置進行計數
  for (int i = 0; i < len; i++)
  {
    int item = arr[i] - min_value;
    int idx = (item / exponent) % range;
    count_list[idx]++;
  }

  // 構建計數排序,當前位置加上左側位置,後面的位數為前面的累加之和
  for (int i = 1; i < range; i++)
  {
    count_list[i] += count_list[i - 1];
  }

  // 構建輸出陣列,根據計數陣列按順序取得排序內容
  for (int i = len - 1; i >= 0; i--)
  {
    int item = arr[i] - min_value;
    int idx = (item / exponent) % range;
    // 根據位置重排結果,減去min值還原資料
    sorted_list[count_list[idx] - 1] = arr[i];
    count_list[idx]--;
  }

  // 複製到陣列重排原始陣列
  for (int i = 0; i < len; i++)
  {
    arr[i] = sorted_list[i];
  }
}

// 基數排序,從低位到高位LSD版,基於計數排序
int *radix_sort(int arr[], int len)
{
  int max_value = arr[0];
  for (int i = 1; i < len; i++)
  {
    if (arr[i] > max_value)
      max_value = arr[i];
  }
  int min_value = arr[0];
  for (int i = 1; i < len; i++)
  {
    if (arr[i] < min_value)
      min_value = arr[i];
  }

  // 根據最大值,逐個按進位(基數)來應用排序,exponent即數位基數,按個十百千遞增。
  for (int exponent = 1; (max_value - min_value) / exponent > 0; exponent *= 10)
  {
    counting_sort(arr, len, exponent);
  }

  return arr;
}
// 根據最大長度來獲取數位第n位的值,從前往後開始,前面不足最大長度時補零
int get_digit_by_position(int num, int position, int max_length)
{
  if (num == 0)
  {
    return 0;
  }
  int number_length = (int)log10(num) + 1;
  // 查詢的位置加上自身長度不足最大長度則返回0
  if ((position + number_length) < max_length)
  {
    return 0;
  }
  int exponent = (int)pow(10, number_length - position);
  int digit = 0;
  if (exponent > 0)
  {
    digit = (num / exponent) % 10;
  }

  return digit;
}

// 基數排序,從高位到逐個對比排序,通過桶排序遞迴呼叫
// arr是陣列,len是當前陣列長度,position為自前往後的位置,max_length是最大值的數位
int *bucket_sort(int arr[], int len, int position, int max_length)
{
  printf("\r\nlen=%d position=%d max_length=%d ", len, position, max_length);

  if (len <= 1 || position > max_length)
  {
    return arr;
  }

  // 找出最小值
  int min_value = arr[0];
  for (int i = 1; i < len; i++)
  {
    if (arr[i] < min_value)
      min_value = arr[i];
  }

  int range = 10;
  // 桶一共從0-9十個數位
  int buckets[range][len];
  for (int i = 0; i < range; i++)
  {
    // 此處未提前使用,也可以不設定預設值
    memset(buckets[i], 0, len * sizeof(int));
    // print_array(buckets[i], len);
  }

  // 預設填充內容為0
  int bucket_count_list[range];
  memset(bucket_count_list, 0, range * sizeof(int));

  for (int i = 0; i < len; i++)
  {
    int item = arr[i] - min_value;
    // 根據數位上的值,減去最小值,分配到對應的桶裡
    int bucket_idx = get_digit_by_position(item, position, max_length);
    // 把資料按下標插入到桶裡
    int number_idx = bucket_count_list[bucket_idx];
    buckets[bucket_idx][number_idx] = arr[i];
    bucket_count_list[bucket_idx] += 1;
  }

  // 將每個桶的資料按順序逐個取出,重新賦值給原陣列
  int sorted_idx = 0;

  for (int i = 0; i < range; i++)
  {
    int *bucket = buckets[i];
    int bucket_len = bucket_count_list[i];
    int bucket_size = sizeof(*bucket) / sizeof(bucket[0]);
    // 如果只有一個值,則直接更新到原陣列
    if (bucket_count_list[i] == 1)
    {
      arr[sorted_idx] = bucket[0];
      sorted_idx += 1;
    }
    else if (bucket_size > 0 && bucket_len > 0)
    {
      // 如果是陣列且記錄追加大於1則繼續遞迴呼叫,位置增加1位
      // 遞迴呼叫傳參時需要傳入當前子序列、子序列長度、當前分解的位數基數
      int *sorted_bucket = bucket_sort(bucket, bucket_len, position + 1, max_length);
      // 依照已排序的子序列實際長度,把各個桶裡的值按順序賦給原陣列
      for (int j = 0; j < bucket_len; j++)
      {
        int num = sorted_bucket[j];
        arr[sorted_idx] = num;
        sorted_idx += 1;
      }
    }
  }
  printf("\r\n position:%d", position);
  print_array(arr, len);
  return arr;
}

// 計數排序,根據數位的位置逐個對比排序,從高到低MSD,遞迴方式
int *radix_sort_msd(int arr[], int len)
{
  // 找出最大值
  int max_value = arr[0];
  for (int i = 1; i < len; i++)
  {
    if (arr[i] > max_value)
      max_value = arr[i];
  }
  // 獲取最小項
  int min_value = arr[0];
  for (int i = 0; i < len; i++)
  {
    if (min_value > arr[i])
    {
      min_value = arr[i];
    }
  }
  // 獲取數位一共有幾位,減去min得到最大值,以支援負數和減少最大值
  int max_length = (int)(log10(max_value - min_value) + 1);
  // 根據陣列最大值的長度,從前往後逐個對比排序。
  return bucket_sort(arr, len, 1, max_length);
}

 

C++

// 基數排序,從個位到高位LSD版,基於計數排序實現
int *radixSort(int *arr, int len)
{

  // 以10倍遞進
  int range = 10;
  int sortedList[len];

  int max = arr[0];
  for (int i = 1; i < len; i++)
  {
    if (arr[i] > max)
    {
      max = arr[i];
    }
  }
  int min = arr[0];
  for (int i = 1; i < len; i++)
  {
    if (arr[i] < min)
    {
      min = arr[i];
    }
  }

  // 根據最大值,逐個按進位(基數)來應用排序,exponent即基數。
  for (int exponent = 1; ((max - min) / exponent) > 0; exponent *= range)
  {

    // 計數陣列,長度為10,0-9一共10個數位
    int countList[range];
    memset(countList, 0, range * sizeof(int));
    // 根據基數得到當前位數,並給計數陣列對應位置加1
    for (int i = 0; i < len; i++)
    {
      int item = arr[i] - min;
      int idx = (item / exponent) % range;
      countList[idx] += 1;
    }

    // 計數排序構建,自前往後,逐個將上一項的值存入當前項
    for (int i = 1; i < range; i++)
    {
      countList[i] += countList[i - 1];
    }

    // 根據計數陣列按順序取出排序內容
    for (int i = len - 1; i >= 0; i--)
    {
      int item = arr[i] - min;
      int idx = (item / exponent) % range;
      sortedList[countList[idx] - 1] = arr[i];
      countList[idx] -= 1;
    }

    // 複製輸出陣列到原始陣列
    for (int i = 0; i < len; i++)
    {
      arr[i] = sortedList[i];
    }
  }

  return arr;
}

 

連結

基數排序與計數排序、桶排序區別

基數排序與計數排序、桶排序三者概念很像,但也有不同,其主要差異如下:
計數排序:根據陣列值設定若干個桶,每個桶對應一個數值,將這些桶的值分別存入下一個桶中用於排序,最後按順序取出對應桶裡的值。
桶排序:根據情況分為若干個桶,每個桶儲存一定範圍的數值,每個桶再單獨排序,最後按桶的順序取出全部資料。
基數排序:根據資料的位數來分配桶,每一位對應一個桶,先將全部資料的位數按最大位數對齊,再根據位數上的值大小排列。基數排序基於計數排序或者桶排序。

基數排序演演算法原始碼:https://github.com/microwind/algorithms/tree/master/sorts/radixsort

其他排序演演算法原始碼:https://github.com/microwind/algorithms