2.7 算法 -- topK问题

算法子目录:https://www.jianshu.com/p/02492be3c5f5

现在有n个数,设计算法找出前k大个数

解决思路

  • 排序后切片
    使用快排排序后切片,复杂度为O(nlogn+k)
  • “LOW B” 三人组
    因为这三种排序可以通过控制外部循环决定排序次数,时间复杂度为O(kn)
    相比之下,这种方法比第一种要快。
  • 堆排序
    最后就是堆排序,我们的主角。

使用堆排序的解决思路

  • 取前K个元素组成一个小根堆,堆顶就是目前第k大的数。
  • 依次向前遍历原列表,对于列表中的元素,如果小于堆顶,则忽略该元素,如果大于堆顶,则将堆顶替换为该元素,并且对堆进行一次调整。
  • 遍历列表所有元素后,倒序弹出堆顶。

例子:
有列表 [6,8,1,9,3,0,7,2,4,5],取前5大的数。
这样我们就要取出前五个数,构建一个小根堆如下:

     1
  3    6
9   8
列表内剩余元素:0,7,2,4,5

然后将剩余元素一个一个和堆顶比较,大于堆顶则替换,小于堆顶则忽略:

元素0:0<1 忽略
元素7:7>1 替换,调整
     3
  7    6
9   8
元素2:2<3 忽略
元素4:4>3 替换,调整
     4
  7    6
9   8
元素5:5>4 替换,调整
     5
  7    6
9   8

最后依次弹出堆顶:[5,7,6,9,8]

复杂度为O(klogk+(n-k)logk)=O(nlogk)。

代码如下:

def sift(li,low,high):
    #li表示树,low表示high的父节点,high表示树的最后一个节点
    tmp = li[low]
    i = low
    j = 2 * i + 1  #初始j指向左子节点
    # i 指向空位,j指向子节点
    while j <= high: #退出的第二种情况:j大于high,说明空
        if j + 1 <= high and li[j] < li [j+1]:#如果右子节点存在,且大于左子节点
            j += 1   #则使用右子节点
        if li[j] > tmp:
            #这里比较 li[j] 与 li[j] 的父节点 li[i] 的大小,如果 li[j] 大于 li[i] ,则交换。
            li[i] = li[j]
            # 同时,让i保存j的位置,j再保存相应的左子节点。 接着到while进行判断 j还在不在堆的范围中。
            i = j
            j = 2 * i + 1
        else: #退出的第一种情况:j的位置比tmp小,说明两个子节点都比tmp小。
            break
    li[i] = tmp  #把拿出来的那个值放在空位上面。
    
    
    
def topk(li,k):
    heap = li[0:k]
    for i in range(k//2-1,-1,-1):
        sift(heap,i,k-1)
    for i in range(k,len(li)):
        if li[i] > heap[0]:
            heap[0] = li[i]
            sift(heap,0,k-1)
    for i in range(k-1,-1,-1):
        heap[0],heap[i] = heap[i],heap[0]
        sift(heap,0,i-1)
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

友情链接更多精彩内容