基数排序-LSD&MSD
Posted CoderLi
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了基数排序-LSD&MSD相关的知识,希望对你有一定的参考价值。
基数排序通过键值的部分信息、将要排序的元素分配至某些桶中、来达到排序的作用
基数排序的方式可以才采用LSD ( least significant digital ) 或 MSD (most significant digital) , LSD 的排序方式由键值的最右边开始而MSD 则相反、由键值的最左边开始
我们常见的排序算法 快速排序、归并排序等都是基于比较的排序方法的、正是因为它们基于比较的特性、这些算法在时间复杂度方面无法做到比 O(nlogn) 更好
基数排序是一种非比较排序算法、这里所谓的非比较、指的是该算法并非基于比较数组中的每个元素来决定他们的相对位置、
分配 + 收集
复杂度分析
时间复杂度:设待排序为 n 记录、d 个关键码、关键码的取值范围为radix、则进行链式基数排序的时间复杂度为
O(d*(n + radix))、其中、一趟分配时间复杂度为 O(n) 一趟收集时间复杂度为 O(radix) 共进行 d 趟 分配和收集
空间效率:需要 2 * radix 个指向队列的辅助空间、以及用于静态链表 n 个指针
实现原理
设关键码有 d 个 、从 k1 - kd
LSD 先从 kd 开始排序、 分配-收集、然后到 kd - 1 直至到 k1、便可得到一个有序序列
MSD 先按 k1 进行分配、同一组记录中、关键码 k1 相等、然后再对各组按 k2 进行分配、直至到 kd、然后再将各组连接起来、便可得到一个有序队列
分析
LSD
初始数组序列为:15,25,105,78,34,21,32,41
第一次分配
第一次收集数组序列为 21 41 32 34 15 25 105 78 (可以看到个位已经是有序的了)
第二次分配
第二次收集数组序列为 105 15 21 25 32 34 41 78
第三次分配
第三次收集数组的序列为 15 21 25 32 34 41 78 105
这个时候已经是有序的了
代码实现
基础类
import java.util.Arrays;
public abstract class RadixSort implements Sort {
/**
* 往数组中增加 元素
*
* @param ints
* @param value
* @return
*/
protected int[] append(int[] ints, int value) {
if (ints == null) {
return new int[]{value};
}
ints = Arrays.copyOf(ints, ints.length + 1);
ints[ints.length - 1] = value;
return ints;
}
/**
* 获取数组中、最大的位数
*
* @param source
* @return
*/
protected int getMaxDigit(int[] source) {
int absMaxValue = Math.abs(source[0]);
for (int value : source) {
if (Math.abs(value) > absMaxValue) {
absMaxValue = Math.abs(value);
}
}
if (absMaxValue == 0) {
return 0;
}
int maxDigit = 0;
while (absMaxValue > 0) {
maxDigit++;
absMaxValue /= 10;
}
return maxDigit;
}
/**
* 判断数组中是否都是同一个元素
*
* @param source
* @return
*/
protected boolean allSameValue(int[] source) {
int value = source[0];
return Arrays.stream(source).allMatch(v -> v == value);
}
}
import java.util.Arrays;
/**
* 基数排序 lsd
*/
public class LSDSort extends RadixSort {
@Override
public void sort(int[] source) {
int maxDigit = getMaxDigit(source);
lsdSort(source, maxDigit);
}
/**
* lsd 基数排序
*
* @param source
* @param maxDigit
*/
private void lsdSort(int[] source, int maxDigit) {
// 0-9 存放负数
int[][] bucket = new int[20][];
int mod = 10;
int dev = 1;
for (; maxDigit > 0; maxDigit--) {
// 分配
for (int value : source) {
// 提取对应的关键字
int index = value % mod / dev + 10;
bucket[index] = append(bucket[index], value);
}
int i = 0;
for (int[] values : bucket) {
if (values == null) {
continue;
}
System.arraycopy(values, 0, source, i, values.length);
i += values.length;
}
mod *= 10;
dev *= 10;
// 因为复用这个数组、所以一遍之后需要清空
Arrays.fill(bucket, null);
}
}
}
MSD
待排序序列 5,25,105,78,34,21,32,41
进行下一次递归分配的条件就是、这个bucket 里面的元素大于1并且里面的元素值不一样
代码实现
/**
* 基数排序 msd
*/
public class MSDSort extends RadixSort {
@Override
public void sort(int[] source) {
int maxDigit = getMaxDigit(source);
msdSort(0, source, Double.valueOf(Math.pow(10, maxDigit - 1)).intValue(), source);
}
/**
* @param startIndex 放置有序序列的起始下标
* @param source 元素数组
* @param dev 除数
* @param data 待排序的数组
*/
public void msdSort(int startIndex, int[] source, int dev, int[] data) {
// 0-9 存放负数
int[][] bucket = new int[20][];
// 将待排序的数字、找出对应的下标
for (int value : data) {
// 因为我们每次取的都是 最左边的数、所以求余 除数为10 固定
int index = value / dev % 10 + 10;
bucket[index] = append(bucket[index], value);
}
for (int[] values : bucket) {
if (values == null) {
continue;
}
// 如果只有一个数值、就不需要继续递归了、其实可以了解为递归出口了这个
if (values.length == 1) {
source[startIndex++] = values[0];
continue;
}
// 如果他们的值相同、就不需要递归了、递归出口也是
if (allSameValue(values)) {
System.arraycopy(values, 0, source, startIndex, values.length);
startIndex += values.length;
continue;
}
// 继续递归
msdSort(startIndex, source, dev / 10, values);
startIndex += values.length;
}
}
}
基数排序百度百科 ·https://baike.baidu.com/item/基数排序/7875498?fr=aladdin
图片来源 https://blog.csdn.net/u011948899/article/details/78027838
以上是关于基数排序-LSD&MSD的主要内容,如果未能解决你的问题,请参考以下文章