《算法导论》 — Chapter 8 线性时间排序

到目前为止,关于排序的问题,前面已经介绍了很多,从插入排序、合并排序、堆排序以及快速排序,每一种都有其适用的情况,在时间和空间复杂度上各有优势。它们都有一个相同的特点,以上所有排序的结果序列,各个元素的次序都是基于输入元素之间的比较,因此,把这类排序成为比较排序。
对一个含有n个元素的输入序列,任何比较排序在最坏情况下都要用(nlogn)次比较来进行排序,由此也可以知道合并排序和堆排序是渐进最优的。
本章介绍了三种线性时间排序算法,计数排序、基数排序和桶排序,这些算法都是用非比较的操作来确定排序顺序。
下面将详细介绍这三种排序算法的实现。

GitHub 算法导论 第八章程序代码

计数排序

计数排序是基于对输入数据作某种假设条件下进行的排序算法。其作出,输入是由一个小范围内整数构成,即n个输入元素中的每一个都是介于0~k之间的整数。当k = O(n)时,计数排序的运行时间为O(n)。
其基本思想是,对每一个输入元素x,确定出小于x的元素个数,即要得出这个元素x是第几个位置,有了这样的信息,就可以把x直接放在最终的输出数组当中。
下面给出计数排序的算法实现,输入数据为data[] , 输出结果存在result[]中,输入数据个数为N = 10 , 每个元素都是位于 0~101 之间的整数,MAX = 101;

#include <iostream>
#include <ctime>
#include <cstdlib>
#define N 10
#define MAX 101

using namespace std;

//计数排序函数声明
void CountingSort(int *data, int *result, int k);

int main()
{
    //声明一个待排序数组   
    int array[N];
    //声明排序后数组
    int result[N];
    //设置随机化种子,避免每次产生相同的随机数    
    srand(time(0));
    for (int i = 0; i<N; i++)
    {
        array[i] = rand() % MAX;//数组赋值使用随机函数产生1-100之间的随机数      
    }
    cout << "排序前:" << endl;
    for (int j = 0; j<N; j++)
    {
        cout << array[j] << "  ";
    }
    cout << endl << "排序后:" << endl;
    //调用快速排序函数对该数组进行排序      
    CountingSort(array, result , MAX);
    for (int k = 0; k<N; k++)
    {
        cout << result[k] << "  ";
    }
    cout << endl;

    system("pause");
    return 0;
}//main

//计数排序算法实现
void CountingSort(int *data, int *result, int k)
{
    int C[MAX] = { 0 };
    //(1) 费时 O(k)
    for (int i = 0; i < k; i++)
        C[i] = 0;

    //(2) 费时O(n)
    for (int j = 0; j < N; j++)
        C[data[j]] = C[data[j]] + 1;

    //(3) 费时O(k)
    for (int i = 1; i < k; i++)
    {
        C[i] = C[i] + C[i - 1];
    }

    //测试当前源数据的目标位置
    /*for (int k = 0; k < N; k++)
    {
        cout << C[data[k]] << "	";

    }*/

    //得到排序后的目标序列 
    //(4)费时 O(n)
    for (int j = 0; j < N ; j++ )
    {
        //保证数据下标不会越界 需-1
        result[C[data[j]]-1] = data[j];
        C[data[j]] -= 1;
    }
}

计数排序测试结果

计数排序是一种稳定的排序算法,所谓稳定性,即是指具有相同值的元素在输出数组中的相对次序与它们在输入数组中的次序相同。

对于计数排序的性能,它由于前面介绍的比较排序时间下界(nlogn),从以上代码可以看出,计数排序算法步骤(1)~(4)所需的时间复杂度为O(n+k) , 当k=O(n)时,运行时间则为O(n)是一个线性时间排序算法。

基数排序

基数排序是一种按位排序算法,对输入待排序序列,求出其最大位数,从低有效位到最高有效位,分别对改组数据进行排序。
代码实现如下:

#include <iostream>
#include <ctime>
#include <cstdlib>
#define N 10
#define MAX 1000


using namespace std;

//基数排序函数声明
void RadixSort(int *data, int n);

//计算待排数组中最长位数
int ComputeDigits(int *data , int n);

//按照d位数字对数组排序算法
void digitSort(int *data, int n, int d);

int main()
{
    //声明一个待排序数组   
    int array[N];

    //设置随机化种子,避免每次产生相同的随机数    
    srand(time(0));
    for (int i = 0; i<N; i++)
    {
        array[i] = rand() % MAX;//数组赋值使用随机函数产生1-100之间的随机数      
    }
    cout << "排序前:" << endl;
    for (int j = 0; j<N; j++)
    {
        cout << array[j] << "  ";
    }
    cout << endl << "排序后:" << endl;
    //调用快速排序函数对该数组进行排序  
    RadixSort(array, N);

    for (int k = 0; k<N; k++)
    {
        cout << array[k] << "  ";
    }
    cout << endl;

    system("pause");
    return 0;
}//main

