常见排序算法(四)(基数排序、桶排序)

相关文章:

常见排序算法(零)(各类排序算法总结与比较)

常见排序算法(一)(冒泡排序、插入排序)

常见排序算法(二)(选择排序)

常见排序算法(三)(快速排序、归并排序、计数排序)

常见排序算法(四)(基数排序、桶排序)


基数排序(Radix Sort)

基数排序分为最低位优先排序(LeastSignificant Digit first, LSD)和最高位优先排序(Most SignificantDigit first, MSD),两者在排序方法,适用数据方面均有所不同。

 

最低位优先排序(Least Significant Digit first, LSD)

从最低位开始,每一位通过一个哈希函数映射到对应的数组中,遍历一次之后,所有的数都分配到了新数组中,按分配的顺序从上到下直接拼接这10个数组。再从倒数第二位开始映射,当进行到第一位时,合并完之后,得到排好序的数组。由于映射到相同数组中的数越少越好,所以用H(x) = x % 10作为哈希函数。

基本思路:

1、找出待排序数组中长度最长的数,设长度为L

2、建立一个二维数组data[10][n],n为待排序数组的长度,建立一个计数数组counts[10]

3、从个位开始,将每个数分配到data数组中,按data[0]~data[9]的顺序合并

4、从十位开始,将每个数分配到data数组中,按data[0]~data[9]的顺序合并

以此类推,直到达到最高位,最终得到排好序的数组。

最低位优先排序的java实现:

int maxLength = String.valueOf(getMax()).length();
int[][] radix;
int[] index;
for (int i = 0; i < maxLength; i++) {
    radix = new int[10][data.length];
    index = new int[10];
    Arrays.fill(index, 0);
    int divider = (int) Math.pow(10, i);  //  使用做除法然后取余的方法来取出某一位
    //  分配桶
    for (int j = 0; j < data.length; j++) {
        int temp = data[j];
        int key = temp / divider % 10;
        //  某个桶中数据增加之后,对应的计数器要加1
        radix[key][index[key]] = temp;
        index[key]++;
    }
    //  拼接桶中的数据
    int counter = 0;
    for (int j = 0; j < 10; j++) {
        for (int k = 0; k < index[j]; k++) {
            data[counter] = radix[j][k];
            counter++;
        }
    }
}

最高位优先排序(Most Significant Digit first, MSD)

       从最高位开始将数据分配到桶中,与最低位优先排序不同的是,在分配完一次之后并不立刻合并,而是根据第二位的数值将桶中的数据再次分配到子桶中,然后再根据第三位的数值将子桶中的数据载分配到子桶中,直到分配到最后一位再进行合并。由算法的描述可知,该算法占用的空间较大,如果各桶分配平行推进,很容易造成内存不足。因此采用递归的方式来执行该算法。使用的哈希函数仍然是H(x) = x % 10。

基本思路:

1、找出待排序数组中数值的最大长度,设为L

2、从第L位开始,通过哈希函数将数据分配到桶中,L递减

3、如果L大于等于0,重复第二步,如果L小于0,说明已经分配到最后一位,拼接每个桶中的数组然后返回

最高位优先排序的java实现:

int maxLength = String.valueOf(getMax()).length();
int divider = (int) Math.pow(10, maxLength - 1);
data = msdIterationSort(data, divider);
/**
 * 迭代的msd排序
 * @param data 待排序数组
 * @param divider 用于取出数位的除数
 * @return
 */
