和基数排序类似,计数排序也是一种非比较型整数排序算法,也是通过“分配”和“收集”来实现排序。
1.排序思路
开辟一个长度为maxVal-minVal+1的数组C,用来记忆各个数字的个数
分配: 扫描一遍原始数组,以当前值-minVal 作为下标,将该下标对应的的计数器加一
收集: 从计数器的第二个元素(C[1])开始,每一项和前一项相加,对所有的计数进行累加
 反向扫描一遍原始数组,将元素 R[i]填充在新阵列的第C[R[i] - minVal]项,每放一个元素就将 C减去1,由于数组下标从0,所以-1需要在填充之前进行。这里采用反向填充保证了稳定性
另一种收集方案: 反向遍历计数器
2.排序算法
以c为例,从小到大排序。
#include <stdio.h>
void counting_sort(int R[], int n){
    int i, maxVal = 0, minVal = 0, tmp[n];
    for (i = 0; i < n; i++)//寻找、记录最值。
    {
        if (R[i] > maxVal)
            maxVal = R[i];
        else if (R[i] < minVal)
            minVal = R[i];
    }
    int count[maxVal - minVal + 1];//设置并初始化计数器数组
    for (i = 0; i < maxVal - minVal + 1; i++){
        count[i] = 0;
    }
    for (i = 0; i < n; i++){//分配
        count[R[i] - minVal]++;
    }
    for (i = 1; i < maxVal - minVal + 1; i++){//收集
        count[i] += count[i - 1];
    }
    for (i = n - 1; i >= 0; i--){
        tmp[--count[R[i] - minVal]] = R[i];
    }
    for (i = 0; i < n; i++){ //放回原数组
        R[i] = tmp[i];
    }
    //打印分配收集后的结果
    for (i = 0; i < n; i++){
        printf("%d ", R[i]);
    }
    printf("\n");
}
int main(void){
    int a[] = {75,23,98,44,57,12,29,64,38,82};
    counting_sort(a, 10);
    return 0;
}输出结果:
12 23 29 38 44 57 64 75 82 98 反向遍历计数器:
#include <stdio.h>
void counting_sort(int R[], int n){
    int i, maxVal = 0, minVal = 0, tmp[n];
    for (i = 0; i < n; i++)//寻找、记录最值
    {
        if (R[i] > maxVal)
            maxVal = R[i];
        else if (R[i] < minVal)
            minVal = R[i];
    }
    int count[maxVal - minVal + 1];//设置并初始化计数器数组
    for (i = 0; i < maxVal - minVal + 1; i++){
        count[i] = 0;
    }
    for (i = 0; i < n; i++){//分配
        count[R[i] - minVal]++;
    }
    for (i = 1; i < maxVal - minVal + 1; i++){//收集
        count[i] += count[i - 1];
    }
    for (i = n - 1; i >= 0; i--){
        tmp[--count[R[i] - minVal]] = R[i];
    }
    for (i = 0; i < n; i++){ //放回原数组
        R[i] = tmp[i];
    }
    //打印分配收集后的结果
    for (i = 0; i < n; i++){
        printf("%d ", R[i]);
    }
    printf("\n");
}
void counting_sort2(int R[], int n){
    int i, maxVal = 0, minVal = 0;
    for (i = 0; i < n; i++)//寻找、记录最值
    {
        if (R[i] > maxVal)
            maxVal = R[i];
        else if (R[i] < minVal)
            minVal = R[i];
    }
    int count[maxVal - minVal + 1];//设置并初始化计数器数组
    for (i = 0; i < maxVal - minVal + 1; i++){
        count[i] = 0;
    }
    for (i = 0; i < n; i++){//分配
        count[R[i] - minVal]++;
    }
    int j, k = n-1;
    for (i = maxVal - minVal; i >=0; i--){//这里可以更改下整理方式,反向遍历计数器。
        for (j = count[i]; j >0 ;j--){
            R[k] = i + minVal;
            k--;
        }
    }
    //打印分配收集后的结果
    for (i = 0; i < n; i++){
        printf("%d ", R[i]);
    }
    printf("\n");
}
int main(void){
    int a[] = {1,2,2,3,6,8,6,5,2,9};
    counting_sort2(a, 10);
    return 0;
}注:上述函数同样不适用于负数情况。
动图演示:

注:图片来自https://visualgo.net,它采用了正序遍历,破坏稳定性,故不推荐
3.算法分析
时间复杂度:
当输入的元素是n个0到k之间的整数时,它的执行时间是O(n+k)
空间复杂度:
需要创建k+1个计数器,空间复杂度为O(k)
稳定性:
分配过程有先后顺序,这是一种稳定的排序方法。
复杂性:
较复杂
			本文由 ukuq 创作,采用 知识共享署名4.0 国际许可协议进行许可
本站文章除注明转载/出处外,均为本站原创或翻译,转载前请务必署名
最后编辑时间为: Feb 2, 2019 at 08:26 pm		
 
				 
				
study 一起学习 go