DSA之十大排序算法第九种:Bucket Sort

桶排序 2019年9月1日22:31:09
建议:在看本篇博客时:请先回顾DSA之十大排序算法第八种:Counting SortDSA之十大排序算法第六种:Quick Sort。桶排序是上一节计数排序的升级版本。其基本思想:就是将数据分到有限个数的木桶里面,然后在具体的每个桶再分别排序(有可能再使用别的排序算法或是以递回方式继续使用桶排序进行排序:这里我们就没必要再自定义操作了,既然数量已是做过了切分,直接C++的sort排序就行了)。当要被排序的数据内的数值是均匀分配的时候,桶排序时间复杂度为O(n)。桶排序不同于之前的快速排序,其实现并非是基于比较操作的,不受时间复杂度 O(nlogn) 下限的制约。它主要是利用了函数上的映射关系,高效与否的关键就在于这个映射函数的确定。为了使桶排序更加高效,我们需要做到以下几点:

  1. 在额外空间充足的情况下,尽量增大桶的数量。极限情况下每个桶只能得到一个数据,这样就完全避开了桶内数据的“比较”排序操作。 当然,做到这一点很不容易,数据量巨大的情况下,映射函数通常会使得桶的数量巨大,空间的使用急剧上升。但是牺牲如此如大的空间,换来的也是快速的时间上操作。
  2. 使用的映射函数能够将输入的 n个数据均匀的分配到 m 个桶中同时,映射函数能够将n个数据平均的分配到m个桶中也是至关重要的,这样每个桶就有[n/m]个数据元素了。对于桶中元素的排序,选择何种比较排序算法对于性能的影响至关重要。
  3. 于是,理所当然的:当输入的数据可以均匀的分配到每一个桶中,最好是一个桶一个数据。这就直接成了O(n)的时间复杂度了。但是与此同时:当输入的数据被分配到了同一个桶中,此时的桶排序的意义 也就不存在了(直接成了桶内所有数据的比较过程)。
  4. 它主要适用于小范围整数数据,且独立均匀分布,可以计算的数据量很大,而且符合线性期望时间,最好是比较的密集型均匀排序。
排序步骤就是:
  1. 得到一定数量的空桶,桶里面的数据清0
  2. 把数据放到对应的桶里面
  3. 在具体的每个不为空的桶中,进行数据元素的排序操作。(注:我们这里的桶间是有序的:或者说 桶与桶间是严格的单调顺序的),所以此时为止,全部的数据其实是已然有序的了。
  4. 把这些桶里面不为空的桶,进行串接操作。读出数据,放置数据。

具体操作如下图所示:
在这里插入图片描述
排序完成之后如下:
在这里插入图片描述
或者看B站上面的小数的桶排序
或者可以根据如下动态图(一位大佬的博客而来,但是后期没找到其地址。特此说明)
在这里插入图片描述

分析具体实现:

如下图的原始数据:[7, 36, 65, 56, 33, 60, 110, 42, 42, 94, 59, 22, 83, 84, 63, 77, 67, 101]
在这里插入图片描述
如上图所示:原始数据在[min,max]之间均匀分布,其中min=7、max=110分别指数据中的最小值和最大值。那么将区间[min,max]等分成n=5份,这n个区间便称为5个桶。将数据加入对应的桶中,然后每个桶内单独排序。由于桶之间有大小关系,因此可以从小到大将桶中元素放入到最终的数组中。

桶分出来了,之前所说的函数上的映射关系 这里:

  1. 人为的设定了桶的个数为5,每一个桶可以容纳的区间绝对值Arr_val就是:(max-min+1)/5 大概在21左右。于是第0号桶的容纳区间【7,28),依次为1号桶【28,49),2号桶【49,71),3号桶【71,92),4号桶【92,113)。
  2. 于是原始数据就可以依次根据 如下函数上的映射关系:floor((data – min) / Arr_val),就可以实现具体元素的按位就座了。
  3. 第三步 就是我上面所说的何种比较排序算法了,这里 我选择直接使用C++ 的泛型算法sort就可以了。
  4. 把这几个有 数据元素的桶的数据依次读入到 放置最终结果的数组里面就可以了。(在第2步:就保证了桶与桶间的大小顺序,因此里面的数据的顺序也就是自然而然的了)。
分析排序算法:

