桶排序的基本思想
桶排序利用函数的映射关系,将待排序的数组分成了N个块(桶)。实际上,桶排序的f(k)值的计算,其作用就相当于快排中划分,已经把大量数据切割成了基本有序的数据块(桶)。
然后仅仅须要对每一个桶中的少量数据做比較排序(比較排序:即在比較的基础上进行交换。达到排序效果)就可以。
假如待排序列K= {49、 38 、 35、 97 、 76、 73 、 27、 49 }。
这些数据所有在1—100之间。因此我们定制10个桶。然后确定映射函数f(k)=(k*10)/(k.max)。则第一个keyword49将定位到第4个桶中(49*10/97=5)。依次将所有keyword所有堆入桶中,并在每一个非空的桶中进行高速排序。例如以下图所看到的。
待排序的数组为:
那么接下来仅仅要对这些子数组排序就可以。
有用范围:
数组的数必须是正数,但我们能够通过对每一个数加上一个值a,让它变为正数。排序完毕后再减去a。
时间复杂度:
(1) 循环计算每一个keyword的桶映射函数。这个时间复杂度是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) 尽量的增大桶的数量。极限情况下每一个桶仅仅能得到一个数据。这样就全然避开了桶内数据的“比較”排序操作。当然,做到这一点非常不easy,数据量巨大的情况下。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有着非常大的相似之处。