zoukankan      html  css  js  c++  java
  • 线性时间运行的排序算法

    在前面的文章中,我们介绍的都是基于比较的排序。

    对于比较排序,对含n个元素的序列进行排序,在最坏情况下都要用O(n logn)次比较(归并排序和堆排序是渐近最优的)。

    本文将继续介绍以线性时间运行的排序算法,他们使用的是非比较排序,因此下界O(n logn)对它们不适用。

    计数排序

    想象下面这种情况:

    一个班有k个人,需要排成一条纵队,地面上已经用粉笔按从小到大的顺序标明了1到k个号码,要求按身高从低到高排列,也就是说,最高的站在标号为k的位置,最矮的站在标号为1的位置。

    那么对于每个人,如何知道自己的位置呢?

    假如每个人都知道所有人的身高,那么他只需要数一数比自己矮的有多少个,比如10个,那么他就可以很自觉地站到标号为11的位置了。

    注意:如果有身高相等的情况,我们需要改变下策略:每个人数一数比自己矮的以及和自己身高一样的人数(包括自己),假如有2个人身高相等,一开始他们得到的计数为8,于是一个人开始站到标号为8的位置,那么另一个人应该站到标号为7的位置(也就是说他的计数值应该减1)。

    当每个人都站好之后,整个队就按身高从低到高排好序了。

    计数排序假设n个输入元素都是0到k之间的整数,基本思想是对每个元素x,确定出小于x的元素个数,之后便可以将x直接放到合适的位置。(需注意元素相等的情况)

    为完成计数排序,我们需要三个数组:输入数组a[0…n-1],排序结果数组b[0…n-1],计数数组c[0…k]。

    算法步骤如下:

    1. 初始化c[0…k]为0;
    2. 对于每个元素a[i], c[ a[i] ]++,此时c记录a中各个元素出现的次数,比如{1, 1, 3}中,c[1] = 2, c[2] = 0, c[3] = 1;
    3. 对于i=1 to k, c[i] = c[i] + c[i-1],此时c记录小于等于i的元素的个数,c[1] = 2, c[2] = 2, c[3] = 3;
    4. 按照计数结果存放元素到b中:对i = n-1 to 0,b[ c[ a[i] ] ] = a[i], c[ a[i] ]–;

    第四步有点绕,我们一步步看: a[i]表示第i个待排元素, c[ a[i] ]表示小于等于第i个待排元素的元素个数,也就是说,第i个待排元素应该放置的位置。之后c[ a[i] ]–,因为a[i]已经放到正确的位置上了,这一个语句使得下一个值等于a[i]的待排元素放置到a[i]的前一个位置,这就解决了相等元素的问题。(同时注意到i是逆序的,它保证了计数排序的稳定性,假如是正序的,以{1,1,3}为例,那么第一个1将放置到第二个1后面,不稳定)

    代码实现:

    void countingSort(int a[], int b[], int size, int k)
    {
        if (NULL == a || size <= 0)
            return ;
        int *c = new int[k+1];
        if (NULL == c)
        {
            cerr << "new error" << endl;
            return ;
        }
        memset(c, sizeof(c), 0);
        for (int i = 0; i < size; ++i)
            ++c[ a[i] ];
        for (int i = 1; i <= k; ++i)
            c[i] += c[i-1];
        for (int i = size-1; i >=0; --i)
            b[ c[ a[i] ]-- ] = a[i];
    }
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    计数排序花费的总的时间为O(k+n),当k = O(n)时,运行时间为O(n)。

    可以看到,计数排序的效率是很高的,但是其局限性也高:排序的元素必须是【正整数】,且由于需要辅助数组c[0…k],所以元素的范围不能太大。

    基数排序

    基数排序就很有意思了,它最初是用在老式穿卡机上的算法。

    先举个扑克牌的例子……

    我们要对一副扑克牌排序,可以这样做:先不管牌的大小,先对花色(比如红桃、黑桃、方块、梅花)排序,之后再根据牌的大小排序,于是一副牌就按花色、大小排好了。

    对于序列:{329,457,657,839,436,720,355},基数排序的工作原理如下:

    先按最低位排序:

    720 
    355 
    436 
    457 
    657 
    329 
    839

    再按次低位排序:

    720 
    329 
    436 
    839 
    355 
    457 
    657

    最后按最高位排序:

    329 
    355 
    436 
    457 
    657 
    720 
    839

    需要强调的一点:每一次按位排序都要是稳定的。

    伪代码:

    void radixSort(int arr[], int d) //d为最高位,1为最低位
    {
        for (int i = 1; i <= d; ++i)
            use a stable sort to sort array on digit i;
    }
    • 1
    • 2
    • 3
    • 4
    • 5
    • 1
    • 2
    • 3
    • 4
    • 5

    以上的基数排序是最低位优先(Least Significant Digit first)的,简称LSD法,同样还有最高位优先(Most Significant Digit first)法,简称MSD法,它从最高位开始排序。

    **以上代码考虑的是正整数,在实际应用中,可能需要处理负数的情况。

    基数排序的时间复杂度取决于选择哪种稳定的中间排序算法。

    桶排序

    桶排序就很好理解了,其思想是:假设所有元素均匀分布在区间[0,1)上,将该区间划分成n个相同大小的子区间(称为桶),之后,将相应的元素放到对应范围的桶里面,对各个桶里的元素进行排序,最后按次序把各个桶里的元素列出来即可。

    桶排序的期望运行时间为O(n)。

    以上只是最基本的桶排序思想,在实际应用中,考虑到实际数据的情况,需要做相应的调整。(比如桶的个数、桶之间是否平均分配等等),有兴趣的读者可以自行深入学习一下(对于并行算法感兴趣的朋友,可以看一看基于桶排序的并行化算法Sample Sort)。

    位图排序

    位图排序是一种效率极高(复杂度可达O(n))并且很节省空间的一种排序方法,但是这种排序方法对输入的数据是有比较严格的要求(数据不能重复,大致知道数据的范围,必须是整数,没有数据与整数记录相关联……)。位图排序即利用位图或者位向量来表示集合。举个例子,假如有一个集合{3,5,7,8,2,1},我们可以用一个8位的二进制向量set[1-8]来表示该集合,如果数据存在,则将set相对应的二进制位置1,否则置0.根据给出的集合得到的set为{1,1,1,0,1,0,1,1},然后再根据set集合的值输出对应的下标即可得到集合{3,5,7,8,2,1}的排序结果。这个就是位图排序的原理。

    位图排序的应用:

          1.给40亿个不重复的unsigned int的整数,没有排过序,然后再给一个数,如果快速判断这个数是否在那40亿个数当中。

          因为unsigned int数据的最大范围在在40亿左右,40*10^8/1024*1024*8=476,因此只需申请512M的内存空间,每个bit位表示一个unsigned int。读入40亿个数,并设置相应的bit位为1.然后读取要查询的数,查看该bit是否为1,是1则存在,否则不存在。

          2.给40亿个unsigned int的整数,如何判断这40亿个数中哪些数重复?

          同理,可以申请512M的内存空间,然后读取40亿个整数,并且将相应的bit位置1。如果是第一次读取某个数据,则在将该bit位置1之前,此bit位必定是0;如果是第二次读取该数据,则可根据相应的bit位是否为1判断该数据是否重复。

     

    参考文献:《算法导论》 第八章 线性时间排序

    《王道》

    http://www.fx114.net/qa-97-116090.aspx

    http://blog.csdn.net/jiange_zh/article/details/50703949

  • 相关阅读:
    Android驱动开发
    LCD控制器时序参数的确定(基于TFT LCD: KD50G9-40NM-A3)
    Delphi IDHTTP用法详解
    Delphi开发Android的几个注意
    Delphi 调试连接 任意Android手机/平板/盒子
    Delphi XE10在 Android下调用静态库a文件
    Delphi的FIFO实现
    Delphi获取Android下GPS的NMEA 0183数据
    【CJOJ1603】【洛谷1220】关路灯
    【洛谷2986】【USACO10MAR】伟大的奶牛聚集
  • 原文地址:https://www.cnblogs.com/shixisheng/p/6840763.html
Copyright © 2011-2022 走看看