非比较排序之 计数排序与基数排序

非比较排序

与插入、希尔、快速、归并、堆排序等等排序方式不同的是
以上这些排序算法都涉及到待排序列中元素值的比较。
然而也有不需要比较的排序算法。

计数排序

计数排序主要思想:
给定一组要排序的序列,找出这组序列中的最大值,然后开辟一个最大值加1大小的数组,将这个数组里面的元素全部置零,然后用这个数组下标统计出要排序的序列中各个元素出现的次数。等到统计完成的时候,排序就已经完成。

这里写图片描述

计数排序是一种非比较的排序方法,它的时间复杂度是O(N+K),空间复杂度是0(K),其中K是要排序的数组的范围。可以看出计数排序是一种以空间呢换取时间的方法。如果当K>N*logN的时候,计数排序就不是好的选择了,因为基于比较排序的算法的下限是O(N*logN)。

优化:

可以看出,计数排序使用于待排序的元素值比较集中的情况。假如先在对1001,1005,1008这三数进行排序,按照上面的方法前1000个空间都被浪费掉了,所以这时候我们对它进行优化。
我们可以找出要排序的这组元素中的最大值和最小值,这样就确定了这组元素的范围,然后开辟这个范围加1大小的数组,然后再将要排序的元素映射到这个新开辟的数组中就可以了。

计数排序适用于数字最大与最小差别不大的序列。

template<class T>
void CountSort(T* arr, size_t n)
{
    assert(arr);
    T max = arr[0];
    T min = arr[0];
    for (int i = 0; i < n; ++i){
        if (arr[i] < min) min = arr[i];
        if (arr[i] > max) max = arr[i];
    }
    size_t range = max - min + 1;
    //T* tmp = new T[range];
    //memset(tmp, 0, sizeof(int)*range);
    vector<T> tmp;
    tmp.resize(range, 0);
    for (int i = 0; i < n; ++i){
        tmp[arr[i] - min]++;
    }
    size_t index = 0;
    for (int i = 0; i < range; ++i){
        while (tmp[i]--){
            arr[index++] = i + min;
        }
    }
    //delete[] tmp;
}

基数排序

基数排序的主要思想:
基数排序又称”桶子法”,他从低位开始将待排序的数按照这一位的值放到相应的编号为0到9的桶中。等到低位排完之后得到一个序列,再将这个序列按照次低位的大小进入相应的桶中。以此类推,直到将所有的位都排完之后,这组数就已经有序了。
基数排序是一种非比较排序,它的时间复杂度是O(N*digit),其中digit是这组待排序中的最大的数的数量级,它的空间复杂度是O(N)。它是一种稳定的排序方法。

实现:
对于桶,我们最直观的想法就是用容器作为桶,将每一个桶的元素都放到一个容器中,这样虽然也能做,但是还是实现起来不是那么容易。我们可以参考稀疏矩阵的转置。例如对个位数的排序,我们开辟一个10元素大小的数组,将这10个大小的数组假想成10个桶,统计出每个桶中第一个元素在原数组中的起始位置。我们在建立一个与要排序的序列一样大辅助数组,然后根据每个桶中的起始位置将每个桶中的元素依次放到辅助数组中,之后再把辅助数组中的元素拷贝回原数组,这样一次排序就完成了。之后再按照同样的方法,再对高位进行排序。

这里写图片描述

代码:

template<class T>
int Count(T* arr, size_t n)
{
    int digit = 1;
    int base = 10;
    for (int i = 0; i < n; ++i){
        while (base <= arr[i]){
            base *= 10;
            digit++;
        }
    }
    return digit;
}

template<class T>
void BaseSort(T* arr, size_t n)
{
    assert(arr);
    int digit = Count(arr, n);
    int base = 1;
    vector<T>  tmp;
    tmp.resize(n, 0);
    while (digit--){
        int count[10] = { 0 };
        for (int i = 0; i < n; ++i){
            int index = arr[i] / base % 10;
            count[index]++;//求这一位数字是出从零到9的那个数字出现的次数。
        }
        int start[10] = { 0 };
        for (int i = 1; i < 10; ++i){
            start[i] = start[i - 1] + count[i - 1];
        }//求这一位数字是从零到9那个数字在辅助空间里第一次出现的位置。
        for (int i = 0; i < n; ++i){
            int index = arr[i] / base % 10;
            tmp[start[index]++] = arr[i];//请注意这里  这时的arr[i]是上一次循环从辅助数组拷贝过来的 。
                                       //arr[i]中的数列是按照比这一次循环处理的位第一位数字从小到大排列的            
                                        //这里往tmp里填的是这一位相同数字第一位数字中较小的元素。
        }
        for (int i = 0; i < n; ++i){
            arr[i] = tmp[i];//把辅助空间里的拷贝回原空间。
        }
        base *= 10;//下一个循环处理高一位的数字。
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值