计数排序

计数排序是一种高效的线性排序

通过计算一个集合中元素出现的次数来确定集合如何排序。不同于插入排序、快速排序等基于元素比较的排序,计数排序是不需要进行元素比较的,而且它的运行效率要比效率为O(nlgn)的比较排序高。

计数排序有一定的局限性,其中最大的局限就是它只能用于整型或那么可以用整型来表示的数据集合。原因是计数排序利用一个数据的索引来记录元素出现的次数,而这个数组的索引就是元素的数值。例如,如果整数3出现过4次,那么4将存储到数组索引为3的位置上。同时,我们还需要知道集合中最大整数的值,以便于为数组分配足够的空间。

除了速度之外,计数排序的另一个优点就是非常稳定稳定的排序能使具有相同数值的元素具有相同的顺序,就像它们在原始集合中表现出来的一样。在某些情况下这是一个重要的特性,可以在基数排序中看到这一点。

 计数排序的接口定义

ctsort


int ctsort(int *data, int size, int k);

返回值:如果排序成功,返回0;否则,返回-1;

描述:   利用计数排序将数组data中的整数进行排序。data中的元素个数由size决定。参数k为data中最大的整数加1。当ctsort返回时,data中包含已经排序的元素。

复杂度:O(n+k),n为要排序的元素个数,k为data中最大的整数加1。

计数排序的实现与分析

计数排序本质上是通过计算无序集合中整数出现的次数来决定集合应该如何排序的

在以下说明的实现方法中,data初始包含size个无序整型元素,并存放在单块连续的存储空间中。另外需要分配存储空间来临时存放已经排序的元素。在ctsort返回时,得到的有序集合将拷贝加data。

分配了存储空间以后,首先计算data中每个元素出现的次数。这些结果将存储到计数数组counts中,并且数组的索引值就是元素本身。一旦data中每个元素的出现次数都统计出来后,就要调整计数值,使元素在进入有序集合之前,清楚每个元素插入的次数。用元素本身的次数加上它前一个元素的次数。事实上,此时counts包含每个元素在有序集合temp中的偏移量

要完成排序,还必须按照元素在temp中的偏移量放置元素。当temp更新时,每个元素的计数要减1,这样,在data中出现不止一次的元素在temp中也会出现不止一次,这样保持同步。

计数排序的时间复杂度为O(n+k),其中n为要排序的元素个数,k为data中最大的整数加1。这是由于计数排序包含三个循环,其中两个的运行时间正比于n,另一个的运行时间正比于k。对于空间上来说,计数排序需要两个大小为n的数组,一个大小为k的数组。

示例:计数排序的实现

  1. /*ctsort.c*/
  2. #include <stdlib.h>
  3. #include <string.h>
  4. #include "sort.h"
  5.  
  6. /*ctsort 计数排序函数*/
  7. int ctsort(int *data, int size, int k)
  8. {
  9. int *counts,
  10. *temp;
  11.  
  12. int i,j;
  13.  
  14. /*为计数器数组分配空间*/
  15. if((counts = (int *)malloc(k * sizeof(int))) == NULL)
  16. return -;
  17.  
  18. /*为已排序元素临时存放数组分配空间*/
  19. if((temp = (int *)malloc(size * sizeof(int))) == NULL)
  20. return -;
  21.  
  22. /*初始化计数数组*/
  23. for(i = ; i < k; i++)
  24. {
  25. counts[i] = ;
  26. }
  27.  
  28. /*统计每个元素出现的次数(counts的下标索引即是要统计的元素本身)*/
  29. for(j=; j<size; j++)
  30. counts[data[j]]=counts[data[j]] + ;
  31.  
  32. /*将元素本身的次数加上它前一个元素的次数(得到元素偏移量)*/
  33. for(i = ; i < k; i++)
  34. counts[i]=counts[i] + counts[i-];
  35.  
  36. /*关键代码:使用上面得到的计数数组去放置每个元素要排序的位置*/
  37. for(j = size -; j >= ; j--)
  38. {
  39. temp[counts[data[j]]-] = data[j]; /*counts的值是元素要放置到temp中的偏移量*/
  40. counts[data[j]] = counts[data[j]] - ; /*counts的计数减1*/
  41. }
  42.  
  43. /*将ctsort已排序的元素从temp拷贝回data*/
  44. memcpy(data,temp,size * sizeof(int));
  45.  
  46. /*释放前面分配的空间*/
  47. free(counts);
  48. free(temp);
  49.  
  50. return ;
  51. }

基数排序

基数排序是另外一种高效的线性排序算法

其方法是将数据按位分开,并从数据的最低有效位到最高有效位进行比较,依次排序,从而得到有序数据集合

我们来看一个例子,用基数排序对十进制数据{15,12,49,16,36,40}进行排序。在对个位进行排序之后,其结果为{40,12,15,16,36,49},在对十位进行排序之后,其结果为{12,15,16,36,40,49}。

