首页 > 代码库 > 排序中topK那点事(转)

排序中topK那点事(转)

问题描述:有 N (N>1000000)个数,求出其中的前K个最小的数(又被称作topK问题)。

 

这类问题似乎是备受面试官的青睐,相信面试过互联网公司的同学都会遇到这来问题。下面由浅入深,分析一下这类问题。

 

思路1:最基本的思路,将N个数进行完全排序,从中选出排在前K的元素即为所求。有了这个思路,我们可以选择相应的排序算法进行处理,目前来看快速排序,堆排序和归并排序都能达到O(NlogN)的时间复杂度。当然,这样的答案也是无缘offer的。

 

思路2:可以采用数据池的思想,选择其中前K个数作为数据池,后面的N-K个数与这K个数进行比较,若小于其中的任何一个数,则进行替换。这种思路的算法复杂度是O(N*K),当答出这种算法时,似乎离offer很近了。

 

有没有算法复杂度更低的方法呢?

 

从思路2可以想到,剩余的N-K个数与前面K个数比较的时候,是顺序比较的,算法复杂度是K。怎么在这方面做文章呢? 采用的数据结构是堆。

 

思路3:大根堆维护一个大小为K的数组,目前该大根堆中的元素是排名前K的数,其中根是最大的数。此后,每次从原数组中取一个元素与根进行比较,如小于根的元素,则将根元素替换并进行堆调整(下沉),即保证大根堆中的元素仍然是排名前K的数,且根元素仍然最大;否则不予处理,取下一个数组元素继续该过程。该算法的时间复杂度是O(N*logK),一般来说企业中都采用该策略处理topK问题,因为该算法不需要一次将原数组中的内容全部加载到内存中,而这正是海量数据处理必然会面临的一个关卡。如果能写出代码,offer基本搞定。

 

还有没有更简单的算法呢?答案是肯定的。

 

思路4:利用快速排序的分划函数找到分划位置K,则其前面的内容即为所求。该算法是一种非常有效的处理方式,时间复杂度是O(N)(证明可以参考算法导论书籍)。对于能一次加载到内存中的数组,该策略非常优秀。如果能完整写出代码,那么相信面试官会对你刮目相看的。

 

下面,给出思路4的Python代码:

 

def partition(L, left, right):
    """
    将L[left:right]进行一次快速排序的partition,返回分割点
   :param L: 数据List
    :param left: 排序起始位置
   :param right: 排序终止位置
   :return: 分割点
    """
    if left < right:
        print left
        key = L[left]
        low = left
        high = right
        while low < high:
            while low < high and L[high] >= key:
                high = high - 1
            L[low] = L[high]
            while low < high and L[low] <= key:
                low = low + 1
            L[high] = L[low]
        L[low] = key
    return low

def topK(L, K):
    """
    求L中的前K个最小值
   :param L: 数据List
    :param K: 最小值的数目
    """
    if len(L) < K:
        pass
    low = 0
    high = len(L) - 1
    j = partition(L, low, high)
    while j != K: # 划分位置不是K则继续处理
      if K > j: #k在分划点后面部分
         low = j + 1
        else:
            high = j           # K在分划点前面部分
      j = partition(L, low, high)

排序中topK那点事(转)