计数排序假设n个输入元素中每一个都是在0到k区间内的一个整数,其中k为某个整数。当
k=O(n)
时,排序的运行时间为
Θ(n)
。
计数排序的基本思想是:对每一个输入元素x,确定小于x的元素的个数。利用这一信息直接将x放到它在输出数组中的位置上。
计数排序的伪代码如下:
假设输入是一个数组A[1..n],A.length = n,还需要两个数组,B[1..n]用来存放排序的结果,C[0..k]用来提供临时存储空间。
COUNTING-SORT(A, B, k)
let C[0..k] be a new array
for i = 0 to k
C[i] = 0
for j = 1 to A.length
C[A[j]] = C[A[j]] + 1
//C[i] now contains the number of elements equal to i
for i = 1 to k
C[i] = C[i] + C[i - 1]
//C[i] now contains the number of elements less than or equal to i
for j = A.length down to 1
B[C[A[j]]] = A[j]
C[A[j]] = C[A[j]] - 1
计数排序总的时间代价为
Θ(n+k)
,在实际工作中,当
k=O(n)
时,我们一般会采用计数排序,这时的运行时间为
Θ(n)
。
计数排序的过程中完全没有输入元素之间的比较操作,所以脱离了比较排序模型,
Ω(nlgn)
这一下界就不适用了。
以上的计数排序是稳定的,即具有相同值的元素在输出数组中的相对次序与它们在输入数组中的相对次序相同。因为其排序是稳定的,所以常被用作基数排序算法的一个子过程。
计数排序的C语言实现如下:
void counting_sort(int *source, int **result, int min, int max, int length) {
if (length <= 0) {
fprintf(stderr, "Error size of source array.\n");
return;
}
if (min > max) {
fprintf(stderr, "Error range of source array.\n");
return;
}
int range = max - min + 1;
int *count = (int*)calloc(range, sizeof(int));
*result = (int*)calloc(length, sizeof(int));
for (int i = 0; i < length; i++) {
if (source[i] > max || source[i] < min) {
fprintf(stderr, "Elements out of range.\n");
return;
}
count[source[i] - min]++;
}
for (int i = 1; i < range; i++) {
count[i] += count[i - 1];
}
for (int i = length - 1; i >= 0; i--) {
(*result)[count[source[i] - min] - 1] = source[i];
count[source[i] - min]--;
}
free(count);
}
可以考虑使计数排序为原址排序,所用时间复杂度仍为 Θ(n+k) ,但是不需要使用额外数组来保存最终的结果。但是该算法则是不稳定的。代码如下:
void swap(int *p1, int *p2) {
int temp = *p1;
*p1 = *p2;
*p2 = temp;
}
void counting_sort_in_place(int *source, int min, int max, int length) {
if (min > max) {
fprintf(stderr, "Error range of source array\n");
return;
}
int range = max - min + 1;
int *count = (int*)calloc(range, sizeof(int));
for (int i = 0; i < length; i++) {
if (source[i] > max || source[i] < min) {
fprintf(stderr, "Elements out of range.\n");
return;
}
count[source[i] - min]++;
}
int temp = 0, sum = 0;
for (int i = 0; i < range; i++) {
temp = count[i];
count[i] = sum;
sum += temp;
}
int i = length - 1;
while (i >= 0) {
temp = source[i] - min;
if (count[temp] > i) {
i--;
continue;
} else {
swap(source + i, source + count[temp]);
count[temp]++;
}
}
free(count);
}
借鉴了Stack Overflow上的一个回答,不过稍做改动,不需要进行嵌套的两个循环,总共元素移动的次数为
Θ(n+k)
,但是最后的while循环的次数多于此。count[k]用于确定大小为k的元素在原数组中的起始位置,由于while循环从后往前确定元素是否已经归位,而count从前往后递增,所以可以得出正确结果。但是该算法并不是稳定的排序。
该算法的原题可见算法导论第三版思考题8-2。