桶排序(Bucket sort)

209 阅读3分钟

三种时间复杂度是 O(n) 的排序算法:桶排序、计数排序、基数排序。因为这些排序算法的时间复杂度是线性的,所以我们把这类排序算法叫作线性排序(Linear sort)。之所以能做到线性的时间复杂度,主要原因是,这三个算法是非基于比较的排序算法,都不涉及元素之间的比较操作。

桶排序,顾名思义,会用到“桶”,核心思想是将要排序的数据分到几个有序的桶里,每个桶里的数据再单独进行排序。桶内排完序之后,再把每个桶里的数据按照顺序依次取出,组成的序列就是有序的了。

如果要排序的数据有 n 个,我们把它们均匀地划分到 m 个桶内,每个桶里就有 k=n/m 个元素。每个桶内部使用快速排序,时间复杂度为 O(k * logk)。m 个桶排序的时间复杂度就是 O(m * k * logk),因为 k=n/m,所以整个桶排序的时间复杂度就是 O(n*log(n/m))。当桶的个数 m 接近数据个数 n 时,log(n/m) 就是一个非常小的常量,这个时候桶排序的时间复杂度接近 O(n)。

桶排序比较适合用在外部排序中。所谓的外部排序就是数据存储在外部磁盘中,数据量比较大,内存有限,无法将数据全部加载到内存中。

比如说我们有 10GB 的订单数据,我们希望按订单金额(假设金额都是正整数)进行排序,但是我们的内存有限,只有几百 MB,没办法一次性把 10GB 的数据都加载到内存中。

桶排序(Bucket sort)是一种分布式排序算法,它将一个数组分到有限数量的桶里。每个桶再个别排序(有可能再使用别的排序算法或是以递归方式继续使用桶排序进行排序)。

桶排序的步骤如下:

  1. 初始化一定数量的桶。
  2. 遍历原始数组,根据某种映射函数,将每个元素分配到对应的桶中。
  3. 对每个桶内的元素进行排序,可以使用不同的排序算法。
  4. 按顺序合并所有桶中的元素。

举例子(Java):

import java.util.*;

public class BucketSort {
    public static void sort(int[] array, int bucketCount) {
        if (array.length <= 1) return;
        
        // 找出数组中的最大值和最小值
        int maxVal = array[0];
        int minVal = array[0];
        for (int i = 1; i < array.length; i++) {
            if (array[i] > maxVal) maxVal = array[i];
            if (array[i] < minVal) minVal = array[i];
        }
        
        // 初始化桶
        List<List<Integer>> buckets = new ArrayList<>();
        for (int i = 0; i < bucketCount; i++) {
            buckets.add(new ArrayList<>());
        }
        
        // 分配元素到各个桶
        float interval = ((float)(maxVal - minVal + 1)) / bucketCount; // 计算间隔
        for (int value : array) {
            int index = (int)((value - minVal) / interval);
            if (index >= bucketCount) { // 防止越界
                index = bucketCount - 1;
            }
            buckets.get(index).add(value);
        }
        
        // 对每个桶进行排序,这里使用了集合的排序
        for (List<Integer> bucket : buckets) {
            Collections.sort(bucket);
        }
        
        // 合并桶到原始数组
        int index = 0;
        for (List<Integer> bucket : buckets) {
            for (int value : bucket) {
                array[index++] = value;
            }
        }
    }

    public static void main(String[] args) {
        int[] data = {22, 45, 38, 56, 97, 12};
        int bucketCount = 5; // 可以根据实际情况选择桶的数量
        
        sort(data, bucketCount);
        System.out.println(Arrays.toString(data));
    }
}

在这个实现中,我们首先计算出数组的最大值和最小值来确定桶的数量和范围。然后根据元素的大小分配到不同的桶中。之后,对每个桶内部进行排序(这里使用了Java内置的Collections.sort方法)。最后,把所有桶中的元素按顺序复制回原始数组。

桶排序的性能取决于数据的分布以及桶的数量。在最佳情况下,桶排序可以达到线性的时间复杂度O(n)。但是,如果所有的元素都被分配到同一个桶中,那么桶排序的性能就会退化到O(n^2)。因此,桶排序特别适用于数据分布均匀的情况。

学习:极客时间《数据结构与算法之美》学习笔记