常见排序算法汇总(下)

开篇问题:如何根据年龄给100万用户数据排序

以下几种排序就比较适用这种数据量比较大的场景。

 

桶排序

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

图片

如果每个桶的数据分布不均匀,可以在数据比较多的桶里继续划分数据。

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

容器版

public static void bucketSort2(int[] arr){
    int max = arr[0];
    int min = arr[0];
    for (int i = 0; i < arr.length; i++) {
        max = Math.max(max,arr[i]);
        min = Math.min(min,arr[i]);
    }
    //计算桶的数量
    int bucketNum = (max - min) / arr.length + 1;
    List<List<Integer>> bucketArr = new ArrayList<>(bucketNum);
    for (int i = 0; i < bucketNum; i++) {
        bucketArr.add(new ArrayList<>());
    }
    //将每个元素放入桶内
    for (int i = 0; i < arr.length; i++) {
        int index = (arr[i] - min) / arr.length;
        bucketArr.get(index).add(arr[i]);
    }
    //对每个桶进行排序
    for (int i = 0; i < bucketArr.size(); i++) {
        Collections.sort(bucketArr.get(i));
    }

    //取出数据存入数组中
    int index = 0;
    for (int i = 0; i < bucketArr.size(); i++) {
        for (int j = 0; j < bucketArr.get(i).size(); j++) {
            arr[index] = bucketArr.get(i).get(j);
            index++;
        }
    }
}

二维数组版(支持自动扩容)

public static void bucketSort(int[] arr,int bucketSize){
    if (arr.length < 2){
        return;
    }
    int minValue = arr[0];
    int maxValue = arr[0];
    for (int i = 0; i < arr.length; i++) {
        if (arr[i] > maxValue){
            maxValue = arr[i];
        }
        if (arr[i] < minValue){
            minValue = arr[i];
        }
    }
    //桶数量
    int bucketCount = (maxValue - minValue) / bucketSize + 1;
    int[][] buckets = new int[bucketCount][bucketSize];
    int[] indexArr = new int[bucketCount];
    //将数值分配到各个桶中
    for (int i = 0; i < arr.length; i++) {
        int bucketIndex = (arr[i] - minValue) / bucketSize;
        if (indexArr[bucketIndex] == buckets[bucketIndex].length){
            //扩容
            ensureCapacity(buckets,bucketIndex);
        }
        buckets[bucketIndex][indexArr[bucketIndex]++] = arr[i];
    }

    //对每个桶进行排序,使用快速排序
    int k = 0;
    for (int i = 0; i < buckets.length; i++) {
        if (indexArr[i] == 0){
            continue;
        }
        //快速排序
        quickSort(buckets[i],0,indexArr[i] - 1);

        //赋值到原数组
        for (int j = 0; j < indexArr[i]; j++) {
            arr[k++] = buckets[i][j];
        }
    }
}


/**
 * 数组扩容
 * @param buckets 数组
 * @param bucketIndex 下标
 */
private static void ensureCapacity(int[][] buckets,int bucketIndex){
    int[] tempArr = buckets[bucketIndex];
    int[] newArr = new int[tempArr.length * 2];
    for (int i = 0; i < tempArr.length; i++) {
        newArr[i] = tempArr[i];
    }
    buckets[bucketIndex] = newArr;
}

/**
 * 快速排序
 * @param arr 数组
 * @param p 分区点左边
 * @param r 分区点右边
 */
private static void quickSort(int[] arr,int p,int r){
    if (p >= r){
        return;
    }
    //分区点
    int q = partition(arr,p,r);
    //往左边继续分区
    quickSort(arr,p,q - 1);
    //往右遍继续分区
    quickSort(arr,q + 1,r);
}

private static int partition(int[] arr, int p,int r){
    //分区点
    int pivot = arr[r];
    int i = p;
    for (int j = p; j < r; j++) {
        //小于分区点的放左边
        if (arr[j] <= pivot){
            swap(arr,i,j);
            i++;
        }
    }
    swap(arr,i,r);
    return i;
}


private static void swap(int[] arr,int i,int j){
    if (i == j){
        return;
    }
    int temp = arr[i];
    arr[i] = arr[j];
    arr[j] = temp;
}

 

计数排序

我们都经历过高考,高考查分数系统你还记得吗?我们查分数的时候,系统会显示我们的成绩以及所在省的排名。如果你所在的省有50万考生,如何通过成绩快速排序得出名次呢?

图片

