基本排序算法总结与对比 之九 ——桶排序
1、桶排序
桶排序是对基数排序的演绎。计数排序是一个萝卜(相同的数)一个坑,而桶排序是几个萝卜(一定范围的数)一个筐。桶排序是将数据分到有限个桶中,然后对每个桶中的数据再排序(可能继续用桶排序,也可能用其他的排序方法)。
那么要解决的问题是:
① 怎么划分桶?即 数据该划分为几个范围(分几个桶的问题),范围又要划分多大(分多大桶的问题)。
② 怎么实现桶?即 用什么容器来储存数。(链表,数组,甚至STL中的容器 都可以)。
③ 桶中的数据再排序用什么排序方法。(相机抉择吧)
解决问题:
① 按照常见的一种方式,桶中数据的 范围 =(最大数 - 最小数 + 1)/ 桶的数量 。这种方法适用于数据分布比较均匀的数列,本文就采取这种方法。 还有其他的划分方式,比如前面的桶范围都一样,但最后一个桶范围特别大,适用于加数递增的数列,等等。
② 本文就选择 vector 作为容器,主要为了表示桶排序的思想。
③ 桶里面就使用std::sort()排序吧。
排序过程解读:
① 找出数列中 最大,最小的数。
② 创建若干个桶;桶的范围则由:范围 =(最大数 - 最小数 + 1)/ 桶的数量 算出,范围取double,否则可能因为计算误差导致数据选择错误的桶,甚至可能选取一个不存在的桶 的编号 而造成程序崩溃。
③ 遍历待排序数组,将数据放进对应的桶;数据arr[k] 与 桶的编号 的映射关系为: 桶的编号 = (arr[k] - 最小数)/ 范围 ,显然数值越大,所在桶的编号越大。
④ 分别对每个桶内的数据排序。
⑤ 依次从桶中取出数据放回原数组。
代码如下:
#include <vector> //用 vector 做桶
//找出最大数
template<typename T>
T findMax(T arr[], int lo, int hi)
{
T maxNum = *(arr + lo);
while(++lo < hi)
{
if(*(arr + lo) > maxNum) maxNum = *(arr + lo);
}
return maxNum;
}
//找出最小数
template<typename T>
T findMin(T arr[], int lo, int hi)
{
T minNum = *(arr + lo);
while(++lo < hi)
{
if(*(arr + lo) < minNum) minNum = *(arr + lo);
}
return minNum;
}
template<typename T>
void bucketSort(T arr[], int lo, int hi)
{
T maxNum = findMax(arr, lo, hi);
T minNum = findMin(arr, lo, hi);
std::vector<T> *buckets = new std::vector<T>[10]; //放有10个vector的数组
double range = (maxNum - minNum + 1) / 10.0; //随便分,这里分10个桶,注意范围取浮点数
for(int i = lo; i < hi; i++)
{
buckets[(int)((arr[i] - minNum) / range)].push_back(arr[i]); //对应范围的数放进桶里面
}
T *p = arr + lo;
for(int i = 0; i < 10; i++)
{
std::sort(buckets[i].begin(), buckets[i].end()); //对桶里的数排序
for(auto x : buckets[i])
{
*(arr++) = x;
}
}
delete [] buckets;
}
上述桶排序的时间复杂度:
设 有M个桶,N个数,桶内排序方法的复杂度为O(nlogn):对于N个数,M个桶 则为N(log2N - log2M),将数据分类放进桶中复杂度为N;数据从桶中放回数组又为N。总共为O(2N+N(logN-logM))。
桶排序本身的排序思维是稳定的,若桶内采用的排序算法依然稳定,则整个算法是稳定的。