排序可以通过比较实现,也可以通过不比较实现。比较排序就是指通过比较操作(通常是“小于或等于”操作)来确定两个元素中哪个应该放在序列前面。比较排序算法理论的算法复杂度下限为:O(nlgn),也就是说比较排序的时间复杂度不可能比O(nlgn)更小了。而非比较的排序,如计数排序、桶排序和计数排序,则可以突破O(nlgn)的时间下限。但要注意的是,非比较排序算法的使用都是有条件限制的,例如计数排序,对数据范围过大的元素进行排序需要耗费大量的内存和时间,但对数据范围较小的大量数据排序,计数排序却可以发挥奇妙的作用。下面将重点介绍这个算法:
(1)什么是计数排序
计数排序是一个非基于比较的排序算法,该算法于1954年由 Harold H. Seward 提出。它的优势在于在对一定范围内的整数排序时,它的复杂度为Ο(n+k)(其中k是整数的范围,n是待排序元素的个数),快于任何比较排序算法。 当然这是一种牺牲空间换取时间的做法,而且当O(k)>O(n*log(n))的时候其效率反而不如基于比较的排序(基于比较的排序的时间复杂度在理论上的下限是O(n*log(n)), 如归并排序,堆排序)。这个要记住:计数排序的适用场合。
(2)计数排序算法思想
计数排序的基本思想是对于给定的输入序列中的每一个元素x,确定该序列中值小于x的元素的个数(此处并非比较各元素的大小,而是通过对元素值的计数和计数值的累加来确定)。一旦有了这个信息,就可以将x直接存放到最终的输出序列的正确位置上。例如,如果输入序列中只有17个元素的值小于x的值,则x可以直接存放在输出序列的第18个位置上。当然,如果有多个元素具有相同的值时,我们不能将这些元素放在输出序列的同一个位置上,有重复时需要特殊处理(保证稳定性),需要在最后反向填充目标数组,并将每个数字的统计减去1。
(3)计数排序算法具体步骤
1.找出待排序的数组中最大和最小的元素
2.统计数组中每个值为i的元素出现的次数,存入数组C的第i项,即扫描序列A,以A中每个元素的值为索引,将C中对应位置的次数统计加1,C[i]为A中i出现的次数。
3.对所有的计数累加(从C中的第一个元素开始,每一项和前一项相加)以确定值为i的元素在数组中出现的位置。
4.反向填充目标数组:将每个元素i放在新数组的第C(i)项,每放一个元素就将C(i)减去1
假定输入是个数组A[1…n], length[A]=n。 另外还需要一个存放排序结果的数组B[1…n],以及提供临时存储区的C[0…k](k是所有元素中最大的一个)例如:
假设数字范围在 0 到 9. A[]={0, 4, 1,2, 7,1, 5, 2,7,9}
1) 使用一个数组c记录每个数组出现的次数
数组索引(下标): 0 1 2 3 4 5 6 7 8 9
数组c: 1 2 2 0 1 1 0 2 0 1
2) 累加所有计数(从C中的第一个元素开始,每一项和前一项相加)
数组索引(下标): 0 1 2 3 4 5 6 7 8 9
数组c: 1 3 5 5 6 7 7 9 9 10
更改过的计数数组就表示每个元素在输出数组中的位置
3) 反向填充目标数组:将每个元素i放在新数组的第C(i)项,每放一个元素就将C(i)减去1
例如对于:0, 4, 1, 2, 7, 5, 2,7,9中0的位置是1,则把0放在输出数组的第一个位置,并将计数减一。1出现的位置是3,就将3放在输出数组的第3个位置,并将计数减一,下一个1出现的时候就放在第2个位置了.(方向可以保持稳定)
(4)计数排序具体C语言实现
#include <stdio.h>
#include <stdlib.h>
#define random(x) rand()%(x)
#define NUM 100 // 产生100个随机数
#define MAXNUM 200 //待排序的数字范围是0-200
void countingSort(int A[], int n, int k){
int *c, *b;
int i;
c = (int *)malloc(sizeof(int)*k);/*临时数组,注意它的大小是待排序序列中值最大的那个。如假定该排序序列中最大值为1000000,则该数组需要1000000*sizeof(int)个存储单元*/
b = (int *)malloc(sizeof(int)*n); /*存放排序结果的数组*/
for (i = 0; i < k; i++)
c[i] = 0; /*初始化*/
for (i = 0; i < n; i++)
c[A[i]] += 1; /*统计数组A中每个值为i的元素出现的次数*/
for (i = 1; i < k; i++)
c[i] = c[i - 1] + c[i]; /*确定值为i的元素在数组c中出现的位置*/
for (i = n - 1; i >= 0; i--)
{
b[c[A[i]] - 1] = A[i]; /*对A数组,从后向前确定每个元素所在的最终位置;*/
c[A[i]] -= 1;
}
for (i = 0; i < n; i++)
A[i] = b[i]; /*这个目的是返回A数组作为有序序列*/
free(c);
free(b);
}
void printArray(int A[], int n){
int i = 0;
for (i = 0; i < n; i++){
printf("%4d", A[i]);
}
printf("\n");
}
/*测试*/
int main()
{
int A[NUM];
int i;
for (i = 0; i < NUM; i++)
A[i] = random(MAXNUM);
printf("before sorting:\n");
printArray(A, NUM);
countingSort(A, NUM, MAXNUM);
printf("after sorting:\n");
printArray(A, NUM);
return 0;
}
时间复杂度: O(n+k)
空间复杂度: O(n+k)
(5)其它
1. 计数排序是有效的,如果输入数据的范围是不显著大于数字的个数。
2. 它不是一个基于比较的排序。它运行的时间复杂度为O(n)
3. 它经常被用来作为另一个排序算法像基数排序的一个子程序。
4. 计数排序可以扩展到负输入也可以。
主要参考:http://www.cnblogs.com/ttltry-air/archive/2012/08/04/2623302.html