Java最小堆解决TopK问题
TopK问题是指从大量数据(源数据)中获取最大(或最小)的K个数据。
TopK问题是个很常见的问题:例如学校要从全校学生中找到成绩最高的500名学生,再例如某搜索引擎要统计每天的100条搜索次数最多的关键词。
对于这个问题,解决方法有很多:
方法一:对源数据中所有数据进行排序,取出前K个数据,就是TopK。
但是当数据量很大时,只需要k个最大的数,整体排序很耗时,效率不高。
方法二:维护一个K长度的数组a[],先读取源数据中的前K个放入数组,对该数组进行升序排序,再依次读取源数据第K个以后的数据,和数组中最小的元素(a[0])比较,如果小于a[0]直接pass,大于的话,就丢弃最小的元素a[0],利用二分法找到其位置,然后该位置前的数组元素整体向前移位,直到源数据读取结束。
这比方法一效率会有很大的提高,但是当K的值较大的时候,长度为K的数据整体移位,也是非常耗时的。
对于这种问题,效率比较高的解决方法是使用最小堆。
最小堆(小根堆)是一种数据结构,它首先是一颗完全二叉树,并且,它所有父节点的值小于或等于两个子节点的值。
最小堆的存储结构(物理结构)实际上是一个数组。如下图:
堆有几个重要操作:
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;
}
}
Java最小堆解决TopK问题的更多相关文章
- 最大堆 最小堆 解决TOPK问题
堆:实质是一颗完全二叉树,最大堆的特点:父节点值均大于子节点:最小堆的父节点值均小于子节点: 一般使用连续内存存储堆内的值,因而可以根据当前节点的索引值推断子节点的索引值: 节点i的父节点为(i-1) ...
- scala写算法-用小根堆解决topK
topK问题是指从大量数据中获取最大(或最小)的k个数,比如从全校学生中寻找成绩最高的500名学生等等. 本问题可采用小根堆解决.思路是先把源数据中的前k个数放入堆中,然后构建堆,使其保持堆序(可以简 ...
- heapsort(Java)(最小堆)
public static void main(String[] args) { Scanner input = new Scanner(System.in); int n = input.nextI ...
- Java解决TopK问题(使用集合和直接实现)
在处理大量数据的时候,有时候往往需要找出Top前几的数据,这时候如果直接对数据进行排序,在处理海量数据的时候往往就是不可行的了,而且在排序最好的时间复杂度为nlogn,当n远大于需要获取到的数据的时候 ...
- 使用加强堆结构解决topK问题
作者:Grey 原文地址: 使用加强堆结构解决topK问题 题目描述 LintCode 550 · Top K Frequent Words II 思路 由于要统计每个字符串的次数,以及字典序,所以, ...
- c++/java/python priority_que实现最大堆和最小堆
#include<iostream>#include<vector>#include<math.h>#include<string>#include&l ...
- Google 面试题:Java实现用最大堆和最小堆查找中位数 Find median with min heap and max heap in Java
Google面试题 股市上一个股票的价格从开市开始是不停的变化的,需要开发一个系统,给定一个股票,它能实时显示从开市到当前时间的这个股票的价格的中位数(中值). SOLUTION 1: 1.维持两个h ...
- java实现最小堆
1.堆:通常通过二叉堆,实为二叉树的一种,分为最小堆和最大堆,具有以下性质: 任意节点小于它的所有后裔,最小元在堆的根上. 堆总是一棵完全树 将根节点最大的堆叫做最大堆或大根堆,根节点最小的堆叫做最小 ...
- 【Java】 用PriorityQueue实现最大最小堆
PriorityQueue(优先队列),一个基于优先级堆的无界优先级队列. 实际上是一个堆(不指定Comparator时默认为最小堆),通过传入自定义的Comparator函数可以实现大顶堆. Pri ...
随机推荐
- SpringBoot | 第四章:日志配置(转)
前言 介于平时工作中,对于日志这块没有过多的接触,也就未有过多的了解.故在编写本文时,上官网查看了相关资料,奈何每个字母我都认识,但合起来就有点晕了,英文阅读水平还是有待大大的提高呀.最后觉得还是转载 ...
- mybatis-plus 异常 Invalid bound statement (not found)
最近吧项目中添加使用了mybatis-plus,发现操作sql的时候出现异常: Invalid bound statement (not found) ,异常位置位于mybatis-plus的jar中 ...
- hibernate课程 初探单表映射2-3 session简介
hibernate流程: 1 配置对象Configurateion 读取 hibernate.cfg.xml 2 会话工厂SessionFactory 读取 user.hbm.xml(创建销毁相当耗费 ...
- Centos_linux系统的区别及实际查看
在Linux系统查看系统版本为 32 位还是 64 位 [root@localhost ~]# cat /etc/redhat-release CentOS release 6.8 (Final) [ ...
- WPF创建SignalR服务端(转)
在网上看到了一个帖子,比较详细,博主写的很好. 地址:http://blog.csdn.net/lordwish/article/details/51786200
- static int a
static int a只被本文件可见,外部文件不可见;而int a如果在外部文件作以下声明: extern int a,那么它在声明的文件里也是可见的 详见:http://bbs.csdn.net/ ...
- sql优化经典例子
场景 我用的数据库是mysql5.6,下面简单的介绍下场景 课程表 create table Course( c_id int PRIMARY KEY, name varchar(10) ) 数据10 ...
- swift 命名空间实现的设计思考:extension YKKit where Base == String
设计问题:谁来构造.构造什么.怎么添加新功能 关键词:本体.客体.构造.映射.功能. 别名:桥接变量.型变变量.容器变量.适配变量,构造变量: 目的:添加命名空间.添加新功能. 原则:不修改本体的实现 ...
- maven操作手册
===Maven的安装=== http://blog.csdn.net/yang5726685/article/details/56486479 ===Maven的jar包仓库地址配置=== http ...
- Linux 命令大全提供 500 多个 Linux 命令搜索
Linux Command 在这里维持一个持续更新的地方 516 个 Linux 命令大全,内容包含 Linux 命令手册.详解.学习,值得收藏的 Linux 命令速查手册.请原谅我写了个爬虫,爬了他 ...