两种非比较排序的原理和实现(计数排序和基数排序)

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/Chengzi_comm/article/details/51494251

前些天总结并实现了一些常见的比较排序算法,主要有这三大类:
插入排序、交换排序、选择排序,读者可以参考一下之前的博文:http://blog.csdn.net/chengzi_comm/article/details/51429165
这些排序方法都是直接或间接比较两个数之间的大小,从而得出数据间的大小关系。

今天这篇博客想要介绍一下两种重要的 非比较排序 算法,计数排序和基数排序, 下面来看一下不用比较的排序是怎么实现的 :)
(ps: 非比较排序并不是不需要比较,只不过不是直接比较两个数的大小,而是通过其他一些手段确定两个值得大小关系,下面再说)

一、计数排序
计数排序是一个什么样的过程呢?下面通过一个场景来引入计数排序:
在一所学校里面,有年老的教师,也有年轻的学生,他们年龄有的相同,有的不同,但是总的来说年龄相差不是太大 :) ,现在要对这些人的年龄进行排序,要求要在O(N)的时间内完成。

一个可行的方案是这样的:
首先统计每个人的年龄,年龄相同的算是一个年龄,但是这个年龄下的人数要加一,这样对年龄排序实际上就已经完成了,
接下来可以把每个年龄写到一张纸上,加入说年龄 i 有 n(i)个人,则年龄 i 要在纸上写 n(i)遍。这样所有人的年龄都在纸上并且是排好序的

我们来总结一下使用计数排序对一个整型数组进行排序的一般步骤:

  1. 遍历一遍整个数组,找出数组中最大数和最小数之间的差距 range,然后开辟一个大小为range的数组count并全部初始化为0;
  2. 再次遍历整个数组,把每个元素的值映射到count的下标index,每映射到一个index,就把count[index]加一;
  3. 根据统计结果count,把数写会原数组,某个值index要写count[index]遍到原数组,这样,原数组就有序了

这里有一个点需要注意一下,假如说1)中的最大值是max,最小值是min,并没有开辟大小为max的数组,而是申请了range = max+1 - min的长度,这是因为min之前是不可能有值的,给它们申请空间就浪费了,这样一来,值到下标的映射也不是直接映射的 而是 val –> (val-min)【index】

下面是计数排序的实现:

void CountSort(int *arr, int len)
{
    assert(arr);
    if(len <= 1)
        return ;

    int max = arr[0];
    int min = arr[0];

    for(int i = 0; i < len; ++i)    //统计数组中的最大值和最小值
    {
        if(arr[i] > max)
            max = arr[i];
        if(arr[i] < min)
            min = arr[i];
    }

    int range = max + 1 - min;  //得到最大最小值的差距
    int *count = new int[range];    //用于记录数组中每个数字出现的次数(下标是对应的数,只不过经过了一些处理,下面说)
    for(int i = 0; i < len; ++i)
        count[i] = 0;

    for(int i = 0; i < len; ++i)    
        count[ arr[i]-min ]++;  //arr[i] - min 映射到tmp中的一个下标,该下标下的值是这个数出现的次数

    int index = 0;
    for(int i = 0; i < range; ++i)  //再把临时数组记录的数分别拷贝到原数组中
    {
        while(count[i]--)   //每个下标下拷贝对应的次数
            arr[index++] = i + min;
    }
}

二、下面说下基数排序:
(基数排序相比计数排序要难一些,因为里面需要的临时数组较多,而且数组之间的关系比较抽象)

基数排序是通过分别比较每个数的各个位来实现数组的最终有序的。
基数排序分成MSD和LSD两种,

最高位优先(Most Significant Digit first)法,简称MSD法:简单地说就是先排各位再排十位,依次类推;
最低位优先(Least Significant Digit first)法,简称LSD法:就是先排最高位,再排次高位,最后排个位。
举个例子 对数组进行基数排序,采用LSD法:

int arr[] = {6, 52, 7, 123,87};

对个位排序后,数组变为: 52, 123, 6, 7, 87
再对十位排序,数组变为: 6, 7, 123, 52, 87
再对百位排序,数组变为 6, 7, 52, 87, 123
(以上三次排序都是对紧接着的上次的数组进行排序)

扫描二维码关注公众号,回复: 3818396 查看本文章

下面总结一下基数排序是怎么做的:(基于LSD)

  1. 遍历数组,统计最大数的位数是多少,用MaxRadix表示,这个值关系到应该进行多少次排序;
  2. 定义一个数组count10,记录一个数的某一位与其他数的这一位相同的数出现了多少次【比较绕口:), 没关系,下面的代码可以很清楚地解释这句话】;
  3. 定义一个数组start[10],记录某一位为start下标的值应该从桶的哪一位开始;
  4. 定义一个数组bucket[len]【len是原数组的长度】,用于盛放一次排序完成后的临时状态,然后拷回原数组;
  5. 重复2)、3)、4),直到把每个数的所有位都比较完。

下面是基数排序的实现:

void BaseSort(int *arr, int len)
{
    assert(arr);
    if(len <= 1)
        return ;

    int base = 10;      //基数,用来统计数的个数
    int maxRadix = 1;   //最长的数的位数

    for(int i = 0; i < len; ++i)    //统计数组中最长的数的位数
    {
        while(arr[i] / base)
        {
            maxRadix++;
            base *= 10;
        }
    }

    int *count  = new int[10];      //某一位为i的数的个数
    int *start  = new int[10];      //某一位为i的数应该映射到bucket的起始位置
    int *bucket = new int[len];     //用来存放某一位已经排好序的数组

    base = 1;
    while(maxRadix--)               //需要比较最大数的位数次,第一次比较个位,第二次比较十位...
    {
        for(int i = 0; i < 10; ++i) //初始化为0,因为下面直接是对数组元素++
            count[i] = 0;

        for(int i = 0; i < len; ++i)                //统计数组中某一位值相同的元素的个数
            count[ (arr[i]/base) % 10 ]++;          //arr[i]/base % 10时当前arr[i]的某一位的值,当前是个位,下一次是十位...

        start[0] = 0;
        for(int i = 1; i < 10; ++i)                 //某一位为i的数应该映射到bucket的起始位置
            start[i] = start[i-1] + count[i-1];

        for(int i = 0; i < len; ++i)                //已经统计好的数字映射到bucket中,映射完成后,某一位的比较就完成了
            bucket[ start[arr[i]/base % 10]++ ] = arr[i];

        for(int i = 0; i < len; ++i)                //把“部分有序”(按某一位排好序)的数组重新拷贝回原数组,为下一位排序做好准备
            arr[i] = bucket[i];

        base *= 10;         //开始排下一位
    }
}

计数排序和基数排序就说到这里吧 :) !!!

猜你喜欢

转载自blog.csdn.net/Chengzi_comm/article/details/51494251
今日推荐