十大排序算法(下):计数排序,基数排序,桶排序

这篇具有很好参考价值的文章主要介绍了十大排序算法(下):计数排序,基数排序,桶排序。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

5. 其他非基于比较的排序

5.1 计数排序

有n个数,取值范围是 0~n,写出一个排序算法,要求时间复杂度和空间复杂度都是O(n)的

我们知道,前面介绍的基于比较的排序算法中,最好的算法,其平均时间复杂度都在O(N),达到线性的时间复杂度就要使用新的排序算法,而这种方法,就称为是计数排序。

计数排序的思路:对于每一待排序元素a,如果知道了待排序数组中有多少比它小的数,就可以直接知道排序后的数组中,a在什么位置上。比如,如果一个数组中有三个数比a小,那么排序后的数组中,a一定会出现在第4位。

那么现在问题转化成,堆排序数组里的数,如何能快速的指导比它小的数字有多少。要解决这个问题,我们不能用比较的办法,因为时间复杂度会高于O(N),只有一个思路,就是用空间来换取时间。不妨设一个大小为(max - min + 1)的数组(其中max是数组中的最大值,min是数组中的最小值)来统计每个数字出现的次数。这就类似于直方图,因此计数排序被称作是基于统计的排序

假设计数排序算法的输入是数组arr,大小为n,而存放排序结果的数组为B,还需要一个 存放临时结果,也就是我们上面提到的直方图结果的数组count。那么计算排序的步骤如下:

  1. 在C中记录A中各值元素的数目

    十大排序算法(下):计数排序,基数排序,桶排序

    for (int i = 0; i < len; i++) {
        count[arr[i]]++;
    }
    
  2. 将count[i]转换成小于等于i的元素个数

    十大排序算法(下):计数排序,基数排序,桶排序

    for (int i = 1; i < max + 1; i++) {
        count[i] += count[i-1];
    }
    
  3. 为A数组从后向前的每个元素找到对应的B中的位置。每次从A中复制一个元素到B中,C中相应的数-1。

    十大排序算法(下):计数排序,基数排序,桶排序

    for (int i = len - 1; i >= 0; i--) {
        int n = count[arr[i]];
        b[n - 1] = arr[i];
        count[arr[i]]--;
    }
    
  4. 当A中的元素都复制到B中后,B就是排好序的结果。然后再将结果赋值给A。

    十大排序算法(下):计数排序,基数排序,桶排序

下面为完整代码

public void countSort(int[] arr) {
    int len = arr.length;
    int max = arr[0];
    for(int i = 1; i < len; i++) {
        if(arr[i] > max) {
            max = arr[i];
        }
    }
    int[] count = new int[max + 1];//以数据的范围作为计数数组的大小
    int[] b = new int[len];
    for (int i = 0; i < len; i++) {
        count[arr[i]]++;
    }
    for (int i = 1; i < max + 1; i++) {
        count[i] += count[i-1];
    }
    for (int i = len - 1; i >= 0; i--) {
        int n = count[arr[i]];
        b[n - 1] = arr[i];
        count[arr[i]]--;
    }
    for (int i = 0; i < len; i++) {
        arr[i] = b[i];
    }
}

需要注意的是,出现多个元素相同的情况时,每当arr[i]放入b中,count[arr[i]]都要减一,这样,下一个与arr[i]相等的元素出现的时候,被放在arr[i]的前面,从而保证了数组的稳定性。

总结

  1. 计数排序在数据范围集中时,效率很高,但是适用范围及场景有限。
  2. 时间复杂度:O(N)
  3. 空间复杂度:O(N)
  4. 稳定。

计数排序还有另外一种做法:只需要一个待排数组和一个计数数组就能做。

大体思路就是:

  1. 找出待排序元素中的最大值和最小值,确定待排序元素的数据范围,然后根据范围确定计数数组的大小。

    int min = arr[0];
    int max = arr[0];
    for (int i = 0; i < arr.length; i++) {
        if(arr[i] < min) {
            min = arr[i];
        }
        if (arr[i] > max) {
            max = arr[i];
        }
    }
    int len = max - min + 1;
    int[] count = new int[len];
    
  2. 遍历arr数组,记录每个待排序元素出现的次数。这里要注意的是,怎么将count数组下标和arr[i]的之联系起来。

    举个例子:

    arr = [91,92,99,92,93,92,99,98,94], max = 99, min = 91

    我们应该这样进行计数:

    十大排序算法(下):计数排序,基数排序,桶排序

    我现在要记录91出现的次数,91应该放在计数数组的0下标,也就是count[0],91 - min = 0,所以我们可以推出来一个等式,设count下标为n,设当前待排序元素的数值为val,n + min = val

    for (int i = 0; i < arr.length; i++) {
        count[arr[i] - min]++;
    }
    
  3. 上述循环走完,计数数组已经存好了对应关系,遍历计数数组,给arr数组重新赋值。

    int k = 0;
    for (int i = 0; i < len; i++) {
        int n = count[i];//记录元素出现的次数
        while (n != 0) {
            arr[k++] = i + min;//运用等式关系
            n--;
        }
    }
    

