Skip to content

Commit fdb1753

Browse files
committed
feat: add RadixSort
1 parent 3ba9613 commit fdb1753

4 files changed

Lines changed: 159 additions & 8 deletions

File tree

README.md

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
排序算法是程序员必备的基础知识,弄明白它们的原理和实现很有必要。
44

5-
我们可以将常见的排序算法可以分成两类
5+
我们可以将常见的**内部排序算法**可以分成两类
66

77
![](sort-category.png)
88

@@ -20,16 +20,17 @@
2020

2121
**非比较类排序**:不通过比较来决定元素间的相对次序,其时间复杂度可以突破 O(nlogn),以线性时间运行。属于非比较类的有:
2222

23-
| 排序算法 | 时间复杂度 | 最差情况 | 最好情况 | 空间复杂度 | 稳定性 | 排序方式 |
24-
| :----------------------: | :--------: | :------: | :------: | :--------: | :----: | :-------: |
25-
| [计数排序](CountingSort) | O(n+k)​ | O(n+k)​ | O(n+k)​ | O(n+k)​ | 稳定 | Out-place |
26-
| [桶排序](BucketSort) | O(n+k)​ | O(n²) | O(n)​ | O(n+m)​ | 稳定 | Out-place |
27-
| 基数排序 | O(n×k)​ | O(n×k)​ | O(n×k)​ | O(n+k)​ | 稳定 | Out-place |
28-
23+
| 排序算法 | 时间复杂度 | 最差情况 | 最好情况 | 空间复杂度 | 稳定性 | 排序方式 |
24+
| :----------------------: | :--------: | :-------: | :------: | :--------: | :----: | :-------: |
25+
| [桶排序](BucketSort) | O(n+k)​ | O(n²) | O(n)​ | O(n+r)​ | 稳定 | Out-place |
26+
| [计数排序](CountingSort) | O(n+k)​ | O(n+k)​ | O(n+k)​ | O(n+k)​ | 稳定 | Out-place |
27+
| [基数排序](RadixSort) | O(d(n+r))​ | O(d(n+r)) | O(d(n+r)) | O(n+r)​ | 稳定 | Out-place |
2928

3029
**n**:待排序列的个数
3130

32-
**m**:“桶”的个数
31+
**r**:“桶”的个数
32+
33+
**d**:待排序列的最高位数
3334

3435
**In-place**:原地算法,指的是占用常用内存,不占用额外内存。空间复杂度为 O(1) 的都可以认为是原地算法
3536

