Java最小堆解决TopK问题
来源:互联网 发布:c语言调用tcl 编辑:程序博客网 时间:2024/06/06 18:09
原文
TopK问题是指从大量数据(源数据)中获取最大(或最小)的K个数据。
TopK问题是个很常见的问题:例如学校要从全校学生中找到成绩最高的500名学生,再例如某搜索引擎要统计每天的100条搜索次数最多的关键词。
对于这个问题,解决方法有很多:
方法一:对源数据中所有数据进行排序,取出前K个数据,就是TopK。
但是当数据量很大时,只需要k个最大的数,整体排序很耗时,效率不高。方法二:维护一个K长度的数组a[],先读取源数据中的前K个放入数组,
对该数组进行升序排序,再依次读取源数据第K个以后的数据,和数组中最小的元素(a[0])比较,如果小于a[0]直接pass,大于的话,就丢弃最小的元素a[0],利用二分法找到其位置,然后该位置前的数组元素整体向前移位,直到源数据读取结束。
这比方法一效率会有很大的提高,但是当K的值较大的时候,长度为K的数据整体移位,也是非常耗时的。
对于这种问题,效率比较高的解决方法是使用最小堆。
最小堆(小根堆)是一种数据结构,它首先是一颗完全二叉树,并且,它所有父节点的值小于或等于两个子节点的值。
最小堆的存储结构(物理结构)实际上是一个数组。如下图:
最小堆和数组之间的关系:父节点=a[i],左叶子=2(a[i] + 1) - 1,右叶子=2(a[i]+1)
堆有几个重要操作:
BuildHeap:将普通数组转换成堆,转换完成后,数组就符合堆的特性:所有父节点的值小于或等于两个子节点的值。
Heapify(int i):当元素i的左右子树都是小根堆时,通过Heapify让i元素下降到适当的位置,以符合堆的性质。
回到上面的取TopK问题上,用最小堆的解决方法就是:先去源数据中的K个元素放到一个长度为K的数组中去,再把数组转换成最小堆。再依次取源数据中的K个之后的数据和堆的根节点(数组的第一个元素)比较,根据最小堆的性质,根节点一定是堆中最小的元素,如果小于它,则直接pass,大于的话,就替换掉根元素,并对根元素进行Heapify,直到源数据遍历结束。
最小堆的实现:
public class MinHeap { // 堆的存储结构 - 数组 private int[] data; // 将一个数组传入构造方法,并转换成一个小根堆 public MinHeap(int[] data) { this.data = data; buildHeap(); } // 将数组转换成最小堆 private void buildHeap() { // 完全二叉树只有数组下标小于或等于 (data.length) / 2 - 1 的元素有孩子结点,遍历这些结点。 // *比如上面的图中,数组有10个元素, (data.length) / 2 - 1的值为4,a[4]有孩子结点,但a[5]没有* for (int i = (data.length) / 2 - 1; i >= 0; i--) { // 对有孩子结点的元素heapify heapify(i); } } private void heapify(int i) { // 获取左右结点的数组下标 int l = left(i); int r = right(i); // 这是一个临时变量,表示 跟结点、左结点、右结点中最小的值的结点的下标 int smallest = i; // 存在左结点,且左结点的值小于根结点的值 if (l < data.length && data[l] < data[i]) smallest = l; // 存在右结点,且右结点的值小于以上比较的较小值 if (r < data.length && data[r] < data[smallest]) smallest = r; // 左右结点的值都大于根节点,直接return,不做任何操作 if (i == smallest) return; // 交换根节点和左右结点中最小的那个值,把根节点的值替换下去 swap(i, smallest); // 由于替换后左右子树会被影响,所以要对受影响的子树再进行heapify heapify(smallest); } // 获取右结点的数组下标 private int right(int i) { return (i + 1) << 1; } // 获取左结点的数组下标 private int left(int i) { return ((i + 1) << 1) - 1; } // 交换元素位置 private void swap(int i, int j) { int tmp = data[i]; data[i] = data[j]; data[j] = tmp; } // 获取堆中的最小的元素,根元素 public int getRoot() { return data[0]; } // 替换根元素,并重新heapify public void setRoot(int root) { data[0] = root; heapify(0); } }利用最小堆获取TopK:
public class TopK { public static void main(String[] args) { // 源数据 int[] data = {56,275,12,6,45,478,41,1236,456,12,546,45}; // 获取Top5 int[] top5 = topK(data, 5); for(int i=0;i<5;i++) { System.out.println(top5[i]); } } // 从data数组中获取最大的k个数 private static int[] topK(int[] data,int k) { // 先取K个元素放入一个数组topk中 int[] topk = new int[k]; for(int i = 0;i< k;i++) { topk[i] = data[i]; } // 转换成最小堆 MinHeap heap = new MinHeap(topk); // 从k开始,遍历data for(int i= k;i<data.length;i++) { int root = heap.getRoot(); // 当数据大于堆中最小的数(根节点)时,替换堆中的根节点,再转换成堆 if(data[i] > root) { heap.setRoot(data[i]); } } return topk; } }执行效果:
阅读全文
0 0
- Java最小堆解决TopK问题
- Java最小堆解决TopK问题
- Java最小堆解决TopK问题
- Java最小堆解决TopK问题
- 最小堆解决topK问题
- 最大堆 最小堆 解决TOPK问题
- Java 利用最小堆解决topK
- TopK问题探索-最小堆JAVA实现
- 自己动手实现使用最小堆解决Topk问题
- java解决topk问题
- 最小优先队列 解决TopK问题
- 最小堆获取topK问题与堆的增删
- 堆及topk问题
- 堆及topk问题
- Java 实现 堆排序 快速排序 以及 TopK问题(一)
- Java 实现 堆排序 快速排序 以及 TopK问题(二)
- 堆排序和TopK问题
- 堆--优先级队列--topK问题
- 插入排序
- python3学习--文件读写
- PHP模糊查询的实现方法
- PTA 友元函数操作
- 网络---TCP协议的握手与挥手
- Java最小堆解决TopK问题
- 面向报文(UDP)和面向字节流(TCP)的区别
- LeeCode And Two Numbers
- PTA 求解给定字符串的前缀
- PHP json_decode/json_encode中文内容为NULL或乱码
- PHP json_decode/json_encode中文内容为NULL或乱码
- Can't create handler inside thread that has not called Looper.prepare()
- NOIP2016题解
- 知识点与实例代码