前言:算法系列的文章源代码将陆续更新到github上,其地址如下:

https://github.com/zhaolingxi/AlgorithTtraining

在上一篇文章中,我们介绍的排序算法均是基于比较的排序算法。

上篇导航:算法入门第二节--基础排序算法归纳1

就是说在实现算法的前提是每一个参与排序的元素,必须有一个明确的可以比较的大小关系,或是明确的大小比较规则。

如果是对自定义的类等元素进行比较,需要重载运算符(C++)或者写比较器(java)。ps:比较排序时间复杂度有下限

实例代码如下:

template <typename T>
bool operator < (T t1, T t2)
{
	//t1.sortnum<t2T.sortnum;
}

基于比较的排序有一个特征,就是排序总依赖于一个大小关系(抽象或是具体)

而在具体的业务中,我们可能会需要基于性别、基于职业、基于种类的排序。在复杂情况下,使用重载运算符不是一个明智的行为。

所以,我们可以选择另一种排序方式,那就是桶排序。

桶排序仅关心种类,将不同的元素划分进入不同的桶。我们称之为入桶。

然后将同一个桶的元素进行二次分类,将其放到更小的桶里面,我们还是称之为入桶。

最后,我们在需要验证、查询的时候,将不同桶的元素依次取出,我们称之为出桶。

那么桶排序需要哪几个步骤呢?

1.确定桶的数量:可以通过找出最大最小值,除以区间的划分大小确定。也可以通过业务直接确定

2.确定桶的大小:可以通过给出一个小空间,不够用在重新申请拷贝(类似vector),或者使用链表之类的数据结构(排序会稍微麻烦一些)

3.如有必要,进行桶内的排序(随便选取一种排序方式)

进行完这些步骤之后,总的时间复杂度是多少呢?

最大最小n,初始化k,再加上每个桶排序(对每个桶内的元素进行排序。可以选择任意一种排序算法。)

$$ O(n) = 3n + k + (\frac{n}{k}) \times log(\frac{n}{k}) \times k \approx n + k $$

当 k 接近于 n 时,桶排序的时间复杂度就可以金斯认为是 O(n) 的。即桶越多,时间效率就越高,而桶越多,空间就越大。

我们这里提供两个具有桶排序思想的代码实例(基数排序和计数排序)

但需要明确的是,这两种排序并不能代表桶排序,它们只是具有桶排序思想的特例。

基数排序实例:

//桶排序
//时间复杂度:O(n)依赖具体数据与场景
//空间复杂度:O(n)依赖具体数据与场景
//算法稳定度:稳定
//排序思想:利用划分区域的分类方式代替比较分类

//基数排序:
//	以不同元素的区域,划定桶处理的元素范围
//	使用了词频表完成如桶与出桶
bool sort_Bucket(std::vector<int>& ioArray)
{
	bool res = false;
	if (ioArray.size() < 2) return res;
	radixSort(ioArray, 0, ioArray.size()-1, maxbits(ioArray));
	res = true;
	return res;
}

void radixSort(std::vector<int>& ioArray, int left, int right, int digit)
{
	static int radix = 10;
	int i = 0, j = 0;
	//准备足够的辅助空间
	std::vector<int> tempvec(right-left+1);
	for (size_t d = 1; d <= digit; d++)
	{
		//10个空间
		//count[0]当前位是零的数
		//count[1]当前位是0和1的数
		//count[n]当前位是0到n的数
		std::vector<int> count(radix);
		for (size_t i = left; i <= right; i++)
		{
			j = getDigit(ioArray[i],d);
			count[j]++;
		}
		for (size_t i = 1; i < radix; i++)
		{
			count[i] = count[i] + count[i - 1];
		}
		for (int i = right; i >= left; i--)
		{
			j = getDigit(ioArray[i], d);
			tempvec[count[j] - 1] = ioArray[i];
			count[j]--;
		}
		for (i = left, j = 0; i <= right; i++, j++) {
			ioArray[i] = tempvec[j];
		}
	}
}

 int maxbits(std::vector<int>& ioArray)
{
	int max = INT_MIN;
	for (size_t i = 0; i < ioArray.size(); i++)
	{
		max = __max(max, ioArray[i]);
	}
	int res = 0;
	while (max != 0)
	{
		res++;
		max = max / 10;
	}
	return res;
}


int getDigit(int iNum, int d)
{
	return ((iNum / (int)pow(10, d - 1)) % 10);
}

计数代码实例:

//桶排序
//时间复杂度:O(n)依赖具体数据与场景
//空间复杂度:O(n)依赖具体数据与场景
//算法稳定度:稳定
//排序思想:利用划分区域的分类方式代替比较分类

//计数排序:
//	对每一个元素可能出现的值进行频率划分
bool sort_Bucket2(std::vector<int>& ioArray)
{
	bool res = false;
	if (ioArray.size() < 2) return res;
	countSort(ioArray, 0, ioArray.size() - 1);
	res = true;
	return res;
}

void countSort(std::vector<int>& ioArray, int left, int right)
{
	int max = INT_MIN;
	for (size_t i = left; i < right; i++)
	{
		max = __max(max, ioArray[i]);
	}
	std::vector<int> tempvec(max+1);//计数排序的计数是每一个数值都需要一个计数位
	for (int i = left; i < right; i++)
	{
		tempvec[ioArray[i]]++;
	}
	int i = left;
	for (int j = 0; j < tempvec.size(); j++) 
	{
		while (tempvec[j] != 0) //这里要求是不为零数才行,实际使用中把它视为不等于初始值就行
		{
			ioArray[i++] = j;
		}
	}
}

Q.E.D.


世界上只有一种英雄主义