【数据结构与算法】2 - 11 桶排序与计数排序

§2-11 桶排序与计数排序

2-11.1 桶排序(Bucket sort)

排序思想:将待排序的序列中的关键字分散到各个桶中,先保证后一个桶的所有元素均比前一个桶的所有元素大,即 “桶间有序”,然后再使用其他排序算法对桶内元素排序,最终使得整个序列有序。

该算法的关键在于桶的划分以及关键字到桶的映射。

算法实现:使用 ArrayList

//排序入口
public static void bucketSort(int[] arr) {
    //分块
    //先找到最大、最小值
    int maxValue = maxValue(arr);
    int minValue = minValue(arr);
    //确定分块区间和个数
    int bucketCount = (int) Math.sqrt((maxValue - minValue)) + 1;
    ArrayList[] buckets = new ArrayList[bucketCount];
    //防止元素为空
    for (int i = 0; i < buckets.length; i++) {
        buckets[i] = new ArrayList<>();
    }

    //遍历数组,将元素放进分块内
    for (int i = 0; i < arr.length; i++) {
        int index = getBucketIndex(arr[i],bucketCount,maxValue,minValue);
        buckets[index].add(arr[i]);
    }

    //对分块内排序:使用Collections.sort()升序排序
    for (int i = 0; i < buckets.length; i++) {
        Collections.sort(buckets[i]);
    }

    //将结果复制到原数组
    int pos = 0;    //源数组索引
    for (int i = 0; i < buckets.length; i++) {
        for (int j = 0; j < buckets[i].size(); j++) {
            arr[pos++] = (Integer) buckets[i].get(j);
        }
    }
}

//分块索引映射
private static int getBucketIndex(int value, int bucketCount, int max, int min) {
    //数组区间长度
    int range = max - min;
    //分块区间长度(可能不足)
    int length = range / bucketCount + 1;
    //分块索引
    int index = -1;

    //先直接排除位于最值的情况
    if (value == max) {
        index = bucketCount - 1;
    } else {
        //计算索引
        for (int i = 0; i < bucketCount; i++) {
            //分块区间边界
            int start = min + i * length;
            int end = Math.min(start + length, max);
            //桶范围区间,左闭右开
            if (start <= value && value < end) {
                index = i;
                break;
            }
        }
    }

    return index;
}

//寻找数组最小值
private static int minValue(int[] arr) {
    int minIndex = 0;
    for (int i = 1; i < arr.length; i++) {
        if (arr[minIndex] > arr[i])
            minIndex = i;
    }

    return arr[minIndex];
}

//寻找数组最大值
private static int maxValue(int[] arr) {
    int maxIndex = 0;
    for (int i = 1; i < arr.length; i++) {
        if (arr[maxIndex] < arr[i]) {
            maxIndex = i;
        }
    }

    return arr[maxIndex];
}

2-11.2 计数排序(Counting sort)

排序思想:计数排序适用于数据量不大、数据取值范围不大的情况。首先遍历数组,记录数组中不同元素的出现次数。然后根据期望顺序,根据计数器中的数据完成对原数组的排序。

以一个长度为 20,内含 0 ~ 10 的随机数数组为例,其内容一定为 0 ~ 10 的整数,因此计数器数组长度为 11,分别记录 0 到 10 元素的出现次数。然后按计数器索引顺序遍历计数器,完成对原数组的排序。

算法实现:以上述情景为例

public static void countSort(int[] arr) {
    //计数器
    int[] counter = new int[11];

    //遍历并计数
    for (int i = 0; i < arr.length; i++) {
        counter[arr[i]]++;
    }

    System.out.println("计数器:" + Arrays.toString(counter));

    //根据计数器排序数组
    int index = 0;  //原数组索引
    for (int i = 0; i < counter.length;) {
        //若当前计数器不为零,迭代计数器中计数
        if (counter[i] != 0) {
            arr[index++] = i;
            counter[i]--;
        } else {
            //直至计数器清零或当前无该元素
            i++;
        }
    }
}

注意

  • 该算法适用于一定范围内的整数排序,且取值范围不大的情况下;
  • 其性能在某些情况下会快于 \(O(n \log n)\) 的排序,例如快速排序、归并排序。
posted @ 2024-01-14 17:07  Zebt  阅读(60)  评论(0)    收藏  举报