0°

非比较排序—计数排序和基数排序

内容预览:
  • 算法有些复杂,但是可以尝试~
  • 例如上图数据中个位为0的出现1次,个位为1的出现2次,个位为2的出现1次...~
  • 欢迎投稿~

始发于微信公众号: 程序员小乐

分享编程技能、互联网技术、生活感悟、打造干货分享平台,将总结的技术、心得、经验分享给大家,这里不只限于技术!还有职场心得、生活感悟、以及面经点击上方 “杨守乐” ,选择“置顶公众号”,第一时间送达!


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

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++代码实现:

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)。

实现代码:

//基数排序  
//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;  
   }  
}  

欢迎大家加入,一起学习和交流, 三月份优惠期 68元/年, 从四月份起恢复为128元。

非比较排序---计数排序和基数排序

如果您觉得不错,请别忘了转发、分享、点赞让更多的人去学习, 您的举手之劳,就是对小乐最好的支持,非常感谢!

如何您想进技术群和大牛们交流,关注公众号在后台回复 “加群”,或者 “学习” 即可

来自:LLZK_

链接:

http://blog.csdn.net/llzk_/article/details/53354071

著作权归作者所有。本文已获得授权。欢迎投稿。

每日英文


One day you’ll understand that it’s harder to be kind than clever. Cleverness is a gift, yet kindness is a choice.

有一天你会明白,善良比聪明更难。聪明是一种天赋,而善良是一种选择。

乐乐有话说

 这个世界已经有很多人和事会让你失望,而最不应该的,就是自己还令自己失望。请记住,社会很残酷,你要活得有温度!

非比较排序---计数排序和基数排序


推荐阅读




看完本文有收获?请转发分享给更多人
关注「杨守乐」,提升技能

以上就是:非比较排序---计数排序和基数排序 的全部内容。

本站部分内容来源于互联网和用户投稿,如有侵权请联系我们删除,谢谢。
Email:[email protected]


0 条回复 A 作者 M 管理员
    所有的伟大,都源于一个勇敢的开始!
欢迎您,新朋友,感谢参与互动!欢迎您 {{author}},您在本站有{{commentsCount}}条评论