private int[] msdIterationSort(int[] data, int divider) {
    int[][] radix = new int[10][data.length];
    int[] index = new int[10];
    Arrays.fill(index, 0);
    int sum = 0;
    //  将数分配到对应的桶中
    for (int j = 0; j < data.length; j++) {
        int pos = data[j] / divider % 10;
        radix[pos][index[pos]] = data[j];
        index[pos]++;
    }
    //  得到下一次迭代所用的除数
    divider = divider / 10;
    //  如果除数大于0,则需要对桶中的数据进行下一次迭代
    if (divider > 0) {
        //  对每个桶都要再次迭代
        for (int i = 0; i < 10; i++) {
            //  需要对桶的大小进行处理,因为创建桶的时候长度为待处理数据的长度,
            //  需要创建一个新数组来存放原来桶中的数据,然后用做下一次迭代
            int[] temp = new int[index[i]];
            for (int j = 0; j < index[i]; j++) {
                temp[j] = radix[i][j];
            }
            radix[i] = msdIterationSort(temp, divider);
        }
    }
    //  除数不大于0的时候说明对数的每一位都处理完了,接下来将每个桶中的数据拼接起来得到排好序的数据
    //  然后返回排好序的数据
    for (int i = 0; i < 10; i++) {
        sum = sum + index[i];
    }
    int[] result = new int[sum];
    int counter = 0;
    for (int i = 0; i < 10; i++) {
        for (int j = 0; j < index[i]; j++) {
            result[counter] = radix[i][j];
            counter++;
        }
    }
    return result;
}

       基数排序也是一种不基于比较并且稳定的排序算法,最优时间复杂度O(d(n + rd)),最差时间复杂度O(d(r + n)),平均时间复杂度O(d(n + rd)),其中r代表关键字的基数,d代表最大数据的长度,n代表关键字的个数。空间复杂度O(rd + n)(不确定最低位优先级排序和最高位优先级排序的空间复杂度是否一致,感觉最高位优先级排序的空间复杂度要更高些)

       最低位优先级排序更适合数据集中数据最高位数不多的情况,当数据集中数据最高位数多时宜使用最高位优先级排序。

       基数排序只能对自然数排序,在数据量不大但数据范围跨度大的情况下表现要优于计数排序,占用空间也要少一些,大多数情况下还是计数排序要优一些。

桶排序(Bucket Sort)

       桶排序,又称箱排序,与基数排序类似,也是使用哈希函数将数据映射到桶中,但与基数排序不同的是,将这个数映射到桶中之后,如果两个数据进入同一个桶,这个桶中将会进行排序来使桶中的数有序。

基本思路:

1、根据哈希函数,创建二维数组data,行数为基数的个数,列数为待排序数组的长度

2、遍历数组,将数组中的数放入对应的桶中,如果桶中使用的是插入排序,可以在这一步开始排序,如果使用的是快速排序,则等遍历完数组后再排序

3、排好序后,将各个数组拼接起来得到排好序的数组

桶排序的java实现(桶中使用插入排序,哈希函数为H(x) = x % 10):

bucket = new int[10][data.length];
index = new int[10];
Arrays.fill(index, 0);
int divider = (int) Math.pow(10, String.valueOf(getMax()).length() - 1);
for (int i = 0; i < data.length; i++) {
    int pos = data[i] / divider;
    //  将新数值放入桶中之后,桶中的数据变为无序,
    //  由于除放在最后的那个数之外,其余的数都是有序的,因此考虑使用插入排序来使桶中的数据重新有序
    bucket[pos][index[pos]] = data[i];
    index[pos]++;
    if (index[pos] <= 1) {  //  桶中只有一个数据时,不需要插入排序,继续循环
        continue;
    }
    //  基本插入排序
    for (int j = index[pos] - 1; j > 0; j--) {
        if (bucket[pos][j] < bucket[pos][j - 1]) {
            swap(j, j - 1, bucket[pos]);
        } else {
            break;
        }
    }
}
int counter = 0;
for (int i = 0; i < 10; i++) {
    counter = getBucketResult(i, counter);
}
/**
 * 取出桶中的数据,并且返回下一个放入data中的数据应存放的位置
 * @param pos 桶的编号
 * @param counter 下一个放入data中的数据应存放的位置
 * @return
 */
private int getBucketResult(int pos, int counter) {
    for (int i = 0; i < index[pos]; i++) {
        data[counter] = bucket[pos][i];
        counter++;
    }
    return counter;
}

       桶排序的最优时间复杂度为O(n),最差时间复杂度取决于桶中的排序算法,如果是插入排序则为O(n2),堆排序为O(nlogn)。空间复杂度为O(d + n),d为基数个数,n为待排序数组长度。

       提高桶排序的效率就要减少每个桶中的数据,极限情况下每个桶中只有一个数据,时间复杂度可以达到O(n),但是会浪费巨大的空间,这是时间和空间的权衡问题。在数据规模合适的情况下,桶排序的表现可以优于快排等基于比较的排序算法,因为桶排序不是基于比较的排序算法。

本文所使用的java代码已上传至github,为java project:https://github.com/sysukehan/SortAlgorithm.git


  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值