堆与堆排序与topK问题

电面的时候问了经典的topK问题,没准备到被问了个措不及防,现在把相关知识点记录下来。
假设我们有一些数据,需要按照某种关键字求出这些数据中最小(大)的K个数,即求出这些数据的topK。
当数据量较小的时候,一个简单的想法是直接对数据进行排序,然后取最前面的K个数据;但是当数据量较大,数据无法一次性放入内存的时候应该怎么办呢?
这时候就需要借助堆这种数据结构。堆通常是一个可以被看做一棵树的数组对象,其总是满足下列性质:
(a)堆中某个节点的值总是不大于或不小于其父节点的值
(b)堆总是一棵完全二叉树
节点值不大于其父节点的堆称为大根堆,反之称为小根堆。

堆排序与topK问题无关,以下内容纯属展开:
以堆为基础的排序方法称为堆排序。堆排序主要可以分为以下几个步骤:
(a)建堆
(b)将堆顶与堆的最后一个元素对换
(c)将堆的大小-1(此时最后一个元素看作已排序好),并对剩下的部分保持堆的性质
(d)重复(b)、(c)直至排序完成
代码如下:

public class MinHeap {
    public final static int MAX = 100;
    private int[] heap = null;
    private int size = 0;
    public MinHeap(){
        heap = new int[MAX];
    }
    public boolean add(int num){
        if (size >= MAX){
            return false;
        }
        heap[size] = num;
        size++;
        return true;
    }
    public void buildHeap(){
        for (int i = (size - 1) / 2;i >= 0;i--){
            heapify(i);
        }
    }
    private void heapify(int pos){
        int left = pos * 2 + 1, right = pos * 2 + 2;
        if (left >= size){
            return;
        }else if (right >= size){
            if (heap[pos] > heap[left]){
                int tmp = heap[pos];
                heap[pos] = heap[left];
                heap[left] = tmp;
            }
            return;
        }else{
            int minPos = pos;
            if (heap[pos] > heap[left]){
                if (heap[left] > heap[right]){
                    minPos = right;
                }else{
                    minPos = left;
                }
            }else if (heap[pos] > heap[right]){
                minPos = right;
            }
            int tmp = heap[pos];
            heap[pos] = heap[minPos];
            heap[minPos] = tmp;
            if (minPos != pos){
                heapify(minPos);
            }
        }
    }
    public void heapSort(){
        buildHeap();
        int length = size;
        while (size > 0){
            int tmp = heap[size-1];
            heap[size-1] = heap[0];
            heap[0] = tmp;
            size--;
            heapify(0);
        }
        size = length;
    }
    public void print(){
        for (int i = 0;i < size;i++){
            System.out.print(heap[i] + " ");
        }
        System.out.println();
    }
    public static void main(String[] args) {
        MinHeap heap = new MinHeap();
        for (int i = 10;i > 0;i--){
            heap.add(i);
        }
        heap.heapSort();
        heap.print();
    }
}

堆排序heapSort()的时间复杂度为O(nlgn),其中建堆buildHeap()的时间复杂度为O(n)(不是笔误,具体见算法导论),每一轮保持堆的性质heapify(int pos)的时间复杂度为O(lgn),一共O(n)轮。

回到topK问题上,我们可以维护一个大小为K的堆来帮助我们解决topK问题。以最小的K个数据为例,我们维护一个大小为K的大根堆在内存中,接下来每次从那一大堆数据当中读出一个数据与堆顶比较,若该数据比堆顶数据小,则说明它比当前topK的最大值要小,因此将堆顶替换为该数据,再用heapify(0)保持该堆的最大堆性质即可。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值