数据结构与算法 - 桶排序

桶排序

桶排序可以看成是计数排序的升级版,它将要排的数据分到多个有序的桶里,每个桶里的数据再单独排序,再把每个桶的数据依次取出,即可完成排序。

image

我们拿一组计数排序啃不掉的数据 [ 500,6123,1700,10,9999 ] 来举例。

第一步,我们创建 10 个桶,分别来装 0-1000 、1000-2000 、 2000-3000 、 3000-4000 、 4000-5000 、5000-6000、 6000-7000 、7000-8000 、8000-9000 区间的数据。

image

第二步,遍历原数组,对号入桶。

image

第三步,对桶中的数据进行单独排序,只有第一个桶中的数量大于 1 ,显然只需要排第一个桶。

image

最后,依次将桶中的数据取出,排序完成。

image

代码实现

这个桶排序乍一看好像挺简单的,但是要敲代码就需要考虑几个问题了。

桶这个东西怎么表示?

怎么确定桶的数量?

桶内排序用什么方法排?

代码如下:

void sort(vector<int>& arr) {
    int max = arr[0];
    int min = arr[0];
    int length = arr.size();

    for(int i = 1; i < length; i++) {
        if(arr[i] > max) {
            max = arr[i];
        } else if(arr[i] < min) {
            min = arr[i];
        }
    }

    int diff = max - min;

    // 桶列表
    vector<vector<int>> bucketList(length);

    // 每个桶的存数区间
    float section = (float)diff / (float)(leng -1);

    // 数据入桶
    for(int i = 0; i < length; i++) {
        // 当前数除以区间得出放桶的位置,减1后得出桶的下标
        int num = (int)(arr[i]/section) - 1;
        if(num < 0) {
            num = 0;
        }
        bucketList[num].push_back(arr[i]);
    }

    // 桶内排序
    for(int i = 0; i < bucketList.size(); i++) {
        sort(bucketList[i].begin(), bucketList[i].end());
    }

    // 写入原数组
    int index = 0;
    for(vector<int> &arrList : bucketList) {
        for(int value: arrList) {
            arr[index] = value;
            index++;
        }
    }
}

桶的数量我认为设置为原数组的长度是合理的,因为理想情况下每个数据装一个桶。

数据入桶的映射算法其实是一个开放性问题,我承认我这里写的方案并不佳,因为我测试过不同的数据集合来排序,如果你有什么更好的方案或想法,欢迎留言讨论。

桶内排序为了方便起见使用了当前语言提供的排序方法,如果对于稳定排序有所要求,可以选择使用自定义的排序算法。

桶排序的思考及其应用

在额外空间充足的情况下,尽量增大桶的数量,极限情况下每个桶只有一个数据时,或者是每只桶只装一个值时,完全避开了桶内排序的操作,桶排序的最好时间复杂度就能够达到 O(n)。

比如高考总分 750 分,全国几百万人,我们只需要创建 751 个桶,循环一遍挨个扔进去,排序速度是毫秒级。

但是如果数据经过桶的划分之后,桶与桶的数据分布极不均匀,有些数据非常多,有些数据非常少,比如[ 8,2,9,10,1,23,53,22,12,9000 ]这十个数据,我们分成十个桶装,结果发现第一个桶装了 9 个数据,这是非常影响效率的情况,会使时间复杂度下降到 \(O(n\log n)\),解决办法是我们每次桶内排序时判断一下数据量,如果桶里的数据量过大,那么应该在桶里面回调自身再进行一次桶排序。

posted @ 2022-03-01 15:40  Logan_Xu  阅读(149)  评论(0编辑  收藏  举报