思想
桶排序(Bucket sort)是一种基于计数的排序算法,工作的原理是将数据分到有限数量的桶子里,然后每个桶再分别排序(有可能再使用别的排序算法或是以递回方式继续使用桶排序进行排序)。当要被排序的数据内的数值是均匀分配的时候,桶排序时间复杂度为 O(n) 。桶排序不同于快速排序,并不是比较排序,不受到时间复杂度 O(nlogn) 下限的影响。
桶排序按下面4步进行:
- 设置固定数量的空桶。
- 把数据放到对应的桶中。
- 对每个不为空的桶中数据进行排序。
- 拼接从不为空的桶中数据,得到结果。
桶排序,主要适用于小范围整数数据,且独立均匀分布,可以计算的数据量很大,而且符合线性期望时间。
例子
图片来源
稳定性
为保证排序是稳定的,分配过程中装箱及收集过程中的连接必须按先进先出原则进行。
(1) 实现方法一
每个箱子设为一个链队列。当一记录装入某箱子时,应做人队操作将其插入该箱子尾部;而收集过程则是对箱子做出队操作,依次将出队的记录放到输出序列中。
(2) 实现方法二
若输入的待排序记录是以链表形式给出时,出队操作可简化为是将整个箱子链表链接到输出链表的尾部。这只需要修改输出链表的尾结点中的指针域,令其指向箱子链表的头,然后修改输出链表的尾指针,令其指向箱子链表的尾即可。
参考链接
算法
我利用链表对同一个桶内的元素进行排序
链表定义
class ListNode{
double value;
ListNode next;
ListNode(){
this.value = -1.0; // 头结点
this.next = null;
}
ListNode(double v){
this.value = v;
this.next = null;
}
}
对于一个排序链表插入的一个元素程序
public void insertListNode(ListNode head ,double value){
ListNode insertNode = new ListNode(value);
// 空
if(head.next==null){
head.next = insertNode;
return;
}
// 插入在头
if(head.next.value >= value){
insertNode.next = head.next;
head.next = insertNode;
return;
}
// 插入在中间
ListNode p = head;
ListNode pre = p;
while(p!=null){
if(p.value>=value){
insertNode.next = p;
pre.next = insertNode;
return;
}else{
pre = p;
p = p.next;
}
}
//插入在末尾
if(p==null){ // 遍历到空结点,说明没有插入,pre 是最后一个结点
pre.next = insertNode;
}
}
下面就很简单了
遍历桶,根据边界放入对应的数据,最后桶内数据输出就是有序数据
public void bucketSort(double[] A){
int n = A.length;
int numBucket = n/4; // 默认桶的个数
double[] maxMin = new double[2];
maxMin(A,maxMin);
double max = maxMin[0];
double min = maxMin[1];
double d = (maxMin[0] - maxMin[1])/numBucket;
ListNode[] bucket = new ListNode[numBucket];
for(int i=0;i< numBucket;i++){
bucket[i] = new ListNode();// 初始化桶
}
// 遍历桶号,放入对应的数字
for(int i=0;i< numBucket;i++){
double low = min + i*d;
double high = min + (i+1) *d;
if(i==numBucket-1){ // 最后一个桶要包括右边界
high++;
}
for(int j = 0;j<n;j++){
double num = A[j];
if(low<= num && num<high){
insertListNode(bucket[i],num);
}
}
}
int k =0;// 合并桶,得到排序结果
for(int i=0;i<numBucket;i++){
ListNode curHead = bucket[i];
while(curHead.next!=null){
A[k++] = curHead.next.value;
curHead = curHead.next;
}
}
}
几点说明:
1.使用了带有头结点的单链表,这样插入的时候简单了
2.桶的数量,为了简单起见,直接设置为:n/4
3.定义桶的时候要初始化桶
4.桶边界直接根据最大值最小值求出步长,这样只是为了简单,当数据均匀的时候这样也是合理的
找到最值
上面桶排序程序太烂,更新如下
public void bucketSort2(double[] A){
int n = A.length;
int a = 0;
int b = n/4;
double[] maxMin = new double[2];
maxMin(A,maxMin);
double max = maxMin[0];
double min = maxMin[1];
int index = 0;
ListNode[] bucket = new ListNode[b+1];
for(int i=0;i< b+1;i++){
bucket[i] = new ListNode();
}
for(int i=0;i<b+1;i++){
bucket[i] = new ListNode(-1);
}
for(int i=0;i<n;i++){
double val = A[i];
if(val<=0)
index = 0;
else{
index = (int) (b - (b-a)*(max-val)/(max-min));
}
insertListNode(bucket[index],val);
}
int k =0;
for(int i=0;i<b+1;i++){
ListNode curHead = bucket[i];
while(curHead.next!=null){
A[k++] = curHead.next.value;
curHead = curHead.next;
}
}
}
当时负数的时候都放到第0个桶
根据原始数据的min max 将数据映射到 a - b 之间,这样快速多了
public void maxMin(double[] A,double[] maxMin){
maxMin[0] = Double.MIN_NORMAL;
maxMin[1] = Double.MAX_VALUE;
for(double a:A){
if(a>= maxMin[0]){
maxMin[0] = a;
}else if(a<maxMin[1]){
maxMin[1] = a;
}
}
}
主函数
public static void main(String[] args){
double[] A = new double[] {23,56,80,46,7,91,94,35,76,37} ;//{49,38,65,97,76,13,27,49};
Print.printArray(A);
BucketSort bucketSort = new BucketSort();
bucketSort.bucketSort(A);
Print.printArray(A);
}
上面两个数据都可以实现了排序
复杂度分析
(1)时间复杂度
平均时间复杂度
O(n)
,证明可以参考《算法导论》
最坏情况下时间复杂度
O(n2)