排序算法——继“计数排序”后,我想到了尽善尽美的“基数排序”

前言

基数排序的实现方法包括最高位优先法(Most Significant Digit first),简称MSD法;也包括最低位优先法(Least Significant Digit first),简称LSD法。本文介绍的是LSD法实现的基数排序,其中包含了计数排序的思想,如果不懂的同学请点击链接先行学习计数排序

概述

基数排序(radix sort)属于“分配式排序”(distribution sort),又称“桶子法”(bucket sort),是一种非比较线性时间排序算法。顾名思义,它是透过键值的部分讯息,将要排序的元素分配至某些“桶”内,藉以达到排序的作用。
在这我要给大家扩展一下,其实常用的三种非比较线性时间算法:计数排序,基数排序,桶排序都利用了的概念,都通过元素自身的值,计算哈希值,根据哈希值将元素存储到相应的桶内,以此完成元素的排序,但是这三种算法对桶的使用方法上有明显差异:
(1)上一回我们提到的计数排序,它是根据当前元素与最小值的差值作为哈希值来选择桶的,也就是说,每个桶只存储单一键值。
(2)基数排序:就如接下来我们将要提到的,基数排序是根据键值的每一位上的数字来分配桶。(分配的过程由低位到高位,进行多次分配)
(3)桶排序:下一章我们将要学习的桶排序中每个桶存储一定范围的数值。

算法过程(实例分析)

假设原序列A中有一串数字如下所示:

vector<int> A = {1755, 27535, 5415, 22200, 18091, 18893, 13482, 4025, 586, 23372};

首先根据个位数的数值,在遍历A过程中将它们分配至编号0到9号桶中:

0	22200
1	18091
2	13482	23372	
3	18893	
4	
5	1755	27535 5415 4025
6	586
7	
8
9

第二步:
接下来将这些桶子中的数值重新排列,按照0号桶优先的次序重新串接起来,成为以下的数列:

A = {22200, 18091, 13482, 23372,  18893, 1755, 27535, 5415, 4025, 586};

接着按照十位数的数值,在遍历A过程中将它们分配至编号0到9号桶中:

0	22200
1	5415
2	4025
3	27535
4	
5	1755
6	
7	23372
8	13482	586
9	18091	18893

第三步:
接下来将这些桶子中的数值重新排列,按照0号桶优先的次序重新串接起来,成为以下的数列:

A = {22200, 5415, 4025, 27535, 1755, 23372, 13482, 586, 18091, 18893};

接着按照百位数的数值,在遍历A过程中将它们分配至编号0到9号桶中:

0	4025	18091
1
2	22200
3	23372
4	5415	13482
5	27535	586
6
7	1755	
8	18893
9

第四步:
接下来将这些桶子中的数值重新排列,按照0号桶优先的次序重新串接起来,成为以下的数列:

A = {4025, 18091, 22200, 23372, 5415, 13482, 27535, 586, 1755, 18893};

接着按照千位数的数值,在遍历A过程中将它们分配至编号0到9号桶中:

0	586
1	1755
2	22200
3	23372	13482
4	4025	
5	5415
6
7	27535
8	18091	18893	
9

第五步:
接下来将这些桶子中的数值重新排列,按照0号桶优先的次序重新串接起来,成为以下的数列:

A = {586, 1755, 22200, 23372, 13482, 4025, 5415, 27535, 18091, 18893 };

接着按照万位数的数值,在遍历A过程中将它们分配至编号0到9号桶中:

0	586 1755	4025	5415
1	13482	18091	18893
2	22200	23372	27535
3
4
5
6
7
8
9

所以整个序列已经排序完毕了,结果如下

A = {586, 1755, 4025, 5415, 13482, 18091, 18893, 22200, 23372, 27535};

程序演示

/*返回当前序列中的最大位数*/
int max_bit(vector<int>& v)
{
	int max = v[0];
	for (size_t i = 1; i < v.size(); ++i)
	{
		if (v[i] > max)
			max = v[i];
	}
	int p = 10;
	int d = 1;	//记录当前的位数
	while (max >= p)
	{
		max /= 10;
		++d;
	}
	return d;
}

/*基数排序算法*/
void radix_sort(vector<int>& v)
{
	int d = max_bit(v);
	int radix = 1;
	vector<int> temp(v.size());
	vector<int> times(10);

	for (size_t i = 0; i < d; ++i)
	{
		/*将times数组中的值清零*/
		for (size_t j = 0; j < times.size(); ++j)
			times[j] = 0;

		/*接下来的过程类似计数排序*/
		/*首先通过辅助数组times记录当前位上数值为0-9的数字分别有多少*/
		for (size_t j = 0; j < v.size(); ++j)
			++times[v[j] / radix % 10];

		/*改变times中数值的意义*/
		for (size_t j = 1; j < times.size(); ++j)
			times[j] += times[j - 1];

		/*倒序遍历原序列v,将排序结果放到临时数组temp中*/
		for (int j = v.size() - 1; j >= 0; --j)
		{
			int k = v[j] / radix % 10;	//取得当前位的数字
			temp[times[k] - 1] = v[j];
			--times[k];
		}

		/*将临时数组temp中的值重新拷贝到v中*/
		v.assign(temp.begin(), temp.end());

		/*将基数乘以10*/
		radix *= 10;
	}
}

大家仔细看就会发现,其实最核心的部分用的就是类似于计数排序的思想,所以学习算法,“基础”也是很重要的,有时候你了解并熟悉了一部分算法,可能别的算法对你来说也会更容易理解。

算法复杂度

基数排序的时间复杂度为 O(d(n + k)),其中n为原序列的元素个数,d为最大值的最高位数,k为同一位中数值的取值范围(通常选十进制就是k = 10)。其中,一趟分配时间复杂度为O(n),一趟收集时间复杂度为O(k),共进行d趟分配和收集。

空间复杂度:O(n + k),用于拷贝的临时数组和包含k个值的辅助数组。

稳定性:基数排序属于稳定算法,因为当比较某一位数字的大小时遇到该位上数字大小一致的情况,基数排序总会将靠前的元素排在前面。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值