//基数排序算法实现
void RadixSort(int *data, int n)
{
    int digits = ComputeDigits(data, n);
    //选用一个稳定排序以各位数字对输入序列排序
    for (int i = 0; i < digits; i++)
    {
        digitSort(data, n, i);
    }

}

//计算待排数组中最长位数
int ComputeDigits(int *data , int n)
{
    int max = data[0];
    for (int i = 1; i < n; i++)
    {
        if (data[i] > max)
            max = data[i];
    }

    //临时计数变量
    int count = 0;

    while (max)
    {
        count++;
        max /= 10;
    }

    return count;
}

//按照d位数字对数组排序算法
void digitSort(int *data, int n, int d)
{
    int digitArray[10][N];
    for (int i = 0; i < 10; i++)
        for (int j = 0; j < N; j++)
            digitArray[i][j] = -1;

    //当前输入序列中有n个数字待排
    for (int i = 0; i < n; i++)
    {
        //得到当前位对应的数字
        int index = data[i] / (int)pow(10, d) % 10;
        for (int j = 0; j < n; j++)
        {
            if (digitArray[index][j] == -1)
            {
                digitArray[index][j] = data[i];
                break;
            }
        }
    }


    int k = 0;
    //将按位排序后的数组更新到源序列
    for (int i = 0; i < 10; i++)
    {
        for (int j = 0; j < N; j++)
        {
            if (digitArray[i][j] != -1)
            {
                data[k++] = digitArray[i][j];
                digitArray[i][j] = -1;
            }       
        }
    }
}

基数排序是一种稳定排序,以上代码中,对各位分别排序采用的是接下来介绍的桶排序。

桶排序

对于桶排序与计数排序类似也是对输入做了某种假设,因而运行很快。假设输入待排序列是由一个随机过程产生,该过程将元素均匀而独立的分布在[0 , 1) 上,桶排序的思想就是将该区间均匀的分成n个大小相同的桶,分别对各个桶中的元素按照直接插入排序,然后再把各个桶列出来即是排序结果。
对于桶排序的程序实现,输入采用[0 , 1000)的一组数据,道理同上,按照元素的最高位,建立下标为0~9的十个桶,将相应元素加入到相应桶中,加入过程采用直接插入排序,然后将桶中元素按照下标递增的方式罗列,即是最终排序结果。

#include <iostream>
#include <ctime>
#include <cstdlib>
#define N 10
#define MAX 1000

using namespace std;


//桶排序函数声明
void BucketSort(int *data, int n);

//计算待排数组中最长位数
int ComputeDigits(int *data, int n);



int main()
{
    //声明一个待排序数组   
    int array[N];

    //设置随机化种子,避免每次产生相同的随机数    
    srand(time(0));
    for (int i = 0; i<N; i++)
    {
        array[i] = (rand() % MAX);//数组赋值使用随机函数产生1-1000之间的随机数      
    }
    cout << "排序前:" << endl;
    for (int j = 0; j<N; j++)
    {
        cout << array[j] << "  ";
    }
    cout << endl << "排序后:" << endl;
    //调用快速排序函数对该数组进行排序      
    BucketSort(array , N);

    for (int k = 0; k<N; k++)
    {
        cout << array[k] << "  ";
    }
    cout << endl;

    system("pause");
    return 0;
}//main

//计算待排数组中最长位数
int ComputeDigits(int *data, int n)
{
    int max = data[0];
    for (int i = 1; i < n; i++)
    {
        if (data[i] > max)
            max = data[i];
    }

    int count = 0;

    while (max)
    {
        count++;
        max /= 10;
    }

    return count;
}

void BucketSort(int *data, int n)
{
    //计算输入序列中最大元素的位数
    int digits = ComputeDigits(data, n);

    //按照最高位0~9 创建10个桶
    int bucket[10][N+1];
    for (int i = 0; i < 10; i++)
    {
        //该桶的第一个元素设置为存储桶中元素个数
        bucket[i][0] = 0;
        //其余元素初始化为-1
        for (int j = 1; j < N + 1; j++)
            bucket[i][j] = -1;
    }

    //对每个输入元素按照插入排序放入相应的桶中
    for (int i = 0; i < n; i++)
    {
        //得到目标桶的序号
        int index = data[i] / (int)pow(10, digits-1);

        //得到当前桶中元素个数
        int count = bucket[index][0];

        int j = count;
        //按照直接插入排序将元素插入桶中
        while (j >0 && bucket[index][j] > data[i])
        {       
            bucket[index][j + 1] = bucket[index][j];
            j--;
        }
        bucket[index][j + 1] = data[i];
        bucket[index][0]++;
    }

    //将每个桶中的元素合并到data中
    int k = 0;
    for (int i = 0; i < 10; i++)
    {
        for (int j = 1; j <= bucket[i][0] ; j++)
        {
            data[k++] = bucket[i][j];
        }
    }
}

桶排序是一种线性时间排序算法,运行时间可以达到 O(n)

原文地址:https://www.cnblogs.com/shine-yr/p/5214938.html