计数排序和基数排序

来源:互联网 发布:淘宝怎么搜索岛国资源 编辑:程序博客网 时间:2024/06/06 04:33

排序总归来说可分为两大类,比较排序与非比较排序。比较排序就是我们常用到的冒泡排序,插入排序,希尔排序,选择排序,堆排序,快速排序,归并排序。非比较排序不常用,但是在对一些特殊的情况进行处理时,它的速度反而更快。


1、计数排序  

排序原理:利用哈希的方法,将每个数据出现的次数都统计下来。哈希表是顺序的,所以我们统计完后直接遍历哈希表,将数据再重写回原数据空间就可以完成排序。

注意事项

①因为要建立哈希表,计数排序只适用于对数据范围比较集中的数据集合进行排序。范围分散情况太浪费空间。如我有100个数,其中前99个都小于100,最后一个数位是10000,我们总不能开辟10000个数据大小的哈希表来进行排序吧!这样空间就太浪费了。 此时,就得考虑其他的算法了。当然,这个例子也可能不恰当,这里只是想让你们直观的理解。
②除了上面那种情况,还有一个问题,例如我有一千个数,1001~2000,此时我的哈希表该怎么开辟呢? 开0~2000个?那前面1000个空间就浪费了!直接从1001开始开辟?你想多了!所以这种情况我们就需要遍历一遍数据,找出最大值与最小值,求出数据范围。范围 = 最大值 - 最小值+1。 例如,2000-1001+1 = 1000,这样我们就开辟0~1000个空间,用1代表1001,1000代表2000。节省了大量的空间。

③肯定有同学想到用位图(BitMap)来做,但是位图(BitMap)有局限性,它要求每个数据只能出现一次。算法有些复杂,但是可以尝试。


时间复杂度分析:综上所述,我们总需要两遍遍历,第一遍统计字数据出现次数,遍历原数据,复杂度为O(N),第二遍遍历哈希表,向原空间写数据,遍历了范围次(range),时间复杂度为O(range),所以总的时间复杂度为O(N+range)。

空间复杂度分析:开辟了范围(range)大小的辅助哈希表,所以空间复杂度为O(range)。


下面我对一组数据进行排序做出图解:





c++代码实现:

[cpp] view plain copy
print?
  1. void CountSort(int *a, int n)  
  2. {  
  3.     assert(a);  
  4.   
  5.     int max = a[0];  
  6.     int min = a[0];  
  7.     //选出最大数与最小数,确定哈希表的大小  
  8.     for (int i = 0; i < n; ++i)  
  9.     {  
  10.         if (a[i] > max)  
  11.         {  
  12.             max = a[i];  
  13.         }  
  14.         if (a[i] < min)  
  15.         {  
  16.             min = a[i];  
  17.         }  
  18.     }  
  19.   
  20.     int range = max - min + 1;  
  21.     int *count = new int[range];  
  22.     memset(count, 0, sizeof(int)*range);//当初始化成0或-1的时候可以用memset,其他情况均用for循环  
  23.     /*for (int i = 0; i < range; ++i) 
  24.     { 
  25.         count[i] = 0; 
  26.     }*/  
  27.     for (int i = 0; i < n; ++i)  
  28.     {  
  29.         count[a[i] - min]++;  
  30.     }  
  31.     //将数据重新写回数组  
  32.     int j = 0;  
  33.     for (int i = 0; i < range; ++i)  
  34.     {  
  35.           
  36.         while ((count[i]–) > 0)  
  37.         {  
  38.             a[j] = i + min;  
  39.             j++;  
  40.         }  
  41.     }  
  42. }  
