首页 > 编程语言 >算法与数据结构——桶排序

算法与数据结构——桶排序

时间:2024-10-18 10:14:37浏览次数:1  
标签:排序 复杂度 元素 bucket 算法 num 数据结构

桶排序

前面的快速排序、归并排序、堆排序等都是属于“基于比较的排序算法”,它们通过比较元素间的大小来实现排序。此类排序算法的时间复杂度无法超越O(nlogn)。下面介绍几种“非比较排序算法”,它们的时间复杂度可以达到线性阶。

桶排序(bucket sort)是分治策略的一个典型应用。它通过设置一些具有大小顺序的桶,每个桶对应一个数据范围,将数据平均分配到各个桶中;然后,在每个桶内部分别执行排序;最终按照桶的顺序将所有数据合并。

算法流程

考虑一个长度为n的数组,其元素时范围[0,1)内的浮点数。桶排序的流程如下所示。

  1. 初始化k个桶,将n个元素分配到k个桶中。
  2. 对每个桶分别执行排序(这里采用编程语言的内置排序函数)。
  3. 按照桶从小到大的顺序合并结果。

/* 桶排序 */
void bucketSort(vector<float> &nums) {
	// 初始化 k = n/2 个桶,预期向每个桶分配 2 个元素
	int k = nums.size() / 2;
	vector<vector<float>> buckets(k);
	// 1. 将数组元素分配到各个桶中
	for (float num : nums) {
		// 输入数据范围为 [0, 1),使用 num * k 映射到索引范围 [0, k-1]
		int i = num * k;
		// 将 num 添加进桶 bucket_idx
		buckets[i].push_back(num);
	}
	// 2. 对各个桶执行排序
	for (vector<float> &bucket : buckets) {
		// 使用内置排序函数,也可以替换成其他排序算法
		sort(bucket.begin(), bucket.end());
	}
	// 3. 遍历桶合并结果
	int i = 0;
	for (vector<float> &bucket : buckets) {
		for (float num : bucket) {
			nums[i++] = num;
		}
	}
}

算法特性

桶排序适用于处理体量很大的数据。例如,输入数据包含100万个元素,由于空间限制,系统内存无法一次性加载所有数据。此时,可以将数据分成1000个桶,然后分别对每个桶进行排序,最后将结果合并。

  • 时间复杂度为O(n + k):假设元素在各个桶内平均分布,那么每个桶内的元素数量为 n/k。假设排序单个桶使用O(n/klogn/k)时间,则排序所有桶使用O(nlogn/k)时间。当桶数量k比较大时,时间复杂度趋向于O(n)。合并结果需要遍历所有桶和元素,花费O(n+k)时间。
  • 自适应排序:最差情况下,所有数据被分配到一个桶中,且排序该桶使用O(n2)时间。
  • 空间复杂度为O(n+k)、非原地排序:需要借助k个桶和总共n个元素的额外空间。
  • 桶排序是否稳定取决于桶内元素的算法是否稳定。

如何实现平均分配

桶排序的时间复杂度理论上可以达到O(n),关键在于将元素均匀分配到各个桶中,因为实际数据往往不是均匀分布的。例如,我们想要将淘宝上的所有商品按价格范围平均分配到10个桶中,但商品价格分布不均,低于100元的非常多,高于1000元的非常少。若将价格区间平均划分为10个,各个桶中的商品数量差距会非常大。

为实现平均分配,我们可以先设定一条大致的分界线,将数据粗略地分到3个桶中。分配完毕后,再将商品较多的桶继续划分为3个桶,直至所有桶中的元素数量大致相等

如下图所示,这种方法本质上是创建一棵递归树,目标是让叶节点的值尽可能平均。当然,不一定要每轮数据划分为3个桶,具体划分方式可根据数据特点灵活选择。

如果我们提前知道商品价格的概率分布,则可以根据数据概率分布设置每个桶的价格分界线。值得注意的是,数据分布不一定需要特意统计,也可以根据数据特点采用某种概率模型进行近似。
如下图所示,我们假设商品价格服从正态分布,这样就可以合理地设定价格区间,从而将商品平均分配到各个桶中。

