精华内容
下载资源
问答
  • 主要介绍了Python实现的计数排序算法,简单描述了计数排序的算法原理并结合具体实例形式分析了Python计数排序的相关实现与使用技巧,需要的朋友可以参考下
  • 关于计数排序算法 当输入的元素是 n 个 0 到 k 之间的整数时,它的运行时间是 Θ(n + k)。计数排序不是比较排序,排序的速度快于任何比较排序算法。 由于用来计数的数组C的长度取决于待排序数组中数据的范围(等于待...
  • Java实现计数排序

    2017-12-05 17:26:47
    Java实现计数排序不是C,Java实现计数排序不是C,Java实现计数排序不是C
  • 主要介绍了java 实现计数排序和桶排序实例代码的相关资料,需要的朋友可以参考下
  • 计数排序(Counting sort)是一种稳定的排序算法。计数排序使用一个额外的数组Count_arr,其中第i个元素是待排序数组Arr中值等于i的元素的个数。然后根据数组Count_arr来将Arr中的元素排到正确的位置。分为四个步骤:1...
  • 本资源针对原始计数排序法进行了修改,输入的数据可以是负数、小数等数据,把输入数据的范围扩展到实数领域。
  • 算法-计数排序

    2016-04-17 22:40:41
    主要是对算法导论中计数排序的实现
  • 本文实例讲述了JS实现的计数排序与基数排序算法。分享给大家供大家参考,具体如下: 计数排序 计数排序就是简单的桶排序,一个桶代表数组中一个数出现的个数,所以需要一个和数组数字范围一样大的辅助数组,一般用在...
  • C++ 计数排序实例详解

    2020-08-30 01:42:27
    主要介绍了C++ 计数排序实例详解的相关资料,需要的朋友可以参考下
  • 计数排序

    2019-05-10 08:39:45
    认识计数排序 计数排序是一种典型的不需要比较的排序方法,它的算法效率高于基于比较的排序算法的效率。计数排序是一种稳定的排序算法。 简单计数排序 计数排序将待排元素值定义为数组下标,用数组对应的值表示该...

    认识计数排序

    计数排序是一种典型的不需要比较的排序方法,它的算法效率高于基于比较的排序算法的效率。计数排序是一种稳定的排序算法。

    简单计数排序

    计数排序将待排元素值定义为数组下标,用数组对应的值表示该下标(元素)出现的次数,最后将遍历数组即可得到有序的元素。

    比如有一组数[4,2,5,1,2,2,4,5,6],由于需要将元素值转换为数组下标,所以需要一个新的数组counting,长度为元素最大值+1,以保证不会越界,转换之后的counting数组如下所示(下标表示元素值,数组存储的值表示该值出现的次数):
    1
    最后只需要顺序地将数组第i个元素输出counting[i]次即可(升序)。

    以上是计数排序的基本思想。代码实现如下:

    public static void simpleCountingSort(int[] values, boolean asc){
        int maxValue=Tools.getMax(values); //获得最大元素
        int[] counting=new int[maxValue+1];
        int i,index=0;
        for(i=0;i<values.length;i++){
            counting[values[i]]++;
        }
        for(i=0;i<counting.length;i++){
            for(int j=0;j<counting[i];j++){
                values[asc?index++:values.length-(index++)-1]=i;
            }
        }
    }
    

    以上计数排序存在缺陷;如果遇到[90,99,95,94,95]这样一组数,就需要申请一个100长度的数组空间,而前90个空间都是毫无用处的,针对这样的问题对计数排序进行了改进。

    改进的计数排序

    由于一组数的同时减去一个相同的值之后它们的相对大小并无变化,这就是计数排序的改进思路。将一组数同时减去最小值,得到一组新的数,对新得到的数进行简单计数排序,最后再将排序的结果同时加上最小值。

    比如[90,99,95,94,95]这组数,首先变成[0,9,5,4,5],然后进行排序[0,4,5,5,9],再加上90,得到最后的结果[90,94,95,95,99]。
    2

    代码实现如下:

    public static void countingSort(int values[],boolean asc){
        int minValue=Tools.getMin(values);
        int[] subMin=new int[values.length];
        for(int i=0;i<values.length;i++){
            subMin[i]=values[i]-minValue;
        }
        simpleCountingSort(subMin,asc);
        for(int i=0;i<values.length;i++){
            values[i]=subMin[i]+minValue;
        }
    }
    

    具有记忆功能的计数排序

    计数排序最大的特点在于不需要比较,通过出现的次数决定元素的排序,所以当遇到重复元素时,根本无法区别两个元素。

    比如有下面的一组键值对:
    [“A”:90,“B”:99,“C”:95,“D”:94,“E”:95]

    虽然有两个95,但是它们是有区别的,上面所提的方法无法区分是C还是E的95;所以我们对改进的计数算法再进行一些优化:

    首先还是要得到counting数组
    3
    然后从第i(i>0)个元素开始,执行counting[i]+=counting[i-1]操作,最后得到的数组如下:
    4
    其中counting[9]=5表示,99应该排在第5位(升序最后一位)。

    如何得到最后的排序结果?比如(A,90),90对应下标0,应该排在第1位,所以最后的结果数组的第一个元素是90,然后将counting[0]=counting[0]-1
    5
    再看(B,99),99对应9,排在第5位,然后再将counting[9]=counting[9]-1;以此类推,直到原始数据遍历完成即可。(如果要降序排序,只需要将排在的位数反过来即可)

    实际上,执行counting[i]+=counting[i-1]操作就是为了记录各元素值的排序的位置。

    public static int[] memoryCountingSort(int[] values,boolean asc){
        int maxValue=Tools.getMax(values);
        int minValue=Tools.getMin(values);
        int[] counting=new int[maxValue-minValue+1];
        int i;
        for(i=0;i<values.length;i++){
            counting[values[i]-minValue]++;
        }
        for(i=1;i<counting.length;i++){
            counting[i]+=counting[i-1];
        }
        int[] result=new int[values.length];
        for(i=0;i<values.length;i++){
    		//请仔细斟酌这一句话
            result[asc?(counting[values[i]-minValue]--)-1:values.length-(counting[values[i]-minValue]--)]=values[i];  
        }
        return result;
    }
    

    总结

    以上就是计数排序的基本思想与改进,最后简单的总结一下计数排序:

    • 计数排序是一种稳定的,线性时间的排序算法,不需要比较
    • 计数排序只能对整数排序,因为数组下标只能是整数
    • 计数排序受数据本身的局限性,只适合数据值大小集中的情况

    感谢阅读!
    个人微信公众号:SmartPig
    个人博客网站:http://smartpig612.club

    展开全文
  • 一个星期没有写了,今天还是留点时间写一写自己的博客,周六去考试了趋势科技,感受到了自己在软件设计方面还存在的知识缺陷,测试、网络安全等方面都是空白,其他的相对来说要好一点,记得第一题是关于unicode方面...
  • 计数排序 用C++实现 简单易懂 欢迎下载
  • C++计数排序详解

    2021-01-20 05:37:52
    计数排序不同于比较排序,是基于计数的方式,对于计数排序,假设每一个输入都是介于0~k之间的整数。对于每一个输入元素x,确定出小于x的元素的个数。假如有17个元素小于x,则x就属于第18个输出位置。 计数排序涉及到...
  • 计数排序 找到给定序列的最小值与最大值 创建一个长度为最大值-最小值+1的数组,初始化都为0 然后遍历原序列,并为数组中索引为当前值-最小值的值+1 此时数组中已经记录好每个值的数量,自然也就是有序的了 ...
  • 使用计数排序计算中位数和平均值 ###安装依赖项 在Windows上 npm install 在Linux上 sudo npm install 启动应用 npm start 测试 npm test 覆盖范围 npm run test-cov ESLint npm run lint
  • 计数排序不是基于元素比较,而是利用数组下标来确定元素的正确位置。计数排序是一个稳定的排序算法。当输入的元素是 n 个 0到 k 之间的整数时,时间复杂度是O(n+k),空间复杂度也是O(n+k),其排序速度快于任何比较...

    计数排序不是基于元素比较,而是利用数组下标来确定元素的正确位置。计数排序是一个稳定的排序算法。当输入的元素是 n 个 0到 k 之间的整数时,时间复杂度是O(n+k),空间复杂度也是O(n+k),其排序速度快于任何比较排序算法。当k不是很大并且序列比较集中时,计数排序是一个很有效的排序算法。
    算法思路
    计数排序的算法思路是定义一个大小为序列最大值-最小值的大小的数组,遍历每一个元素,将其映射到数组中,例如最大值和最小值相差10,就定义一个大小为10的数组,每一个整数按照其值对号入座,对应数组下标的元素进行加1操作。

    比如第一个整数是9,那么数组下标为9的元素加1:
    在这里插入图片描述
    最终,数列遍历完毕时,数组中的每一个值,代表了数列中对应整数的出现次数。有了这个统计结果,排序就很简单了,直接遍历数组,输出数组元素的下标值,元素的值是几,就输出几次。
    不稳定的计数排序
    算法的实现可以分为三个步骤

    1. 第一步 定义最大值和最小值,和一个最后添加会原来数组的下标index,然后遍历输入的序列,找到最大值和最小值赋值给max和min。
     public int[] countingSort(int[] array){
            int index = 0;
            int min = array[1];
            int max = array[1];
            for (int i:array) {
                if (i>max){
                    max = i;
                }
                if (i<min){
                    min = i;
                }
            }
    
    1. 第二步 定义一个大小为max-min+1大小的数组,遍历序列将序列中的每一个元素减去min,将其映射到数组下标中,如果有相同的元素,则++;
            int[] list = new int[max-min+1];
            for (int i:array){
                list[i-min]++;
            }
    
    1. 第三步 遍历临时储存的list数组,当list[i]的大小不等于0,即还有元素,将其赋给原数组array,注意这里的赋值是i+min而不是list[i+min]
            for (int i = 0; i < list.length; i++) {
                while (list[i]>0){
                    array[index++] = i+min;
                    list[i]--;
                }
            }
            return array;
        }
    

    稳定的计数排序
    稳定的基数排序跟上者的差别是在,如果值相同,则遵循原表固有顺序。
    算法思路
    算法的具体思路是在遍历完序列映射到数组中后,例如下图是已经映射好的数组。原序列为{90,99,95,95,94}
    在这里插入图片描述在第二个下标开始,加上前面元素的和,例如上图第一个下标为1,那么第二个下标的数值就为本身的0加上前面的1等于1。第三个就等于第三加上前面的数的总个数,以此类推。
    这个数值其实就代表了映射的元素在原数组中因该在的位置,整个数组中有用的下标就只有0,4,5,9四个下标是由代表有数值的,其他都是空的,下标为0的数值为1即使第一个数字,下标为4的数值为2即第二个数,下标为5的数值为4,我们看到95是有两个数,所以是第3的数和第4的数,最后一个为第五的数。
    理解了上面后,在赋值给原数组的过程中,从后往前遍历,例如原序列为{90,99,94,95,95},第一个数为95,在映射的数组中下标为4,即原数组array[4]=95,然后4-1=3,即如果有第二个95,它的位置就是3,然后第二个数是95,这时候下标为3,即array[3]=95,依照这种算法随后得到的结果就为{90,94,95,95,99},而且是稳定的排序。

    在这里插入图片描述代码的实现分为两步
    第一步在得到了映射后的数组countArray后,从第二个下标开始,加上前面的数的个数,即countArray[i] = countArray[i-1]+countArray[i]。

            for (int i = 1; i < countArray.length; i++) {
                countArray[i] = countArray[i-1]+countArray[i];
            }
    

    第二步是定义一个数组用于存储,countArray[i-min]即映射数组的值,即第几个元素,例如第5个元素,那么下标就为4,所以要-1,然后countArray[i-min]自减。

            int[] sortArray = new int[array.length];
            for (int i:array){
                sortArray[countArray[i-min]-1] = i;
                countArray[i-min]--;
            }
    
    
    展开全文
  • Python实现计数排序

    千次阅读 2020-07-19 00:39:20
    Python实现计数排序

    Python实现计数排序

    一、计数排序简介

    计数排序(Counting Sort)是一种不比较数据大小的排序算法,是一种牺牲空间换取时间的排序算法。

    计数排序适合数据量大且数据范围小的数据排序,如对人的年龄进行排序,对考试成绩进行排序等。

    计数排序先找到待排序列表中的最大值 k,开辟一个长度为 k+1 的计数列表,计数列表中的所有初始值都为 0。走访待排序列表,如果走访到的元素值为 i,则计数列表中索引 i 的值加1,走访完整个待排序列表,就可以统计出待排序列表中每个值的数量。然后创建一个新列表,根据计数列表中统计的数量,依次在新列表中添加对应数量的 i ,得到排好序的列表。

    二、计数排序原理

    计数排序的原理如下:

    1. 找到待排序列表中的最大值 k,开辟一个长度为 k+1 的计数列表,计数列表中的值都为 0。

    2. 走访待排序列表,如果走访到的元素值为 i,则计数列表中索引 i 的值加1。

    3. 走访完整个待排序列表,计数列表中索引 i 的值 j 表示 i 的个数为 j,统计出待排序列表中每个值的数量。

    4. 创建一个新列表,遍历计数列表,依次在新列表中添加 j 个 i,新列表就是排好序后的列表,整个过程没有比较待排序列表中的数据大小。

    以列表 [5, 7, 3, 7, 2, 3, 2, 5, 9, 5, 7, 6] 进行升序排列为例。列表的初始状态如下图。

    1. 待排序列表中的最大值为9,所以开辟一个长度为10的计数列表,索引为0~9,值全为0。

    2. 走访待排序列表,如果走访到的元素值为 i,则计数列表中索引 i 的值加1。第一个元素值为5,计数列表中索引5的值加1,从0变为1。

    3. 第二个元素值为7,计数列表中索引7的值加1,从0变为1。

    4. 第三个元素值为3,计数列表中索引3的值加1,从0变为1。

    5. 第四个元素值为7,计数列表中索引7的值加1,从1变为2。

    6. 重复走访完整个待排序列表,所有元素的个数都统计到了计数列表中。

    7. 创建一个新列表,遍历计数列表,依次在新列表中添加对应数量的元素。0和1都是0个,不需要添加,2有两个,在新列表中添加两个2。添加后计数列表中减掉对应的数量。

    8. 3有两个,继续在新列表中添加两个3。添加后计数列表中减掉对应的数量。

    9. 遍历整个计数列表,添加完所有的数据,新列表就是排序完成后的列表。排序结果如下图。

    三、Python实现计数排序

    # coding=utf-8
    def counting_sort(array):
        if len(array) < 2:
            return array
        max_num = max(array)
        count = [0] * (max_num + 1)
        for num in array:
            count[num] += 1
        new_array = list()
        for i in range(len(count)):
            for j in range(count[i]):
                new_array.append(i)
        return new_array
    
    
    if __name__ == '__main__':
        array = [5, 7, 3, 7, 2, 3, 2, 5, 9, 5, 7, 6]
        print(counting_sort(array))

    运行结果:

    [2, 2, 3, 3, 5, 5, 5, 6, 7, 7, 7, 9]

    代码中,使用Python内置函数max()求出待排序列表中的最大值。然后根据上面分析的排序原理,进行计数,再将数据添加到新列表中。i 表示计数列表的索引,也表示待排序列表中值为 i 的元素,j 表示值为 i 的元素有 j 个。

    四、计数排序的时间复杂度和稳定性

    1. 时间复杂度

    在计数排序中,需要走访待排序列表中的每一个元素,进行计数,列表长度为 n ,然后需要遍历计数列表,添加数据到新列表中,计数列表长度为 k+1 ,时间复杂度为 T(n)=n+k+1,再乘计数和添加数据的步骤数(常数,不影响大O记法),所以计数排序的时间复杂度为 O(n+k) 。

    2. 稳定性

    根据计数排序的应用场景,待排序列表中有很多值相等的元素。不过,计数排序没有比较待排序列表中的数据大小,也没有进行位置交换,相等数据的相对次序是保持不变的。所以计数排序是一种稳定的排序算法。

     

     

    展开全文
  • 十大经典排序算法-堆排序,计数排序,桶排序,基数排序 1-堆排序 算法思想: 算法图解: 示例代码: 在这里插入代码片 复杂度分析: 2-计数排序 算法思想: 算法图解: 示例代码: 在这里插入代码片 复杂度分析: 3-桶排序 ...

    养成习惯,先赞后看!!!

    你的点赞与关注真的对我非常有帮助.如果可以的话,动动手指,一键三连吧!!!

    十大经典排序算法-堆排序,计数排序,桶排序,基数排序

    前言

    这是十大经典排序算法详解的最后一篇了.
    还没有看多之前两篇文章的小伙伴可以先去看看之前的两篇文章:

    十大经典排序算法详解(一)冒泡排序,选择排序,插入排序
    十大经典排序算法详解(二)希尔排序,归并排序,快速排序

    这一篇文章真的耗费了我巨大的时间和精力,由于 堆排序是基于二叉树 的,所以写的篇幅比较大并且由于是关于树的,所以画图动态演示的工程量就进一步增加,其次就是因为计数排序,桶排序以及基数排序并不是基于比较的,所以算法的思想讲解相对于之前的基于比较的算法而言会稍微难一点.

    其次就是这次的调试过程也比之前多了很多需要注意的地方,这些我都会在下面的代码中通过注释的方式提醒大家.

    最后如果大家觉得我的文章写得还可以或者觉得文章对你有帮助的话,可以选择关注我的公众号:萌萌哒的瓤瓤,或者你也可以帮忙给文章来个一键三连吧.你的支持对我真的很有用.

    在这里插入图片描述

    1-堆排序

    算法思想:

    在介绍算法之前我们首先需要了解一下下面这些概念:什么是二叉树,什么是完全二叉树,什么是大根堆,什么是小根堆.

    • 二叉树

    学过数据结构的小伙伴肯定知道什么是二叉树,这部分主要是为那些可能还不太了解数据结构的小伙伴们说的.

    二叉树的定义就是每个结点至多能有两个子树,二叉树是一种最简单的树,下面我们举几个树的例子:

    在这里插入图片描述

    我们可以来哦稍微区分一下,很明显只有4号树并不是我们所说的二叉树,因为它的1号结点下有三棵子树,所以4号树并不是我们所说的二叉树.到这里,相信大家也已经基本了解二叉树得出概念了,那么接下来我们再来了解一下另一个概念完全二叉树.

    • 完全二叉树

    说到完全二叉树,就应该知道它首先应该满足二叉树的一些条件,就比如说每个节点至多只能有两个子树,那么除了这个条件以外,还需要什么条件才能称得上是完全二叉树呢.

    官方是这样说的: 一棵深度为k的有n个结点的二叉树,对树中的结点按从上至下、从左到右的顺序进行编号,如果编号为i(1≤i≤n)的结点与满二叉树中编号为i的结点在二叉树中的位置相同,则这棵二叉树称为完全二叉树。

    但是很明显说的太官方了,我们肯定需要简化一下概念.其实说白了就是在二叉树的基础上,所有的节点顺序必须要按照下面这样的顺序进行排列,否则就不能说是完全二叉树

    在这里插入图片描述

    并且每次必须是已经放满2的幂数之后才能放到下一层,并且必须是从每层最左边的节点开始添加节点,并且必须是先添加左节点在添加右节点.否则就不能称为是完全二叉树,这里呢,我们举几个反例,大家就知道我说的是什么意思了:

    在这里插入图片描述

    上面的这两棵树就是最明显的反例,看完这两棵树之后,相信大家就能更加了解什么是完全二叉树了.

    • 大根堆

    大根堆其实很容易理解,大根堆在完全二叉树的基础上就一条判定条件就是:每个根节点的值必须大于它的左孩子节点已经右孩子节点的值.满足这样条件的二叉树,我们就称这个二叉树是大根堆.当然了只要有一个节点不满足这样的情况,那么就不能称这是大根堆.

    举个例子,下面这棵二叉树就是一个大根堆:

    在这里插入图片描述

    举完正确的例子之后,我们当然也需要来举几个反例来帮助我们更好的理解什么是大根堆:

    在这里插入图片描述

    看完这两个反例之后相信大家就能更加理解什么是大根堆了.

    • 小根堆

    当然了,理解完什么是大根堆了之后,大家就能举一反三的了解什么叫做小根堆了.这里就不再给大家废话.

    在了解完上面的这些概念之后,我们就可以来讲解什么是堆排序了.

    堆排序的算法步骤其实很简单,总共就三步.

    • 1.将数组重构成大根堆

    • 2.将数组的队头元素与队尾元素交换位置

    • 3.对去除了队尾元素的数组进行重构,再次重构成大根堆

    之后重复上述2,3步骤,直到需要重构成大根堆的数组为空为止.

    算法的步骤的确简洁明了,其实大家看了也应该已经懂了.

    因为每次重构成大根堆之后,根据大根堆的特性,每个节点的值一定大于左右孩子节点的值,所以很明显大根堆的根节点就是二叉树中值最大的值同时也就是数组中最大的值.所以重构成大根堆之后交换数组队头与队尾元素的操作就是在将最大的元素进行定位.也就意味着这一轮结束之后,数组中已经确定了一个元素的最终位置了.

    算法的思想就是这样,谁都能说的出来,但是呢,堆排序的难点就是在于我们如何将数组重构成我们大根堆.这个就是堆排序的难点.

    那么接下来,我们就着重讲解一下重构大根堆的过程是怎么样的.

    首先我们需要明白一点就是我们一开始构建二叉树的时候遵循的是这样的原则: 从上往下,从左往右 .但是到了将二叉树重构成大根堆的时候我们的原则就必须要反过来了:从下往上,从右往左.这个大家动动小脑瓜应该就能理解了.

    显然我们每次对小子树进行重构成大根堆的操作时,最后都会使得最大的元素上移,对不对,既然大的元素是在上移的,那么很显然我们就应该从下往上开始构建.

    既然我们已经知道重构的顺序是什么样的之后,我们就需要再明白一点,那就是我们应该对哪些元素进行重构的操作呢?上面我们已经说过了,大根堆的一个硬性条件就是每个节点必需要大于它的左右孩子节点,那么很显然如果节点本身没有孩子节点的话,就不需要进行重构了.所以我们需要进行重构的元素必定包含孩子节点.并且结合我们上面所说重构顺序基本就可以得出一个结论:重构的元素就是最后一个非叶子节点之前的所有节点,包括该节点本身.,就比方下面这张图中红色圈圈的元素.

    在这里插入图片描述

    之后我们只需要通过循环,依次进行下面的操作:

    比较节点及其左右孩子节点,如果有孩子节点的值大于该节点,那么就交换两者的位置.
    这里需要大家特别注意!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

    这里需要大家特别注意!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

    这里需要大家特别注意!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
    交换两者的位置之后我们还需要进行一个步骤 重新校验
    特别注意!!!特别注意!!!重复这个过程,直到最后一个元素为止.

    到这里堆排序的基本思想也就已经基本讲解完毕了,接下来就是通过图来动态的演示一下堆排序的过程,这样能够让大家更好的理解堆排序:

    这是第一轮堆排序:
    在这里插入图片描述)
    第二次堆排序:
    在这里插入图片描述)
    第三次堆排序:
    在这里插入图片描述)
    这里给大家模拟三次,大家应该就差不多懂这个流程了.主要是这图图画起来实在是太麻烦了.能力有限就只画这三次的了.在下面的代码里面,我还会着重讲重新校验的过程,大家如果这里还没理解的,也可以接着往下面看.

    算法的基本思想大家应该基本上就能理解了.那么我们再来稍微聊聊堆排序的一些特点:

    • 堆排序是不稳定的,这个其实还是比较好理解的.因为我们在进行小分支的调整时时有序的,但是之后可能会出现小分支扩大到大分支进行重新的调整,那么这时候就有可能会出现元素的相对位置混乱的情况.

      这个混乱的过程其实有点像我们之前所说的希尔排序,希尔排序也是小区间内的插入排序是有序的,但是一旦扩散到更大的区间进行二次的插入排序时就有可能造成相对位置混乱的情况.

      说的差不多了,那么我们接下来还是举一个例子来帮助大家更好的理解:

      在这里插入图片描述
      通过上面这张图,相信大家就能更好的理解堆排序为啥是不稳定的了.

    • 堆排序每轮排序都可以确定一个元素的最终位置,这个大家看我上面的演示图也能看出来.

    算法图解:

    在这里插入图片描述

    示例代码:
    这是我写的第一版的代码:

    //交换数组中的元素
    	public static void swap(int[]num ,int i,int j) {
    		int temp=num[i];
    		num[i]=num[j];
    		num[j]=temp;
    	}
    	//将待排序的数组构建成大根堆
    	public static void buildbigheap(int []num,int end) {
    		//从最后一个非叶子节点开始构建,依照从下往上,从右往左的顺序
    		for(int i=end/2;i>=0;i--) {
    			int left=2*i+1;
    			int right=2*i+2;
    			int big=i;
    			//判断小分支那个是大元素
    			if(left<end&&num[i]<num[left])
    //				swap(num, i, left);
    				i=left;
    			if(right<end&&num[i]<num[right])
    //				swap(num, i, right);
    				i=right;
    			swap(num, i, big);
    		}
    	}
    	public static void main(String[] args) {
    		int []num ={7,4,9,3,2,1,8,6,5,10};
    		long startTime=System.currentTimeMillis();  
    		//第一次构建大根堆
    		buildbigheap(num, num.length);
    		for(int j=num.length-1;j>0;j--) {
    			//交换队头已经排序得到的最大元素与队尾元素
    			swap(num, 0, j);
    			System.out.print("第"+(num.length-j)+"次排序:  ");
    			for(int k=0;k<num.length;k++) {
    				System.out.print(num[k]+" ");
    			}
    			System.out.println();
    			//交换结束之后,大根堆已经内破坏,需要开始重新构建大根堆
    			buildbigheap(num,j);
    		}
    		long endTime=System.currentTimeMillis(); 
    		System.out.println("程序运行时间: "+(endTime-startTime)+"ms"); 
    	}
    

    在这里插入图片描述

    一开始我觉得我的代码是对的,并且运行出来的结果也和我预期的一样,但是当我自己画图以后画到这张图的时候我就知道算法还是有BUG的,这个BUG就是每次构建大根堆的时候:我们的确能够在每次构建大根堆的时候将最大的元素挑选出来,但是,我们在挑选出当前最大的元素之后,我们的大根堆真的还是大根堆吗,这里用上面画的图,我们就能看出来了:

    在这里插入图片描述

    很明显这个这一步我们的确已经将最大的元素挑选出来了,但是我们当前的已经不是大根堆了,所以我就在想我到底是哪一步做错了呢.之后我参考了网上的资料发现,该算法还有一个重点就是:如果我们发现根节点与孩子节点交换顺序之后,我们就需要重新检查交换之后的孩子节点以下的所有节点是否还满足大根堆的定义,因为可能我们交换后的孩子节点的值还是比他的孩子节点要小的.就比方上面那张图里我们所看到的.所以修改后的代码主要就是加上了重新校验的过程.

    修改后的第二版代码:

    //交换数组中的元素
    		public static void swap(int[]num ,int i,int j) {
    			int temp=num[i];
    			num[i]=num[j];
    			num[j]=temp;
    		}
    		//将待排序的数组构建成大根堆
    		public static void buildbigheap(int []num,int end) {
    			//从最后一个非叶子节点开始构建,依照从下往上,从右往左的顺序
    			for(int i=end/2;i>=0;i--) {
    				adjustnode(i, end, num);
    			}
    		}
    		//调整该节点及其以下的所有节点
    		public static void  adjustnode(int i,int end,int []num) {
    			int left=2*i+1;
    			int right=2*i+2;
    			int big=i;
    			//判断小分支那个是大元素
    			if(left<end&&num[i]<num[left])
    				i=left;
    			if(right<end&&num[i]<num[right])
    				i=right;
    			 if(i!=big) {
    			     //交换顺序之后需要继续校验
    				 swap(num, i, big);
    				 //重新校验,防止出现交换之后根节点小于孩子节点的情况
    				 adjustnode(i, end, num);
    			 }
    		}
    		public static void main(String[] args) {
    			int []num ={5,3,7,1,4,6,2};
    			long startTime=System.currentTimeMillis();  
    			//第一次构建大根堆
    			buildbigheap(num, num.length);
    			for(int j=num.length-1;j>0;j--) {
    				System.out.print("第"+(num.length-j)+"次排序前:  ");
    				for(int k=0;k<num.length;k++) {
    					System.out.print(num[k]+" ");
    				}
    				//交换队头已经排序得到的最大元素与队尾元素
    				swap(num, 0, j);
    				System.out.print("第"+(num.length-j)+"次排序后:  ");
    				for(int k=0;k<num.length;k++) {
    					System.out.print(num[k]+" ");
    				}
    				System.out.println();
    				//交换结束之后,大根堆已经被破坏,需要开始重新构建大根堆
    				buildbigheap(num,j);
    			}
    			long endTime=System.currentTimeMillis(); 
    			System.out.println("程序运行时间: "+(endTime-startTime)+"ms"); 	
    		}
    

    在这里插入图片描述
    这里我们将这两个排序结果对比一下,大家就更加能了解重新校验步骤的重要性了.
    在这里插入图片描述
    相信经过我这样的讲解之后,大家一定能够更好的理解堆排序了.

    复杂度分析:

    理解完堆排序的基本思想之后,我们就需要来分析一下他的时间复杂度,空间复杂度.

    • 时间复杂度

      堆排序的本质思想也是利用了二叉树的特性,所以根据他的遍历次数以及二叉树的层数可以得到堆排序的时间复杂度为O(N*logn),不仅仅是平情况是这样最好与最坏的情况都是如此.

    • 空间复杂度

      这个我们可以看到我们整个排序的过程中只增加一个存储交换元素的temp,所以堆排序的空间复杂是常量级别的仅为O(1).

    2-计数排序

    算法思想:
    计数排序最核心的思想就是计数序列中每个元素出现的次数,我们将每个元素的数量都记录下来之后.我们就可以通过按

    了解完计数排序的基本思想之后,我们就来看看看这个算法的实现步骤又是怎么样的呢?主要就是下面这几个步骤:

    • 1.第一次遍历序列,找出序列中的最大值以及最小值,然后根据最大值MAX最小值MIN创建一个MAX-MIN+1长度的数组.为什么创建这样长度的数组呢,因为只有创建了这样长度的数组,MIN-MAX区间内的每个元素才有对应的位置进行存放,如下图所示:
      在这里插入图片描述

    • 2.第二次遍历序列,我们每次遍历一个元素都将该元素所对应的区间数组对应的位置进行+1操作,这个步骤其实就是我们计数排序的核心----计数了.遍历结束之后,区间数组中的元素值就代表相应元素出现的次数,如下图所示:
      在这里插入图片描述

    • 3.最后一步就只需要按照区间数组中的次数一次将该元素打印出来即可.如下图所示:
      在这里插入图片描述

    计数排序的基本思想基本就是这样,按照惯例,还是通过下面的图来帮助大家更好的理解计数排序的基本思想:
    在这里插入图片描述

    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述

    了解完计数排序的基本思想之后,我们还是按照惯例分析一下计数排序算法的一些特点:

    -计数排序是稳定的 ,这个大家应该能很明显的看出来,因为计数排序本身并不是基于比较的算法.

    -计数排序需要的额外空间比较大,这个大家很明显的看出来,并且空间浪费的情况也会比较严重,因为一旦序列中MAX与MIN的差距过大,那么需要的内存空间就会非常大.并且假如序列中的元素都是散布在一个特定的区间内,那么内存空间浪费的情况也会非常明显.

    算法图解:

    在这里插入图片描述

    示例代码:

    public static void main(String[] args) {
    		int []num ={7,4,9,3,2,1,8,6,5,10};
    		long startTime=System.currentTimeMillis();  
    		int min=Integer.MAX_VALUE;
    		int max=Integer.MIN_VALUE;
    		//先找出数组中的最大值与最小值
    		for(int i=0;i<num.length;i++) {
    			if(num[i]<min)
    				min=num[i];
    			if(num[i]>max)
    				max=num[i];
    		}
    		//创建一个长度为max-min+1长度的数组来进行计数
    		int []figure=new int [max-min+1];
    		for(int i=0;i<num.length;i++) {
    			//计算每个数据出现的次数
    			figure[num[i]-min]++;
    		}
    		int begin=0;
    		//创建一个新的数组来存储已经排序完成的结果
    		int []num1=new int [num.length];
    		for(int i=0;i<figure.length;i++) {
    			//循环将数据pop出来
    			if(figure[i]!=0) {
    				for(int j=0;j<figure[i];j++) {
    					num1[begin++]=min+i;
    				}
    			}
    		}
    		System.out.println("数据范围:"+min+"~"+max);
    		System.out.println("计数结果:  ");
    		for(int i=0;i<num.length;i++)
    			System.out.println("         "+num[i]+"出现"+figure[num[i]-min]+"次");
    		System.out.print("排序结果:  ");
    		for(int i=0;i<num1.length;i++)
    			System.out.print(num1[i]+"   ");
    		System.out.println();
    		long endTime=System.currentTimeMillis(); 
    		System.out.println("程序运行时间: "+(endTime-startTime)+"ms"); 
    	}
    

    在这里插入图片描述

    复杂度分析:

    理解完计数排序的基本思想之后,我们就需要来分析一下他的时间复杂度,空间复杂度.

    • 时间复杂度

      计数排序很明显是一种通过空间来换时间的算法,因为我们可以很明显的看到计数排序需要三次遍历,两次遍历我们的原序列,第三次是遍历我们的区间数组.那么很明显时间复杂度一定是线性级别的但是因为第三次遍历的并不是我们的原序列,而是我们的区间数组,所以时间复杂度并不是我们的平常的O(n),而是应该加上我们遍历区间数组的时间,假设我们的区间数组长度为k的话,那么我们的时间复杂度就是O(n+k)

    • 空间复杂度

      上面我们已经说过了,计数排序本身就是一个通过空间来换取时间的算法,所以很明显他的空间复杂度就会很高.并且这个空间复杂度主要就取决于我们区间数组的长度,所以假设我们的区间数组长度为k的话,那么我们的空间复杂度就为O(k)

    3-桶排序

    算法思想:
    大家第一眼看到这个算法的名字时相信大家的反应应该和我是一样的,桶排序?排序怎么还需要用到桶呢?桶排序里的桶又是主要是干什么的呢?

    其实这个大家类比到我们平常生活中就能基本知道桶排序的桶是干嘛的呢?在我们的日常生活中,我们的桶一般都是用来装东西的,我们可能是用来装水,又或者是装钱的反正不管怎么样,我们的桶最后都是一个容器,是用来存储相应的物质的.

    在这里插入图片描述

    显然我们当前存在的只有我们的待排序的序列,那么我们的桶就是用来存储我们的序列中的元素的.就像下图所示:
    在这里插入图片描述

    可以看到我们把相应的元素放入相应的桶里面了.这个放入的规则是这样的:桶是从大到小排列的,并且每一个桶都会有一个数据范围,意思就是0号桶存放是1~ 2数据范围的数据,1号桶存放3~4数据范围的数据,2号桶存放吧5 ~6数据范围内的数据.详细的放入规则我会在下面的实现步骤里面说.这里大家先简单了解一下.

    这里大家要注意的一点就是,我们在把元素放入各自相应的桶里面的时候,是需要对桶内的序列进行排序,意思就是等到每个元素都放入相应的桶里面之后,桶里面相应的序列本身也已经有序了.就如下图所示:
    在这里插入图片描述
    可以看到上面,每个桶内的序列都已经排好序了,那么很显然我们最后就只需要按照桶的序号大小将桶内的元素打印出来,那么我们的序列就已经排好序了.

    说完桶排序的基本思想之后,我们接下来就说一下桶排序在代码上是如何实现的,大致有下面这几步:

    • 1.我们首先需要第一次遍历我们的序列,得到我们序列中的最大值MAX以及序列中的最小值MIN,找到我们序列中的最大值与最小值之后,那么我们就可以确定序列中的所有都是在MIN~MAX这个数据范围区间之中.

    • 2.第二步我们就是需要根据序列的数据范围来确定我们到底需要几个桶来存放我们的元素,这一步其实是比较关键的,因为桶的数量太多或者太少都会降低桶排序的效率.

      我们举两个例子:

      假设我们桶的数量太少,就比如说只有一个桶:
      在这里插入图片描述
      那么很显然我们的桶排序就又重新退化成我们前两篇内容里介绍的比较算法了.

      又假设我们桶的数量太多,就比如说有MAX-MIN+1个桶:
      在这里插入图片描述
      那么很显然这时候的桶排序又重新退化成了我们上面刚刚讲解过的计数排序了.

      所以说我们需要确定好一个适中的桶的数量,不然回就会出现我们上面所说到的几种情况.但是有没有一个特定的公式来确定桶的数量.所以我们还是只能自己确定桶的数量.但是有一个规则我们还是可以考虑进去的,那就是最好让元素平均的分散到每一个桶里.

    • 3.确定完桶的数量之后,我们就可以给每个桶来划分数据范围了.一般是这样划分的,(MAX-MIN+1)/桶的数量,得到的结果就是桶长.之后每个桶的数据范围就通过桶的编号以及桶长就可以确定每个桶的数据范围.就如下面的公式:

      左闭右开
      桶的数据范围=[MIN+(桶的编号-1)*桶长,MIN+桶的编号 *桶长)
      有了每个桶的数据范围时候,我们第二次遍历序列将每个元素存到相应的桶里面了.这个过程我们要注意,在往桶里面添加元素的时候,就需要在每个桶里面将元素排好序.

    • 4.当我们第二次遍历结束之后,我们就只需要按照桶的编号,在将该编号的桶里面的元素打印出来,桶排序就已经完成了.

    接下来我们还是通过下面的图来动态演示一下桶排序的过程:

    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述

    了解完桶排序的基本思想之后,按照惯例我们还是来简单分析一下他的一些特点:

    • 桶排序是稳定的,原因和上面计数排序的理由是一样的.
    • 桶排序也是一个通过空间换取时间的算法,但是他的空间复杂度是可以控制的.就像我们上面说的主要就是控制桶的数量.如果桶的数量设置的合理,既能降低时间复杂度,也能降低空间复杂度.

    算法图解:
    在这里插入图片描述

    示例代码:

    //在链表中添加元素的同时需要进行元素的排序
    	public static void sort(ArrayList<Integer>list,int i) {
    		if(list==null)
    			list.add(i);
    		//这里采用的排序方式为插入排序
    		else {
    			int flag=list.size()-1;
    			while(flag>=0&&list.get(flag)>i) {
    				if(flag+1>=list.size())
    					list.add(list.get(flag));
    				else  
    					list.set(flag+1, list.get(flag));
    				flag--;
    			}
    			if(flag != (list.size()-1))
    				//注意这里是flag+1,自己可以尝试将这里换成flag看看,会出现数组越界的情况
    			    list.set(flag+1, i);
    			else
    				list.add(i);
    		}
    	}
    	public static void Bucketsort(int []num,int sum) {
           //遍历得到数组中的最大值与最小值
    		int min=Integer.MAX_VALUE;
    		int max=Integer.MIN_VALUE;
    		for(int i=0;i<num.length;i++) {
    			min = min <= num[i] ? min: num[i];
    	        max = max >= num[i] ? max: num[i];
    		}
    		//求出每个桶的长度,这里必须使用Double
    		double size=(double)(max-min+1)/sum; 
    		ArrayList<Integer>list[]=new ArrayList[sum];
    		for(int i=0;i<sum;i++) {
    			list[i]=new ArrayList<Integer>();
    		}
    		//将每个元素放入对应的桶之中同时进行桶内元素的排序
    		for(int i=0;i<num.length;i++) {
    			System.out.println("元素:"+String.format("%-2s", num[i])+", 被分配到"+(int)Math.floor((num[i]-min)/size)+"号桶");
    			sort(list[(int)Math.floor((num[i]-min)/size)], num[i]);
    		}
    		System.out.println();
    		for(int i=0;i<sum;i++) {
    			System.out.println(String.format("%-1s", i)+"号桶内排序:"+list[i]);
    		}
    		System.out.println();
    		//顺序遍历各个桶,得出我们 已经排序号的序列
    		for(int i=0;i<list.length;i++) {
    			if(list[i]!=null){
    				for(int j=0;j<list[i].size();j++) {
    					System.out.print(list[i].get(j)+" ");
    				}
    			}
    		}
    		System.out.println();
    		System.out.println();
    	}
    	public static void main(String[] args) {
    		
    		int []num ={7,4,9,3,2,1,8,6,5,10};
    		long startTime=System.currentTimeMillis();
    		//这里桶的数量可以你自己定义,这里我就定义成了3
    		Bucketsort(num, 3);
    		long endTime=System.currentTimeMillis(); 
    		System.out.println("程序运行时间: "+(endTime-startTime)+"ms"); 
    	}
    

    在这里插入图片描述
    这里的时间是不准确的,因为我需要将每轮排序的结果打印出来给大家看,所以会多花费一些时间,如果大家想要看真实的时间的话,大家可以把我打印结果的代码注释掉再看看算法的执行时间.

    复杂度分析:

    理解完桶排序的基本思想之后,我们就需要来分析一下他的时间复杂度,空间复杂度.

    • 时间复杂度

      桶排序的时间复杂度和上面的计数排序是一样的,同样也是线性级别的,但是也是增加了桶的时间,所以也是O(n+k)

    • 空间复杂度

      上面我们已经说过了,桶排序本身也是一个通过空间来换取时间的算法,所以很明显他的空间复杂度就会很高.并且这个空间复杂度主要就取决于桶的数量以及桶的范围,所以假设有k个桶的话,那么空间复杂度就为O(n+k)

    4-基数排序

    算法思想:

    基数排序的实现步骤非常好理解,但是想要真正理解他的算法思想就稍微有点难度了.那么接下来就来讲解基数排序的算法思想.

    首先基数排序是根据数位来进行排序的.他是从个位开始,然后按照每一位的数进行排序,如下图所示:
    在这里插入图片描述

    排完序之后就往前进一位,然后再将所有的数按照这一位所在的数进行排序,如下图所示:
    在这里插入图片描述

    重复这个过程直到所有的位数都已经被排过序了.如下图所示:
    在这里插入图片描述

    并且如果这个过程中碰到某个数在这个为上没有数的话就进行补零操作,将该位看成是0.就比方下图我用红框圈出来的部分:
    在这里插入图片描述
    等到所有的位数都已经排序完毕之后,我们就可以看到我们已经排序好的序列了.

    这个过程相信大家肯定都很好理解,但是相信大家如果是第一次看这个算法的肯定会有和我当初一样的困惑,那就是为什么基数排序选择的是从低位到高位来进行排序的呢,而不是像我们平常比较数据的大小一样,从高位到低位来比较呢?

    这里呢我们先不讲为什么,我们先看下面这两个案例:

    • 从高位到低位进行比较
    原序列百位排好序后十位排好序后个位排好序后
    329839657839
    457720457329
    657657355657
    839457839457
    436436436436
    720329720355
    355355329720
    • 从低位到高位进行比较
    原序列个位排好序后十位排好序后百位排好序后
    329720720329
    457355329355
    657436436436
    839457839457
    436657355657
    720329451720
    355839657839

    对比看了上面两个例子之后相信大家就知道了,很明显我们看到如果是从该我到低位来进行比较的话,我们会发现比较到最后之后序列仍然是无序的,但是从低位到高位进行比较的话,我们就会发现序列到最后已经排好序了.

    是不是很奇怪,同样的执行过程,只不过执行的顺序发生了改变,为什么结果就回产生这样的结果呢?产生这个差异的重点就是在我们忽略了一个问题,那就是我们在进行高位到低位比较的时候,高位的权重是高于低位的.就比方下图所示:
    在这里插入图片描述
    正是因为这个原因,所以我们采取的是从低位到高位比较的过程.

    说完基本思想之后,我们来说一下算法的实现步骤:

    • 1.我们第一次遍历序列.找出序列中的最大值MAX,找到MAX之后我们可以确定我们需要比较多少数位了.

    • 2.这时候我们就需要按照元素在该位数对应的数字将元素存入到相应的容器之中.如下图所示:
      在这里插入图片描述

    • 3.之后我们再按照容器的顺序将元素重新弹出构成我们接下来需要排序的序列,如下图所示:
      在这里插入图片描述
      这个从容器弹出的过程需要注意一点,那就是遵循先进先出的原则,所以这个容器选择队列或者是链表比较合适,不能选择栈,因为栈是先进后出,拿取元素的时候回非常麻烦.

    • 4.最后只需要重复2,3步骤,直到最高位也比较完毕,那么整个基数排序就已经完成了.

    接下来我们还是通过下面的图来动态演示一下基数排序的过程:
    个位排序:
    在这里插入图片描述
    十位排序:
    在这里插入图片描述
    百位排序:
    在这里插入图片描述
    千位排序:
    在这里插入图片描述

    在了解完基数排序的基本思想之后,按照惯例我们还是来简单分析一下基数排序的特点:

    • 基数排序是稳定的,原因和桶排序以及计数排序的原因一样.

    算法图解:

    在这里插入图片描述

    示例代码:

    //将所有的数组合并成原来的数组
    	public static void merge(ArrayList<Integer> list[],int num[]) {
    		int k=0;
    		for(int i=0;i<list.length;i++) {
    			if(list[i]!=null) {
    				for(int j=0;j<list[i].size();j++) {
    					num[k++]=list[i].get(j);
    					System.out.print(num[k-1]+" ");
    				}
    			}
    			//合并完成之后需要将链表清空,否则元素会越来越多
    			list[i].clear();
    		}
    		System.out.println();
    	}
    	//将所有的元素分散到各个链表之中
    	public static void split(ArrayList<Integer> list[],int num[],int k) {
    		for(int j=0;j<num.length;j++) {
    			list[num[j]/k%10].add(num[j]);
    		}
    		System.out.println("-----------------------------------------------------------------------");
    		System.out.println("个位开始数,第"+(String.valueOf(k).length())+"位排序结果:");
    		for(int j=0;j<10;j++) {
    			System.out.println((String.valueOf(k).length())+"号位,数值为"+j+"的链表结果:"+list[j]);
    		}
    	}
    	public static void main(String[] args) {
    		ArrayList<Integer>list[]=new ArrayList [10];
    		for(int i=0;i<10;i++) {
    			list[i]=new ArrayList<Integer>();
    		}
    		int []num ={7,14,9,333,201,1,88,6,57,10,56,74,36,234,456};
    		long startTime=System.currentTimeMillis();  
    		int max=Integer.MIN_VALUE;
    		//第一次遍历获得序列中的最大值
    		for(int i=0;i<num.length;i++) {
    			if(num[i]>max)
    				max=num[i];
    		}
    		int k=1;
    		for(int i=0;i<String.valueOf(max).length();i++) {
    			split(list, num, k);
    			System.out.println("第"+(i+1)+"次排序");
    			merge(list, num);
    			k=k*10;
    		}
    		long endTime=System.currentTimeMillis(); 
    		System.out.println("程序运行时间: "+(endTime-startTime)+"ms"); 
    	}
    

    在这里插入图片描述

    复杂度分析:

    理解完基数排序的基本思想之后,我们就需要来分析一下他的时间复杂度,空间复杂度.

    • 时间复杂度

      看完我们上面的动图演示之后我们可以看到基数排序只需要根据最大元素的位数,遍历相应次数的序列即可,所以我们可以确定基数排序的时间复杂度一定是线性级别的,但是虽然是线性级别的,但是有一个系数的,这个系数就是最大元素的位数K,所以时间复杂度应该是O(n*k)

    • 空间复杂度

      空间复杂度我们也可以看出来,主要就是取决于链表的数量以及序列元素的数量,所以空间复杂度为O(n+k)

    到这里十大经典排序算法详解的内容就已经全部讲解完毕了.这一次文章不管是在内容的质量上或者是在文章的排版上,都是目前工作量比较大的一期.所以如果大家觉得文章还行或者觉得文章对你有帮助的话,UP希望能关注一下我的公众号:萌萌哒的瓤瓤,或者觉得关注公众号麻烦的话,也可以给我的文章一键三连.新人UP真的很需要你的支持!!!

    在这里插入图片描述

    不点在看,你也好看!

    点点在看,你更好看!

    展开全文
  • NULL 博文链接:https://128kj.iteye.com/blog/1754756
  • C语言版的排序方法---计数排序.非常有用的代码,可以实际中使用。
  • 计数排序(Counting Sort)

    千次阅读 多人点赞 2019-09-27 16:52:53
    计数排序的核心在于将输入的数据值转化为键存储在额外开辟的数组空间中。 作为一种线性时间复杂度的排序,计数排序要求输入的数据必须是有确定范围的整数。 计数排序(Counting sort)是一种稳定的排序算法。计数排序...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 167,453
精华内容 66,981
关键字:

计数排序