桶排序算法

 

桶排序的基本思想

桶排序利用函数的映射关系,将待排序的数组分成了N个块(桶)。实际上,桶排序的f(k)值的计算,其作用就相当于快排中划分,已经把大量数据分割成了基本有序的数据块(桶)。然后只需要对每个桶中的少量数据做比较排序(比较排序:即在比较的基础上进行交换,达到排序效果)即可。

假如待排序列K= {49、 38 、 35、 97 、 76、 73 、 27、 49 }。这些数据全部在1—100之间。因此我们定制10个桶,然后确定映射函数f(k)=(k*10)/(k.max)。则第一个关键字49将定位到第4个桶中(49*10/97=5)。依次将所有关键字全部堆入桶中,并在每个非空的桶中进行快速排序,如下图所示。


待排序的数组为:


那么接下来只要对这些子数组排序即可。

实用范围:

数组的数必须是正数,但我们可以通过对每个数加上一个值a,让它变为正数,排序完成后再减去a。

时间复杂度:

(1) 循环计算每个关键字的桶映射函数,这个时间复杂度是O(N)。

(2) 利用先进的比较排序算法对每个桶内的所有数据进行排序,其时间复杂度为:对于N个待排数据,M个桶,平均每个桶[N/M]个数据的桶排序平均时间复杂度为:O(N)+O(M*(N/M)*log(N/M)) = O(N+N*(logN-logM)) = O(N+N*logN-N*logM)。

提高算法在于:

(a) 映射函数f(k)能够将N个数据平均的分配到M个桶中,这样每个桶就有[N/M]个数据量。当N=M时,即极限情况下每个桶只有一个数据时。桶排序的最好效率能够达到O(N),最坏(所有元素在一个桶中)。

(b) 尽量的增大桶的数量。极限情况下每个桶只能得到一个数据,这样就完全避开了桶内数据的“比较”排序操作。当然,做到这一点很不容易,数据量巨大的情况下,f(k)函数会使得桶集合的数量巨大,空间浪费严重。这就是一个时间代价和空间代价的权衡问题了。

C++代码:

 

#include <iostream>
using namespace std;
namespace mySort
{
	float Max(float *array, int begin,int end)
	{//获取数组中最大的数
		float ret = -1;
		for (int i = begin; i <= end; ++i) 
			ret = (ret < array[i]) ? array[i] : ret;
		return ret;
	}
	int getIndex(float a, float max)
	{//获取桶的索引位置。
		return (int) ((a * 10) / max);
	}
	void insertSort(float * array, int begin, int end)
	{
		for (int i = begin; i < end; ++i)
		{
			int j = i + 1 ;
			float tmp = array[j];
			for (; j > 0;j--)
			{
				if (tmp < array[j - 1])
					array[j] = array[j - 1];
				else					
					break;
			}
			array[j] = tmp;
		}
	}
	void radixSorting(float array[], int begin, int end)
	{
		int arraySize = end - begin + 1;
		float max = Max(array, begin, end);
		const int countSize = 11; //(N * 10 / M && N < M ) 有 11 种可能
		int count[countSize];
		float * temp = new float[arraySize];
		memset((void*)count,0, sizeof(count)); //初始化为0

		for (int i = begin; i <= end; ++i) //记录每个桶中的元素个数
		{
			count[getIndex(array[i], max)] += 1;
		}
		for (int i = 0; i < countSize-1; ++i)
		{
			count[i + 1] += count[i];
		}
		for (int i = end; i >= begin; --i)
		{
			int j = getIndex(array[i], max);
			temp[count[j]-1] = array[i];
			--count[j];
		}
		for (int i = 0; i < countSize - 1 ; ++i)
		{
			if (count[i] < count[i + 1])
			{
				mySort::insertSort(temp, count[i], count[i + 1] - 1);
			}
		}
		memcpy((void*)array, (void*)temp, arraySize*sizeof(float));
		delete [] temp;


		
	}
};

int main()
{
	float a[] = { 49, 38 , 35, 97 , 76, 73 , 27, 49 };
	int length = sizeof(a) / sizeof(float);
//	mySort::insertSort(a, 0, length - 2);
	mySort::radixSorting(a, 0, length - 1);
	return 0;
}


总结:桶排序和计数排序有着惊人的相似之处。计数排序将每个元素投射于数组的每个空间,但有着:必须是整数,并且空间耗费大,与具体待排序的数有关。而桶排序则用更小的空间(O(N)且与具体待排序数无关),只记录了每个桶的索引,但需要对每个桶的数据进行排序,适用范围也更广泛。计数排序这种用空间换时间的方法和Hash有着很大的相似之处。

 

posted @ 2014-08-08 21:40  旧客图新  阅读(325)  评论(0编辑  收藏  举报