Skip to content

Commit

Permalink
feat: add RadixSort
Browse files Browse the repository at this point in the history
  • Loading branch information
fiteen committed Dec 27, 2019
1 parent 3ba9613 commit fdb1753
Show file tree
Hide file tree
Showing 4 changed files with 159 additions and 8 deletions.
17 changes: 9 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

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

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

![](sort-category.png)

Expand All @@ -20,16 +20,17 @@

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

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

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

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

**m**:“桶”的个数
**r**:“桶”的个数

**d**:待排序列的最高位数

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

Expand Down
90 changes: 90 additions & 0 deletions RadixSort/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
## 基数排序

基数排序(Radix Sort)是**非比较型**排序算法,它和[计数排序](../CountingSort)[桶排序](../BucketSort)一样,利用了“****”的概念。基数排序不需要进行记录关键字间的比较,是一种**借助多关键字排序的思想对单逻辑关键字进行排序**的方法。比如数字100,它的个位、十位、百位就是不同的关键字。

那么,对于一组乱序的数字,基数排序的实现原理就是将整数按位数(关键字)切割成不同的数字,然后按每个位数分别比较。对于关键字的选择,有最高位优先法(MSD法)和最低位优先法(LSD法)两种方式。MSD 必须将序列先逐层分割成若干子序列,然后再对各子序列进行排序;而 LSD 进行排序时,不必分成子序列,对每个关键字都是整个序列参加排序。

### 算法步骤

以 LSD 法为例:
1. 将所有待比较数值(非负整数)统一为同样的数位长度,数位不足的数值前面补零
2. 从最低位(个位)开始,依次进行一次排序
3. 从最低位排序一直到最高位排序完成以后, 数列就变成一个有序序列

如果要支持负数参加排序,可以将序列中所有的值加上一个常数,使这些值都成为非负数,排好序后,所有的值再减去这个常数。

### 动图演示

![](radix-sort.gif)

### 代码实现

#### C语言
```c
// 基数,范围0~9
#define RADIX 10

void radix_sort(int arr[], int n) {
// 获取最大值和最小值
int max = arr[0], min = arr[0];
int i, j, l;
for (i = 1; i < n; i++) {
if (max < arr[i]) max = arr[i];
if (min > arr[i]) min = arr[i];
}
// 假如序列中有负数,所有数加上一个常数,使序列中所有值变成正数
if (min < 0) {
for (i = 0; i < n; i++) arr[i] -= min;
max -= min;
}
// 获取最大值位数
int d = 0;
while (max > 0) {
max /= RADIX;
d ++;
}
int queue[RADIX][n];
memset(queue, 0, sizeof(queue));
int count[RADIX] = {0};
for (i = 0; i < d; i++) {
// 分配数据
for (j = 0; j < n; j++) {
int key = arr[j] % (int)pow(RADIX, i + 1) / (int)pow(RADIX, i);
queue[key][count[key]++] = arr[j];
}
// 收集数据
int c = 0;
for (j = 0; j < RADIX; j++) {
for (l = 0; l < count[j]; l++) {
arr[c++] = queue[j][l];
queue[j][l] = 0;
}
count[j] = 0;
}
}
// 假如序列中有负数,收集排序结果时再减去前面加上的常数
if (min < 0) {
for (i = 0; i < n; i++) arr[i] += min;
}
}
```
### 算法分析
基数排序是**稳定排序**,适用于关键字取值范围固定的排序。
#### 时间复杂度
基数排序可以看作是若干次“分配”和“收集”的过程。假设给定 n 个数,它的最高位数是 d,基数(也就是桶的个数)为 r,那么可以这样理解:共进行 d 趟排序,每趟排序都要对 n 个数据进行分配,再从 r 个桶中收集回来。所以算法的时间复杂度为 O(d(n+r)),在整数的排序中,r = 10,因此可以简化成 O(dn),是**线性阶**的排序。
#### 空间复杂度
占用额外内存,还需要 r 个桶,所以空间复杂度为 O(n+r)。
### 计数排序 & 桶排序 & 基数排序
这三种排序算法都利用了桶的概念,但对桶的使用方法上有明显差异:
- 桶排序:每个桶存储一定范围的数值,适用于元素尽可能分布均匀的排序;
- 计数排序:每个桶只存储单一键值,适用于最大值和最小值尽可能接近的排序;
- 基数排序:根据键值的每位数字来分配桶,适用于非负整数间的排序,且最大值和最小值尽可能接近。
Binary file added RadixSort/radix-sort.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
60 changes: 60 additions & 0 deletions RadixSort/radix_sort.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
#include <stdio.h>
#include <string.h>
#include <math.h>

// 基数,范围0~9
#define RADIX 10

void radix_sort(int arr[], int n) {
// 获取最大值和最小值
int max = arr[0], min = arr[0];
int i, j, l;
for (i = 1; i < n; i++) {
if (max < arr[i]) max = arr[i];
if (min > arr[i]) min = arr[i];
}
// 假如序列中有负数,所有数加上一个常数,使序列中所有值变成正数
if (min < 0) {
for (i = 0; i < n; i++) arr[i] -= min;
max -= min;
}
// 获取最大值位数
int d = 0;
while (max > 0) {
max /= RADIX;
d ++;
}
int queue[RADIX][n];
memset(queue, 0, sizeof(queue));
int count[RADIX] = {0};
for (i = 0; i < d; i++) {
// 分配数据
for (j = 0; j < n; j++) {
int key = arr[j] % (int)pow(RADIX, i + 1) / (int)pow(RADIX, i);
queue[key][count[key]++] = arr[j];
}
// 收集数据
int c = 0;
for (j = 0; j < RADIX; j++) {
for (l = 0; l < count[j]; l++) {
arr[c++] = queue[j][l];
queue[j][l] = 0;
}
count[j] = 0;
}
}
// 假如序列中有负数,收集排序结果时再减去前面加上的常数
if (min < 0) {
for (i = 0; i < n; i++) arr[i] += min;
}
}

int main() {
int arr[] = {35, 10, 43, 3, 52, 21, 86, 19, 63, 60};
int n = sizeof(arr) / sizeof(*arr);
radix_sort(arr, n);
printf("Sort result:\n");
for (int i = 0; i < n; i++)
printf("%d ", arr[i]);
return 0;
}

0 comments on commit fdb1753

Please sign in to comment.