从topK问题学习堆栈的思想以及python标准库方式与python实现笔记

leetcode703. 数据流中的第 K 大元素

方法一 排序

使用快速排序,维护数组前K大的值,每次添加时,和数组的最小值作比较,如果加入的值大于最小值,则将数组中最小值弹出,将该值添加进去后排序,时间复杂度:O(n*klogk),空间复杂度O(k)

缺点:时间复杂度比较高。

方法二 堆

维护一个二叉堆,初始化二叉堆的时间复杂度:O(nlogk), 其中n代表初始化nums的长度。单次插入的时间复杂度为O(logk)。空间复杂度:O(k)。

实现方式1:调用heapq

python中的heapq堆,堆是非线性的树形的树状结构,有小根堆和大根堆两种堆。(python标准库heapq库中的堆默认是小根堆)

  • 大根堆:树中各个父节点的值总是大于或等于任何一个子节点的值。
  • 小根堆:树中各个父节点的值总是小于或等于任何一个子节点的值。

如图所示:
在这里插入图片描述

我们一般使用二叉堆来实现优先队列,它的内部调整算法复杂度为logN。

heapq堆的常用方法:

  1. heapq.heappop(heap)

    删除并返回最小值,因为堆的特征是heap[0]永远是最小的元素,所以一般都是删除最小的元素。

    >>> list
    [1, 1, 3, 5, 2, 5, 8, 9, 6]
    >>> heapq.heappop(list)
    1
    >>> list
    [1, 2, 3, 5, 6, 5, 8, 9]
    
  2. heapq.heappush(heap, item)

heap为定义堆,item为增加的元素

demo1: 维持一个topK的堆:

import heapq

k = 5
h = [] # 预定义的堆
lst = [2,45,65,87,854,244,24,5,73,46,78,9]
for i in lst:
    heapq.heappush(h,i) # 向堆中增加元素
    if len(h) > k:
        heapq.heappop(h) # 向堆中减少元素
print(h)
print('该列表中第%d大的数为:' % k,h[0])
  1. heapq.heapify(list)

将列表转化为堆:

>>> list = [1,2,3,5,1,5,8,9,6]
>>> heapq.heapify(list)
>>> list
[1, 1, 3, 5, 2, 5, 8, 9, 6]
  1. 其他:

    函数名描述
    heapq.heapreplace(heap.item)删除并返回最小元素值,添加新的元素值
    heapq.heappushpop(list, item)判断添加元素值与堆的第一个元素值对比;如果大,则删除并返回第一个元素,然后添加新元素值item.如果小,则返回item. 原堆不变。
    heapq.merge(…)将多个堆合并
题目实现:
class KthLargest:
    def __init__(self, k: int, nums: List[int]):
        self.heap = []
        self.k = k
        for num in nums:
            heapq.heappush(self.heap,num)
            if len(self.heap) > k:
                heapq.heappop(self.heap)

    def add(self, val: int) -> int:
        heapq.heappush(self.heap,val)
        if len(self.heap) > self.k:
            heapq.heappop(self.heap)
        return self.heap[0]
实现方式2:手写实现一个堆

堆的特点

  1. 内部数据是有序的
  2. 可以弹出栈顶的元素,大顶堆就是弹出最大值,小项堆就是弹出最小值。
  3. 每次加入新元素或者弹出栈顶元素后,调整堆使之重新有序仅需要O(logn)的时间。
  4. 支持在线算法

堆的本质

  1. 它是一个完全二叉树

  2. 实现的时候我们不需要建造一个树,改用一个数组即可

    • 将完全二叉树和一个数组关联到一起:给树的节点编号,节点的编号就是元素在数组中的下标。
  3. 于是我们可以得出一个结论:

    已知一个节点的编号为index,那么它的父节点的编号为:
    f a t h e r _ i n d e x = ⌊ i n d e x − 1 2 ⌋ father\_index = \lfloor {index-1 \over 2}\rfloor father_index=2index1
    左孩子节点的编号为:
    l e f t _ i n d e x = i n d e x ∗ 2 + 1 left\_index = index*2 + 1 left_index=index2+1
    有孩子节点的编号为:
    r i g h t _ i n d e x = i n d e x ∗ 2 + 2 right\_index = index*2+2 right_index=index2+2

如何调整堆?

  1. 添加元素
  • 把新数据添加到数的最后一个元素,也就是数组的末尾
  • 把末尾节点向上调整
  1. 弹出堆顶
  • 交换根节点与最后一个节点的值
  • 删除最后一个节点
  • 把根节点向下调整
class Heap:
    def __init__(self,desc=False):
        """初始化,默认创建一个小顶堆"""
        self.heap = []
        self.desc = desc

    @property
    def size(self):
        return len(self.heap)

    def top(self):
        if self.size:
            return self.heap[0]
        return None

    def push(self, item):
        """
        添加元素
        第一步,把元素加入到数组末尾
        第二步,把末尾元素向上调整
        """
        self.heap.append(item)
        self._sift_up(self.size-1)
        
    def pop(self):
        """
        弹出栈顶
        第一步:记录栈顶元素的值
        第二步:交换栈顶元素与末尾元素的值
        第三步:删除数组末尾的值
        第四步:新的栈顶元素向下调整
        第五步:返回答案
        """
        item = self.heap[0]
        self._swap(0,self.size-1)
        self.heap.pop()
        self._sift_down(0)
        return item
    
    def _smaller(self,lhs,rhs):
        return lhs > rhs if self.desc else lhs < rhs
    
    def _sift_up(self,index):
        """
        向上调整
        如果父节点和当前节点满足交换的关系
        (对于小根堆是父节点元素更大,对于大根堆是父节点更小)
        则持续将当前节点向上调整
        """
        while index:
            parent = (index-1) // 2
            if self._small(self.heap[parent],self.heap[index]):
                break
            self._swap(parent,index)
            index = parent
    
    def _sift_down(self,index):
        """
        向下调整
        如果子节点和当前节点满足交换的关系
        则持续将当前节点向下调整
        """
        while index*2+1 < self.size:
            smallest = index
            left = index*2+1
            right = index*2+2
            
            if self._smaller(self.heap[left],self.heap[smallest]):
                smallest = left
                
            if right < self.size and self._smaller(self.heap[right],self.heap[smallest]):
                smallest = right
                
            if smallest == index:
                break
            
            self._swap(index,smallest)
            index = smallest
    
    def _swap(self,i,j):
        self.heap[i],self.heap[j] = self.heap[j],self.heap[i]

class KthLargest:
    def __init__(self, k: int, nums: List[int]):
        self.heap = Heap()
        self.k = k
        for num in nums:
            self.heap.push(num)
            if self.heap.size > k:
                self.heap.pop()

    def add(self, val: int) -> int:
        self.heap.push(val)
        if self.heap.size > self.k:
            self.heap.pop()
        return self.heap.top() 

参考来源:

python中heapq堆的讲解https://blog.csdn.net/qq_35883464/article/details/99410423

leetcode 题解:https://leetcode-cn.com/problems/kth-largest-element-in-a-stream/solution/python-dong-hua-shou-xie-shi-xian-dui-by-ypz2/

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值