排序算法之 桶排序

一、简介

桶排序也是时间复杂度仅为 O(n) 的一种排序方法,它假设输入数据服从均匀分布,我们将数据分别放入到 n 个桶内,先对桶内数据进行排序,然后遍历桶依次取出桶中的元素即可完成排序。

和计数排序类似,桶排序也对输入数据作了某种假设,因此它的速度也很快。具体来说,计数排序假设了输入数据都属于一个小区间内的整数,而桶排序则假设输入数据是均匀分布的,即落入每个桶中的元素数量理论也是差不多的,不会出现很多数落入同一个桶内的情况。

二、原理图

在这里插入图片描述
例如输入数据:21,8,6,11,36,50,27,42,0,12。

然后分别放入对应的桶内排序,最后依次遍历桶取出元素即可完成排序。

三、时间复杂度

平均情况下,桶排序的时间复杂度为 O(n)。

最坏情况下,所有数据都放到同一个桶内,桶排序的时间复杂度为 O(n^2) 或 O(n * lg n),这取决于桶内元素自排序的算法。

在《算法导论》中关于桶排序的时间复杂度推导,即使数据不服从均匀分布,只有输入数据满足下列性质:所有桶的大小的平方和与总的元素数呈线性关系,那么桶排序仍然能在线性时间完成。

四、代码示例

这里我想举这样一个例子,假设输入元素是均匀分布的浮点数。为什么要选择浮点数呢?因为我觉得这是计数排序中很难处理的一种情况,计数排序比较适用于整数的情况,如果我们依旧想在线性时间内完成元素的排序,那么桶排序将是一个很好的选择。

public class Main {
 
    public static void main(String[] args) {
        // 输入元素均在 [0, 10) 这个区间内
        float[] arr = new float[] { 0.12f, 2.2f, 8.8f, 7.6f, 7.2f, 6.3f, 9.0f, 1.6f, 5.6f, 2.4f };
        bucketSort(arr);
        printArray(arr);
    }
 
    public static void bucketSort(float[] arr) {
        // 新建一个桶的集合
        ArrayList<LinkedList<Float>> buckets = new ArrayList<LinkedList<Float>>();
        for (int i = 0; i < 10; i++) {
            // 新建一个桶,并将其添加到桶的集合中去。
            // 由于桶内元素会频繁的插入,所以选择 LinkedList 作为桶的数据结构
            buckets.add(new LinkedList<Float>());
        }
        // 将输入数据全部放入桶中并完成排序
        for (float data : arr) {
            int index = getBucketIndex(data);
            insertSort(buckets.get(index), data);
        }
        // 将桶中元素全部取出来并放入 arr 中输出
        int index = 0;
        for (LinkedList<Float> bucket : buckets) {
            for (Float data : bucket) {
                arr[index++] = data;
            }
        }
    }
 
    /**
     * 计算得到输入元素应该放到哪个桶内
     */
    public static int getBucketIndex(float data) {
        // 这里例子写的比较简单,仅使用浮点数的整数部分作为其桶的索引值
        // 实际开发中需要根据场景具体设计
        return (int) data;
    }
 
    /**
     * 我们选择插入排序作为桶内元素排序的方法 每当有一个新元素到来时,我们都调用该方法将其插入到恰当的位置
     */
    public static void insertSort(List<Float> bucket, float data) {
        ListIterator<Float> it = bucket.listIterator();
        boolean insertFlag = true;
        while (it.hasNext()) {
            if (data <= it.next()) {
                it.previous(); // 把迭代器的位置偏移回上一个位置
                it.add(data); // 把数据插入到迭代器的当前位置
                insertFlag = false;
                break;
            }
        }
        if (insertFlag) {
            bucket.add(data); // 否则把数据插入到链表末端
        }
    }
 
    public static void printArray(float[] arr) {
        for (int i = 0; i < arr.length; i++) {
            System.out.print(arr[i] + ", ");
        }
        System.out.println();
    }
 
}
 

五、分析

桶排序中很重要的一步就是桶的设定了,我们必须根据输入元素的情况,选择一个恰当的 “getBucketIndex” 算法,使得输入元素能够正确的放入对应的桶内,且保证输入数据能够尽量均匀的放入不同的桶内。

最糟糕的情况下,即所有的数据都放入了一个桶内,桶内自排序算法为插入排序,那么其时间复杂度就为 O(n ^ 2) 了。

其次,我们可以发现,区间划分的越细,即桶的数量越多,理论上分到每个桶中的元素就越少,桶内数据的排序就越简单,其时间复杂度就越接近于线性。

扫描二维码关注公众号,回复: 8515112 查看本文章

极端情况下,就是区间小到只有1,即桶内只存放一种元素,桶内的元素不再需要排序,因为它们都是相同的元素,这时桶排序差不多就和计数排序一样了。

发布了125 篇原创文章 · 获赞 236 · 访问量 2万+

猜你喜欢

转载自blog.csdn.net/qq_33709508/article/details/103841325