必学排序算法——桶排序
目录
- 前言
- 一、什么是桶排序
- 二、桶排序的基本思想
- 三、桶排序的算法步骤
- 四、桶排序的复杂度分析
-
- 时间复杂度:
- 空间复杂度:
- 五、桶排序的适用场景
- 六、桶排序的优缺点
-
- 优点:
- 缺点:
- 七、桶排序的优化方案
- 八、动态算法图解
- 九、c++代码模板
- 十、经典例题
-
- 1.根据字符出现频率排序
-
- 代码题解
- 十一、结语
前言
桶排序算法是必须掌握的一种基础算法,在一些比较出名的竞赛acm、蓝桥杯,并且在一些公司面试题中都可能会出现,而且作为简单题我们必须要拿下,所以我们要引起重视,下面让我们来深入了解桶算法。
一、什么是桶排序
桶排序(Bucket Sort)是一种基于分配策略的排序算法,其工作原理是将数组分到有限数量的桶子里,然后对每个桶内的元素进行排序(可能再使用其他排序算法或是以递归方式继续使用桶排序),最后合并所有已排序的桶,从而完成整个数据集的排序
二、桶排序的基本思想
桶排序利用函数的映射关系,将数据分散到多个“桶”中,再对每个桶内部进行排序,最后合并所有已排序的桶,得到全局有序序列。这个过程类似于日常生活中将物品按照类别放入不同的箱子,然后分别整理每个箱子内的物品,最后按箱顺序合并所有物品的过程。
三、桶排序的算法步骤
1.初始化桶:确定桶的数量及每个桶对应的数据范围。桶的数量和待排序数据的范围有关,每个桶应尽可能均匀地包含一部分数据。
2.分配元素:遍历待排序序列,将每个元素放入对应的桶中。这一步相当于对数据进行初步的划分和聚集。
3.桶内排序:对每个非空桶内部使用其他排序算法(如插入排序、快速排序等)进行排序,确保每个桶内的数据有序。
4.合并桶:按照桶的顺序,依次从每个桶中取出元素,合并成一个有序序列,即完成整个数据集的排序。
四、桶排序的复杂度分析
时间复杂度:
最好情况:当数据均匀分布在各个桶中,并且桶内排序采用线性时间复杂度的算法(如计数排序、基数排序等)时,桶排序的时间复杂度可以达到O(n + k),其中n为数据量,k为桶的数量。
最坏情况:当所有数据都集中在一个桶中时,桶排序退化为桶内排序算法的时间复杂度,例如O(n^2)。
空间复杂度:
桶排序需要额外的空间存储桶,空间复杂度为O(n + k)。如果桶内排序算法也是原地排序,则总空间复杂度主要取决于桶的数量。
五、桶排序的适用场景
1.数据分布均匀且范围明确:当待排序数据分布均匀且范围已知时,桶排序能充分利用数据特性,实现高效排序。例如,统计学中的随机样本数据、均匀分布的模拟数据等。
2.高效内部排序算法可用:若桶内排序可选用计数排序、基数排序等线性时间复杂度的排序算法,桶排序的整体效率将显著提升。
3.对稳定性有要求:在需要保持相等元素原始相对顺序的场景中,桶排序的稳定性使其成为理想选择。
六、桶排序的优缺点
优点:
高效:对于元素分布均匀、桶内排序算法高效的情况,桶排序具有出色的性能。
稳定:桶排序是稳定的排序算法,能保持相等元素的原始相对顺序。
缺点:
数据分布要求高:若数据分布不均匀,可能导致部分桶过载,影响整体效率。
空间消耗大:需要额外空间存储桶,对于大规模数据,空间复杂度可能较高。
综上所述,桶排序是一种利用元素分布特性的非比较型排序算法,在处理特定类型数据或满足特定条件时展现出显著优势。然而,对于数据分布不均或空间限制严格的场景,选择桶排序时需谨慎评估其适用性。
七、桶排序的优化方案
1.选择合适的桶数量
2.桶内排序算法的选择
3.处理数据分布不均的情况
4.并行化处理
5.减少内存使用
八、动态算法图解
九、c++代码模板
#include #include #include // 用于std::sort // 桶排序函数 std::vector<int> bucketSort(const std::vector<int>& arr, int bucketSize) { if (arr.empty()) { return std::vector<int>(); } // 1. 确定数据的最小值和最大值 int minValue = *std::min_element(arr.begin(), arr.end()); int maxValue = *std::max_element(arr.begin(), arr.end()); // 2. 计算桶的数量 int bucketCount = (maxValue - minValue) / bucketSize + ((maxValue - minValue) % bucketSize != 0 ? 1 : 0); // 3. 初始化桶(使用vector的vector) std::vector<std::vector<int>> buckets(bucketCount); // 4. 将元素分配到各个桶中 for (int num : arr) { int bucketIndex = (num - minValue) / bucketSize; buckets[bucketIndex].push_back(num); } // 5. 对每个桶进行排序(使用std::sort) std::vector<int> sortedArr; for (const auto& bucket : buckets) { std::sort(bucket.begin(), bucket.end()); // 对桶内元素进行排序 sortedArr.insert(sortedArr.end(), bucket.begin(), bucket.end()); // 合并桶内已排序元素 } return sortedArr; } // 主函数,用于测试桶排序 int main() { std::vector<int> arr = {42, 32, 33, 52, 37, 47, 51, 46, 41, 43}; int bucketSize = 5; // 桶的大小 std::vector<int> sortedArr = bucketSort(arr, bucketSize); // 输出排序后的数组 for (int num : sortedArr) { std::cout << num << \" \"; } std::cout << std::endl; return 0; }
十、经典例题
1.根据字符出现频率排序
(帅哥们这个蓝色字体可以点进去看原题)
代码题解
class Solution { #define ArrayType char vector<vector<ArrayType>>bucket; vector<int>count;//用于记录每个字符出现的次数 void bucketSort(ArrayType* a,int n,int max){//max代表字符的最大值 bucket.clear(); count.resize(max);//count的下标最大值为max for(int i=0;i<n;i++){ count[a[i]]++;//对每个元素进行计数 } for(int i=0;i<=n;i++){//创建n个表 bucket.push_back({}); } for(int i=0;i<max;i++){ int cnt=count[i];//遍历每个字符的个数 bucket[cnt].push_back(i);//出现cnt的元素i就插入到第cnt个桶 } for(int i=0;i<=n;i++){ sort(bucket[i].begin(),bucket[i].end());//对桶每个桶中的元素进行排序 } }public: string frequencySort(string s) { int n=s.size(); string ret=\"\"; bucketSort((char*)s.c_str(),n,256);//把字符串s转化成char指针,256代表字母的最大值 for(int i=n;i>0;i--){//因为元素频率降序排列,所以要从后遍历 for(int j=0;j<bucket[i].size();j++){ for(int k=0;k<i;k++){//因为i表示出现元素的个数,所以要在结果数组加i次 ret+=bucket[i][j]; } } } return ret; }};
十一、结语
学习算法是一个很艰难,漫长的过程,我们要克服一切困难,学习算法本就是由易到难,我相信通过我们坚持不懈的努力一定能理解并熟练掌握其中细节,加油,你一定能行。
关注我让我们一起学习编程,希望大家能点赞关注支持一下,让我有持续更新的动力,谢谢大家