下面为完整代码:

public static void countArray(int[] arr) {
    int min = arr[0];
    int max = arr[0];
    for (int i = 0; i < arr.length; i++) {
        if(arr[i] < min) {
            min = arr[i];
        }
        if (arr[i] > max) {
            max = arr[i];
        }
    }
    int len = max - min + 1;
    int[] count = new int[len];
    for (int i = 0; i < arr.length; i++) {
        count[arr[i] - min]++;
    }
    int k = 0;
    for (int i = 0; i < len; i++) {
        int n = count[i];
        while (n != 0) {
            arr[k++] = i + min;
            n--;
        }
    }
}

这个方法写出来的排序不一定是稳定的,但是计数排序一定是稳定的。

5.2 桶排序

桶排序的思想就是:划分成多个范围相同的区间,将待排序元素放入对应的区间,每个区间内部进行排序,最后合并。

桶排序可以看成计数排序的扩展版本,计数排序可以看作每个桶放相同的元素,而桶排序中每个桶储存一定范围内的元素。

十大排序算法(下):计数排序,基数排序,桶排序

知道了思想,我们就可以写出相应代码:

  1. 得出最大值和最小值。

    int max = arr[0];
    int min = arr[0];
    for(int i = 1; i < arr.length; i++){
        max = Math.max(max, arr[i]);
        min = Math.min(min, arr[i]);
    }
    
  2. 计算桶的数量,桶可以用ArrayList来表示。

    int count = (max - min) / arr.length + 1;
    ArrayList<ArrayList<Integer>> bucketArr = new ArrayList<>(count);
    for(int i = 0; i < count; i++){
        bucketArr.add(new ArrayList<>());//分配内存
    }
    
  3. 将每个元素放入相应桶内

    for(int i = 0; i < arr.length; i++){
        int num = (arr[i] - min) / (arr.length);
        bucketArr.get(num).add(arr[i]);
    }
    
  4. 对每个桶进行排序

    for(int i = 0; i < bucketArr.size(); i++){
        Collections.sort(bucketArr.get(i));
        //这个方法是专门对实现List接口的数据结构进行排序
    }
    
  5. 将桶中的元素赋值给原数组

    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);
        }
    }
    

完整代码:

public static void bucketSort(int[] arr){
    int max = arr[0];
    int min = arr[0];
    for(int i = 1; i < arr.length; i++){
        max = Math.max(max, arr[i]);
        min = Math.min(min, arr[i]);
    }

    int count = (max - min) / arr.length + 1;
    ArrayList<ArrayList<Integer>> bucketArr = new ArrayList<>(count);
    for(int i = 0; i < count; i++){
        bucketArr.add(new ArrayList<>());//分配内存
    }

    for(int i = 0; i < arr.length; i++){
        int num = (arr[i] - min) / (arr.length);
        bucketArr.get(num).add(arr[i]);
    }

    for(int i = 0; i < bucketArr.size(); i++){
        Collections.sort(bucketArr.get(i));
        //这个方法是专门对实现List接口的数据结构进行排序
    }

    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);
        }
    }
}

总结

  1. 时间复杂度:O(N*(log(N/M)+1))

    设数组元素有N个,桶有M个,平均每个桶有N/M个元素。

    主要步骤有

    • 每个元素放入桶中O(N)
    • 对每个桶进行排序,一共有M次,每次复杂度为O((N/M)*log(N/M)),所以为O(M*(N/M)*log(N/M))

    O(N) + O(M*(N/M)*log(N/M)) = O(N*(log(N/M)+1))

    当N == M时,复杂度为O(N)

  2. 空间复杂度:O(M + N)

  3. 稳定性:取决于桶内排序的算法

5.3 基数排序

基数排序是一种非比较型整数排序算法,其原理是将整数按位数切割成不同的数字,然后按每个位数分别比较。由于整数也可以表达字符串(比如名字或日期)和特定格式的浮点数,所以基数排序也不是只能使用于整数。

基数排序也运用到了桶,根据元素的每位数字来分配桶

十大排序算法(下):计数排序,基数排序,桶排序

