基数排序(Radix Sort)是一种非比较型的排序算法,它通过逐位比较元素的每一位(从最低位到最高位)来实现排序。基数排序的核心思想是将整数按位数切割成不同的数字,然后按每个位数分别进行排序。基数排序的时间复杂度为 O(n * k),其中 n 是列表长度,k 是最大数字的位数。

 基数排序 vs 计数排序 vs 桶排序

基数排序有两种方法:

这三种排序算法都利用了桶的概念,但对桶的使用方法上有明显差异:

  • 基数排序:根据键值的每位数字来分配桶;
  • 计数排序:每个桶只存储单一键值;
  • 桶排序:每个桶存储一定范围的数值;

一、算法步骤

  1. 确定最大位数:找到列表中最大数字的位数,确定需要排序的轮数。

  2. 按位排序:从最低位开始,依次对每一位进行排序(通常使用计数排序或桶排序作为子排序算法)。

  3. 合并结果:每一轮排序后,更新列表的顺序,直到所有位数排序完成。

二、图像演示

假设有一个待排序的列表 [170, 45, 75, 90, 802, 24, 2, 66],基数排序的过程如下:

  1. 确定最大位数

    • 最大数字是 802,有 3 位数,因此需要 3 轮排序。

  2. 第一轮(按个位数排序)

    • 使用计数排序对个位数进行排序:

      • 个位数统计:[0, 2, 4, 5, 6, 0, 2, 6]

      • 排序后列表:[170, 90, 802, 2, 24, 45, 75, 66]

  3. 第二轮(按十位数排序)

    • 使用计数排序对十位数进行排序:

      • 十位数统计:[7, 9, 0, 0, 2, 4, 7, 6]

      • 排序后列表:[802, 2, 24, 45, 66, 170, 75, 90]

  4. 第三轮(按百位数排序)

    • 使用计数排序对百位数进行排序:

      • 百位数统计:[8, 0, 0, 0, 1, 0, 0, 0]

      • 排序后列表:[2, 24, 45, 66, 75, 90, 170, 802]

  5. 最终结果

    • 列表完全有序:[2, 24, 45, 66, 75, 90, 170, 802]

 三、代码实现 

       实例

