基数排序(Radix Sort)是一种非比较型的排序算法,它通过逐位比较元素的每一位(从最低位到最高位)来实现排序。基数排序的核心思想是将整数按位数切割成不同的数字,然后按每个位数分别进行排序。基数排序的时间复杂度为 O(n * k),其中 n 是列表长度,k 是最大数字的位数。
算法步骤:
确定最大位数:找到列表中最大数字的位数,确定需要排序的轮数。
按位排序:从最低位开始,依次对每一位进行排序(通常使用计数排序或桶排序作为子排序算法)。
合并结果:每一轮排序后,更新列表的顺序,直到所有位数排序完成。
1. 基数排序 vs 计数排序 vs 桶排序
基数排序有两种方法:
这三种排序算法都利用了桶的概念,但对桶的使用方法上有明显差异:
- 基数排序:根据键值的每位数字来分配桶;
- 计数排序:每个桶只存储单一键值;
- 桶排序:每个桶存储一定范围的数值;
2. LSD 基数排序动图演示
假设有一个待排序的列表 [170, 45, 75, 90, 802, 24, 2, 66],基数排序的过程如下:
确定最大位数:
最大数字是
802
,有 3 位数,因此需要 3 轮排序。
第一轮(按个位数排序):
使用计数排序对个位数进行排序:
个位数统计:
[0, 2, 4, 5, 6, 0, 2, 6]
。排序后列表:
[170, 90, 802, 2, 24, 45, 75, 66]
。
第二轮(按十位数排序):
使用计数排序对十位数进行排序:
十位数统计:
[7, 9, 0, 0, 2, 4, 7, 6]
。排序后列表:
[802, 2, 24, 45, 66, 170, 75, 90]
。
第三轮(按百位数排序):
使用计数排序对百位数进行排序:
百位数统计:
[8, 0, 0, 0, 1, 0, 0, 0]
。排序后列表:
[2, 24, 45, 66, 75, 90, 170, 802]
。
最终结果:
列表完全有序:
[2, 24, 45, 66, 75, 90, 170, 802]
。
实例
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]
时间复杂度
-
每一轮排序:O(n),使用计数排序对每一位进行排序。
-
总轮数:k 轮,其中 k 是最大数字的位数。
-
总时间复杂度:O(n * k)。
空间复杂度
-
O(n + k),需要额外的存储空间来存放计数数组和输出数组。
优缺点
-
优点:
-
时间复杂度为 O(n * k),当 k 较小时,性能优异。
-
稳定排序算法(相同元素的相对顺序不会改变)。
-
-
缺点:
-
仅适用于整数或固定长度的字符串。
-
当最大数字的位数 k 很大时,性能下降。
-
适用场景
-
数据范围较小的整数排序。
-
需要稳定排序算法的场景。
-
适合外部排序(如对磁盘文件进行排序)。
代码实现
JavaScript
实例
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
实例
* 基数排序
* 考虑负数的情况还可以参考: https://code.i-harness.com/zh-CN/q/e98fa9
*/
public class RadixSort implements IArraySort {
@Override
public int[] sort(int[] sourceArray) throws Exception {
// 对 arr 进行拷贝,不改变参数内容
int[] arr = Arrays.copyOf(sourceArray, sourceArray.length);
int maxDigit = getMaxDigit(arr);
return radixSort(arr, maxDigit);
}
/**
* 获取最高位数
*/
private int getMaxDigit(int[] arr) {
int maxValue = getMaxValue(arr);
return getNumLenght(maxValue);
}
private int getMaxValue(int[] arr) {
int maxValue = arr[0];
for (int value : arr) {
if (maxValue < value) {
maxValue = value;
}
}
return maxValue;
}
protected int getNumLenght(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[mod * 2][0];
for (int j = 0; j < arr.length; j++) {
int bucket = ((arr[j] % mod) / dev) + mod;
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
实例
{
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++
实例
{
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
实例
#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;
}
Lua
实例
local maxBit = function (tt)
local weight = 10; -- 十進制
local bit = 1;
for k, v in pairs(tt) do
while v >= weight do
weight = weight * 10;
bit = bit + 1;
end
end
return bit;
end
-- 基数排序
local radixSort = function (tt)
local maxbit = maxBit(tt);
local bucket = {};
local temp = {};
local radix = 1;
for i = 1, maxbit do
for j = 1, 10 do
bucket[j] = 0; --- 清空桶
end
for k, v in pairs(tt) do
local remainder = math.floor((v / radix)) % 10 + 1;
bucket[remainder] = bucket[remainder] + 1; -- 每個桶數量自動增加1
end
for j = 2, 10 do
bucket[j] = bucket[j - 1] + bucket[j]; -- 每个桶的数量 = 以前桶数量和 + 自个数量
end
-- 按照桶的位置,排序--这个是桶式排序,必须使用倒序,因为排序方法是从小到大,顺序下来,会出现大的在小的上面清空。
for k = #tt, 1, -1 do
local remainder = math.floor((tt[k] / radix)) % 10 + 1;
temp[bucket[remainder]] = tt[k];
bucket[remainder] = bucket[remainder] - 1;
end
for k, v in pairs(temp) do
tt[k] = v;
end
radix = radix * 10;
end
end;
参考地址:
https://github.com/hustcc/JS-Sorting-Algorithm/blob/master/10.radixSort.md
https://zh.wikipedia.org/wiki/%E5%9F%BA%E6%95%B0%E6%8E%92%E5%BA%8F
locenco
zha***[email protected]
java 代码里,mod 每次循环会乘 10,但 counter 的行数是不需要变的,能包含 [-9,9] 就可以了。
locenco
zha***[email protected]
艾孜尔江
bju***[email protected]
艾孜尔江补充使用C#基数排序算法如下:
艾孜尔江
bju***[email protected]
奋斗的昌老师
153***[email protected]
参考地址
补充一下python的基数排序代码实现:
奋斗的昌老师
153***[email protected]
参考地址
秋月
luj***[email protected]
go 的补一个吧:
秋月
luj***[email protected]
柠檬茶
150***[email protected]
补上python的实现代码:
柠檬茶
150***[email protected]
散步留馨
she***[email protected]
上面 Java 版本有点问题:
counter 数组的定义,会随着 mod 不断乘 10 变得越来越大。理论上 counter 数组只需要容量为 20 就可以表示负数与正数的所有数字字符。
另外,方法 getMaxDigit 计算数字的最大长度,只考虑到最大值的长度,没有考虑当存在负数时,最小值负数的字符长度也可能是最大的长度。
更新后的版本:
散步留馨
she***[email protected]