下面是具体步骤:

  1. 获得待排序元素中,最大元素的位数

    int maxDigit = getMaxDigit(arr);
    private static int getMaxDigit(int[] arr) {
        int maxValue = getMaxValue(arr);//获取最大元素
        return getNumLength(maxValue);//获取最高位数
    }
    private static int getMaxValue(int[] arr) {
        int maxValue = arr[0];
        for (int value : arr) {
            if (maxValue < value) {
                maxValue = value;
            }
        }
        return maxValue;
    }
    private static int getNumLength(long num) {
        if (num == 0) {
            return 1;
        }
        int length = 0;
        for (long temp = num; temp != 0; temp /= 10) {
            length++;
        }
        return length;
    }
    
    
  2. 根据元素的每一位数字,对其进行排序。

    1.怎么遍历元素的每一位数字呢?

    我们可以创建一个循环,循环的次数是最大位数,在每一次循环中遍历元素的每位数字。

    2.那如何得到元素的每位数字呢?

    举个例子,假如我们要得到361这个数字的个位数字1,先%10,得到1,再/1;要得到十位数字6,先%100,得到61,再/10,得到6;要得到百位数字3,先%1000,得到361,再/100,得到3。

    发现规律了吗?随着位数从个位到百位,我们%的数字和/的数字也以10倍增长,这样我们便可以利用循环,在每次循环中,对此进行*10操作。

    3.现在我们得到了每位数字,如何对他进行排序呢?

    就如同上面的动图,我们可以创建一个二维数组arr[10][],分别对应0~9,然后把相应的数字放入数组中,然后再从arr[0][]开始,将数组中的元素一个个放回到原数组,就完成了本轮排序。

    现在我们把比较重要的3步都已经理解清楚了,下面就是实现代码:

    private static int[] sort(int[] arr, int maxDigit) {
        int mod = 10;
        int dev = 1;
    
        for (int i = 0; i < maxDigit; i++, dev *= 10, mod *= 10) {
            int[][] counter = new int[20][0];
            //这里设定成20,是因为考虑到了负数,[0,10)存储负数,[10,20)存储正数
            for (int j = 0; j < arr.length; j++) {
                int bucket = ((arr[j] % mod) / dev) + 10;
                //(arr[j] % mod) / dev:得到每位数字
                //+10是因为考虑了负数,比如说得到的该位数字为-1,就把他存储到arr[9]中。
                counter[bucket] = arrayAppend(counter[bucket], arr[j]);
                //给数组扩容,因为一开始创建没有分配内存。然后添加元素
            }
            int pos = 0;
            for (int[] bucket : counter) {
                for (int value : bucket) {
                    arr[pos++] = value;//重新赋值
                }
            }
        }
        return arr;
    }
    private static int[] arrayAppend(int[] arr, int value) {
        arr = Arrays.copyOf(arr, arr.length + 1);
        arr[arr.length - 1] = value;
        return arr;
    }
    

完整代码如下:


	public static void radixSort (int[] arr){
	   int maxDigit = getMaxDigit(arr);
	    sort(arr, maxDigit);
	}
	
	private static int getMaxDigit(int[] arr) {
	    int maxValue = getMaxValue(arr);
	    return getNumLength(maxValue);
	}
	
	private static int getMaxValue(int[] arr) {
	    int maxValue = arr[0];
	    for (int value : arr) {
	        if (maxValue < value) {
	            maxValue = value;
	        }
	    }
	    return maxValue;
	}
	
	protected static int getNumLength(long num) {
	    if (num == 0) {
	        return 1;
	    }
	    int length = 0;
	    for (long temp = num; temp != 0; temp /= 10) {
	        length++;
	    }
	    return length;
	}
	
	private static int[] arrayAppend(int[] arr, int value) {
	    arr = Arrays.copyOf(arr, arr.length + 1);
	    arr[arr.length - 1] = value;
	    return arr;
	}
	
	private static int[] sort(int[] arr, int maxDigit) {
	    int mod = 10;
	    int dev = 1;
	
	    for (int i = 0; i < maxDigit; i++, dev *= 10, mod *= 10) {
	        int[][] counter = new int[20][0];
	        for (int j = 0; j < arr.length; j++) {
	            int bucket = ((arr[j] % mod) / dev) + 10;
	            counter[bucket] = arrayAppend(counter[bucket], arr[j]);
	        }
	        int pos = 0;
	        for (int[] bucket : counter) {
	            for (int value : bucket) {
	                arr[pos++] = value;//重新赋值
	            }
	        }
	    }
	    return arr;
	}

总结

  1. 时间复杂度:O(k*N)

    设最大位数为k位

    外层循环O(k),内层循环O(N),整体是O(k*N)

  2. 空间复杂度:O(N)

  3. 稳定性:稳定

十大排序算法已经全部更新完啦!🎉累鼠了累鼠了💀

如果想了解其他的排序算法,可以移步到我的前两篇文章呦

  1. 十大排序算法(上)直接插入排序、希尔排序、直接选择排序、堆排序
  2. 十大排序算法(中):冒泡排序,快速排序(递归和非递归)、归并排序(递归和非递归)