void CountSort(int *a, int n){    assert(a);    int max = a[0];    int min = a[0];    //选出最大数与最小数,确定哈希表的大小    for (int i = 0; i < n; ++i)    {        if (a[i] > max)        {            max = a[i];        }        if (a[i] < min)        {            min = a[i];        }    }    int range = max - min + 1;    int *count = new int[range];    memset(count, 0, sizeof(int)*range);//当初始化成0或-1的时候可以用memset,其他情况均用for循环    /*for (int i = 0; i < range; ++i)    {        count[i] = 0;    }*/    for (int i = 0; i < n; ++i)    {        count[a[i] - min]++;    }    //将数据重新写回数组    int j = 0;    for (int i = 0; i < range; ++i)    {        while ((count[i]--) > 0)        {            a[j] = i + min;            j++;        }    }}


2、基数排序

基数排序是基于数据位数的一种排序算法,什么叫排序位数呢?个,十,百,千,万…明白了吧!。

它有两种算法

①LSD–Least Significant Digit first  从低位(个位)向高位排。

②MSD– Most Significant Digit first 从高位向低位(个位)排。


排序原理:

因为是按位进行排序,数据可能不统一,有的最大位为十位,有的最大位为百位。我们需要找到最大的位数来进行决定循环的次数。


LSD和MSD的两种算法思想一致,下面以一组数据用LSD进行排序:


在上图中最大的位数为4位,所以我们要对个,十,百,千,四位各进行一次循环排序。下面以个位为例进行示范。

①我们知道无论个十百千万哪个位,都只有10种情况,0-9,所以我们先创建一个大小为10 的哈希表,统计出每个位各有多少数据。例如上图数据中个位为0的出现1次,个位为1的出现2次,个位为2的出现1次,个位为3的出现2次,个位为9的出现1次。所以哈希表创建后得:


②再得出次数之后,我们需要开辟一个辅助空间tmp,并按0~9的顺序将其重新写入辅助空间,但是怎么写呢?这里我们就需要用到矩阵转置的思想了,再开辟一个大小为10 的数组,用来记录每个位(0~9)上的数据重新写入辅助空间时的起始下标。

例如:

0位有1个数据,但是0位之前再其他位,所以0位的起始下标就为0。

1位有2个数据,1位之前有个0位,并且0位有一个数据,所以1位的起始下标为1。

2位有1个数据,2位之前有两个位(0和1),所以2位之前一共有3个数据,所以2位的起始下标就为3。

由上面刻得出,每个位的起始下标就等于每个位之前的总数据个数之和。

所以得出起始下标数组为:


③现在起始下标得到了,我们就可以遍历原数组往辅助空间里写数据了,但是有一点要注意,每写入一个数据,起始下标数组相应位的起始下标就要加1。最后得:


到这里,我们的数据还在辅助空间tmp内,我们需要将其重新写回我们原数据空间。这样我们一趟排序就完成了,其他十位,百位,千位的排序只要在低位排序的基础上进行上面相似的排序即可。


时间复杂度分析:排序最大位数次,每次都要对数组进行一次排序,所以时间复杂度为O(N*最大位数)。

空间复杂度分析:建立与原数组同样大小的辅助空间,再无其他空间开销,所以空间复杂度为O(N)。


实现代码:

[cpp] view plain copy
print?
  1. //基数排序  
  2. //LSD  先以低位排,再以高位排  
  3. //MSD  先以高位排,再以低位排  
  4. void LSDSort(int *a, int n)  
  5. {  
  6.     assert(a);  
  7.     //求出其最大位数  个 十 百 千 万….  
  8.     int digit = 0;  
  9.     int bash = a[0];  
  10.     for (int i = 0; i < n; ++i)  
  11.     {  
  12.         while (a[i] > (pow(10,digit)))  
  13.         {  
  14.             digit++;  
  15.         }  
  16.     }  
  17.     int flag = 1;  
  18.     for (int j = 1; j <= digit; ++j)  
  19.     {  
  20.         //建立数组统计每个位出现数据次数  
  21.         int Digit[10] = { 0 };  
  22.         for (int i = 0; i < n; ++i)  
  23.         {  
  24.             Digit[(a[i] / flag)%10]++;  
  25.         }  
  26.          //建立数组统计起始下标  
  27.         int BeginIndex[10] = { 0 };  
  28.         for (int i = 1; i < 10; ++i)  
  29.         {  
  30.             BeginIndex[i] = BeginIndex[i - 1] + Digit[i - 1];  
  31.         }  
  32.         //建立辅助空间  
  33.         int *tmp = new int[n];  
  34.         memset(tmp, 0, sizeof(int)*n);//初始化  
  35.         //将数据写入辅助空间  
  36.         for (int i = 0; i < n; ++i)  
  37.         {  
  38.             int index = (a[i] / flag)%10;  
  39.             tmp[BeginIndex[index]++] = a[i];  
  40.         }  
  41.         //将数据重新写回原空间  
  42.         for (int i = 0; i < n; ++i)  
  43.         {  
  44.             a[i] = tmp[i];  
  45.         }  
  46.         flag = flag * 10;  
  47.         delete[] tmp;  
  48.     }  
  49. }  
//基数排序//LSD  先以低位排,再以高位排//MSD  先以高位排,再以低位排void LSDSort(int *a, int n){    assert(a);    //求出其最大位数  个 十 百 千 万....    int digit = 0;    int bash = a[0];    for (int i = 0; i < n; ++i)    {        while (a[i] > (pow(10,digit)))        {            digit++;        }    }    int flag = 1;    for (int j = 1; j <= digit; ++j)    {        //建立数组统计每个位出现数据次数        int Digit[10] = { 0 };        for (int i = 0; i < n; ++i)        {            Digit[(a[i] / flag)%10]++;        }         //建立数组统计起始下标        int BeginIndex[10] = { 0 };        for (int i = 1; i < 10; ++i)        {            BeginIndex[i] = BeginIndex[i - 1] + Digit[i - 1];        }        //建立辅助空间        int *tmp = new int[n];        memset(tmp, 0, sizeof(int)*n);//初始化        //将数据写入辅助空间        for (int i = 0; i < n; ++i)        {            int index = (a[i] / flag)%10;            tmp[BeginIndex[index]++] = a[i];        }        //将数据重新写回原空间        for (int i = 0; i < n; ++i)        {            a[i] = tmp[i];        }        flag = flag * 10;        delete[] tmp;    }}