面试题:求第K大元素(topK)?

一、引言二、普通算法算法A:算法B:三、较好算法算法C:算法D:四、总结

一、引言

​ 这就是类似求Top(K)问题,什么意思呢?怎么在无序数组中找到第几(K)大元素?我们这里不考虑海量数据,能装入内存。

二、普通算法

算法A:

将数组中的元素升序排序,找到数组下标k-1的元素即可。这是大家最容易想到的方法,如果使用简单排序算法,时间复杂度为O(n^2)。

算法B:

  1. 第一步:初始化长度为K的一个数组,先读入K个元素,将元素降序排序(升序也可以),这时候第K大元素就在最后一个。
  2. 第二步:读入下一个元素就与已排序的第K大元素比较,如果大于,则将当前的第K大元素删掉,并将此元素放到前K-1个正确的位置上(这里就是简单的插入排序了。不了解插入排序的朋友可以看这里图解选择排序与插入排序)。
  3. 时间复杂度:第一步采用普通排序算法时间复杂度是O(k^2);第二步:(N-k)*(k-1) = Nk-k^2+k。所以算法B的时间复杂度为O(NK)当k=N/2(向下取整)时,时间复杂度还是O(n^2)。

其实求第K大问题,也可以求反,即求第N-k+1小问题。这是等价的。所以当K=N/2时,是最难的地方,但也很有趣,这时候的K对应的值就是中位数。

三、较好算法

算法C:

  1. 算法思想:将数据读入一个数组,对数组进行buildHeap(我们这里构建大顶堆),之后对堆进行K次deleteMax操作,第K次的结果就是我们需要的值。(因为是大顶堆,所以数据从大到小排了序,堆排序以后会详细说)。

  2. 现在我们来解决上节遗留的问题,为什么buildHeap是线性的?不熟悉堆的可以看一下 图解优先队列(堆)。我们先来看看代码实现。

    public PriorityQueue(T[] items) {
           //当前堆中的元素个数
           currentSize = items.length;
           //可自行实现声明
           array = (T[]) new Comparable[currentSize +1];
           int i = 1;
           for (T item : items){
               array[i++] = item;
           }
           buildHeap();
       }

    private void buildHeap() {
           for (int i = currentSize / 2; i > 0; i--){
               //堆的下滤方法,可参考上面的链接
               percolateDown(i);
           }
       }

图中初始化的是一颗无序树,经过7次percolateDown后,得到一个大顶堆。从图中可以看到,共有8条虚线,每一条对应于2次比较,总共16次比较。为了确定buildHeap的时间界,我们需要统计虚线的条数,这可以通过计算堆中所有节点的高度和得到,它是虚线的最大条数。该和是O(N)。

定理:包含2h+1-1个节点、高为h的理想二叉树(满二叉树)的节点的高度的和是2h+1-1-(h+1)。

  1. 什么叫满二叉树?满二叉树是完全填满的二叉树,最后一层都是填满的,如图中所示。完全二叉树,是除最后一层以外都是填满的,最后一层外也必须从左到右依次填入,就是上一篇中说的堆的结构。满二叉树一定是完全二叉树,完全二叉树不一定是满二叉树。

  2. 证明定理:

    容易看出,满二叉树中,高度为h上,有1个节点;高度h-1上2个节点,高度h-2上有2^2个节点及一般在高度h-i上的2i个节点组成。

方程两边乘以2得到:

两式相减得到:

所以定理得证。因为堆由完全二叉树构成,所以堆的节点数在2h和2h+1之间,所以意味着这个和是O(N)。所以buildHeap是线性的。所以算法C的时间复杂度是:初始化数组为O(N),buildHeap为O(N),K次deleeMax需要O(klogN),所以总的时间复杂度是:O(N+N+klogN)=O(N+klogN)如果K为N/2时,运行时间是O(NlogN)

算法D:

  1. 算法思想:我们采用算法B的思想,只是我们这里构建一个节点数为K的小顶堆,只要下一个数比根节点大,就删除根节点,并将这个数进行下滤操作。所以算法最终的第K大数就是根节点的数。
  2. 时间复杂度:对K个数进行buildHeap是O(k),最坏情况下假设剩下的N-k个数都要进入堆中进行下滤操作,总的需要O(k+(N-k)logk)。如果K为N/2,则需要O(NlogN)。

四、总结

本篇详述了 求top(K)问题的几种解法,前两种十分平凡普通,后两种比较优一点,暂时给出求解中位数需要O(NlogN)时间。以后还会介绍更优的方式,可以以平均时间O(N)解决这个问题。

猜你喜欢

转载自www.cnblogs.com/9dragon/p/10777017.html