桶排序(箱排序)
思想: 把区间[0,1)划分成n个相同大小的子区间,或称桶,然后将n个输入数分布到各个桶中去。因为输入数均匀分布在[0,1)上,所以一般不会有很多数落在 一个桶中的情况。为得到结果,先对各个桶中的数进行排序,然后按次序把各桶中的元素列 出来即可。
复杂度:平均情况下桶排序以线性时间运行。像计数排序一样,桶排序也对输入作了某种假设, 因而运行得很快。桶排序的时间复杂度,取决与对各个桶之间数据进行排序的时间复杂度,因为其它部分部分的时间复杂度都为O(n);很显然,桶划分的越小,各个桶之间的数据越少,排序所用的时间也会越少。但相应的空间消耗就会增大。
可以证明,即使选用插入排序作为桶内排序的方法,桶排序的平均时间复杂度为线性。具体证明,请参考算法导论。其空间复杂度也为线性。
假设有一组长度为N的待排关键字序列K[1....n],首先将这个序列划分成M个的子区间(桶) 。然后基于某种映射函数,将待排序列的关键字k映射到第i个桶中(即桶数组B的下标 i),那么该关键字k就作为B[i]中的元素(每个桶B[i]都是一组大小为N/M的序列)。接着对每个桶B[i]中的所有元素进行比较排序(可以使用快排)。然后依次枚举输出B[0]....B[M]中的全部内容即是一个有序序列。
比如考试分数通常为0-100分,我们可以建立11个桶,然后确定映射函数f(k)=k/10。则分数49将定位到第4个桶中(49/10=4)。(类似哈希表)
桶排序的f(k)值的计算,其作用就相当于快排中划分,已经把大量数据分割成了基本有序的数据块(桶)。然后只需要对桶中的少量数据做先进的比较排序即可。应该尽量做到以下两点:
(1) 映射函数f(k)能够将N个数据平均的分配到M个桶中,这样每个桶就有[N/M]个数据量。
(2) 尽量的增大桶的数量。极限情况下每个桶只能得到一个数据,这样就完全避开了桶内数据的“比较”排序操作。当然,做到这一点很不容易,数据量巨大的情况下,f(k)函数会使得桶集合的数量巨大,空间浪费严重。这就是一个时间代价和空间代价的权衡问题了。
伪代码:

利用C++ STL的vector容器,我们就可以很容易地实现桶排序。
代码:
// 桶排序#include<iostream>#include<vector>#include<iterator>using namespace std;void BucketSort(int *pData, int size){vector<int> Bucket[11];//vector数组,每一个vector作为一个桶memset(Bucket,0,sizeof(Bucket));int i,j,k,pos,key;for(i=0; i<size; ++i){ // 将每个元素插入到相应的桶中key=pData[i];pos = key/10; // 求出该元素在哪个桶j=Bucket[pos].size()-1;Bucket[pos].push_back(key); // 把该元素放入某个桶中while(j>=0 && Bucket[pos][j]>key){ // 用插入排序在某个桶里排序swap(Bucket[pos][j],Bucket[pos][j+1]);--j;}}//一次从每个桶中取出所以元素k=0;for(i=0; i<11; ++i){// for(j=0; j<Bucket[i].size(); ++j)// pData[k++] = Bucket[i][j];vector<int>::const_iterator cit = Bucket[i].begin();for(; cit != Bucket[i].end(); ++cit)pData[k++] = *cit;}}int main(){int arr[]={3,5,45,34,2,78,67,34,56,98};BucketSort(arr,10);for(int i=0; i<10; ++i)printf("%d ",arr[i]);printf("\n");return 0;}
结果:
最后,对三种线性排序(计数排序,基数排序,桶排序)进行比较:
从整体上来说,计数排序,桶排序都是非基于比较的排序算法,而其时间复杂度依赖于数据的范围,桶排序还依赖于空间的开销和数据的分布。而基数排序是一种对多元组排序的有效方法,具体实现要用到计数排序或桶排序。
相对于快速排序、堆排序等基于比较的排序算法,计数排序、桶排序和基数排序限制较多,不如快速排序、堆排序等算法灵活性好。但反过来讲,这三种线性排序算法之所以能够达到线性时间,是因为充分利用了待排序数据的特性,如果生硬得使用快速排序、堆排序等算法,就相当于浪费了这些特性,因而达不到更高的效率。



浙公网安备 33010602011771号