在一个大文件里有1亿条记录,每一行记录为1个数字,统计最大的前10000个数字
* 思路:
* 维护一个大小为K的最小堆,并认为初始化之后的堆就是最大的K个元素
* 接来下将从第K+1个元素开始与堆顶元素比较,若大于堆顶元素则将堆顶元素抛弃后新元素入堆
其中生成指定范围的随机数的代码如下:
通过以上代码生成1亿个数字,数字的范围在0到10亿之间。
最终运行结果图:
* 思路:
* 维护一个大小为K的最小堆,并认为初始化之后的堆就是最大的K个元素
* 接来下将从第K+1个元素开始与堆顶元素比较,若大于堆顶元素则将堆顶元素抛弃后新元素入堆
* 全部读取完后将该最小堆进行一次排序即可得到最大的K个数字
为了模拟这个问题,生成海量数据的代码如下:
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
import Tools.MyRandom;
public class Main {
public static void main(String[] args) throws Exception {
File file = new File("C:/Users/XXX/test.txt");
if(!file.exists()){
try {
file.createNewFile();
} catch (IOException e) {
e.printStackTrace();
}
}
FileOutputStream fos = new FileOutputStream(file,true);
BufferedWriter bfw = new BufferedWriter(new OutputStreamWriter(fos));
for (int i = 0; i < 100000000; i++) {
bfw.write(Integer.toString(MyRandom.random(0, 1000000000)));
bfw.newLine();
}
bfw.close();
}
}
其中生成指定范围的随机数的代码如下:
package Tools;
import java.util.Random;
public class MyRandom {
/**
* 获得指定范围的随机数
* @param min 下界
* @param max 上界
* @return
*/
public static int random(int min,int max){
Random random = new Random();
int s = random.nextInt(max)%(max-min+1) + min;
return s;
}
}
通过以上代码生成1亿个数字,数字的范围在0到10亿之间。
因为数字范围太小会产生去重的问题,因此将数字的范围调高到0到10亿,尽量减小重复的冲突
* 但是如果文件中的前K个数字存在重复,就涉及到去重的问题了
给出执行代码如下:
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStreamReader;
import sort.HeapSort;
/**
* 海量数据的TOP K问题:
* 在一个大文件里有1亿条记录,每一行记录为1个数字,统计最大的前K个数字
* 思路:
* 维护一个大小为K的最小堆,并认为初始化之后的堆就是最大的K个元素
* 接来下将从第K+1个元素开始与堆顶元素比较,若大于堆顶元素则将堆顶元素抛弃后新元素入堆
* 全部读取完后将该最小堆进行一次排序即可得到最大的K个数字
* 但是如果文件中的数字存在重复,就涉及到去重的问题了
* @author shuaicenglou
*/
public class TopK {
public static void main(String[] args) {
long begin = System.currentTimeMillis();
final int K = 10000;
File file = new File("C:/Users/XXX/test.txt");
FileInputStream fis = null;
BufferedReader br =null;
try {
fis = new FileInputStream(file);
br = new BufferedReader(new InputStreamReader(fis));
} catch (FileNotFoundException e) {
e.printStackTrace();
}
int[] m = new int[K];
if(br!=null){
try {
for(int i=0;i<K;i++) m[i] = Integer.parseInt(br.readLine()); //首先读取前1万个数字,并将该1万个数字初始化为最小堆
HeapSort.initMinHeap(m); //维护一个大小为K的最小堆
for(;;){
String buff = br.readLine();
if(buff==null) break; //读到文件末尾,结束
int temp = Integer.parseInt(buff); //读取一个数字
int top = m[0]; //取出堆顶元素
if(temp>top){ //将读取到的数字与堆顶元素比较,若大于堆顶元素则入堆,重新调整堆
m[0] = temp;
HeapSort.MinHeapAdjust(m, 0, K);
}
}
HeapSort.MinSort(m);
long end = System.currentTimeMillis();
for(int i:m) System.out.println(i); //输出
System.out.println("程序执行时间:"+((end-begin)/1000)+"s");
br.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
其中堆排序的代码如下:
package sort;
/**
* 堆排序
* 堆排序的时间复杂度为O(NlogN),是一种不稳定的排序算法
* @author shuaicenglou
*
*/
public class HeapSort {
public static void main(String[] args) {
int[] m = {9,8,7,6,5,4,3,2,1};
MaxSort(m);
for(int i:m) System.out.println(i);
MinSort(m);
for(int i:m) System.out.println(i);
}
/**
* 大根堆排序
* @param m
*/
public static void MaxSort(int[] m){
initMaxHeap(m); //首先初始化一个大根堆
for(int i=m.length-1;i>=0;i--){
swap(m, 0, i); //将堆顶元素放到数组末尾
MaxHeapAdjust(m, 0, i);
}
}
/**
* 初始化大根堆,其时间复杂度为O(N)
* @param m
*/
private static void initMaxHeap(int[] m){
for(int i=m.length/2;i>=0;i--){
MaxHeapAdjust(m, i, m.length);
}
}
/**
* 维护堆
* 此处的堆为大根堆
* @param array
* @param parent
* @param length
*/
public static void MaxHeapAdjust(int[] m,int parent,int length){
int temp = m[parent]; //记录当前父节点的值
int child = 2*parent+1; //计算左孩子
/**
* 类似直接插入排序,比父节点大的节点往根节点方向不停地移动
*/
while(child<length){
if(child+1<length&&m[child+1]>m[child]) child+=1; //若右孩子存在且大于左孩子,则将
if(m[child]<temp) break;
m[parent] = m[child]; //将左右孩子中的较大值赋予父节点
parent = child; //选择孩子节点作为下一轮比较的父节点
child = child*2+1; //选择当前孩子节点的左孩子节点作为下一轮比较的孩子节点
}
m[parent] = temp;
}
/**
* 交换下标i和j的元素的位置
* @param m
* @param i
* @param j
*/
private static void swap(int[] m,int i,int j){
int temp = m[i];
m[i] = m[j];
m[j] = temp;
}
/**
* 维护小根堆
* @param m
* @param parent
* @param length
*/
public static void MinHeapAdjust(int[] m,int parent,int length){
int temp = m[parent];
int child = 2*parent+1;
while(child<length){
if(child+1<length&&m[child+1]<m[child]) child+=1;
if(m[child]>temp) break;
m[parent] = m[child];
parent = child;
child = child*2+1;
}
m[parent] = temp;
}
/**
* 初始化小根堆
* @param m
*/
public static void initMinHeap(int[]m){
for(int i=m.length/2;i>=0;i--) MinHeapAdjust(m, i, m.length);
}
/**
* 小根堆排序
* @param m
*/
public static void MinSort(int[]m){
initMinHeap(m);
for(int i=m.length-1;i>=0;i--){
swap(m, 0, i);
MinHeapAdjust(m, 0, i);
}
}
}
/**
* 模拟初始堆的建立过程
*
* [1,2,3,4,5]
*
* 1
* 2 3
* 4 5 (初始完全二叉树,从i=2开始比较,因i=2无孩子,倒退到i=1进行比较)
*
* m[parent]=5
* 1
* 5 3
* 4 5 parent=4,child=9
*
* m[parent] = temp (temp=2)
* 1
* 5 3
* 4 2 (此轮比较结束,5上浮到2的位置,2下沉到5的位置,退到根节点开始比较)
*
* 5
* 5 3 (temp=1),parent=1,child=3
* 4 2
*
* 5
* 4 3
* 1 2 (大根堆初始化完成)
**/
堆排序代码中,MinSort为最小堆排序,MinHeapAdjust为维护最小堆的代码,initMinHeap为初始化最小堆的代码,最大堆为Max
最终运行结果图: