算法——TOP K问题最小堆实现

时间:2023-01-30 16:24:11

1. 问题背景

在实际应用中,我们经常会遇到在一大推数据中找出最大的几个数的问题,也就是我们提到的TOP K问题。K表示需要找出数据的数量

2. 解决方案

TOP K问题也有多种解决方案,比如排序,最后截取靠前或者靠后的K个数据。当数据量小的时候,排序解决起来当然可以,算法简单,排序算法也有很多现成的。当数据量很大时,维护一个很长的数组,不管是空间存储上还是排序耗费的时间上都可能难以接受。这时我们可以采用最小堆的方式来实现,只需要维护一个K长度的数组就行

3. 具体实现

数据结构最小堆MinHeap, 其中最重要的是堆的调整adjustHeap

package org.cyxl.common;

/**
* Created by jeff on 16/5/11.
*/

public class MinHeap {
// 堆的存储结构 - 数组
private int[] data;

/**
* 初始化堆中的数据,以int的最小值作为初始值
*
* @param k
*/

public MinHeap(int k)
{
this.data = new int[k];
for(int i=0;i<k;i++){
data[i]=Integer.MIN_VALUE;
}
}

private void adjustHeap(int i)
{
//获取左右结点的数组下标
int l = left(i);
int r = right(i);

// 这是一个临时变量,表示 跟结点、左结点、右结点中最小的值的结点的下标
int min = i;

// 存在左结点,且左结点的值小于根结点的值
if (l < data.length && data[l]<data[i]){
min = l;
}
// 存在右结点,且右结点的值小于以上比较的较小值
if (r < data.length && data[r]<data[min]){
min = r;
}

// 左右结点的值都大于根节点,直接return,不做任何操作
if (i == min)
return;

// 交换根节点和左右结点中最小的那个值,把根节点的值替换下去
swap(i, min);

// 由于替换后左右子树会被影响,所以要对受影响的子树再进行adjustHeap
adjustHeap(min);
}

/**
* 获取右结点的数组下标
* @param i
* @return
*/

private int right(int i)
{
return (i + 1) << 1;
}

/**
* 获取左结点的数组下标
* @param i
* @return
*/

private int left(int i)
{
return ((i + 1) << 1) - 1;
}

/**
* 交换元素位置
*
* @param i
* @param j
*/

private void swap(int i, int j)
{
int tmp = data[i];
data[i] = data[j];
data[j] = tmp;
}

/**
* 将数值加入到堆中
*
* @param element
*/

public void add(int element)
{
if(element>data[0]) {
data[0] = element;
adjustHeap(0);
}
}

public int[] getData(){
return data;
}

@Override
public String toString(){
StringBuilder builder = new StringBuilder();
builder.append("{");
for (int i=0;i<data.length;i++){
builder.append(data[i]);
if(i!=data.length-1){
builder.append(",");
}
}
builder.append("}");

return builder.toString();
}
}

4. 测试

public static void main(String[] args){

int[] a = {12,1,5425,12,43,734,343,999,8,99,55,2,222,444,11};

//求最大的10个
int k=10;

MinHeap heap = new MinHeap(k);
for(int i:a){
heap.add(i);
}

System.out.print(heap);
}

输出结果是

{12,43,55,99,222,343,999,734,5425,444}

5. 扩展

5.1 当然实际应用中,我们找的可能不是单纯的数字。更可能是和这些数字相关的某些对象。比如找出年龄最大的10个用户,这时我们的User对象可以实现Comparable接口来实现比较。

5.2 这里我们是最小堆来实现TOP K问题,当然也可以用最大堆来找出最小的K个值

5.3 对于最小(大)堆的定义以及特点可以网上查看一下