桶排序:本身桶排序的概念就不太确定,有的人把计数排序也称为桶排序(各个桶中元素的排序采用计数排序),得到数组C后直接从前往后遍历,输出数组值次数组下标,为0就不输出(或者存入原数组,不稳定),不过我认为这种说法不严谨(一个很明显的问题是输出会是双重for循环,不过也有那个意思,叫鸽巢排序也未尝不可),因为桶排序要求输入数据在[0,1)范围内(计数排序要求整数;实际上要么全是整数,要么小数,便于划分桶),先把区间[0,1)划分成n个相同大小的子区间,称为桶,然后将n个输入数分布到各个桶中去。桶排序是鸽巢排序的一种归纳结果。当要被排序的阵列内的数值是均匀分配的时候,桶排序使用线性时间(Θ(n))。但桶排序并不是比较排序,他不受到 O(n log n) 下限的影响。排序原理如图(来自于算法导论):
java源码:
package com.tangbo;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.Random;
import java.util.Scanner;
public class BucketSort {
static Scanner scanner;
static Random random = new Random();
public static void bucketSort(double array[]) {
int length = array.length;
ArrayList<Double> arrList[] = new ArrayList[length];
/*
* 每个桶是一个list,存放落在此桶上的元素
* 上次的基数排序我采用的是计数排序实现的(可以参考http://blog.csdn.net/tangbo713/article/details/41204313),但我使用了动态数组(使用我认为使用计数排序有点麻烦)
*/
//划分桶,并且把对应的元素放到对应的桶中
for (int i = 0; i < length; i++) {
int temp = (int) Math.floor(10 * array[i]);//放到以第一个非零的数字的桶中
if (null == arrList[temp])//判断这个桶是否创建
arrList[temp] = new ArrayList<Double>();
arrList[temp].add(array[i]);//向桶中添加该元素
}
// 对每个桶中的数进行插入排序
for (int i = 0; i < length; i++) {
if (null != arrList[i]) {
//此处排序方法不定,不过越快越好,除了三大线性排序外,都没有Collections和Arrays里的sort好,因为这是调优后的快拍Arrays里也有,在基数排序里用过copyOf和fill方法
Collections.sort(arrList[i]);
}
}
//输出排序数组
int count = 0;
for (int i = 0; i < length; i++) {
if (null != arrList[i])
{
Iterator<Double> iter = arrList[i].iterator();
while (iter.hasNext()) {
Double d = iter.next();
array[count] = d;
count++;
}
}
}
}
public static void main(String[] args) {
double array[] = productArray();
bucketSort(array);
print(array);
}
static double [] productArray()//生成一个数组
{
int arrayayLength=0;
System.out.println("请输入数组长度:");
scanner = new Scanner(System.in);
arrayayLength = scanner.nextInt();
double [] arrayayTemp = new double[arrayayLength];
for (int i = 0; i < arrayayLength; i++) {
arrayayTemp[i]=(random.nextInt(100))/100.0;
}
return arrayayTemp;
}
static void print(double []array)//打印函数
{
for(int i=0;i<array.length;i++)
{
System.out.print(array[i]+" ");
}
System.out.println();
}
}
==============转载的分割线===============
=====http://hxraid.javaeye.com/blog/649831#comments=====
(有修改)
桶排序在海量数据中的应用
一年的全国高考考生人数为500 万,分数使用标准分,最低100 ,最高900 ,没有小数,你把这500 万元素的数组排个序。
分析:对500W数据排序,如果基于比较的先进排序,平均比较次数为O(5000000*log5000000)≈1.112亿。但是我们发现,这些数据都有特殊的条件: 100=<score<=900。那么我们就可以考虑桶排序这样一个“投机取巧”的办法、让其在毫秒级别就完成500万排序。
方法:创建801(900-100)个桶。将每个考生的分数丢进f(score)=score-100的桶中。这个过程从头到尾遍历一遍数据只需要500W次。然后根据桶号大小依次将桶中数值输出,即可以得到一个有序的序列。而且可以很容易的得到100分有***人,501分有***人。
实际上,桶排序对数据的条件有特殊要求,如果上面的分数不是从100-900,而是从0-2亿,那么分配2亿个桶显然是不可能的。所以桶排序有其局限性,适合元素值集合并不大的情况。
题目:在一个文件中有 10G 个整数,乱序排列,要求找出中位数。内存限制为 2G。只写出思路即可(内存限制为 2G的意思就是,可以使用2G的空间来运行程序,而不考虑这台机器上的其他软件的占用内存)。
关于中位数:数据排序后,位置在最中间的数值。即将数据分成两部分,一部分大于该数值,一部分小于该数值。中位数的位置:当样本数为奇数时,中位数=(N+1)/2 ; 当样本数为偶数时,中位数为N/2与1+N/2的均值(那么10G个数的中位数,就第5G大的数与第5G+1大的数的均值了)。
分析: 既然要找中位数,很简单就是排序的想法。那么基于字节的桶排序是一个可行的方法。
思想:将整型的每1byte作为一个关键字,也就是说一个整形可以拆成4个keys,而且最高位的keys越大,整数越大。如果高位keys相同,则比较次高位的keys。整个比较过程类似于字符串的字典序。
第一步:把10G整数每2G读入一次内存,然后一次遍历这536,870,912即(1024*1024*1024)*2 /4个数据。每个数据用位运算">>"取出最高8位(31-24)。这8bits(0-255)最多表示255个桶,那么可以根据8bit的值来确定丢入第几个桶。最后把每个桶写入一个磁盘文件中,同时在内存中统计每个桶内数据的数量,自然这个数量只需要255个整形空间即可。
代价:(1) 10G数据依次读入内存的IO代价(这个是无法避免的,CPU不能直接在磁盘上运算)。(2)在内存中遍历536,870,912个数据,这是一个O(n)的线性时间复杂度。(3)把255个桶写会到255个磁盘文件空间中,这个代价是额外的,也就是多付出一倍的10G数据转移的时间。
第三步:继续以内存中的整数的次高8bit进行桶排序(23-16)。过程和第一步相同,也是255个桶。
第四步:一直下去,直到最低字节(7-0bit)的桶排序结束。我相信这个时候完全可以在内存中使用一次快排就可以了。
注意,变态的情况下,这个需要读入的第128号文件仍然大于2G,那么整个读入仍然可以按照第一步分批来进行读取。第二步:根据内存中255个桶内的数量,计算中位数在第几个桶中。很显然,2,684,354,560个数中位数是第1,342,177,280个。假设前127个桶的数据量相加,发现少于1,342,177,280,把第128个桶数据量加上,大于1,342,177,280。说明,中位数必在磁盘的第128个桶中。而且在这个桶的第1,342,177,280-N(0-127)个数位上。N(0-127)表示前127个桶的数据量之和。然后把第128个文件中的整数读入内存。(平均而言,每个文件的大小估计在10G/128=80M左右,当然也不一定,但是超过2G的可能性很小)。
整个过程的时间复杂度在O(n)的线性级别上(没有任何循环嵌套)。但主要时间消耗在第一步的第二次内存-磁盘数据交换上,即10G数据分255个文件写回磁盘上。一般而言,如果第二步过后,内存可以容纳下存在中位数的某一个文件的话,直接快排就可以了。