RadixSort/README.md

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
## 基数排序
2+
3+
基数排序(Radix Sort)是**非比较型**排序算法,它和[计数排序](../CountingSort)[桶排序](../BucketSort)一样,利用了“****”的概念。基数排序不需要进行记录关键字间的比较,是一种**借助多关键字排序的思想对单逻辑关键字进行排序**的方法。比如数字100,它的个位、十位、百位就是不同的关键字。
4+
5+
那么,对于一组乱序的数字,基数排序的实现原理就是将整数按位数(关键字)切割成不同的数字,然后按每个位数分别比较。对于关键字的选择,有最高位优先法(MSD法)和最低位优先法(LSD法)两种方式。MSD 必须将序列先逐层分割成若干子序列,然后再对各子序列进行排序;而 LSD 进行排序时,不必分成子序列,对每个关键字都是整个序列参加排序。
6+
7+
### 算法步骤
8+
9+
以 LSD 法为例:
10+
1. 将所有待比较数值(非负整数)统一为同样的数位长度,数位不足的数值前面补零
11+
2. 从最低位(个位)开始,依次进行一次排序
12+
3. 从最低位排序一直到最高位排序完成以后, 数列就变成一个有序序列
13+
14+
如果要支持负数参加排序,可以将序列中所有的值加上一个常数,使这些值都成为非负数,排好序后,所有的值再减去这个常数。
15+
16+
### 动图演示
17+
18+
![](radix-sort.gif)
19+
20+
### 代码实现
21+
22+
#### C语言
23+
```c
24+
// 基数,范围0~9
25+
#define RADIX 10
26+
27+
void radix_sort(int arr[], int n) {
28+
// 获取最大值和最小值
29+
int max = arr[0], min = arr[0];
30+
int i, j, l;
31+
for (i = 1; i < n; i++) {
32+
if (max < arr[i]) max = arr[i];
33+
if (min > arr[i]) min = arr[i];
34+
}
35+
// 假如序列中有负数,所有数加上一个常数,使序列中所有值变成正数
36+
if (min < 0) {
37+
for (i = 0; i < n; i++) arr[i] -= min;
38+
max -= min;
39+
}
40+
// 获取最大值位数
41+
int d = 0;
42+
while (max > 0) {
43+
max /= RADIX;
44+
d ++;
45+
}
46+
int queue[RADIX][n];
47+
memset(queue, 0, sizeof(queue));
48+
int count[RADIX] = {0};
49+
for (i = 0; i < d; i++) {
50+
// 分配数据
51+
for (j = 0; j < n; j++) {
52+
int key = arr[j] % (int)pow(RADIX, i + 1) / (int)pow(RADIX, i);
53+
queue[key][count[key]++] = arr[j];
54+
}
55+
// 收集数据
56+
int c = 0;
57+
for (j = 0; j < RADIX; j++) {
58+
for (l = 0; l < count[j]; l++) {
59+
arr[c++] = queue[j][l];
60+
queue[j][l] = 0;
61+
}
62+
count[j] = 0;
63+
}
64+
}
65+
// 假如序列中有负数,收集排序结果时再减去前面加上的常数
66+
if (min < 0) {
67+
for (i = 0; i < n; i++) arr[i] += min;
68+
}
69+
}
70+
```
71+
72+
### 算法分析
73+
74+
基数排序是**稳定排序**,适用于关键字取值范围固定的排序。
75+
76+
#### 时间复杂度
77+
78+
基数排序可以看作是若干次“分配”和“收集”的过程。假设给定 n 个数,它的最高位数是 d,基数(也就是桶的个数)为 r,那么可以这样理解:共进行 d 趟排序,每趟排序都要对 n 个数据进行分配,再从 r 个桶中收集回来。所以算法的时间复杂度为 O(d(n+r)),在整数的排序中,r = 10,因此可以简化成 O(dn),是**线性阶**的排序。
79+
80+
#### 空间复杂度
81+
82+
占用额外内存,还需要 r 个桶,所以空间复杂度为 O(n+r)。
83+
84+
### 计数排序 & 桶排序 & 基数排序
85+
86+
这三种排序算法都利用了桶的概念,但对桶的使用方法上有明显差异:
87+
88+
- 桶排序:每个桶存储一定范围的数值,适用于元素尽可能分布均匀的排序;
89+
- 计数排序:每个桶只存储单一键值,适用于最大值和最小值尽可能接近的排序;
90+
- 基数排序:根据键值的每位数字来分配桶,适用于非负整数间的排序,且最大值和最小值尽可能接近。

RadixSort/radix-sort.gif

1.34 MB
Loading

RadixSort/radix_sort.c

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
#include <stdio.h>
2+
#include <string.h>
3+
#include <math.h>
4+
5+
// 基数,范围0~9
6+
#define RADIX 10
7+
8+
void radix_sort(int arr[], int n) {
9+
// 获取最大值和最小值
10+
int max = arr[0], min = arr[0];
11+
int i, j, l;
12+
for (i = 1; i < n; i++) {
13+
if (max < arr[i]) max = arr[i];
14+
if (min > arr[i]) min = arr[i];
15+
}
16+
// 假如序列中有负数,所有数加上一个常数,使序列中所有值变成正数
17+
if (min < 0) {
18+
for (i = 0; i < n; i++) arr[i] -= min;
19+
max -= min;
20+
}
21+
// 获取最大值位数
22+
int d = 0;
23+
while (max > 0) {
24+
max /= RADIX;
25+
d ++;
26+
}
27+
int queue[RADIX][n];
28+
memset(queue, 0, sizeof(queue));
29+
int count[RADIX] = {0};
30+
for (i = 0; i < d; i++) {
31+
// 分配数据
32+
for (j = 0; j < n; j++) {
33+
int key = arr[j] % (int)pow(RADIX, i + 1) / (int)pow(RADIX, i);
34+
queue[key][count[key]++] = arr[j];
35+
}
36+
// 收集数据
37+
int c = 0;
38+
for (j = 0; j < RADIX; j++) {
39+
for (l = 0; l < count[j]; l++) {
40+
arr[c++] = queue[j][l];
41+
queue[j][l] = 0;
42+
}
43+
count[j] = 0;
44+
}
45+
}
46+
// 假如序列中有负数,收集排序结果时再减去前面加上的常数
47+
if (min < 0) {
48+
for (i = 0; i < n; i++) arr[i] += min;
49+
}
50+
}
51+
52+
int main() {
53+
int arr[] = {35, 10, 43, 3, 52, 21, 86, 19, 63, 60};
54+
int n = sizeof(arr) / sizeof(*arr);
55+
radix_sort(arr, n);
56+
printf("Sort result:\n");
57+
for (int i = 0; i < n; i++)
58+
printf("%d ", arr[i]);
59+
return 0;
60+
}

0 commit comments

Comments
 (0)