排序算法-9-计数排序

计数排序

前面已经介绍过的所有排序,包括插入排序选择排序冒泡排序归并排序鸡尾酒排序希尔排序快速排序堆排序都是比较排序,因为在他们的排序过程中,都需要经过比较,比较元素的大小然后调整顺序。可以证明,在最坏情况下,任何比较排序都需要经过 Ω ( n log ⁡ n ) \Omega(n \log n) Ω(nlogn) 次比较,所以比较排序的时间复杂度最好也就能到 O ( n log ⁡ n ) O(n \log n) O(nlogn) 了。

那如果我们想进一步降低时间复杂度怎么办呢?自然我们就不能用比较排序了。那么有不比较就能排序的方法吗?答案是有,还不止一种。这一篇我们就先讲一种:计数排序。

原理

计数排序的思想是对每一个元素,确定不大于它的元素个数,然后把它放到对应的位置就行了。如同一个班的学生从低到高排序,对一个学生来说,如果知道班上有10个人不比他高,那么他就应该排到第11位。

如果有相同的元素,这里还需要一点小技巧。比如有3个相同的元素,则要把这3个挨着放,最好能保持其稳定,即虽然都是一样大小,但之前在前面的排序完还是在前面,3个元素的相对位置不变。

具体步骤如下:

  1. 用一个数组记录每个元素分别有几个。
  2. 计算出对每个元素分别有多少个元素不大于它。
  3. 把每个元素按位置放好,若有相同值的元素,注意要调整其位置以避免相同元素放到相同位置。

实现

按照以上原理我们来用代码实现。

下面就是用C语言实现的代码。

  • 要排序的数组a有n个元素。注意a中所有元素值都在[0,k]范围内。
  • 数组c先记录每个元素分别有几个,后记录对每个元素分别有多少个元素不大于它。
  • 数组b用于保存排序结果。
  • 最终将排序结果复制到a中。
/* a中所有元素值都在[0,k]范围内 */
void count_sort(int a[], int n, int k)
{
	int *b, *c;
	int i;

	b = (int*)malloc(sizeof(int)*n);
	if (b==NULL) return;
	c = (int*)malloc(sizeof(int)*(k+1));
	if (c==NULL) {
		free(b);
		return;
	}

	memset(c, 0, sizeof(int)*(k+1));

	for (i=0; i<n; i++) {
		c[a[i]]++;
	} //此时c[i]中存的是a中有几个i

	for (i=1; i<=k; i++) {
		c[i] += c[i-1];
	} //此时c[i]中存的是有几个元素<=i

	for (i=n-1; i>=0; i--) {
		b[c[a[i]]-1] = a[i];   //将a[i]放入b中正确的位置
		c[a[i]]--;             //调整b的位置指针,避免相同元素放到相同位置
	}

	memcpy(a, b, sizeof(int)*n); //将排序结果复制到a中

	free(c);
	free(b);
}

为了验证此函数的效果,加上了如下辅助代码,对3个数组进行排序,运行结果在最后,可见排序成功。

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

#define SIZE_ARRAY_1 5
#define SIZE_ARRAY_2 6
#define SIZE_ARRAY_3 20

void show_array(int a[], int n);

void count_sort(int a[], int n, int k);

void main()
{
	int array1[SIZE_ARRAY_1]={1,4,2,9,0};
	int array2[SIZE_ARRAY_2]={10,5,2,1,9,2};
	int array3[SIZE_ARRAY_3];

	for(int i=0; i<SIZE_ARRAY_3; i++) {
		array3[i] = (int)((10.0*rand())/(RAND_MAX+1.0));
	}

	printf("Before sort, ");
	show_array(array1, SIZE_ARRAY_1);
	count_sort(array1, SIZE_ARRAY_1, 9);
	printf("After sort, ");
	show_array(array1, SIZE_ARRAY_1);

	printf("Before sort, ");
	show_array(array2, SIZE_ARRAY_2);
	count_sort(array2, SIZE_ARRAY_2, 10);
	printf("After sort, ");
	show_array(array2, SIZE_ARRAY_2);

	printf("Before sort, ");
	show_array(array3, SIZE_ARRAY_3);
	count_sort(array3, SIZE_ARRAY_3, 10);
	printf("After sort, ");
	show_array(array3, SIZE_ARRAY_3);
}

void show_array(int a[], int n)
{
	if(n>0)
		printf("This array has %d items: ", n);
	else
		printf("Error: array size should bigger than zero.\n");

	for(int i=0; i<n; i++) {
		printf("%d ", a[i]);
	}
	printf("\n");
}

运行结果:

Before sort, This array has 5 items: 1 4 2 9 0
After sort, This array has 5 items: 0 1 2 4 9
Before sort, This array has 6 items: 10 5 2 1 9 2
After sort, This array has 6 items: 1 2 2 5 9 10
Before sort, This array has 20 items: 8 3 7 7 9 1 3 7 2 5 4 6 3 5 9 9 6 7 1 6
After sort, This array has 20 items: 1 1 2 3 3 3 4 5 5 6 6 6 7 7 7 7 8 9 9 9

分析

时间复杂度

从代码可见,count_sort中只有一层循环,有三次,量级分别是 n n n k k k n n n,所以其时间复杂度为 O ( n + k ) O(n+k) O(n+k),若 k = O ( n ) k = O(n) k=O(n),则计数排序的时间复杂度为 O ( n ) O(n) O(n)

空间复杂度

因为计数排序需要两个辅助数组b和c来存储中间结果,所以其空间复杂度为 O ( n + k ) O(n+k) O(n+k),若 k = O ( n ) k = O(n) k=O(n),则计数排序的空间复杂度为 O ( n ) O(n) O(n)

要注意的是计数排序要求所有数据都是属于某个小区间的整数。如果范围太大,即 k k k 太大,则时间复杂度、空间复杂度都会变成增加,会影响排序效率。

稳定性

稳定。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值