基数排序(radix sort)属于“分配式排序”(distribution sort),
又称“桶子法”(bucket sort)或bin sort,顾名思义,它是透过键值的部份资讯,
将要排序的元素分配至某些“桶”中,藉以达到排序的作用,基数排序法是属于稳定性的排序,
其时间复杂度为O(nlog(r)m) ,其中r为所采取的基数,而m为堆数,在某些时候,
基数排序法的效率高于其它的稳定性排序法。

解释

以上是 百度百科 关于 基数排序 的描述。核心点如下:

  • 是一种非比较的排序
  • 计数排序的进阶版
  • 按照相同位有效数字的值分组排序
  • 的概念 通过一个 从右至左 按照每一位的大小依次进行排序
  • 每一位的排序都遵循队列 进行先入先出重新写入

这样讲比较晦涩,下面用一个例子来完整的叙述基数排序

1⃣️ 初始化
比如说现在有一个这样的无序数组 arr

const arr = [10, 200, 13, 12, 7, 88, 91, 24]

而且也有这样的一个桶 buckets

const buckets = Array.from({length:10},()=>[]);
// 通过Array.from新建一个长度为10的二维数组
// 等同于:
// const buckets = [[],[],[],[],[],[],[],[],[],[]]

// 为啥长度必须是10 因为是十进制啊

2⃣️ 取原数组的每个元素的个位
那么分别取原数组arr个位就是:

// const arr = [10, 200, 13, 12, 7, 88, 91, 24]
[0, 0, 3, 2, 7, 8, 1, 4]

3⃣️ 根据个位值的大小放到对应的桶中
其实这一步也可以理解为根据个位值的大小进行第一次排序
个位数组的每个元素对应到桶的索引,(是不是很熟悉,不熟悉请回忆上节的计数排序),通过此步骤 目前就变成了:

buckets = [[10, 200], [91], [12], [13], [24], [], [], [7], [88]]

4⃣️ 按照桶的索引进行取值
通过此步 就已经完成了原数组个位排序

arr = [10,200,91,12,13,24,7,88]

5⃣️ 那接下来就是按照十位进行排序

6⃣️ 取原数组的每个元素的十位

// arr = [10,200,91,12,13,24,7,88]
[1, 0, 9, 1, 1, 2, 0, 8]
// 如果某个元素没有该位, 就为0
// 比如7仅是个位数 那么它的十位就为0

7⃣️ 根据十位值的大小放到对应的桶中

buckets = [[200,7], [10,12,13], [24], [], [], [], [], [], [88], [91]]

8⃣️ 按照桶的索引进行取值
通过此步 就已经完成了原数组十位排序

arr = [200,7,10,12,13,24,88,91]

重复以上通过同样步骤来处理百位
9⃣️ 按照百位进行排序 并取原数组的每个元素的百位,最后就完成了基数排序

// 按照百位进行排序
[2,0,0,0,0,0,0,0,0,0]

// 取原数组的每个元素的百位
[[7,10,12,13,24,88,91],[],[200],...]

// 完成排序
[7,10,12,13,24,88,91,200]

基数排序确实有点绕,主要是有了这个东西。如果知道计数排序,而且能看懂上方的步骤分解,那么就很容易了。

JS实现基数排序
/*
 * 基数排序
 * @param{无序数组} arr
*/
function radix_sort(arr) {
  // 取最大值 最大值的位数就是要循环遍历的次数
  const max = Math.max(...arr);

  // 定义一个桶
  const buckets = Array.from({ length: 10 }, () => []);

  // 定义当前要遍历的位数 个位 十位 百位...
  let m = 1;
  while (m < max) {
    // m < 最大值
    // 下方m要 m*=10 -> 每次遍历增加一位
    // 保证遍历完所有可能的位数

    // 放入桶
    arr.forEach(number => {
      // digit表示某位数的值
      const digit = ~~((number % (m * 10)) / m);

      // 把该位数的值放到桶buckets中
      // 通过索引确定顺序 类比计数排序
      buckets[digit].push(number);
    });

    // 从桶buckets中取值
    // 完成此步后 就完成了一次位数排序
    let ind = 0;
    buckets.forEach(bucket => {
      while (bucket.length > 0) {
        // shift从头部取值
        // 保证按照队列先入先出
        arr[ind++] = bucket.shift();
      }
    });

    // 每次最外层while循环后m要乘等10
    // 也就是要判断下一位 比如当前是个位 下次就要判断十位
    m *= 10;
  }
}

上方还有一个点,就是计算当前位数值digit。 为了代码的阅读性,没有在上方代码中说明。在此稍作说明:

const digit = ~~((number % (m * 10)) / m);

上段代码的意思就是计算出某个数字的某位数的值
比如说 一个数字521,如何分别拿到125呢?

首先 ~~ === Math.floor() 也就是向下取整
m首次是1 以后每次乘等10
取 个位1:

    a. 取模: 521 % 10 = 1 
    b. 除以m:  1 / 1 = 1 
    c. 向下取整:  1
    

取 十位2:

    a. 取模: 521 % 100 = 21
    b. 除以m:  21 / 10 = 2.1
    c. 向下取整:  2
    

取 百位5:

    a. 取模: 521 % 1000 = 521
    b. 除以m:  521 / 100 = 5.21
    c. 向下取整:  5
            
测试一下
const arr = [10, 200, 13, 12, 7, 88, 91, 24];

radix_sort(arr);
console.log(arr);

/*
    [
       7, 10, 12,  13,
      24, 88, 91, 200
    ]
*/

Funky_Tiger
443 声望33 粉丝

刷题,交流,offer,内推,涨薪,相亲,前端资源共享...