排序-计数排序

零、数据结构和算法系列目录

数据结构和算法系列目录(不断更新):

http://blog.csdn.net/adrastos/article/details/9093857

一、计数排序简介

计数排序是一种线性时间的排序,它的时间复杂程度为O(n),虽然是线性的时间复杂程度,但是它的空间复杂程度比较高,而且用之前需要有一个硬性的前提。这个前提在后面给出,这里先来简单介绍一下计数排序。计数排序是先扫描一边待排序数组,并用一个辅助数组记录待排序每个元素应该在排序好数组中的位置信息。
现在说一下用计数排序的前提。计数排序需要用辅助数组记录排序位置信息,当然就需要知道这个辅助数组的大小。辅助数组的大小取决于待排序数组中元素的取值范围。例如,待排序数组中的元素范围介于0到k,那么就需要用大小为k+1的数组来当作辅助数组。所以,计数排序需要事先知道待排序数组元素的取值范围,但是当这个取值范围区间非常大时,将会需要很多的额外空间,所以这个情况不建议使用计数排序。那么什么时候适合用呢?我这里给出一个可能不太恰当的场景,就是学生分数的排名。比如百分制的时候,只需要101个元素的辅助数组,但是待排序的学生成绩可能有上千甚至更多,这时使用计数排序还是很好的。计数排序的使用范围是一段元素的离散值密集在一个不太大区间范围内。这里待排序元素不一定都是要从0开始,比如从50~100的区间也是可以适用的,就是在算法实现时映射的算法稍加改变即可,在这个例子中可以把原来从0开始的映射进行加50的操作。当然,步长也不一定是1,对应改变映射关系即可。

二、计数排序的步骤

  1. 找出待排序数组中的最大值和最小值,即待排序数组的区间范围
  2. 统计出数组中每个值为i的元素出现的次数,存入的辅助数组中对应的映射位置(映射关系根据情况确定)
  3. 对辅助数组进行累加,即从辅助数组中第二个元素开始,更新每个元素的值为该项和前一项的和
  4. 反向填充(从待排序数组最后一个元素开始),将每个元素i放在存储排序结果数组中对应映射关系的Map(i)项中,并将辅助数组中该元素的值进行减1操作

三、计数排序的一些分析

首先说明一点,计数排序并不是一种比较排序(如冒泡排序,比较排序等),它的实际运行效率为O(2n+k),其中n是待排序数组的元素个数,k是待排序区间范围包含的不重复元素个数。计数排序的最优,最差以及平均时间复杂程度都是O(2n+k)。从时间复杂程度可以看出,计数排序比比较排序都要快。缺点就是它需要的辅助空间可能会很多,所以是否需要计数排序要根据实际情况来判断。另外,在实现计数排序最后一步是用反向填充,这一点可以保证计数排序是一种稳定排序算法。

四、计数排序的例子

比较懒了,这个要画图,以后再附上。大家可以参考算法导论上的例子。 


五、代码实现

#include <stdio.h>
#include <stdlib.h>

int 
main(int argc, char const *argv[])
{
	int* numbers = NULL;
	int length = 0;
	int i = 0;
	int max_number = 0;

	int counting_sort(int *numbers, int length, int max_number);

	while(EOF != scanf("%d", &length))
	{
		scanf("%d", &max_number);
		if (NULL == (numbers = (int*)malloc(length * sizeof(int))))
		{
			return 0;
		}
		
		for(i = 0; i < length; i++)
			scanf("%d", &numbers[i]);
		
		counting_sort(numbers, length, max_number + 1);
		
		printf("%d", numbers[0]);
		for(i = 1; i < length; i++)
			printf(" <= %d", numbers[i]);
		printf("\n");

		free(numbers);
	}

	return 0;	
}

int
counting_sort(int *numbers, int length, int max_number)
{
	int *count = NULL;
	int *target = NULL;
	int i = 0;

	if (NULL == (count = (int *)calloc(max_number, sizeof(int))) ||
		NULL == (target = (int *)calloc(length, sizeof(int))))
	{
		exit(0);
	}

	for(i = 0; i < length; i++)
		count[numbers[i]] += 1;
	for(i = 1; i < max_number; i++)
		count[i] += count[i - 1];

	for(i = length - 1; i >= 0; i--)
	{
		target[count[numbers[i]] - 1] = numbers[i];
		count[numbers[i]]--;
	}

	for(i = 0; i < length; i++)
		numbers[i] = target[i];

	free(count);
	free(target);

	return 1;
}
程序较为简单,只给出了一种最简单的映射关系,复杂点的映射关系读者可自行尝试。

六、后续工作

计数排序只是众多排序算法中的一种。其他排序算法会在后面的一些博客中进行总结。另外,计数排序的映射关系可以看作是一种hash映射的特殊形式,与桶排序有很多相似的地方,后面介绍桶排序的时候再进行比较。

七、参考资料

1. Introduction to Algorithms(Second Edition), Thomas H.Cormen & Charles E.Leiserson

2. Counting Sort, http://en.wikipedia.org/wiki/Counting_sort


说明:

数据结构和算法博客系列的目录为:http://blog.csdn.net/adrastos/article/details/9093857

如有错误还请各位指正,欢迎大家一起讨论给出指导。

上述程序完整代码下载链接:

https://github.com/zeliliu/BlogPrograms/tree/master/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%26%E7%AE%97%E6%B3%95/sort/counting%20sort


最后更新时间2013-07-04

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值