参考:《漫画算法-小灰的算法之旅》
目录
一、计数排序
1、计数排序的过程
2、计数排序的局限性
3、计数排序代码
二、基数排序
1、基数排序思想
2、例子
3、解决对齐问题
4、基数排序代码
三、两者的时间复杂度和空间复杂度
一、计数排序
1、计数排序的过程
给定如下20个随机整数的值: 9,3,5,4,9,1,2,7,8,1,3,6,5,3,4,0,10,9,7, 9。这些整数的范围是从0到10这11个数,分别对应着待排序的随机整数0到10:
接下来遍历这个无序的随机数列,每一个整数按照其值对号入座, 对应数组下标的元素进行加1操作。 比如第一个整数是9,那么数组下标为9的元素加1:
第二个整数是3,那么数组下标为3的元素加1:
继续遍历数列并修改数组…… 最终,数列遍历完毕时,数组的状态如下:
数组每一个下标位置的值,代表了数列中对应整数出现的次数。
有了这个“统计结果”,排序就很简单了。直接遍历数组,输出数组元素的下标值,元素的值是几,就输出几次: 0,1,1,2,3,3,3,4,4,5,5,6,7,7,8,9,9,9,9, 10 显然,这个输出的数列已经是有序的了。
2、计数排序的局限性
当待排序数组的最大值和最小值相差过大时,就很难实现了。比如:
为一组给定的手机号排序: 18914021920 13223132981 13566632981 13660891039 13361323035 …… 按照计数排序的思路,我们要根据手机号的取值范围,创建一个空数组。 可是,11位手机号有多少种组合?恐怕要建立一个大得不可想象的 数组,才能装下所有可能出现的11位手机号!
所以就需要用到基数排序了!!
3、计数排序代码
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
void CountSort(vector<int>& vecRaw, vector<int>& vecObj) {
//确保待排序容器非空
if (vecRaw.size() == 0) {
return;
}
//使用vecRaw的最大值+1作为计数容器countVec的大小
int vecCountLength = (*max_element(begin(vecRaw), end(vecRaw))) + 1;
vector<int> vecCount(vecCountLength, 0);
//统计每个键值出现的次数
for (int i = 0; i < vecRaw.size(); i++) {
vecCount[vecRaw[i]]++;
}
/*for (int i = 0; i < vecRaw.size(); i++) {
cout << vecCount[i] << endl;
}*/
//将键值放到目标位置
int sum = 0;
for (int i = 0; i < vecCountLength; i++) {
if (i == 0) {
for (int j = 0; j < vecCount[i]; j++) {
vecObj[i + j] = i;
}
}
else {
sum = sum + vecCount[i - 1];
for (int j = 0; j < vecCount[i]; j++) {
vecObj[sum+ j] = i;
}
}
}
}
int main()
{
vector<int> vecRaw = { 0,5,7,9,6,3,4,5,2,8,6,9,2,1 };
vector<int> vecObj(vecRaw.size(), 0);
CountSort(vecRaw, vecObj);
for (int i = 0; i < vecObj.size(); ++i)
{
cout << vecObj[i] << " ";
}
cout << endl;
return 0;
}
二、基数排序
1、基数排序思想
把排序工作拆分成多个阶段,没有一个阶段只根据一个字符进行计数排序,一共排序k轮(k是字符串长度)。
2、例子
数组中又若干个字符串元素,每一个字符串元素都由三个英文字母组成:
bda,cfd,qwe,yui,abc,rrr,uue
可以将排序工作分成三轮:
第1轮:按照最低位字符排序,排序过程使用计数排序,把字母的 ASCII码对应到数组下标,第1轮排序结果如下:
第2轮:在第1轮排序结果的基础上,按照第2位字符排序。
需要注意的是,这里使用的计数排序必须是稳定排序,这样才能保证第1轮排出的先后顺序在第2轮还能继续保持。 比如在第1轮排序后,元素uue在元素yui之前,那么第2轮排序时, 两者的第2位字符虽然同样是u,但先后顺序万万不能变,否则第1轮排序就白做了。
第3轮:在第2轮排序结果的基础上,按照最高位字符排序。
像这样把字符串元素按位拆分,每一位进行一次计数排序的算法, 就是基数排序(Radix Sort)。 基数排序既可以从高位优先进行排序(Most Significant Digit first,简称MSD),也可以从低位优先进行排序(Least Significant Digit first,简称LSD)。
3、解决对齐问题
当字符串的长度不规则的时候该怎么办呢?我们以最长的字符串为准,其他长度不足的字 符串,在末尾补0即可。
什么意思呢?比如给定如下几个单词:
banana
apple
orange
ape
he
这里最长的单词有6个字符(banana,orange),其余不足6个字符 的单词在末尾补0即可: banana
apple0
orange
ape000
he0000
在排序时,我们把字符0当作比a更小的字符,排序结果如下:
ape000
apple0
banana
he0000
orange
4、基数排序代码
三、两者的时间复杂度和空间复杂度
原本计数排序的时间复杂度是O(n+m),而基数排序总共执行了k次 计数排序,所以时间复杂度是O(k(n+m)),其中k是字符串的最大长 度,m是字符范围。 至于空间复杂度,由于基数排序的辅助数组是反复使用的,所以它 的空间复杂度和计数排序一样,都是O(n+m),其中m是字符的取值范围 大小。
虽然基数排序的时间复杂度是O(k(n+m)),但由于字符 串元素的长度k通常是一个固定常量,所以我们仍然认为它是一个线性 排序算法。