[算法]计数排序和基数排序

参考:《漫画算法-小灰的算法之旅》

目录

目录

一、计数排序简单回顾

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通常是一个固定常量,所以我们仍然认为它是一个线性 排序算法。

猜你喜欢

转载自blog.csdn.net/weixin_45922730/article/details/129443170