def counting_sort(arr, exp):
    n = len(arr)
    output = [0] * n  # 初始化输出数组
    count = [0] * 10  # 初始化计数数组

    # 统计每个数字的出现次数
    for num in arr:
        index = (num // exp) % 10
        count[index] += 1

    # 累加计数数组,得到每个数字的最终位置
    for i in range(1, 10):
        count[i] += count[i - 1]

    # 将元素放入输出数组
    i = n - 1
    while i >= 0:
        index = (arr[i] // exp) % 10
        output[count[index] - 1] = arr[i]
        count[index] -= 1
        i -= 1

    # 将输出数组复制到原始数组
    for i in range(n):
        arr[i] = output[i]

def radix_sort(arr):
    # 找到最大数字的位数
    max_num = max(arr)
    exp = 1  # 从个位数开始
    while max_num // exp > 0:
        counting_sort(arr, exp)  # 对当前位数进行计数排序
        exp *= 10  # 移动到下一位
    return arr

# 示例
arr = [170, 45, 75, 90, 802, 24, 2, 66]
sorted_arr = radix_sort(arr)
print(sorted_arr)  # 输出: [2, 24, 45, 66, 75, 90, 170, 802]

        JavaScript

//LSD Radix Sort
var counter = [];
function radixSort(arr, maxDigit) {
    var mod = 10;
    var dev = 1;
    for (var i = 0; i < maxDigit; i++, dev *= 10, mod *= 10) {
        for(var j = 0; j < arr.length; j++) {
            var bucket = parseInt((arr[j] % mod) / dev);
            if(counter[bucket]==null) {
                counter[bucket] = [];
            }
            counter[bucket].push(arr[j]);
        }
        var pos = 0;
        for(var j = 0; j < counter.length; j++) {
            var value = null;
            if(counter[j]!=null) {
                while ((value = counter[j].shift()) != null) {
                      arr[pos++] = value;
                }
          }
        }
    }
    return arr;
}

       Java

/** 基数排序  */
public class RadixSort  {

  public int[] sort(int[] arr) {

    int maxDigit = getMaxDigit(arr);
    return radixSort(arr, maxDigit);
  }

  /**   * 获取最高位数   */  private int getMaxDigit(int[] arr) {
    int maxValue = getMaxValue(arr);
    int minValue = getMinValue(arr);
    return Math.max(getNumLength(maxValue), getNumLength(minValue));
  }

  private int getMaxValue(int[] arr) {
    int maxValue = arr[0];
    for (int value : arr) {
      if (maxValue < value) {
        maxValue = value;
      }
    }
    return maxValue;
  }

  private int getMinValue(int[] arr) {
    int minValue = arr[0];
    for (int value : arr) {
      if (minValue > value) {
        minValue = value;
      }
    }
    return minValue;
  }

  protected int getNumLength(long num) {
    if (num == 0) {
      return 1;
    }
    int lenght = 0;
    for (long temp = num; temp != 0; temp /= 10) {
      lenght++;
    }
    return lenght;
  }

  private int[] radixSort(int[] arr, int maxDigit) {
    int mod = 10;
    int dev = 1;

    for (int i = 0; i < maxDigit; i++, dev *= 10, mod *= 10) {
      // 考虑负数的情况,这里扩展一倍队列数,其中 [0-9]对应负数,[10-19]对应正数 (bucket + 10)      int[][] counter = new int[20][0];

      for (int j = 0; j < arr.length; j++) {
        int bucket = ((arr[j] % mod) / dev) + 10;
        counter[bucket] = arrayAppend(counter[bucket], arr[j]);
      }

      int pos = 0;
      for (int[] bucket : counter) {
        for (int value : bucket) {
          arr[pos++] = value;
        }
      }
    }

    return arr;
  }

  /**   * 自动扩容,并保存数据   *   * @param arr   * @param value   */  private int[] arrayAppend(int[] arr, int value) {
    arr = Arrays.copyOf(arr, arr.length + 1);
    arr[arr.length - 1] = value;
    return arr;
  }
}

        PHP

function radixSort($arr, $maxDigit = null)
{
    if ($maxDigit === null) {
        $maxDigit = max($arr);
    }
    $counter = [];
    for ($i = 0; $i < $maxDigit; $i++) {
        for ($j = 0; $j < count($arr); $j++) {
            preg_match_all('/\d/', (string) $arr[$j], $matches);
            $numArr = $matches[0];
            $lenTmp = count($numArr);
            $bucket = array_key_exists($lenTmp - $i - 1, $numArr)
                ? intval($numArr[$lenTmp - $i - 1])
                : 0;
            if (!array_key_exists($bucket, $counter)) {
                $counter[$bucket] = [];
            }
            $counter[$bucket][] = $arr[$j];
        }
        $pos = 0;
        for ($j = 0; $j < count($counter); $j++) {
            $value = null;
            if ($counter[$j] !== null) {
                while (($value = array_shift($counter[$j])) !== null) {
                    $arr[$pos++] = $value;
                }
          }
        }
    }

    return $arr;
}

        C

#include<stdio.h>
#define MAX 20
//#define SHOWPASS
#define BASE 10

void print(int *a, int n) {
  int i;
  for (i = 0; i < n; i++) {
    printf("%d\t", a[i]);
  }
}

void radixsort(int *a, int n) {
  int i, b[MAX], m = a[0], exp = 1;

  for (i = 1; i < n; i++) {
    if (a[i] > m) {
      m = a[i];
    }
  }

  while (m / exp > 0) {
    int bucket[BASE] = { 0 };

    for (i = 0; i < n; i++) {
      bucket[(a[i] / exp) % BASE]++;
    }

    for (i = 1; i < BASE; i++) {
      bucket[i] += bucket[i - 1];
    }

    for (i = n - 1; i >= 0; i--) {
      b[--bucket[(a[i] / exp) % BASE]] = a[i];
    }

    for (i = 0; i < n; i++) {
      a[i] = b[i];
    }

    exp *= BASE;

#ifdef SHOWPASS
    printf("\nPASS   : ");
    print(a, n);
#endif
  }
}

int main() {
  int arr[MAX];
  int i, n;

  printf("Enter total elements (n <= %d) : ", MAX);
  scanf("%d", &n);
  n = n < MAX ? n : MAX;

  printf("Enter %d Elements : ", n);
  for (i = 0; i < n; i++) {
    scanf("%d", &arr[i]);
  }

  printf("\nARRAY  : ");
  print(&arr[0], n);

  radixsort(&arr[0], n);

  printf("\nSORTED : ");
  print(&arr[0], n);
  printf("\n");

  return 0;
}

        C++

int maxbit(int data[], int n) //辅助函数,求数据的最大位数
{
    int maxData = data[0];              ///< 最大数
    /// 先求出最大数,再求其位数,这样有原先依次每个数判断其位数,稍微优化点。
    for (int i = 1; i < n; ++i)
    {
        if (maxData < data[i])
            maxData = data[i];
    }
    int d = 1;
    int p = 10;
    while (maxData >= p)
    {
        //p *= 10; // Maybe overflow
        maxData /= 10;
        ++d;
    }
    return d;
/*    int d = 1; //保存最大的位数
    int p = 10;
    for(int i = 0; i < n; ++i)
    {
        while(data[i] >= p)
        {
            p *= 10;
            ++d;
        }
    }
    return d;*/
}
void radixsort(int data[], int n) //基数排序
{
    int d = maxbit(data, n);
    int *tmp = new int[n];
    int *count = new int[10]; //计数器
    int i, j, k;
    int radix = 1;
    for(i = 1; i <= d; i++) //进行d次排序
    {
        for(j = 0; j < 10; j++)
            count[j] = 0; //每次分配前清空计数器
        for(j = 0; j < n; j++)
        {
            k = (data[j] / radix) % 10; //统计每个桶中的记录数
            count[k]++;
        }
        for(j = 1; j < 10; j++)
            count[j] = count[j - 1] + count[j]; //将tmp中的位置依次分配给每个桶
        for(j = n - 1; j >= 0; j--) //将所有桶中记录依次收集到tmp中
        {
            k = (data[j] / radix) % 10;
            tmp[count[k] - 1] = data[j];
            count[k]--;
        }
        for(j = 0; j < n; j++) //将临时数组的内容复制到data中
            data[j] = tmp[j];
        radix = radix * 10;
    }
    delete []tmp;
    delete []count;
}

        C#

///基数排序
static void RadixSort(List<int> list)
{
    int maxValue = list.Max();//列表内部方法拿过来用用(在Linq中)
    int it = 0;//需要几趟
                //maxvalue 9-1 99-2 999-3
                //10^0<=9 10^1>9 it=1
                //10^0<99 10^1<99 10^2>99 it=2
    while (Math.Pow(10, it) <= maxValue)
    {
        List<List<int>> buckets = new List<List<int>>(10);//分10个桶对应0-9
        for (int i = 0; i < 10; i++)
        {
            buckets.Add(new List<int>());
        }//列表初始化大小
        for (int i = 0; i < list.Count; i++)//入桶
        {
            //989 it=0 989/10^it=989 989%10=9;
            int digit = (int)((list[i]) / (Math.Pow(10, it)) % 10);//得到对应桶
            buckets[digit].Add(list[i]);
        }//全部入桶
        list.Clear();//依次取出来
        for (int i = 0; i < buckets.Count; i++)
        {
            list.AddRange(buckets[i]);
        }
        it += 1;//继续下一次循环入桶出桶
    }
}

        Python

def radixSort(nums):
    """
    基数排序,数组元素必须是正整数
    >>>nums = [334, 5, 67, 345, 7, 99, 4, 23, 78, 45, 1, 3453, 23424]
    >>>radixSort(nums)
    >>>[1, 4, 5, 7, 23, 45, 67, 78, 99, 334, 345, 3453, 23424]
    """
    #遍历数组获取数组最大值和最大值对应的位数
    maxValue = nums[0]
    for n in nums:
        maxValue = max(n, maxValue)
    #迭代次数
    iterCount = len(str(maxValue))
    for i in range(iterCount):
        #定义桶,大小为10,对应0-9
        bucket = [[] for _ in range(10)]
        for n in nums:
            index = (n//10**i)%10
            bucket[index].append(n)
        #nums数组清零,并合并桶内元素至nums
        nums.clear()
        for b in bucket:
            nums.extend(b)
        print(nums)
    return nums
nums = [334, 5, 67, 345, 7, 99, 4, 23, 78, 45, 1, 3453, 23424]
radixSort(nums)

        go

// 基数排序
func RadixSort(arr []int) {
    // 计算最长的数字
    var (
        maxVal int
        maxLen int
    )
    for _, v := range arr {
        if maxVal < v {
            maxVal = v
        }
    }
    for maxVal > 0 {
        maxLen++
        maxVal /= 10
    }

    // 循环进行数据分配与回归
    var (
        base    = 1           // 取余基数,初始是1,用于取出每个元素的倒数第 i+1 位的值,计算公式:v / base %10
        buckets = [10][]int{} // 基数桶,10个
    )
    for i := 0; i < maxLen; i++ { // 遍历位
        for _, v := range arr { // 遍历数组
            d := v / base % 10                 // 每个数字当前位值
            buckets[d] = append(buckets[d], v) // 存入对应桶中
        }

        // 将桶中元素还原到arr
        idx := 0
        for x, bucket := range buckets {
            if len(bucket) == 0 {
                continue
            }

            for _, v := range bucket {
                arr[idx] = v
                idx++
            }

            // 桶清空
            buckets[x] = []int{}
        }

        base *= 10 // 基数*10
    }
}

四、复杂度

1、时间复杂度

  • 每一轮排序:O(n),使用计数排序对每一位进行排序。

  • 总轮数:k 轮,其中 k 是最大数字的位数。

  • 总时间复杂度:O(n * k)。

2、空间复杂度

  • O(n + k),需要额外的存储空间来存放计数数组和输出数组。

五、优缺点

  • 优点

    • 时间复杂度为 O(n * k),当 k 较小时,性能优异。

    • 稳定排序算法(相同元素的相对顺序不会改变)。

  • 缺点

    • 仅适用于整数或固定长度的字符串。

    • 当最大数字的位数 k 很大时,性能下降。

六、使用场景        

  • 数据范围较小的整数排序。

  • 需要稳定排序算法的场景。

  • 适合外部排序(如对磁盘文件进行排序)。

Logo

魔乐社区(Modelers.cn) 是一个中立、公益的人工智能社区,提供人工智能工具、模型、数据的托管、展示与应用协同服务,为人工智能开发及爱好者搭建开放的学习交流平台。社区通过理事会方式运作,由全产业链共同建设、共同运营、共同享有,推动国产AI生态繁荣发展。

更多推荐