桶排序

基本原理

1.桶排序也是一种分而治之思想,其时间复杂度在最好的情况下比快排还要好,但是需要使用空间换区时间。
2.假设有一个长度为N的待排序的序列K[1….n],现在将这个序列划分为M个子区间,每个子区间叫一个桶,那么现在就有了M个桶,现在通过一个映射函数f(x)将K[i]映射到桶B[j]中,也就是K[i]成为了B[j]中的一个元素。然后对每个桶进行排序,这里可以选择继续递归使用桶进行数据集的规模减小,也可以选择如快速排序、选择排序等比较排序方法进行桶中元素的排序。
3.其实桶排序跟HashMap很像,都是使用一个函数f(x)将数据进行分组,不过这里需要注意的是对于f(x),需要满足f(ki)<=f(kj),当ki<=kj的时候。也就是说B[i]桶中的最小数据要大于等于B[i-1]桶中的最大数据。

简单样例

假如待排序列K= {49、 38 、 35、 97 、 76、 73 、 27、 49 }。这些数据全部在1—100之间。因此我们定制10个桶,然后确定映射函数f(k)=k/10。则第一个关键字49将定位到第4个桶中(49/10=4)。依次将所有关键字全部堆入桶中,并在每个非空的桶中进行快速排序后得到如下图所示:
这里写图片描述
对上图只要顺序输出每个B[i]中的数据就可以得到有序序列了。

时间复杂度、空间复杂度

对N个关键字进行桶排序的时间复杂度分为两个部分:
(1) 循环计算每个关键字的桶映射函数,这个时间复杂度是O(N)。
(2) 利用先进的比较排序算法对每个桶内的所有数据进行排序,其时间复杂度为 ∑ O(Ni*logNi) 。其中Ni 为第i个桶的数据量。

很显然,第(2)部分是桶排序性能好坏的决定因素。尽量减少桶内数据的数量是提高效率的唯一办法(因为基于比较排序的最好平均时间复杂度只能达到O(N*logN)了)。因此,我们需要尽量做到下面两点:
(1) 映射函数f(k)能够将N个数据平均的分配到M个桶中,这样每个桶就有[N/M]个数据量。
(2) 尽量的增大桶的数量。极限情况下每个桶只能得到一个数据,这样就完全避开了桶内数据的“比较”排序操作。 当然,做到这一点很不容易,数据量巨大的情况下,f(k)函数会使得桶集合的数量巨大,空间浪费严重。这就是一个时间代价和空间代价的权衡问题了。

对于N个待排数据,M个桶,平均每个桶[N/M]个数据的桶排序平均时间复杂度为:
O(N)+O(M*(N/M)log(N/M))=O(N+N(logN-logM))=O(N+N*logN-N*logM)
当N=M时,即极限情况下每个桶只有一个数据时。桶排序的最好效率能够达到O(N)。

总结: 桶排序的平均时间复杂度为线性的O(N+C),其中C=N*(logN-logM)。如果相对于同样的N,桶数量M越大,其效率越高,最好的时间复杂度达到O(N)。 当然桶排序的空间复杂度 为O(N+M),如果输入数据非常庞大,而桶的数量也非常多,则空间代价无疑是昂贵的。此外,桶排序是稳定的。
注:稳定排序是指,在一个待排序序列中,存在两个或以上相等的关键字(ai,aj,..),如果排完序后,ai,aj…的相对位置没有发生变化,那么就是稳定的。

海量数据的桶排序

一年的全国高考考生人数为500 万,分数使用标准分,最低100 ,最高900 ,没有小数,你把这500 万元素的数组排个序。

分析:对500W数据排序,如果基于比较的先进排序,平均比较次数为O(5000000*log5000000)≈1.112亿。但是我们发现,这些数据都有特殊的条件: 100<=score<=900。那么我们就可以考虑桶排序这样一个“投机取巧”的办法、让其在毫秒级别就完成500万排序。

方法:创建801(900-100)个桶。将每个考生的分数丢进f(score)=score-100的桶中。这个过程从头到尾遍历一遍数据只需要500W次。然后根据桶号大小依次将桶中数值输出,即可以得到一个有序的序列。而且可以很容易的得到100分有多少人,501分有多少人。

实际上,桶排序对数据的条件有特殊要求,如果上面的分数不是从100-900,而是从0-2亿,那么分配2亿个桶显然是不可能的。所以桶排序有其局限性,适合元素值集合并不大的情况。

伪代码

A:待排序序列
B:桶
BUCKET-SORT(A)
1. n = A.length
2. new B[n-1]  //可以按照具体情况决定B的大小
3. for i=0 to n-1
4.    insert A[i] into B[f(A[i])] //使用映射函数f(x)划分A
5. for i=0 to n-1
6.    sort B[i]

图来自《算法导论》,仅说明桶的实现可以是数组+链表
这里写图片描述

java代码实现

//使用的排序方式是插入排序
public static void BucketSort(int[] num) {
        int length = num.length;
        Entry[] bucket = new Entry[10];
        for (int i = 0; i < bucket.length; i++) {
            bucket[i] = new Entry();   //初始化桶
        }
        for (int i = 0; i < length; i++) {
            Entry node = new Entry(num[i], null);
            int index = num[i] / 10;
            if (bucket[index].next == null) {
                bucket[index].next = node;
            } else {
                Entry head = bucket[index];
                Entry temp = head.next;
                while (temp != null) {
                    if (node.value < temp.value) {
                        //head.next = node;
                        break;
                    } else {
                        head = temp;
                        temp = temp.next;
                    }
                }
                head.next = node;
            }
        }
        for (int i = 0; i < bucket.length; i++) {
            Entry temp = bucket[i].next;
            while (temp != null) {
                System.out.print(temp.value + " ");
                temp = temp.next;
            }
        }
    }

参考资料:
【排序结构6】 桶排序
啊哈!算法

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值