首页 > 代码库 > 排序算法(4)-线性时间排序

排序算法(4)-线性时间排序

在前面三节排序算法中,我们分别分析了不同策略,思想用于排序,而这些算法都是基于数据间的比较来确定顺序的。假设我不用比较,换一种思路,那么就可以达到时间复杂度为O(n)的排序算法,当然是以付出额外的空间为代价的。

一、基本思想

线性时间排序的算法思想:

(1):在计数排序中,利用比x小或等的元素个数和的来确定x位置。比如2 5 4 9 1 6.9比其余5个数都大,那就说明9 在排序后的第6个位置,这样我们只要得到比某个数大的元素个数就能得到元素在排序后数组中的位置了。

(2):在桶排序中,是通过映射的方式,将大范围内的数映射到[0,1]之间的10个桶在中,这样再对每个桶中元素进行排序,将最后结果组合就行。本质也是分治的思想,不过这个分治有一定的规律,且分的子问题规模在一般情况下都很小,除了把元素都映射到少数几个桶的情况。

1.计数排序

计数排序的思想在前面已经阐述过了,前面举的例子也比较简单,我们没有考虑出现多个元素是相同的情况,如果出现多个元素是相同的情况,那应该怎么处理呢?多个元素不可能在同一个位置,必然有先后顺序,怎么处理先后顺序能决定计数排序的稳定性。下面结合图和程序来说明

 

image image

输入的是数组a,计数的数组为c,利用c的数组标号来计数,这里标号有可能超过10,标号0存的a中0的个数,1存的是1的个数。存好后,计算大于等于a中元素的个数,如右图,小于等于0的元素有1。

首先初始化计数数组,大小要等于元素a中的最大元素,可以利用一个循环求的,在下面程序未考虑,仅设置为a长度的两倍。然后计算每个元素的个数并存在c对应标号的位置。计算大于等于a中元素的个数,从右向左扫描数组a(为什么不左向右)取元素成为c的标号,得出这一元素在输出数组中的位置,把元素存于在一位置。因为可能有多个元素都存在这一位置,所以需要把c的数值减一。假设我后面扫描到相同大小的元素,我还是来取c的这个位置来确定排序后的元素所在位置,因为已经-1了,所以自然就排到前面的位置去了。这里就保持了排序的稳定,也回答为什么要从尾开始扫描。多个相同元素的前后位置保持一致。

#include <iostream>const int length=10;using namespace std;void counting_sort(int *a,int *b,int length){    int *c=new int[2*length];    for (int i=0;i<2*length;i++)        c[i]=0;    for (int j=0;j<length;j++)        c[a[j]]=c[a[j]]+1;    for (int k=1;k<2*length;k++)        c[k]=c[k]+c[k-1];    for (int l=0;l<length;l++)    {        b[c[a[l]]-1]=a[l];//输出的元素编号是从0开始        c[a[l]]--;    }}int main(){      int a[length]={0,3,2,1,3,2,1,2,2,3};    int *b=new int[length];    counting_sort(a,b,length);    for(int i=0;i<length;i++) cout<<b[i]<<" ";} //main end

二:基数排序

image

基数排序是以计数排序为基础的,实现在关键在于分离各个位数,以及在之前排序在基础在进行排序。这里的问题主要就是为什么不同高位牌开始呢?假设从高位排开始,那么对于每个位数就必须先保存其他的,等最小的那个排好后,然后排大二小的。基数排序当然也可以用于位数不相等的数据,第一步就是求最大的位数,然后从低位开始应用计数排序,直到最高位。

#include <iostream>using namespace std;const int radix=10;void radixsort(int a[],int n){    //求数组的最大位数    int temp=0,bi=1,mbi=0,count[radix],rad=1;    int *b=new int[n];    for (int i=0;i<n;i++)    {        temp=a[i];        while (temp/=10)        {            bi++;        }        if (bi>mbi)        {            mbi=bi;        }        bi=0;    }    //从低位开始对数组进行计数排序    for (int i=0;i<mbi;i++)    {        for (int j=0;j<radix;j++)            count[j]=0;        for (int k=0;k<n;k++)        {            count[a[k]/rad%10]++;//a[k]/rad%10:用于分离各个位,rad不断变化。        }        for (int j=1;j<radix;j++)            count[j]+=count[j-1];        for (int k=n-1;k>=0;k--)        {            b[count[a[k]/rad%10]-1]=a[k];            count[a[k]/rad%10]--;        }        for (int k=0;k<n;k++)        {            a[k]=b[k];        }        rad=rad*radix;    }}int main(){    int temp,k=10;    //int *data=http://www.mamicode.com/new int [radix];    /*while (cin>>temp)    {       data[k]=temp;        k++;    }      for(int i=0;i<10;i++)    cout<<data[i]<<" ";    cout<<endl;*/    int data[10]={713, 221, 93, 243, 55, 4, 8, 65, 39, 81};    radixsort(data,k);    for(int i=0;i<10;i++)        cout<<data[i]<<" ";    cout<<endl;    return 0;}

 

 

 

 

三、桶排序

参考http://www.roading.org/algorithm/introductiontoalgorithm/%E7%AC%AC%E5%85%AB%E7%AB%A0%EF%BC%884%EF%BC%89-%E6%A1%B6%E6%8E%92%E5%BA%8F%EF%BC%88bucket-sort%EF%BC%89.html