标签:排序,复杂度,元素,bucket,算法,num,数据结构
From: https://www.cnblogs.com/1873cy/p/18471654

相关文章

  • 八种经典排序算法
    以下是八种经典排序算法的介绍,包括它们的基本思想、时间复杂度、稳定性以及代码示例:1.插入排序基本思想:每步将一个待排序的元素按其关键码值的大小插入到前面已经排序的部分中,直到全部插入完为止。时间复杂度:最坏和平均情况为O(n²),最佳情况为O(n)(当数据基本有序时)。稳定性:......
  • 格点拉格朗日插值与PME算法
    技术背景在前面的一篇博客中,我们介绍了拉格朗日插值法的基本由来和表示形式。这里我们要介绍一种拉格朗日插值法的应用场景:格点拉格朗日插值法。这种场景的优势在于,如果我们要对整个实数空间进行求和或者积分,计算量是随着变量的形状增长的。例如分子动力学模拟中计算静电势能,光是......
  • 传统特征算法——人脸识别
    人脸识别是目前人工智能领域中成熟较早、落地较广的技术之一,广泛应用于手机解锁、支付验证、安防布控等多个领域。其核心在于通过特定的算法识别图像或视频中人脸的身份,这一过程的实现离不开特征算法的支持。以下是对人脸识别特征算法的详细介绍:一、人脸识别系统概述一个......
  • Leetcode刷题. 贪心算法
    贪心算法:    比较传统的解释:将整个问题拆解为几个小问题,找到小问题的最优解,加起来就是整个问题的全局最优解。对于现在的我理解贪心就是一种感觉,给出证明很难,解题思路一般就是认真读题,发掘题目的条件,然后尝试给出算法。11.盛最多水的容器     一个显而易......
  • [算法日常] 逆序对
    [算法日常]逆序对定义在一个长度为\(n\)的数组\(a\)中,若存在\(\forall1\lei,j\len\),使得\(a_i>a_j\),则称\(<a_i,a_j>\)为一对逆序对。举个例子,一个长度为\(5\)的数组为:15364则存在\(3\)个逆序对,分别是\(<5,3>,<5,4>,<6,4>\)。解法F1:显然,可以枚举......
  • STL容器和算法
    1、C++的STL介绍(内存管理、allocator、函数、实现机理、多线程实现)STL一共提供六大组件,包括容器、算法、迭代器、仿函数、配接器和配置器,彼此可以组合套用。容器通过配置器取得数据存储空间,算法通过迭代器存取容器内容,仿函数可以协助算法完成不同的策略变化,配接器可以应用于容......
  • 武汉大学卫星导航算法程序设计——解码与数据获取
    还在为解码发愁吗?面对二进制文件还是无从下手吗?一篇文章帮你搞定。我们从接收机获取的数据并不是rinex格式的文件,而是NovAtel数据格式的二进制文件。我们需要从文件中提取出我们需要的导航数据,也就是解码的过程。废话不多说,我们直接开始讲解。一、Binary数据头格式请不要使......
  • 排序算法 - 快速排序
    排序算法-快速排序  概要  快速排序(Quicksort)是对冒泡排序算法的一种改进。快速排序是一种基于分而治之的排序算法。  它的基本思想是:选择一个基准数,通过一趟排序将要排序的数据分割成独立的两部分;其中一部分的所有数据都比另外一部分的所有数据都要小。然后,再按......
  • 【算法】C++中的二分查找
    ......
  • 代码随想录算法训练营 | 1143.最长公共子序列,1035.不相交的线,53. 最大子序和,392.判断
    1143.最长公共子序列题目链接:1143.最长公共子序列文档讲解︰代码随想录(programmercarl.com)视频讲解︰最长公共子序列日期:2024-10-17想法:这里的子序列不要求连续了,dp[i][j]要表示为在text1[0,i-1]和text2[0,j-1]的范围里,最长的公共子序列长度,-1同样是为了初始化方便,如果te......