非比较排序

§1 计数排序(Counting Sort)

计数排序(Counting sort)

计数排序(Counting sort)是一种稳定的排序算法,和基数排序一样都是桶排序的变体。计数排序使用一个额外的数组C,其中第i个元素是待排序数组A中值小于等于i的元素的个数。然后根据数组C来将A中的元素排到正确的位置。

计数排序的原理

设被排序的数组为A,排序后存储到B,C为临时数组。所谓计数,首先是通过一个数组C[i]计算大小等于i的元素个数,此过程只需要一次循环遍历就可以;在此基础上,计算小于或者等于i的元素个数,也是一重循环就完成。下一步是关键:逆序循环,从length[A]到1,将A[i]放到B中第C[A[i]]个位置上。原理是:C[A[i]]表示小于等于a[i]的元素个数,正好是A[i]排序后应该在的位置。而且从length[A]到1逆序循环,可以保证相同元素间的相对顺序不变,这也是计数排序稳定性的体现。在数组A有附件属性的时候,稳定性是非常重要的。

计数排序的前提及适用范围

A中的元素不能大于k,而且元素要作为数组的下标,所以元素应该为非负整数。而且如果A中有很大的元素,不能够分配足够大的空间。所以计数排序有很大局限性,其主要适用于元素个数多,但是普遍不太大而且总小于k的情况,这种情况下使用计数排序可以获得很高的效率。由于用来计数的数组C的长度取决于待排序数组中数据的范围(等于待排序数组的最大值与最小值的差加上1),这使得计数排序对于数据范围很大的数组,需要大量时间和内存。例如:计数排序是用来排序0到100之间的数字的最好的算法,但是它不适合按字母顺序排序人名。但是,计数排序可以用在基数排序中的算法来排序数据范围很大的数组。



当输入的元素是 n 个 0 到 k 之间的整数时,它的运行时间是 Θ(n + k)。计数排序不是比较排序,排序的速度快于任何比较排序算法。

计数排序算法的步骤:

1.找出待排序的数组中最大和最小的元素

2.统计数组中每个值为i的元素出现的次数,存入数组C的第i项

3.对所有的计数累加(从C中的第一个元素开始,每一项和前一项相加)

4.反向填充目标数组:将每个元素i放在新数组的第C(i)项,每放一个元素就将C(i)减去1
void CountSort(int* a,size_t n)
{
    int min = a[0];
    int max = a[0];
    for(size_t i = 0; i < n; ++i)
    {
        if(a[i] < min)
            min = a[i];
        if(a[i] > max)
            max = a[i];
    }
    int range = max-min+1;
    int* count = new int[range];
    memset(count,0,range*sizeof(int));
    for(size_t i = 0;i < n;++i)
    {
        count[a[i]-min]++;//相对位置
        }
    int index = 0;
    for(size_t i = 0;i < range;++i)
    {
    while(count[i]--)  //i--,i次
        a[index++] = i+min;
    }
}

§2 基数排序(Radix Sort)

基数排序(Radix Sort)

基数排序(Radix Sort)是对桶排序的改进和推广。唯一的区别是基数排序强调多关键字,而桶排序没有这个概念,换句话说基数排序对每一种关键字都进行桶排序,而桶排序同一个桶内排序可以任意或直接排好序。

1、单关键字和多关键字

 文件中任一记录R[i]的关键字均由d个分量构成。若这d个分量中每个分量都是一个独立的关键字,则文件是多关键字的(如扑克牌有两个关键字:点数和花色);否则文件是单关键字的, (0≤j<d)只不过是关键字中其中的一位(如字符串、十进制整数等)。多关键字中的每个关键字的取值范围一般不同。如扑克牌的花色取值只有4种,而点数则有13种。单关键字中的每位一般取值范围相同。

2、基数

  设单关键字的每个分量的取值范围均是:C0≤kj≤Crd-1(0≤j<d)可能的取值个数rd称为基数。

基数的选择和关键字的分解因关键宇的类型而异:
(1) 若关键字是十进制整数,则按个、十等位进行分解,基数rd=10,C0=0,C9=9,d为最长整数的位数;

(2) 若关键字是小写的英文字符串,则rd=26,Co=’a’,C25=’z’,d为字符串的最大长度。

3、基数排序的基本思想

 基数排序的基本思想是:从低位到高位依次对Kj(j=d-1,d-2,…,0)进行箱排序。在d趟箱排序中,所需的箱子数就是基数rd,这就是"基数排序"名称的由来。

基数排序的时间复杂度是 O(k·n),其中n是排序元素个数,k是数字位数。注意这不是说这个时间复杂度一定优于O(n·log(n)),因为k的大小一般会受到 n 的影响。基数排序所需的辅助存储空间为O(n+rd)。

基数排序的方式可以采用LSD(Least significant digital)或MSD(Most significant digital),LSD的排序方式由键值的最右边开始,而MSD则相反,由键值的最左边开始。
int GetDigets(int* a,size_t n)
{
    int base = 10;
    int digit = 1;
    for(int i = 0;i < n;++i)
    {
        while(a[i] >= base)
        {
            ++digit;
            base *= 10;
        }
    }
    return digit;
}

先求取最大数的位数,并开出数据总数的桶大小
void LSDSort(int* a,size_t n)
{
    int digit = GetDigets(a,n);
    int* buckets = new int[n];
    int base = 1;

    for(int i = 0;i < digit ; ++i)
    {
        int counts[10] = {0}; //纪录每位 个数的数量
        for(int j = 0;j < n;++j)
        {
            int num = (a[i]/base)%10;
            counts[num]++;
        }
        //纪录完后,需要为从头到尾的读取做准备
        //纪录每种数量的前面的数量的个数
        int start[10];
        start[0] = 0;
        for(int j = 1;j < 10; ++j)
        {
            start[j] = start[j-1] + counts[i-1]; 
        }
        //重新布局每个元素的位置

        for(int j = 0;j < n;++j)
        {
            int num = (a[j]/base)%10;
            int pos = start[num];
            buckets[pos] = a[i];
            pos++;
        }
        memcpy(a,buckets,n*sizeof(int));
        base *= 10;
    }
    delete[] buckets;
}

猜你喜欢

转载自blog.csdn.net/niukeming/article/details/80570212