非比较排序---计数排序&基数排序
2016-12-03 10:41
337 查看
如果给出一组数:2, 2, 9, 5, 3, 9,怎样才能遍历一次该数组就能使其有序呢?!试试之前讲的算法,好像都不行。这里我给出另一种排序算法------非比较排序!
非比较排序包括计数排序和基数排序。
一、计数排序
算法思想:
很明显,计数排序当然就需要计数啦,这里就是对这组数中的每个数据进行计数,并以每个数据的相对位置为下标,将统计出来的次数存放在一个count[ ]数组中,并根据count数组还原数组_a。
具体的做法是:选出这组数中的最大值,并用这个值来开辟一个数组count,来存放每个数据出现的次数,统计完后,再重新遍历count,根据count数组中每个下标出现的次数还原原数组_a,count[index]是几就输出几个index。
![](https://img-blog.csdn.net/20161203092117986?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQv/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast)
上边提出了,需要以每个数据的相对位置为下标,想想为什么要用相对位置而不用绝对位置呢?!
现在假如有一组数是:1000,1010,1005,2000,1010,1990,1556,1778,同样,按照上面的思路,首先要选出这组数中的最大值,并用这个数来开辟一个数组。那么问题就来了,这里的最大数是2000,如果按照最大值开辟的环就需要2000个空间,但是仔细观察后发现,这些数都是在1000~2000之间的,也就是说count数组是从下标999的位置开始统计的,这样的话前一半的空间都浪费了。所以这里提出了使用相对位置,同样开辟count大小也需要使用相对大小,即实际只需要开辟(2000-1000+1)个空间。
存在的问题:
上边举得两组例子都是数据相对比较集中的,假如是这样的一组数时:0,222,1000,34,100000,999,1010,3005,这样不仅空间浪费比较严重,另一个缺点是效率比较低,因为数据太分散了,还原数组时需要遍历100000次。所以,计数排序在数据比较集中时是非常快的,此时也不会浪费太多空间。
代码实现:
时间复杂度分析:
首先需要便利一遍原数组_a,时间复杂度为O(N),还原数组_a需要遍历一遍数组count,时间复杂度为O(数据的范围),因此总体的时间复杂度时O(N+数据范围)。
空间复杂度分析:
这里重新开辟了一个数组count,其大小为(最大数-最小数),因此空间复杂度为O(最大数-最小数)
二、基数排序
基数排序又称为"桶子法",与哈希桶有点相似之处。其实现有两种:LSD(低位优先)和MSD(高位优先)。
算法思想:
下面两种实现都以这组数为例:73,22,93,43,55,14,28,65,39,81
LSD:先按低位排序,再按高位排序
具体做法是:
(1)先遍历原数组,按个位数将该数据挂在0-9号桶的某一个下面;
(2)将按照个位排好序的数据收集到tmp数组:需要计算每个桶下面数据存入的起始位置,计算方法是:第i号桶数据的起始位置 = 第(i-1)号桶数据的起始位置 + 第(i-1)号桶的数据的个数,这里与矩阵转置计算起始位置相似《矩阵的的存储及转置算法》
(3)还原数组a:将排好序的数组tmp重新赋给数组a。
(4)再按照十位进行上步骤排序,直到最大数据的每个位都遍历完
![](https://img-blog.csdn.net/20161203101652935?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQv/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast)
MSD:先按高位排序,再按低位排序
![](https://img-blog.csdn.net/20161203102306000?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQv/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast)
代码实现(LSD):
时间复杂度分析:
每次排序都需要遍历的次数是数据的个数个,共需要排序最大数据的位数位,则时间复杂度是O(N*最多的位数)
空间复杂度分析:
这里开辟了一个tmp来收集每次排序后的数据,其大小是N,因此空间复杂度为O(N)
非比较排序包括计数排序和基数排序。
一、计数排序
算法思想:
很明显,计数排序当然就需要计数啦,这里就是对这组数中的每个数据进行计数,并以每个数据的相对位置为下标,将统计出来的次数存放在一个count[ ]数组中,并根据count数组还原数组_a。
具体的做法是:选出这组数中的最大值,并用这个值来开辟一个数组count,来存放每个数据出现的次数,统计完后,再重新遍历count,根据count数组中每个下标出现的次数还原原数组_a,count[index]是几就输出几个index。
上边提出了,需要以每个数据的相对位置为下标,想想为什么要用相对位置而不用绝对位置呢?!
现在假如有一组数是:1000,1010,1005,2000,1010,1990,1556,1778,同样,按照上面的思路,首先要选出这组数中的最大值,并用这个数来开辟一个数组。那么问题就来了,这里的最大数是2000,如果按照最大值开辟的环就需要2000个空间,但是仔细观察后发现,这些数都是在1000~2000之间的,也就是说count数组是从下标999的位置开始统计的,这样的话前一半的空间都浪费了。所以这里提出了使用相对位置,同样开辟count大小也需要使用相对大小,即实际只需要开辟(2000-1000+1)个空间。
存在的问题:
上边举得两组例子都是数据相对比较集中的,假如是这样的一组数时:0,222,1000,34,100000,999,1010,3005,这样不仅空间浪费比较严重,另一个缺点是效率比较低,因为数据太分散了,还原数组时需要遍历100000次。所以,计数排序在数据比较集中时是非常快的,此时也不会浪费太多空间。
代码实现:
//计数排序 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); for (int i=0; i<n; ++i) { int index = a[i] - min; //找出a[i] 在 count[]中的相对位置 count[index]++; } //还原排序以后的数组a int index = 0; for (int i=0; i<range; ++i) { while (count[i]) { a[index++] = i+min; count[i]--; } } delete[] count; }
时间复杂度分析:
首先需要便利一遍原数组_a,时间复杂度为O(N),还原数组_a需要遍历一遍数组count,时间复杂度为O(数据的范围),因此总体的时间复杂度时O(N+数据范围)。
空间复杂度分析:
这里重新开辟了一个数组count,其大小为(最大数-最小数),因此空间复杂度为O(最大数-最小数)
二、基数排序
基数排序又称为"桶子法",与哈希桶有点相似之处。其实现有两种:LSD(低位优先)和MSD(高位优先)。
算法思想:
下面两种实现都以这组数为例:73,22,93,43,55,14,28,65,39,81
LSD:先按低位排序,再按高位排序
具体做法是:
(1)先遍历原数组,按个位数将该数据挂在0-9号桶的某一个下面;
(2)将按照个位排好序的数据收集到tmp数组:需要计算每个桶下面数据存入的起始位置,计算方法是:第i号桶数据的起始位置 = 第(i-1)号桶数据的起始位置 + 第(i-1)号桶的数据的个数,这里与矩阵转置计算起始位置相似《矩阵的的存储及转置算法》
(3)还原数组a:将排好序的数组tmp重新赋给数组a。
(4)再按照十位进行上步骤排序,直到最大数据的每个位都遍历完
MSD:先按高位排序,再按低位排序
代码实现(LSD):
//基数排序 int GetDigit(int* a,int n) //统计最多的位数 { int base = 1; int digit = 0; for (int i=0; i<n; ++i) { while (a[i] >= base) { digit++; base *= 10; } } return digit; } void LSDSort(int* a,int n) { assert(a); int digit = GetDigit(a,n); int* tmp = new int ; //每次按某个位排序后的数据收集到tmp数组中 int base = 1; while (digit) { //统计0-9号桶中数据出现的次数 int count[10] = {0}; for (int i=0; i<10; ++i) { int index = (a[i]/base)%10; count[index]++; } //计算起始位置 int start[10] = {0}; for (int i=1; i<10; ++i) { //每个桶中数据的起始位置 = 上一桶数据起始位置 + 上一桶数据的个数 start[i] = start[i-1] + count[i-1]; } //将数据收集到tmp中 for (int i=0; i<n; ++i) { int num = (a[i]/base)%10; tmp[start[num]] = a[i]; start[num]++; //每次写入一个数据后将start中每个桶的起始位置向后挪动一位 } //将排好一次序的数组重新赋给原来的数组 for (int i=0; i<n; ++i) { a[i] = tmp[i]; } --digit; base *= 10; } delete[] tmp; }
时间复杂度分析:
每次排序都需要遍历的次数是数据的个数个,共需要排序最大数据的位数位,则时间复杂度是O(N*最多的位数)
空间复杂度分析:
这里开辟了一个tmp来收集每次排序后的数据,其大小是N,因此空间复杂度为O(N)
相关文章推荐
- 非比较排序之基数排序(参考他人)
- 数据结构 学习笔记(十一):排序(下):快速 / 表 / 桶 / 基数 排序,排序算法的比较
- 三种排序的比较(基数排序,qsort,sort)
- C++算法 冒泡排序,快速排序,插入排序,希尔排序,计数排序,基数排序 性能比较
- 归并排序,堆排序,基数排序,希尔排序,快速排序,交换排序,选择排序和插入排序的总结和比较
- 【更新】排序算法比较:插入排序,冒泡排序,归并排序,堆排序,快速排序,计数排序,基数排序,桶排序
- 内部非比较排序---基数排序
- 非比较排序-----计数排序,基数排序。
- 归并排序 & 计数排序 & 基数排序 & 冒泡排序 & 选择排序 ----> 内部排序性能比较
- 排序之表排序、基数排序及所有排序算法比较
- c++排序part3:箱子排序、基数排序、排序比较
- 【排序五】非比较排序(计数排序&&基数排序)
- 非比较排序—计数排序&基数排序
- 各种内部排序方法的比较和选择
- 快速排序(递归,非递归),希尔排序,冒泡排序的比较
- 各种排序比较
- 基数排序
- 排序 简单排序(冒泡,插入)先进排序(快排,归并)堆排序,基数排序
- zz5个数用7次比较进行排序
- C++的STL库,vector sort排序时间复杂度 及常见容器比较