在这里插入图片描述
在这里插入图片描述
由上述公式推演可得:桶排序利用函数上的映射关系,大大的避免了元素之间的比较工作。实际上,桶排序的f(k)值的计算,其作用就相当于快排中划分,已经把大量数据分割成了基本有序的数据块(桶)。然后只需要对桶中的少量数据做先进的比较排序即可。
对n个数据元素进行桶排序的时间复杂度分为两个部分:

  1. 循环计算每个数据元素的对应的映射函数,其时间复杂度即O(n)。
  2. 即使使用比较快速的比较排序算法,来对每个桶内的全部数据进行排序 比较操作。但其时间复杂度为仍有nlogn这个下线的制约,即 ∑ O(ni*logni) 。其中ni 为第i个桶的数据个数。
  3. 正如上面所言,如何优化第2部分是桶排序整体性能优劣的决定因素。尽量减少桶内数据的数量是提高效率的唯一办法(毕竟基于数据两两比较的排序算法的最好平均时间复杂度只能达到O(n*logn)了)。

如上图所示:对于n个待排数据元素,m个桶,平均每个桶[n/m]个数据的桶排序平均时间复杂度为:
O(n)+O(m * (n/m) * log(n/m))=O(n+n * (logn-logm))=O(n+n * logn-n * logm)

当n=m时,即极限情况下每个桶只有一个数据时。桶排序的最好效率能够达到O(n)。

总结: 桶排序的平均时间复杂度为线性复杂度的O(n+f(n)),其中f(n)=n*(logn-logm)。如果相对于同样的n,桶数量m越大,其效率越高,最好的时间复杂度达到O(n)。 当然桶排序的空间复杂度 为O(n+m),如果输入数据非常庞大,而桶的数量也非常多,则空间消耗也是极为巨大。而且桶排序是稳定的排序算法。

排序算法实现 :
/**══════════════════════════════════╗
*作    者:songjinzhou                                                 ║
*CSND地址:https://blog.csdn.net/weixin_43949535                       ║
**GitHub:https://github.com/TsinghuaLucky912/My_own_C-_study_and_blog║
*═══════════════════════════════════╣
*创建时间: 2019年9月1日22:31:09                                                           
*功能描述:                                                            
*                                                                      
*                                                                      
*═══════════════════════════════════╣
*结束时间: 2019年9月2日13:14:20                                                            
*═══════════════════════════════════╝
//                .-~~~~~~~~~-._       _.-~~~~~~~~~-.
//            __.'              ~.   .~              `.__
//          .'//              西南\./联大               \\`.
//        .'//                     |                     \\`.
//      .'// .-~"""""""~~~~-._     |     _,-~~~~"""""""~-. \\`.
//    .'//.-"                 `-.  |  .-'                 "-.\\`.
//  .'//______.============-..   \ | /   ..-============.______\\`.
//.'______________________________\|/______________________________`.
*/
#include <iostream>
#include <iomanip>
#include <algorithm>
#include <vector>
#include <math.h>

using namespace std;

const int BucketNum = 6;

void Bucket_sort(vector<int>& src)
{
	int size = src.size();
	int minval = src[0];
	int maxval = src[0];

	for (int i = 1; i < size; ++i)
	{
		if (src[i] > maxval)
			maxval = src[i];
		if (src[i] < minval)
			minval = src[i];
	}
	int bucket_range = ceil((maxval - minval + 1) / BucketNum) + 1;//每个桶的范围  这里大概是9

	//所以说:从0号到5号桶的范围:【1,10)、【10,19)、【19,28)、【28,37)、【37,46)、【46,55)
	vector<vector<int>>myvec;
	myvec.resize(BucketNum);//先整出来 6个桶

	//函数上的映射关系:floor((data – min) / Arr_val),就可以实现具体元素的按位就座了
	int index_of_data = 0;//每个数据的桶的下标
	for (int val : src)
	{
		index_of_data = floor((val - minval) / bucket_range);
		myvec[index_of_data].push_back(val);
	}
	//到此为止每个数据,都进入了 属于自己的桶,接下来进行排序
	for (int i = 0; i < BucketNum; ++i)
	{
		sort(myvec[i].begin(), myvec[i].end());//桶内排序
	}
	src.clear();

	for (int i = 0; i < BucketNum; ++i)
	{
		vector<int>::iterator it = myvec[i].begin();
		while (it != myvec[i].end())
		{
			src.push_back(*it);
			it++;
		}
	}
}
int main()
{
	int Array[] = { 1,50,38,5,47,15,36,26,27,2,46,4,19,44,48 };
	vector<int>myvec(begin(Array), end(Array));
	
	cout << "数列初始状态:";
	for (int val : myvec)
		cout << setw(2) << val << " ";
	cout << endl;
	cout << "/*------------------------------------*/" << endl;
	Bucket_sort(myvec);
	cout << "/*------------------------------------*/" << endl;
	cout << "数列最终状态:";
	for (int val : myvec)
		cout << setw(2) << val << " ";
	cout << endl;
		
	return 0;
}
/**
*备用注释:
*
*
*
*/

在这里插入图片描述
2019年9月2日13:14:26

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

孤傲小二~阿沐

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值