如果有什么疑问,欢迎在评论区给我留言,或者是私信我~文章来源地址https://www.toymoban.com/news/detail-454327.html

到了这里,关于十大排序算法(下):计数排序,基数排序,桶排序的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 桶排序 — 计数排序和基数排序

    计数排序 int类型数组,其中存的是员工的年龄。比如说16 - 150。对于这样的数据来讲,数据状况是受限的。此时如果将数组从小到大进行排序,该如果实现? 这个实现很简单,实现一个统计数组范围从 0 ~ 150,新数组下标对应着老数组的值,新数组的值对应着老数组相同值的

    2024年02月07日
    浏览(37)
  • 排序 | 冒泡 插入 希尔 选择 堆 快排 归并 非递归 计数 基数 排序

    排序算法是一种将一组数据按照特定顺序排列的算法。数据结构排序算法的选择取决于数据的特征、规模和性能需求。 接下来我们就要实现排序~~ 我们需要实现的一些功能: 冒泡排序是一种基本的排序算法,其核心思想是通过多次交换相邻元素的位置,使得每一轮循环都将

    2024年02月04日
    浏览(49)
  • 数据结构:排序干货!(7大排序汇总+快速排序的优化+计数排序+基数排序+桶排序)

    目录 概念 插入排序 直接插入排序 希尔排序 选择排序 直接选择排序 双向选择排序 堆排序 交换排序 冒泡排序 快速排序 Hoare法 挖坑法 前后指针法 快排的优化 三数取中法 非递归快排 归并排序 分治算法+二路归并 非递归归并 应用 排序总结 其他排序 计数排序 简单版本 复杂

    2024年02月06日
    浏览(44)
  • 考研算法33天:基数排序 【基数排序】

    我们前一天写了一道桶排序,今天开始看它的进化版:基数排序,为啥会有这个算法呢?因为我们桶排序有一部是需要统计每个数字出现的次数因此需要开一个相对大的数组 但是就像快速排序这道题,它的数值到了10的9次方了,咋映射呢?总不可能再开这么大一个数组吧?会

    2024年02月11日
    浏览(46)
  • 排序算法————基数排序(RadixSort)

    什么是基数排序???基数排序是一种和快排、归并、希尔等等不一样的排序...它不需要比较和移动就可以完成整型的排序。它是时间复杂度是O(K*N),空间复杂度是O(K+M ) 基数排序是一种借助多的思想对单逻辑进行排序的方法。 基数排序根据每个位来分配

    2024年02月13日
    浏览(34)
  • 【手撕排序算法】---基数排序

    个人主页:平行线也会相交 欢迎 点赞👍 收藏✨ 留言✉ 加关注💓本文由 平行线也会相交 原创 收录于专栏【数据结构初阶(C实现)】 我们直到一般的排序都是通过的 比较和移动 这两种操作来进行排序的。 而今天介绍的基数排序并不是依靠的 比较和移动 来

    2024年02月16日
    浏览(33)
  • 排序算法——基数排序(C语言)

    什么是基数排序???基数排序是一种和快排、归并、希尔等等不一样的排序...它不需要比较和移动就可以完成整型的排序。它是时间复杂度是O(K*N),空间复杂度是O(K+M ) 基数排序是一种借助多的思想对单逻辑进行排序的方法。 基数排序根据每个位来分配

    2024年02月13日
    浏览(33)
  • 深入浅出排序算法之基数排序

    目录 1. 前言 1.1 什么是基数排序⭐⭐⭐ 1.2 执行流程⭐⭐⭐⭐⭐ 2. 代码实现⭐⭐⭐ 3. 性能分析⭐⭐ 3.1 时间复杂度 3.2 空间复杂度 一个算法,只有理解算法的思路才是真正地认识该算法,不能单纯记住某个算法的实现代码! (1) 通过键值得各个位的值,将要排序的元素分配

    2024年02月08日
    浏览(54)
  • 【算法】排序算法(插入排序、希尔排序、选择排序、冒泡排序、快速排序、归并排序、基数排序、堆排序)

    插入排序 :插入排序、希尔排序 选择排序 :选择排序、堆排序 交换排序 :冒泡排序、快速排序 归并排序 基数排序(又叫桶排序) (1)思路图解 从头开始比较相邻元素的值(就是从下标较小的元素开始),使值较大的元素逐渐从前移向后部,就像水里的气泡一样,越来越大,向上

    2024年03月25日
    浏览(51)
  • 【数据结构与算法】基数排序

    基数排序 基数排序(Radix Sort)属于“分配式排序”,又称“桶子法”或 bin sort,顾名思义,它是通过键值的各个位的值,将要排序的元素分配至某些“桶”中,达到排序的作用。 基数排序法是属于稳定性的排序,基数排序法是效率高的稳定排序法。 基数排序是桶排序。 基

    2024年02月15日
    浏览(41)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包