给定一个无序的整型数组arr,找到其中最小的k个数。

O(N*logK)的解法说起来非常简单,就是一直维护一个k个数的大根堆,这个堆代表目前选出的k个最小的数,在堆里的k个元素中堆顶的元素是最小的k个数里最大的那个。
在遍历整个数组的过程中,看看当前数是否比堆顶元素小:
如果是,就把堆顶的元素替换成当前的数,然后从堆顶的位置调整整个堆,让替换操作后的堆的最大元素 继续处在堆顶的位置;
如果不是,不进行任何的操作,继续遍历下一个数;
在遍历完成后,堆中的k个数就是所有数组中最小的k个数。

import heapq
def bfprt(a,k):
    b = [-va for va in a]
    kheap = b[:k]
    heapq.heapify(kheap)
    for i in range(k,len(a)):
        if b[i] > kheap[0]:
            kheap[0] = b[i]
            heapq.heapify(kheap)
    result = [-va for va in kheap]

    return result


a = [12,34,15,2,1,46,12,13,35,18,16]
print(bfprt(a,6))

O(N)的解法需要用到一个经典的算法–BFPRT算法。BFPRT算法解决了这样一个问题:在无序的数组中找到第k小的数。显而易见的是,如果我们找到了第k小的数,那么想求arr中最小的k个数,就是再遍历一次数组的工作量而已,所以关键问题就变成了如何理解并实现BFPRT算法。
BFPRT算法是如何找到第k小的数的呢?以下是BFPRT算法的过程。
假设BFPRT算法的函数是int select(int[] arr, k),该函数的功能为在arr中找到第k小的数,然后返回该数。
select(arr, k)的过程为:

  1. 将arr中的n个元素划分成n/5组,每组5个元素,如果最后的组不够5个元素,那么最后剩下的元素为一组(n%5个元素)
  2. 对每个组进行插入排序(只是每个组最多5个元素之间的组内排序,组与组之间并不排序),排序后找到每个组的中位数,如果组的元素个数为偶数,统一找到下中位数。
  3. 步骤2中一共会找到n/5个中位数,让这些中位数组成一个新的数组,记为medianArr。递归调用select(medianArr,medianArr.length/2),意义是找到medianArr这个数组中的中位数(median中的第(medianArr.length/2)小的数)。
  4. 假设步骤3中递归调用select(medianArr,medianArr.length/2)后,返回的数为x。根据这个x划分整个arr数组(partition过程),划分完成的功能为在arr中,比x小的数都在x的左边,大于x的数都在x的右边,x在中间;并求出x在arr中的位置记为i。
  5. 如果i==k,说明x为整个数组中第k小的数,直接返回;
    如果i < k,说明x的处在第k小的数的左边,应该在x的右边寻找第k小的数,所以递归调用select函数,在左半区寻找第k小的数;
    如果i > k,说明x的处在第k小的数的右边,应该在x的左边寻找第k小的数,所以递归调用select函数,在右半区寻找第(i-k)小的数;

你可能感兴趣的:(数据结构)