计数排序和基数排序
来源:互联网 发布:淘宝怎么搜索岛国资源 编辑:程序博客网 时间: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++代码实现:
- 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++;
- }
- }
- }
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;
- }
- }
//基数排序//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; }}
- 计数排序和基数排序
- 计数排序和基数排序
- 基数排序和计数排序
- 计数排序和基数排序
- 计数排序和基数排序
- 计数排序和基数排序
- 计数排序和基数排序
- 计数排序和基数排序
- 计数排序和基数排序
- 计数排序和基数排序
- Java 基数排序和计数排序
- 计数排序和基数排序和桶排序
- 计数排序、桶排序和基数排序
- 计数排序、桶排序和基数排序
- 计数排序、桶排序和基数排序
- 计数排序,基数排序和桶排序
- 计数排序、桶排序和基数排序
- 大话桶排序 基数排序和计数排序
- 最近的公共祖先
- hdu 6105-二分匹配
- 使用Trepn Profiler工具分析Dragonboard410c的功耗和性能(一)
- glibc源码分析之系统调用(一)
- nginx之负载均衡(轮询方式)
- 计数排序和基数排序
- codevs 1003 电话连线
- 上传jar包到nexus私服
- Vulkan Tutorial 29 Loading models
- 面向切面的 Spring —— 如何注入 AspectJ 切面?
- mysql的主从复制
- 站立会的丑陋现象
- 多重背包问题
- 使用adb shell dumpsys检测Android的Activity任务栈