计数排序(C语言版本)
让我们来谈谈数的排序思维:
计数排序假定待排序的全部元素都是介于0到K之间的整数。计数排序使用一个额外的数组countArray。当中第i个元素是待排序数组array中值等于i的元素的个数。然后依据数组countArray来将array中的元素排到正确的位置。
算法的步骤例如以下:
- 找出待排序的数组中最大和最小的元素
- 统计数组中每一个值为i的元素出现的次数,存入数组countArray的第i项
- 对全部的计数累加(从countArray中的第一个元素開始,每一项和前一项相加)
- 反向填充目标数组:将每一个元素i放在新数组的第countArray[i]项。每放一个元素就将countArray[i]减去1
稳定性和复杂度:
计数排序是稳定的排序算法;平均时间复杂度、最优时间复杂度和最差时间复杂度都为O(n+k),空间复杂度为O(n+k)。当中,n为待排元素个数,k为待排元素的范围(0~k)。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <assert.h> #define RANDMAX 1000000
#define RANDMIN 900000 void getRandArray(int array[], int size);
void countSort(int array[], int size);
void isSorted(int array[], int size); int main(int argc, char const *argv[])
{
int size = 0;
scanf("%d", &size);
assert(size > 0); int *array = (int *)calloc(size, sizeof(int));
getRandArray(array, size); clock_t begin;
clock_t end; begin = clock();
countSort(array, size);
end = clock();
//打印排序所花费的时间。在linux下单位为ms
printf("%ld\n", (end - begin) / 1000); isSorted(array, size);
free(array); return 0;
} //利用伪随机数填充数组array。伪随机数的范围在RANDMIN~RANDMAX-1之间
void getRandArray(int array[], int size)
{
assert(array != NULL && size > 0); srand((unsigned) time(NULL));
int i = 0;
for (i = 0; i < size; ++i) {
array[i] = rand() % (RANDMAX - RANDMIN) + RANDMIN ;
}
} //从小到大进行排序
void countSort(int array[], int size)
{
assert(array != NULL && size > 0); //计数数组,用于统计数组array中各个不同数出现的次数
//由于数组array中的数属于0...RANDMAX-1之间,所以countArray的大小要够容纳RANDMAX个int型的值
int *countArray = (int *) calloc(RANDMAX, sizeof(int));
//用于存放已经有序的数列
int *sortedArray = (int *) calloc(size, sizeof(int)); //统计数组array中各个不同数出现的次数,循环结束后countArray[i]表示数值i在array中出现的次数
int index = 0;
for (index = 0; index < size; ++index) {
countArray[array[index]]++;
} //统计数值比index小的数的个数。循环结束之后countArray[i]表示array中小于等于数值i的元素个数
for (index = 1; index < RANDMAX; ++index) {
countArray[index] += countArray[index - 1];
} for (index = size - 1; index >= 0; --index) {
//由于数组的起始下标为0。所以要减一
sortedArray[countArray[array[index]] - 1] = array[index];
//这里减一是为了保证当有多个值为array[index]的元素时,后出现的值为array[index]的元素
//放在后面,也就是为了保证排序的稳定性
--countArray[array[index]];
} memcpy(array, sortedArray, size * sizeof(int));
free(sortedArray);
free(countArray);
} //推断数组array是否已经是有序的
void isSorted(int array[], int size)
{
assert(array != NULL && size > 0); int unsorted = 0;
int i = 0;
for (i = 1; i < size; ++i) {
if (array[i] < array[i - 1]) {
unsorted = 1;
break;
}
} if (unsorted) {
printf("the array is unsorted!\n");
} else {
printf("the array is sorted!\n");
}
}
计数排序是非比較的排序算法,据说其排序速度要快于不论什么的比較排序算法(我还未验证,可是在排序100000000个10000以内的数时花费为606毫秒,而C语言的qsort函数则为10802毫秒,由此可见一斑),因为计数排序须要一个计数数组以及一个存放有序数列的数组。故计数排序对内存的要求比較高。
------------------------------------------------------------------更新------------------------------------------------------------------
2014年10月20日
以上的实现有两个问题:
1、比較耗费内存:假设元素值的范围在9百万~1千万之间。上面的实现中countArray的大小须要1千万,而实际出现的不同值的元素就仅仅有1千万-9百万=1百万,也就是说用上面的实现会导致countArray中有90%的空间没有被利用,这个是非常浪费内存空间的!
尽管这个样例比較极端可是在详细应用面前还是须要注意!
2、不能出现负数:假设元素值中有负数。就不能用上面的实现方法了,由于counArray的下标是待排数组的元素值,所以上面的实现不能排序包括负数的数列。
解决方法:
1、第一个问题好解决:仅仅要首先一次遍历待排数组。找出最大值和最小值(眼下我所知的比較快的方法是时间复杂度为O(1.5n)的方法)。然后分配大小为(max-min+1)*sizeof(int)的空间给countArray即可了。至于统计的方法就得改变一点点了:
//从小到大进行排序,节省空间的版本号
void countSort2(int array[], int size)
{
assert(array != NULL && size > 0); int min;
int max;
getMinAndMax(array, size, &min, &max);
// printf("max - min + 1 = %d\n", max - min + 1);
int countArraySize = max - min + 1; //计数数组,用于统计数组array中各个不同数出现的次数
int *countArray = (int *) calloc(countArraySize, sizeof(int));
//用于存放已经有序的数列
int *sortedArray = (int *) calloc(size, sizeof(int)); //统计数组array中各个不同数出现的次数。循环结束后countArray[i]表示数值i+min在array中出现的次数
int index = 0;
for (index = 0; index < size; ++index) {
countArray[array[index] - min]++;
} //统计数值比index小的数的个数。循环结束之后countArray[i]表示array中小于等于数值i+min的元素个数
for (index = 1; index < countArraySize; ++index) {
countArray[index] += countArray[index - 1];
} for (index = size - 1; index >= 0; --index) {
//由于数组的起始下标为0。所以要减一
sortedArray[countArray[array[index] - min] - 1] = array[index];
//这里减一是为了保证当有多个值为array[index]的元素时。后出现的值为array[index]的元素
//放在后面,也就是为了保证排序的稳定性
--countArray[array[index] - min];
} memcpy(array, sortedArray, size * sizeof(int));
free(sortedArray);
free(countArray);
} //待优化
void getMinAndMax(int array[], int size, int *min, int *max)
{
assert(array != NULL && size > 0);
if (size == 1) {
*min = *max = array[0];
return ;
} *min = array[0];
int index;
for (index = 1; index < size; ++index) {
if (array[index] < *min) {
*min = array[index];
}
}
*max = array[0];
for (index = 1; index < size; ++index) {
if (array[index] > *max) {
*max = array[index];
}
}
}
这样的方法能够改进在元素值范围比較小的数列的排序时间(在大部分情况时间较短)和空间复杂度。因为仅仅是为了验证想法的可行性,就没有在查找最值的函数上下功夫优化。等以后有时间了。再进一步优化。
2、第二个问题:刚刚在洗漱的时候忽然想到了一个可行的方法:先一次遍历待排数组,找出负数中的最大值和最小值。正数中的最大值和最小值。创建两个计数数组,negativeCountArray用来统计待排数组中各个不同负数的出现个数,positiveCountArray用来统计待排数组中各个正数出现的个数,先统计总的负数的个数。然后再统计各个不同正数的个数,然后在往sortedArray中放元素的时候对正数和负数差别对待。这样就能让计数排序对包括负数的数列进行排序了。
代码例如以下:
//从小到大进行排序。节省空间,可排序负数的版本号
void countSort3(int array[], int size)
{
assert(array != NULL && size > 0); int negativeMin;
int negativeMax;
int positiveMin;
int positiveMax;
_getMinAndMax(array, size, &negativeMin, &negativeMax, &positiveMin, &positiveMax);
int positiveCountArraySize = positiveMax - positiveMin + 1;
int negativeCountArraySize = negativeMax - negativeMin + 1; //计数数组,用于统计数组array中各个不同数出现的次数
int *positiveCountArray = (int *) calloc(positiveCountArraySize, sizeof(int));
int *negativeCountArray = (int *) calloc(negativeCountArraySize, sizeof(int));
//用于存放已经有序的数列
int *sortedArray = (int *) calloc(size, sizeof(int)); //统计数组array中各个不同的数出现的次数,循环结束后positiveCountArray[i]表示数值i+positiveMin>=0在array中出现的次数
//negativeCountArray[i]表示数值i+negativeMin<0在array中出现的次数
int index = 0;
for (index = 0; index < size; ++index) {
if (array[index] < 0) {
negativeCountArray[array[index] - negativeMin]++;
} else {
positiveCountArray[array[index] - positiveMin]++;
}
} //统计数值比index+negativeMin小的负数的个数。循环结束之后negativeCountArray
for (index = 1; index < negativeCountArraySize; ++index) {
negativeCountArray[index] += negativeCountArray[index - 1];
}
int negativeCount = negativeCountArray[index - 1];//负数的总数 //统计数值比index小的非负数数的个数,循环结束之后positiveCountArray[i]表示array中小于等于数值i的值为非负数的元素个数
for (index = 1; index < positiveCountArraySize; ++index) {
positiveCountArray[index] += positiveCountArray[index - 1];
} for (index = size - 1; index >= 0; --index) {
if (array[index] < 0) {
sortedArray[negativeCountArray[array[index] - negativeMin] - 1] = array[index];
--negativeCountArray[array[index] - negativeMin];
} else {
//因为非负数总是比负数来的大,所以在把正数放到sortedArray时要在下标上加上负数的总个数
sortedArray[positiveCountArray[array[index] - positiveMin] - 1 + negativeCount] = array[index];
--positiveCountArray[array[index] - positiveMin];
}
} memcpy(array, sortedArray, size * sizeof(int));
free(sortedArray);
free(positiveCountArray);
free(negativeCountArray);
} //待优化
void _getMinAndMax(int array[], int size, int *negativeMin, int *negativeMax, int *positiveMin, int *positiveMax)
{
assert(array != NULL && size > 0);
if (size == 1) {
*negativeMin = *negativeMax = *positiveMin = *positiveMax = array[0];
return ;
} int firstNegative = 0;
int firstPositive = 0;
int index;
for (index = 0; index < size; ++index) {
if (array[index] < 0) {
firstNegative = array[index];
}
}
for (index = 0; index < size; ++index) {
if (array[index] > 0) {
firstPositive = array[index];
}
} *negativeMin = firstNegative;
*positiveMin = firstPositive;
for (index = 0; index < size; ++index) {
if (array[index] < 0 && array[index] < *negativeMin) {
*negativeMin = array[index];
}
if (array[index] > 0 && array[index] < *positiveMin) {
*positiveMin = array[index];
}
}
*negativeMax = firstNegative;
*positiveMax = firstPositive;
for (index = 0; index < size; ++index) {
if (array[index] < 0 && array[index] > *negativeMax) {
*negativeMax = array[index];
}
if (array[index] > 0 && array[index] > *positiveMax) {
*positiveMax = array[index];
}
}
}
因为仅仅是为了验证想法的可行性,就没有在查找最值的函数上下功夫优化,等以后有时间了。再进一步优化。
其它八种排序算法的博客:
版权声明:本文博客原创文章,博客,未经同意,不得转载。
计数排序(C语言版本)的更多相关文章
- 选择排序C语言版本
算法思路,从头至尾扫描序列. 首先从第二个到最后,找出最小的一个元素,和第一个元素交换: 接着从第三个到最后,后面找出最小的一个元素,和第二个元素交换: 依次类推最终得到一个有序序列. void Se ...
- 排序算法的C语言实现(下 线性时间排序:计数排序与基数排序)
计数排序 计数排序是一种高效的线性排序. 它通过计算一个集合中元素出现的次数来确定集合如何排序.不同于插入排序.快速排序等基于元素比较的排序,计数排序是不需要进行元素比较的,而且它的运行效率要比效率为 ...
- C语言-计数排序
计数排序的基本思想是:统计一个数序列中小于某个元素a的个数为n,则直接把该元素a放到第n+1个位置上.当然当过有几个元素相同时要做适当的调整,因为不能把所有的元素放到同一个位置上.计数排序假设输入的元 ...
- 计数排序算法——时间复杂度O(n+k)
计数排序 计数排序是一个非基于比较的排序算法,该算法于1954年由 Harold H. Seward 提出.它的优势在于在对一定范围内的整数排序时,它的复杂度为Ο(n+k)(其中k是整数的范围),快于 ...
- Java实现堆排序和计数排序
堆排序代码: 思想:每次都取堆顶的元素,将其放在序列最后面,然后将剩余的元素重新调整为最小堆,依次类推,最终得到排序的序列. import java.util.Arrays; /** * 思路:首先要 ...
- 计数排序、桶排序python实现
计数排序在输入n个0到k之间的整数时,时间复杂度最好情况下为O(n+k),最坏情况下为O(n+k),平均情况为O(n+k),空间复杂度为O(n+k),计数排序是稳定的排序. 桶排序在输入N个数据有M个 ...
- JavaScript 数据结构与算法之美 - 桶排序、计数排序、基数排序
1. 前言 算法为王. 想学好前端,先练好内功,只有内功深厚者,前端之路才会走得更远. 笔者写的 JavaScript 数据结构与算法之美 系列用的语言是 JavaScript ,旨在入门数据结构与算 ...
- 计数排序(counting-sort)——算法导论(9)
1. 比较排序算法的下界 (1) 比较排序 到目前为止,我们已经介绍了几种能在O(nlgn)时间内排序n个数的算法:归并排序和堆排序达到了最坏情况下的上界:快速排序在平均情况下达到该上界. ...
- 计数排序和桶排序(Java实现)
目录 比较和非比较的区别 计数排序 计数排序适用数据范围 过程分析 桶排序 网络流传桶排序算法勘误 桶排序适用数据范围 过程分析 比较和非比较的区别 常见的快速排序.归并排序.堆排序.冒泡排序等属于比 ...
随机推荐
- easyui动力头 && 动态加入tabs
今天,在实现了业务时的,我们需要根据后台操作,以产生多个数据tab页,而且每一个tab页表格根据需要动态生成的标题数据. 返回后台数据格例如,下面的公式: 实现方法例如以下: //$("#c ...
- [WebGL入门]二十四,补色着色
注:文章译自http://wgld.org/,原作者杉本雅広(doxas),文章中假设有我的额外说明,我会加上[lufy:],另外,鄙人webgl研究还不够深入,一些专业词语,假设翻译有误,欢迎大家指 ...
- 【转】static_cast和reinterpret_cast
static_cast和reinterpret_cast揭秘 收藏 本文讨论static_cast<> 和 reinterpret_cast<>. reinterpret_ca ...
- 关于csrss.exe和winlogon.exe进程多、占用CPU高的解决办法
原地址 http://blog.sina.com.cn/s/blog_912e77480101nuif.html 最近VPS的CPU一直处在100%左右,后台管理上去经常打不开,后来发现上远程都要 ...
- 【Android进阶】自定义控件实现底部扇形展开菜单效果
这个项目是优化的其他人的,主要优化了界面菜单的显示,下面开始. 先看效果图 项目的总结构 下面开始贴代码,由于必要的地方都添加了注释,所以不过多讲解 anim_button.xml <?xml ...
- 大哥可以写KMP该——达到strstr()
在最后采访,面试官要求实现strstr(),当场就蒙了. 这个题目是模式匹配问题.<算法导论>里列出了几种字符串匹配算法: 朴素算法 | Rabin-Karp | 有限自己主动机算法 | ...
- 站点系统压力測试Jmeter+Badboy
近期项目须要压力測试,因此搜了几款试用,首选的是LoadRunner这款大名鼎鼎的測试软件: LoadRunner11 下载请猛戳这里 传送门LoadRunner破解文件 下载请猛戳这里 传送门Loa ...
- 小代码编写神器:LINQPad 使用入门
原文:小代码编写神器:LINQPad 使用入门 一:概述 1:想查看程序运行结果,又不想启动 VS 怎么办? 2:想测试下自己的 C# 能力,不使用 VS 的智能感知,怎么办? 那么,我们有一个选择, ...
- PKU A Simple Problem with Integers (段树更新间隔总和)
意甲冠军:一个典型的段树C,Q问题,有n的数量a[i] (1~n),C, a, b,c在[a,b]加c Q a b 求[a,b]的和. #include<cstdio> #include& ...
- Vijos.1096 津津储蓄计划
见问题: https://vijos.org/p/1096 主题概述 津津的零花钱一直都是自己的管理.每月初的母亲津津300美元,津津将于本月支出预算.而且总是做同样的实际支出与预算. 为了让津津学 ...