排序算法——桶排序

桶排序是计数排序的升级版。它利用了函数的映射关系,高效与否的关键就在于这个映射函数的确定。为了使桶排序更加高效,我们需要做到这两点:

  1. 在额外空间充足的情况下,尽量增大桶的数量
  2. 使用的映射函数能够将输入的 N 个数据均匀的分配到 K 个桶中

桶排序的工作的原理:假设输入数据服从均匀分布,将数据分到有限数量的桶里,每个桶再分别排序(有可能再使用别的排序算法或是以递归方式继续使用桶排序进行。

目录

一.算法实现

二.算法特性

三.如何实现平均分配


一.算法实现

假设这里有一个数组nums

  1. 首先根据数组去得到该数组中的最大值max以及最小值min。
  2. 然后去设置桶的属性,size作为桶中存储数据的数量,可由size=(max-min)/n+1得到,其中n为数据总量,加1是为了让桶中至少有一个元素。然后再去设置桶的数量cnt,可(max-min)/size+1得到,加1是为了至少有一个桶。
  3. 拿到桶的两个属性之后就开始创建桶了,并把元素根据(num[i]-min)/size得到该放在哪个桶当中。
  4. 最后将每个桶依次桶内排序(递归桶排序或者其他排序方法均可),并把数据返回数组当中。
public class CrossPrintWithLock {

    public static void main(String[] args) {

        int[] nums = {7, 3, 2, 5, -6, -1, 4, 4, 9, 8};
        bucketSort(nums);
        for (int i = 0; i < nums.length; i++) {
            System.out.print(nums[i] + " ");
        }
    }
    public static void bucketSort(int[] nums) {
        int n = nums.length;
        int mn = nums[0], mx = nums[0];
        // 找出数组中的最大最小值
        for (int i = 1; i < n; i++) {
            mn = Math.min(mn, nums[i]);
            mx = Math.max(mx, nums[i]);
        }
        int size = (mx - mn) / n + 1; // 每个桶存储数的范围大小,使得数尽量均匀地分布在各个桶中,保证最少存储一个
        int cnt = (mx - mn) / size + 1; // 桶的个数,保证桶的个数至少为1
        List<Integer>[] buckets = new List[cnt]; // 声明cnt个桶
        for (int i = 0; i < cnt; i++) {
            buckets[i] = new ArrayList<>();
        }
        // 扫描一遍数组,将数放进桶里
        for (int i = 0; i < n; i++) {
            int idx = (nums[i] - mn) / size;
            buckets[idx].add(nums[i]);
        }
        // 对各个桶中的数进行排序,这里用库函数快速排序
        for (int i = 0; i < cnt; i++) {
            buckets[i].sort(null); // 默认是按从小打到排序
        }
        // 依次将各个桶中的数据放入返回数组中
        int index = 0;
        for (int i = 0; i < cnt; i++) {
            for (int j = 0; j < buckets[i].size(); j++) {
                nums[index++] = buckets[i].get(j);
            }
        }
    }
}

二.算法特性

桶排序适用于处理体量很大的数据。例如,输入数据包含 100 万个元素,由于空间限制,系统内存无法一次性加载所有数据。此时,可以将数据分成 1000 个桶,然后分别对每个桶进行排序,最后将结果合并。

  • 时间复杂度为 O(n+k) :假设元素在各个桶内平均分布,那么每个桶内的元素数量为 nk 。假设排序单个桶使用 O(nklog⁡nk) 时间,则排序所有桶使用 O(nlog⁡nk) 时间。当桶数量 k 比较大时,时间复杂度则趋向于 O(n) 。合并结果时需要遍历所有桶和元素,花费 O(n+k) 时间。在最差情况下,所有数据被分配到一个桶中,且排序该桶使用 O(n2) 时间。
  • 空间复杂度为 O(n+k)、非原地排序:需要借助 k 个桶和总共 n 个元素的额外空间。
  • 桶排序是否稳定取决于排序桶内元素的算法是否稳定。

三.如何实现平均分配

桶排序的时间复杂度理论上可以达到 O(n) ,关键在于将元素均匀分配到各个桶中,因为实际数据往往不是均匀分布的。例如,我们想要将淘宝上的所有商品按价格范围平均分配到 10 个桶中,但商品价格分布不均,低于 100 元的非常多,高于 1000 元的非常少。若将价格区间平均划分为 10 个,各个桶中的商品数量差距会非常大。

为实现平均分配,我们可以先设定一条大致的分界线,将数据粗略地分到 3 个桶中。分配完毕后,再将商品较多的桶继续划分为 3 个桶,直至所有桶中的元素数量大致相等

这种方法本质上是创建一棵递归树,目标是让叶节点的值尽可能平均。当然,不一定要每轮将数据划分为 3 个桶,具体划分方式可根据数据特点灵活选择。

如果我们提前知道商品价格的概率分布,则可以根据数据概率分布设置每个桶的价格分界线。值得注意的是,数据分布并不一定需要特意统计,也可以根据数据特点采用某种概率模型进行近似。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值