学习笔记--排序算法之桶排序

原理:
桶排序(BucketSort)顾名思义,会用到“桶”,桶我们可以将其想象成一个容器,核心思想是将要排序的数据分到几个有序的桶里,每个桶里的数据再单独进行排序。桶内排完序之后,再把每个桶里的数据按照顺序依次取出,组成的序列就是有序的了,换句话说:桶排序是将待排序集合中处于同一个值域的元素存入同一个桶中,也就是根据元素值特性将集合拆分为多个区域,则拆分后形成的多个桶,从值域上看是处于有序状态的。对每个桶中元素进行排序,则所有桶中元素构成的集合是已排序的。
在这里插入图片描述
桶排序过程中存在两个关键环节:
•元素值域的划分,也就是元素到桶的映射规则。映射规则需要根据待排序集合的元素分布特性进行选择,若规则设计的过于模糊、宽泛,则可能导致待排序集合中所有元素全部映射到一个桶上。
•从待排序集合中元素映射到各个桶上的过程,并不存在元素的比较和交换操作,在对各个桶中元素进行排序时,可以自主选择合适的排序算法,每个桶内的排序算法的复杂度和稳定性决定了最终的算法的复杂度和稳定性。

那么桶排序的时间复杂度是多少呢?
如果要排序的数据有n个,我们把它们均匀地划分到m个桶内,每个桶里就有k=n/m个元素。假设每个桶内部使用快速排序,时间复杂度为O(klogk)。m个桶排序的时间复杂度就是O(mklogk),因为k=n/m,所以整个桶排序的时间复杂度就是O(nlog(n/m))。当桶的个数m接近数据个数n时,log(n/m)就是一个非常小的常量,这个时候桶排序的时间复杂度接近O(n)。桶排序看起来是如此的优秀,那是不是可以替代我们之前讲到的排序算法呢?答案是否定的。首先,要排序的数据需要很容易就能划分成m个桶,并且,桶与桶之间有着天然的大小顺序。这样每个桶内的数据都排序完之后,桶与桶之间的数据不需要再进行排序。其次,数据在各个桶之间的分布是比较均匀的。如果数据经过桶的划分之后,有些桶里的数据非常多,有些非常少,很不平均,那桶内数据排序的时间复杂度就不是常量级了。在极端情况下,如果数据都被划分到一个桶里,那就退化为O(nlogn)的排序算法了。

应用
桶排序比较适合用在非内存排序中。所谓的非内存排序就是说数据存储在外部磁盘中,数据量比较大,数据量比较大,内存有限,无法将数据全部加载到内存中。此外由桶排序的过程可知,当待排序集合中存在元素值相差较大时,对映射规则的选择是一个挑战,有时可能导致元素集中分布在某一个桶中或者绝大多数桶是空桶的现象,对算法的时间复杂度或空间复杂度有较大影响,所以桶排序适用于元素值分布较为集中的序列,或者说待排序的元素能够均匀分布在某一个范围[MIN,MAX]之间之间。
接下来笔者以一个亲身经历过的面试题为例来说明:比如说我们有10GB的订单数据,我们希望按订单金额(假设金额都是正整数)进行排序,但是我们的内存有限,只有几百MB,没办法一次性把10GB的数据都加载到内存中。这个时候该怎么办呢?
现在我来讲一下,如何借助桶排序的处理思想来解决这个问题。我们可以先扫描一遍文件,看订单金额所处的数据范围。假设经过扫描之后我们得到,订单金额最小是1元,最大是10万元。我们将所有订单根据金额划分到100个桶里,第一个桶我们存储金额在1元到1000元之内的订单,第二桶存储金额在1001元到2000元之内的订单,以此类推。每一个桶对应一个文件,并且按照金额范围的大小顺序编号命名(00,01,02…99)。理想的情况下,如果订单金额在1到10万之间均匀分布,那订单会被均匀划分到100个文件中,每个小文件中存储大约100MB的订单数据,我们就可以将这100个小文件依次放到内存中,用快排来排序。等所有文件都排好序之后,我们只需要按照文件编号,从小到大依次读取每个小文件中的订单数据,并将其写入到一个文件中,那这个文件中存储的就是按照金额从小到大排序的订单数据了。
不过,你可能也发现了,订单按照金额在1元10万元之间并不一定是均匀分布的,所以10GB订单数据是无法均匀地被划分到100个文件中的。有可能某个金额区间的数据特别多,划分之后对应的文件就会很大,没法一次性读入内存。这又该怎么办呢?针对这些划分之后还是比较大的文件,我们可以继续划分,比如,订单金额在1元到1000元之间的比较多,我们就将这个区间继续划分为10个小区间,1元到0元,101元到200元,201元到300元…901元到1000元。如果划分之后,101元到200元之间的订单还是太多,无法一次性读入内存,那就继续再划分,直到所有的文件都能读入内存为止。
接下来为了能对该算法做具体的实现,我们对该算法进一步做具体的描述:
•人为设置一个BucketSize,作为每个桶所能放置多少个不同数值(例如当BucketSize==5时,该桶可以存放{1,2,3,4,5}这几种数字,但是容量不限,即可以存放100个3);
•遍历输入数据,并且把数据一个一个放到对应的桶里去;
•对每个不是空的桶进行排序,可以使用其它排序方法,也可以递归使用桶排序;
•从不是空的桶里把排好序的数据拼接起来。
注意,如果递归使用桶排序为各个桶排序,则当桶数量为1时要手动减小BucketSize增加下一循环桶的数量,否则会陷入死循环,导致内存溢出。

总结
1:桶排序的时间复杂度是多少?
桶排序的时间复杂度,取决与对各个桶之间数据进行排序的时间复杂度,如果我们将待排序元素映射到某一个桶的映射规则做的很好的话,很显然,桶划分的越小,各个桶之间的数据越少,排序所用的时间也会越少。但相应的空间消耗就会增大。我们一般对每个桶内的元素进行排序时采用快排也可以采用递归桶排序,通过我们刚开始的分析,当我们对每个桶采用快排时如果桶的个数接近数据规模n时,复复杂度为杂度为O(n),如果在极端情况下复杂度退化为O(n*logn)。
2:桶排序的空间复杂度是多少?
由于需要申请额外的空间来保存元素,并申请额外的空间来存储每个桶,所以空间复杂度为间复杂度为O(N+M),其中M代表桶的个数。所以桶排序虽然快,但是它是采用了用空间换时间的做法。
3:桶排序是稳定的排序算法吗?
桶排序是否稳定取决于对每一个桶内元素排序的算法的稳定性,如果我们对桶内元素使用快排时桶排序就是一个不稳定的排序算法。

代码实现:

package com.troy.share.BucketSort;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;

/**
 * @Description
 * @Author Wu Jie yan
 * @Date 2020/7/5 19:53
 **/
public class BucketSort {
   
    /**
     * 桶排序
     *
     * @param array      待排序集合
     * @param BucketSize 桶中元素类型的个数即每个桶所能放置多少个不同数值(BucketSize==5时,该桶可以存放{1,2,3,4,5}这几种数字,但是容量不限,即可以存放100个3)
     * @return 排好序后的集合
     */
    public static List<Integer> bucketSort(List<Integer> array, int BucketSize) {
   
        //初始化
        if (array == null || array.size() < 2 || BucketSize < 1) {
   
            return array;
        }
        //找出集合中元素的最大值,最小值
        int max = array.get(0);
        int min = array
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值