桶排序Bucket Sort从1956年就开始被使用,该算法的基本思想是由E.J.Issac R.C.Singleton提出来。本博介绍BucketSort算法相关知识。
假设输入的待排序元素是等可能的落在等间隔的值区间内.一个长度为N的数组使用桶排序, 需要长度为N的辅助数组. 等间隔的区间称为桶, 每个桶内落在该区间的元素. 桶排序是基数排序的一种归纳结果.算法的主要思想: 待排序数组A[1...n]内的元素是随机分布在[0,1)区间内的的浮点数.辅助排序数组B[0....n-1]的每一个元素都连接一个链表.将A内每个元素乘以N(数组规模)取底,并以此为索引插入(插入排序)数组B的对应位置的连表中. 最后将所有的链表依次连接起来就是排序结果.这个过程可以简单的分步如下:
注意:1)note: 待排序元素越均匀, 桶排序的效率越高. 均匀意味着每个桶在中间过程中容纳的元素个数都差不多,不会出现特别少或者特别多的情况, 这样在排序子程序进行桶内排序的过程中会达到最优效率.2)note: 将元素通过恰当的映射关系将元素尽量等数量的分到各个桶(值区间)里面, 这个映射关系就是桶排序算法的关键.桶的标记(数组索引Index)的大小也要和值区间有对应关系
BUCKET_SORT (A) n ← length [A] For i = 1 to n do Insert A[i] into list B[nA[i]] For i = 0 to n-1 do Sort list B with Insertion sort Concatenate the lists B[0], B[1], . . B[n-1] together in order.
映射函数 bindex=f(key) 其中,bindex 为桶数组B的下标(即第bindex个桶), k为待排序列的关键字。桶排序之所以能够高效,其关键在于这个映射函数,它必须做到:如果关键字k1<k2,那么f(k1)<=f(k2)。也就是说B(i)中的最小数据都要大于B(i-1)中最大数据。很显然,映射函数的确定与数据本身的特点有很大的关系.
桶排序利用函数的映射关系,减少了几乎所有的比较工作。实际上,桶排序的f(k)值的计算,其作用就相当于快排中划分,已经把大量数据分割成了基本有序的数据块(桶)。然后只需要对桶中的少量数据做先进的比较排序即可。对N个关键字进行桶排序的时间复杂度分为两个部分:
很显然,第2部分是桶排序性能好坏的决定因素。尽量减少桶内数据的数量是提高效率的唯一办法(因为基于比较排序的最好平均时间复杂度只能达到O(N*logN)了)。因此,我们需要尽量做到下面两点:
对于N个待排数据,M个桶,平均每个桶[N/M]个数据的桶排序平均时间复杂度为:
O(N)+O(M*(N/M)*log(N/M))=O(N+N*(logN-logM))=O(N+N*logN-N*logM)
当N=M时,即极限情况下每个桶只有一个数据时。桶排序的最好效率能够达到O(N)。
总结: 桶排序的平均时间复杂度为线性的O(N+C),其中C=N*(logN-logM)。如果相对于同样的N,桶数量M越大,其效率越高,最好的时间复杂度达到O(N)。 当然桶排序的空间复杂度 为O(N+M),如果输入数据非常庞大,而桶的数量也非常多,则空间代价无疑是昂贵的。此外,桶排序是稳定的。void BucketSort(int *A, int bucket_size){ int size =sizeof(A)/sizeof(int); KeyNode **bucket_table=(KeyNode **)malloc(bucket_size*sizeof(KeyNode *)); for(int i=0;i<bucket_size;i++){ bucket_table[i]=(KeyNode *)malloc(sizeof(KeyNode)); bucket_table[i]->key=0; bucket_table[i]->next=NULL; } for(int j=0;j<size;j++){ KeyNode *node=(KeyNode *)malloc(sizeof(KeyNode)); node->key=A[j]; node->next=NULL; int index=A[j]/10; KeyNode *p=bucket_table[index]; if(p->key==0){ bucket_table[index]->next=node; (bucket_table[index]->key)++; } else{ while(p->next!=NULL&&p->next->key<=node->key) p=p->next; node->next=p->next; p->next=node; (bucket_table[index]->key)++; } } }注:上述部分codes采用桶内数据排序,我们使用了基于单链表的直接插入排序算法。可以使用基于双向链表的快排算法提高效率。
关于程序算法艺术与实践更多讨论与交流,敬请关注本博客和新浪微博songzi_tea.