经典排序算法分析和代码-下篇

这篇文档我们来讲两种非比较排序,计数排序,基数排序

计数排序

计数排序是一种非比较算法,其时间复杂度为O(N+K)

举例说明

先用一个例子来说明计数排序算法,比如需要排序的集合为{1, 2, 1, 0, 4, 5},在该集合中,最大的数值为5,那么通过遍历整个集合,

可以得到这样的数组

   int counter[] = {1, 2, 1, 0, 1, 1}

                              0, 1, 2, 3, 4, 5

counter数组描述了被排序数组里有1个0, 2个1, 1个2, 0个3, 1个4和1个5,当这个数组形成时,排序也就结束了。

代码设计

1)根据集合中最大的数值K,来申请辅助空间counter数组

2)遍历被排序集合,讲计数记录到counter数组中

代码实现

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int data[] = {1, 2, 1, 0, 4, 5};

// 计数排序参数列表
// int d[] 为待排序数据列表
// int n 为待排序数据个数
// int min, max为待排序数据中最大和最小的数,通过其计算待排数据跨度K
void sort_counter(int d[], int n, int k)
{
    int i, j = 0;
    k++; // 实际申请空间比K大1
    // 申请辅助空间
    int* counter = malloc(sizeof(int)*k);
    memset(counter, 0, sizeof(int)*k);

    // 计数
    for(i=0; i<n; ++i)
    {
        ++counter[d[i]];
    }

    // 讲计数结果保存到待排数据空间
    for(i=0; i<k; ++i)
    {
        while(counter[i]-- > 0)
        {
            d[j++] = i;
        }
    }

    // 释放辅助空间
    free(counter);
}

int main(void)
{
    sort_counter(data, 6, 5);
    int i;
    for(i=0; i<6; ++i)
    {
        printf("%d
", data[i]);
    }
    return 0;
}




特点

计数排序的特单是时间复杂度,与其他的排序算法不同,它的时间复杂度为O(N+K),这个时间复杂度表明了当K相对比较大时,不适合使用,比如对集合{1, 0, 100}

但是对于N远大于K的情况下,是相当适合的

基数排序

在数量大时,计数排序需要大量的辅助空间,并且时间复杂度有可能比较大,所以推出基数排序。

举例说明

假如有待排序数据 data[] = {312, 213, 545, 893};

先排序个位数:排序结果为 312, 213, 893, 545

再排序十位数:排序结果为 321, 213, 545, 893

再排序百位数:排序结果为 213, 312, 545, 893

完毕

对位数的排序,可以使用计数排序,速度快而且稳定。

代码设计

1)获取待排序数据中的最大位数

2)对位数进行循环,按位对待排序数据进行计数排序,并将其中间结果保存到临时空间

3)将临时数据保存到待排序数据,继续步骤2)

代码实现

#include <stdio.h>

// int data[] = {1, 100, 321, 121, 333, 586, 1100};

// 该函数计算data中最大位数,本例子中,最大位数是1100,所以结果是4
int maxbit(int data[],int n)
{
    int d = 1; //保存最大的位数
    int p =10;
    for(int i = 0;i < n; ++i)
    {
        while(data[i] >= p)
        {
            p *= 10;
            ++d;
        }
    }
    return d;
}

// 基数排序
void sort_radix(int data[],int n)
{
    int d = maxbit(data, n);    // 计算位数
    int * tmp = new int[n];     // 中间变量,用来存储中间排序结果
    int * count = new int[10];  // 计数排序中的计数器
    int i,j,k;
    int radix = 1;

    // 根据最大位数进行循环,对没一位进行计数排序
    for(i = 1; i<= d;i++)
    {
        // 初始化计数器
        for(j = 0; j < 10; j++)
            count[j] = 0;

        // 对位数进行计数排序
        for(j = 0; j < n; j++)
        {
            k = (data[j]/radix)%10; // 注意这里进行取模
            count[k]++;             // 计数
        }
        for(j = 1; j < 10; j++)
            count[j] = count[j-1] + count[j];

        // 将排序中间结果保存到tmp
        for(j = n-1; j >= 0;j--)
        {
            k = (data[j]/radix)%10;
            tmp[count[k]-1] = data[j];
            count[k]--;
        }

        // 将中间结果保存到data
        for(j = 0;j < n;j++)
            data[j] = tmp[j];

        // 取模时的被除数,需要提高一位
        radix = radix*10;
    }
    delete [] tmp;
    delete [] count;
}

int main()
{
    int data[] = {1, 100, 321, 121, 333, 586, 1100};
    sort_radix(data, 7);
    for(int i=0; i<7; i++)
    {
        printf("%d
", data[i]);
    }
    return 0;
}


特点

基数排序比较适合对取值很大的数进行排序,也可用来对字符串进行排序。

但基数排序的缺点是不呈现时空局部性,因为在按位对每个数进行排序的过程中,一个数的位置可能发生巨大的变化,所以不能充分利用现代机器缓存提供的优势。同时计数排序作为中间稳定排序的话,不具有原地排序的特点,当内存容量比较宝贵的时候,还是有待商榷。

原文地址:https://www.cnblogs.com/new0801/p/6177573.html