考生的满分是900分,最小是0分,这个数据的范围很小,所以我们可以分成901个桶,对应分数从0分到900分。根据考生的成绩,我们将这50万考生划分到这901个桶里。桶内的数据都是分数相同的考生,所以并不需要再进行排序。我们只需要依次扫描每个桶,将桶内的考生依次输出到一个数组中,就实现了50万考生的排序。因为只涉及扫描遍历操作,所以时间复杂度是O(n)。

 

当要排序的n个数据,所处的范围并不大的时候,比如最大值是k,我们就可以把数据划分成k个桶。每个桶内的数据值都是相同的,省掉了桶内排序的时间。

图片

我们对C[6]数组顺序求和,C[6]存储的数据就变成了下面这样子。C[k]里存储小于等于分数k的考生个数。通过这样,就能直接知道每个数的排名了。

图片

 

最后将这些数据依次放入数组R中,即排序成功。

图片

 

从后到前依次扫描数组A。比如,当扫描到3时,我们可以从数组C中取出下标为3的值7,也就是说,到目前为止,包括自己在内,分数小于等于3的考生有7个,也就是说3是数组R中的第7个元素(也就是数组R中下标为6的位置)。

当3放入到数组R中后,小于等于3的元素就只剩下了6个了,所以相应的C[3]要减1,变成6。

图片

代码实现:

public static void countingSort(int[] arr){
    int min = arr[0];
    int max = arr[0];
    for (int i = 0; i < arr.length; i++) {
        max = Math.max(max,arr[i]);
        min = Math.min(min,arr[i]);
    }
    int[] bucket = new int[max - min + 1];
    for (int i = 0; i < arr.length; i++) {
        //减去min是为了防止array[i]小于0,
        // 数组索引不能小于0,相当于做了个偏移。++是为了记录次数
        bucket[arr[i] - min]++;
    }
    int index = 0;
    for (int i = 0; i < bucket.length; i++) {
        while (bucket[i]-- > 0){
            arr[index++] = i + min;
        }
    }
}

基数排序

假设我们有10万个手机号码,希望将这10万个手机号码从小到大排序,你有什么比较快速的排序方法呢?

桶排序、计数排序能派上用场吗?手机号码有11位,范围太大,显然不适合用这两种排序算法。

这种情况下就要用到基数排序:

基数排序是按照低位先排序,然后收集;再按照高位排序,然后再收集;依次类推,直到最高位。

手机号码稍微有点长,画图比较不容易看清楚,可以用字符串排序来模拟一下

图片

可以借助相同的处理思路,先按照最后一位来排序手机号码,然后,再按照倒数第二位重新排序,以此类推,最后按照第一位重新排序。经过11次排序之后,手机号码就都有序了。

图片

根据每一位来排序,用桶排序或者计数排序,时间复杂度可以做到O(n)。如果要排序的数据有k位,那我们就需要k次桶排序或者计数排序,总的时间复杂度是O(k*n)。当k不大的时候,比如手机号码排序的例子,k最大就是11,所以基数排序的时间复杂度就近似于O(n)。

代码实现:


public static void randixSort(int[] arr){
    int max = arr[0];
    //找到最大值
    for (int i = 0; i < arr.length; i++) {
        if (arr[i] > max){
            max = arr[i];
        }
    }
    // 从个位开始,对数组arr按“指数”进行排序
    for (int exp = 1;max / exp > 0;exp *= 10){
        countingSort(arr,exp);
    }
}


private static void countingSort(int[] arr,int exp){
    if (arr.length <= 1){
        return;
    }
    //计算每个元素的个数
    int[] c = new int[10];
    for (int i = 0; i < arr.length; i++) {
        c[(arr[i] / exp) % 10]++;
    }
    //计算排序后的位置
    for (int i = 1; i < c.length; i++) {
        c[i] += c[i - 1];
    }
    //临时数组temp,存储排序之后的结果
    int[] temp = new int[arr.length];
    for (int i = arr.length - 1; i >= 0; i--) {
        temp[c[(arr[i] / exp) % 10] - 1] = arr[i];
        c[(arr[i] / exp) % 10]--;
    }
    //将临时数组的值赋值到原数组中
    for (int i = 0; i < arr.length; i++) {
        arr[i] = temp[i];
    }
}

问题解答

最后我们来看下一开始的问题,如何根据年龄给100万用户排序?

我们假设人大年龄为150,最小1岁,我们可以遍历这100万个用户将其年龄放入150个有序桶中,然后按顺序取出即可。

 

图片

喜欢本文的话,可以关注一下公众号,每天定时更新一篇学习日记,让我们一起成长!

 

 

 

 

 

 

 

 

 

 

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值