有一点非常重要,在对每一位进行排序时其排序过程必须是稳定的一旦一个数值通过较低有效位的值进行排序之后,此数据的位置不应该改变,除非通过较高有效位的值进行比较后需要调整它的位置。例如,在上述的例子中,12和15的十位都是1,当对其十位进行排序时,一个不稳定的排序算法可能不会维持其在个数排序过程中的顺序。而一个稳定的排序算法可以保证它们不重新排序。基数排序会用到计数排序,对于基数排序来说,除了稳定性,它还是一种线性算法,且必须知道每一位可能的最大整数值

基数排序并不局限于对整数进行排序,只要能把元素分割成整型数,就可以使用基数排序。例如,可以对一个以28为基数字符串进行基数排序;或者,可以对一个64位的整数,按4位以216为基数的值进行排序。具体该选择什么值作为基数取决于数据本身,同时考虑到空间的限制,需要将pn+pk最小化。(其中p为每个元素的位数,n为元素的个数,k为基数)。一般情况下,通常使k小于等于n。

基数排序的接口定义

rxsort


int rxsort(int *data, int size, int p, int k);

返回值:如果排序成功,返回0;否则返回-1。

描述:利用计数排序将数组data中的整数进行排序。数组data中整数的个数由size决定。参数p指定每个整数包含的位数,k指定基数。当rxsort返回时,data包含已经排序的整数。

复杂度:O(pn+pk),n为要排序的元素个数,k为基数,p为位的个数。

基数排序的实现与分析

基数排序实质上是在元素每一位上应用计数排序来对数据集合排序。在以下介绍的实现方法中,data初始包含size个无序整型元素,并存放在单块连续的存储空间中。当rxsort返回时,data中的数据集完全有序。

如果我们理解了计数排序的方法,那么基数排序也就非常简单了。单个循环控制正在进行排序的位置。从最低位开始一个位置一个位置地应用计数排序来不断调整元素。一旦调整完了最高有效位的数值,排序过程就完成了。

获取每位数值的简单方法就是使用幂运算和模运算。这对整数来说特别有效,但不同的数据类型需要使用不同的方法。有一些方法可能需要考虑机器具体细节,例如字节顺序和字对齐等。

毫无疑问,基数排序的时间复杂度取决于它选择哪种稳定排序来对数值进行排序。由于基数排序对每个p位置的位数值使用计数排序,因此基数排序消耗的运行时间是计数排序的p倍,即O(pn+pk)。其对空间的要求与计数排序一样:两个大小为n的数组,一个大小为k的数组。

示例:基数排序的实现

  1. /*rxsort.c*/
  2. #include <limits.h>
  3. #include <math.h>
  4. #include <stdlib.h>
  5. #include <string.h>
  6.  
  7. #include "sort.h"
  8.  
  9. /*rxsort*/
  10. int rxsort(int *data, int size, int p, int k)
  11. {
  12. int *counts, *temp;
  13. int index, pval, i, j, n;
  14.  
  15. /*为计数器数组分配空间*/
  16. if((counts = (int *)malloc(k * sizeof(int))) == NULL)
  17. return -;
  18. /*为已排序元素集分配空间*/
  19. if((temp = (int *)malloc(size * sizeof(int))) == NULL)
  20. return -;
  21.  
  22. /*从元素的最低位到最高位开始排序*/
  23. for(n=; n<p; n++)
  24. {
  25. /*初始化计数器*/
  26. for(i=; i<k; i++)
  27. count[i] = ;
  28. /*计算位置值(幂运算k的n次方)*/
  29. pval = (int)pow((double)k,(double)n);
  30.  
  31. /*统计当前位上每个数值出现的次数*/
  32. for(j=; j<size; j++)
  33. {
  34. index = (int)(data[j] / pval) % k;
  35. counts[index] = counts[index]+;
  36. }
  37. /*计算偏移量(本身的次数加上前一个元素次数)*/
  38. for(i=; i<k; i++)
  39. counts[i] = counts[i] + counts[i-];
  40.  
  41. /*使用计数器放置元素位置*/
  42. for(j=size-; j>=; j--)
  43. {
  44. index = (int)(data[j] / pval) % k;
  45. temp[counts[index]-] = data[j];
  46. counts[index] = counts[index] - ;
  47. }
  48.  
  49. /*将已排序元素拷贝回data*/
  50. memcpy(data, temp, size*sizeof(int));
  51.  
  52. }
  53.  
  54. /*释放已排序空间*/
  55. free(counts);
  56. free(temp);
  57.  
  58. return ;
  59. }

