前面讲过了几种常见的排序方法,比如堆排序,归并,插入,快排等等,这些排序算法的时间复杂度都限制在O(nlogn)的上界。他们所有的排序结果都依赖于各个元素之间的比较来确定,所以我们把这类算法称之为比较排序。今天我们介绍时间复杂度为O(n)上界的排序算法:计数排序。
计数排序假设N个输入元素的每一个都是确定在一个range里面,比如一个整数序列,其中所有的元素都不大于K,当k=O(n)时,计数排序的运行时间为O(n).
计数排序(CountingSort)原理:对于每一个输入的元素x,我们首先确定出小于x的元素的个数,然后我们想想,当我们知道小于x的个数时,我们是不是就可以直接确认出x在最终排序后的位置?这样我们就可以很简单的不通过比较久能把x直接放在它最终的输出序列的位置。For example 我们有10个元素进行排序,其中小于元素7的元素个数为3,那么我们就可以直接把7放在输出数组的第四个位置上。对于每一个元素都采用这样的方式去确定出位置,最终便得到一个排序好的数组序列,很明显看出计数排序是稳定排序。
待解决问题:细心的同学或许发现,上述描述中如果出现相同的元素就会有些问题,比如7元素出现了2次,小于元素7的元素的个数时3, 那么你会发现第一个7就会被放在第4这个位置,而第二个7也需要放在第4个位置。为了避免这个情况,我们将统计改为小于等于元素x的元素个数减一,然后每次放置一个元素后该元素对应的小于等于的计数值减一就可以解决上述问题。比如有两个7,小于等于7的数量就是5-1=4,那么第一个其放在第5个位置,然后统计值再减一4-1=3,第二个7就放在第4个位置。
辅助空间:count[k+1]用来统计每个元素出现的次数,并最终转换成小于等于各个元素的个数,如count[i]表示小于等于i的元素个数, bak[n]辅助数组,用来存放最终的结果
code:
/*
*计数排序
*输入:待排序数组arr,数组长度n,元素范围k
*输出:无
*/
void counting_sort(int* arr, int n, int k)
{
int nk = k+1;
int *count_arr = (int *)malloc(sizeof(int) * nk); // 统计数组
int *sorted_arr = (int *)malloc(sizeof(int) * n); // 辅助数组
memset(count_arr, 0x00, sizeof(int)*nk); // 初始化数组
memset(sorted_arr, 0x00, sizeof(int)*n); // 初始化数组
for (int i = 0; i < n; i++)
count_arr[arr[i]]++; // 统计每个元素出现的次数
for (int i = 1; i <= k; i++)
count_arr[i] += count_arr[i-1]; // 统计小于等于元素i的个数
for (int i = n; i>0; i--)
{
// 将统计出来的元素放在
sorted_arr[count_arr[arr[i-1]]-1] = arr[i-1]; //放置元素到合适的位置
count_arr[arr[i-1]]--; // 统计计数自动减一
}
memcpy(arr, sorted_arr, sizeof(int)*n); // 赋值元素到原始数组
free(count_arr);
free(sorted_arr);
}
计数排序是一种稳定的时间复杂度为O(N)的非比较性排序算法,但是他也有弊端,对于所有的元素都是要在某个范围内进行限制,如果没有限制条件或者限制条件过大,则不能使用。这对于限制范围比较小的比如字符串排序等等有较强的应用,另外一个常用的是作为后缀树组优化的一个方式。