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

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/CYXLZZS/article/details/51375974

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 对于最小(大)堆的定义以及特点可以网上查看一下

猜你喜欢

转载自blog.csdn.net/CYXLZZS/article/details/51375974