排序算法的C语言实现(下 线性时间排序:计数排序与基数排序)的更多相关文章

  1. 【最全】经典排序算法(C语言)

    算法复杂度比较: 算法分类 一.直接插入排序 一个插入排序是另一种简单排序,它的思路是:每次从未排好的序列中选出第一个元素插入到已排好的序列中. 它的算法步骤可以大致归纳如下: 从未排好的序列中拿出首 ...

  2. 排序算法总结(C语言版)

    排序算法总结(C语言版) 1.    插入排序 1.1     直接插入排序 1.2     Shell排序 2.    交换排序 2.1     冒泡排序 2.2     快速排序 3.    选择 ...

  3. 【转载】常见十大经典排序算法及C语言实现【附动图图解】

    原文链接:https://www.cnblogs.com/onepixel/p/7674659.html 注意: 原文中的算法实现都是基于JS,本文全部修改为C实现,并且统一排序接口,另外增加了一些描 ...

  4. [算法] 常见排序算法总结(C语言版)

    常见排序算法总结 本文对比较常用且比较高效的排序算法进行了总结和解析,并贴出了比较精简的实现代码,包括选择排序.插入排序.归并排序.希尔排序.快速排序等.算法性能比较如下图所示: 1 冒泡排序 基本原 ...

  5. [answerer的算法课堂]简单描述4种排序算法(C语言实现)

    [answerer的算法课堂]简单描述4种排序算法(C语言实现) 这是我第一次写文章,想要记录自己的学习生活,写得不好请包涵or指导,本来想一口气写好多种,后来发现,写太多的话反而可读性不强,而且,我 ...

  6. 排序算法的C语言实现(上 比较类排序:插入排序、快速排序与归并排序)

    总述:排序是指将元素集合按规定的顺序排列.通常有两种排序方法:升序排列和降序排列.例如,如整数集{6,8,9,5}进行升序排列,结果为{5,6,8,9},对其进行降序排列结果为{9,8,6,5}.虽然 ...

  7. 几种经典排序算法的R语言描述

    1.数据准备 # 测试数组 vector = c(,,,,,,,,,,,,,,) vector ## [] 2.R语言内置排序函数 在R中和排序相关的函数主要有三个:sort(),rank(),ord ...

  8. 链表插入和删除,判断链表是否为空,求链表长度算法的,链表排序算法演示——C语言描述

    关于数据结构等的学习,以及学习算法的感想感悟,听了郝斌老师的数据结构课程,其中他也提到了学习数据结构的或者算法的一些个人见解,我觉的很好,对我的帮助也是很大,算法本就是令人头疼的问题,因为自己并没有学 ...

  9. 常用七大经典排序算法总结(C语言描述)

    简介 其中排序算法总结如下: 一.交换排序 交换排序的基本思想都为通过比较两个数的大小,当满足某些条件时对它进行交换从而达到排序的目的. 1.冒泡排序 基本思想:比较相邻的两个数,如果前者比后者大,则 ...

随机推荐

  1. Android:android sdk源码中怎么没有httpclient的源码了

    欢迎关注公众号,每天推送Android技术文章,二维码如下:(可扫描) 今天想使用这个API,怎么也找不到.废了好多时间... 查阅资料才知道如下解释: 在android 6.0(API 23)中,G ...

  2. 我眼中的Linux设备树(二 节点)

    二 节点(node)的表示首先说节点的表示方法,除了根节点只用一个斜杠"/"表示外,其他节点的表示形式如"node-name@unit-address".@前边 ...

  3. iOS中 DataBase SQL数据库 UI_高级

    SQL(Structured query Lauguage) :结构化 查询 语言 1.创建表格的SQL语句 create table if not exists Teacher(tea_id int ...

  4. 调用sed命令的三种方式

    调用sed命令的三种方式 调用sed有三种方式,一种为Shell命令行方式,另外两种是将sed命令写入脚本文件,然后执行该脚本文件. 三种方式的命令格式归纳如下: 一.在Shell命令行输入命令调用s ...

  5. 防止Android程序被系统kill掉的处理方法

    转载请注明出处:http://blog.csdn.net/cuiran/article/details/38851401 目前遇到一个问题程序需要一直运行,并显示在最前端,但是运行一段时间发现会被系统 ...

  6. 海量数据挖掘MMDS week2: 频繁项集挖掘 Apriori算法的改进:基于hash的方法

    http://blog.csdn.net/pipisorry/article/details/48901217 海量数据挖掘Mining Massive Datasets(MMDs) -Jure Le ...

  7. 17_Android中Broadcast详解(有序广播,无序广播)最终广播,Bundle传递参数,传递参数的时候指定权限

     1  Broadcast是Android中的四大组件之一,他的用途很大,比如系统的一些广播:电量低.开机.锁屏等一些操作都会发送一个广播. 2  广播被分为两种不同的类型:"普通广播( ...

  8. STL算法设计理念 - 二元函数,二元谓词以及在set中的应用

    demo 二元函数对象 #include <iostream> #include <cstdio> #include <vector> #include <a ...

  9. 【代码管理】GitHub超详细图文攻略 - Git客户端下载安装 GitHub提交修改源码工作流程 Git分支 标签 过滤 Git版本工作流

    GitHub操作总结 : 总结看不明白就看下面的详细讲解. . 作者 :万境绝尘  转载请注明出处 : http://blog.csdn.net/shulianghan/article/details ...

  10. nodejs安装及故障解决

    本文来自:http://hxl2009.blog.51cto.com/779549/1031320 给开发搞nodejs环境,遇到点问题记录下过程,备忘. wget http://nodejs.org ...