堆与堆排序与topK问题

来源:互联网 发布:淘宝店主贷款 编辑:程序博客网 时间:2024/06/04 19:25

电面的时候问了经典的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