python基础-数据结构-leetcode刷题必看-heapq --- 堆队列算法,TopK问题

文章目录

    • 堆的定义
    • 堆的主要操作
    • 堆的构建
    • 堆排序
    • heapq模块
      • `heapq.heappush(heap, item)`
      • `heapq.heappop(heap)`
      • `heapq.heappushpop(heap, item)`
      • `heapq.heapreplace(heap, item)`
      • `heapq.merge(*iterables, key=None, reverse=False)`
      • `heapq.nlargest(n, iterable, key=None)`
      • `heapq.nsmallest(n, iterable, key=None)`
    • TopK问题求解
      • 遍历
      • 排序
  • 参考

堆的定义

堆队列算法,即优先队列算法。在leetcode中一般需要求前k个xxxx的题目基本都需要使用堆。

  • 堆是一种完全二叉树,这个性质保证了堆使用数组表示就不会出现None,数组中每一个值都不会是空的
  • 而且假设父节点索引为i那么左右子节点的下标分别是2i+12i+2
  • 大顶堆中每个上级节点的值都大于等于它的任意子节点。我们将这一条件称为堆的不变性。

这个实现使用了数组,其中对于所有从 0 开始计数的 k 都有 heap[k] <= heap[2*k+1]heap[k] <= heap[2*k+2]。为了便于比较,不存在的元素将被视为无穷大。堆(大顶堆)最有趣的特性在于其最大的元素始终位于根节点 heap[0]
在这里插入图片描述

在这里插入图片描述

堆的主要操作

堆主要有两个重要操作:上滤、下虑
上虑操作,主要用于添加元素,如果子节点的值比父节点值要大,那么需要该节点递归执行上虑操作,以重新满足堆的性质。
例如下图是一个添加一个新元素的例子,我们先将新元素插入到数组的末尾,如果他比父节点((i-1)//2)大,那么他将向上执行上虑操作,直到根节点或者找到一个大于它的父节点。

def up_heapify(nums, i):
    while i > 0:
        parent = (i - 1) // 2
        if nums[i] > nums[parent]:
            nums[i], nums[parent] = nums[parent], nums[i]
            i = parent
        else:
            break

在这里插入图片描述
在这里插入图片描述

下虑操作,主要用于删除元素(例如在堆排序中,总是弹出根节点),删除元素之后,我们会将末尾元素移动到根节点,然后执行下虑操作,如果比其中子节点小,我们就需要交换与子节点的位置,直到叶子节点,或者没有大于它的子节点。下虑更一般的操作是树中有节点比其子节点要小,我们需要执行下虑操作,以维持堆的特性。

def down_heapify(nums, n, i):
    while i < n:
        left = i * 2 + 1
        right = i * 2 + 2
        largest = i
        if left < n and nums[left] > nums[largest]:
            largest = left
        if right < n and nums[right] > nums[largest]:
            largest = right
        if largest == i:
            break
        nums[largest], nums[i] = nums[i], nums[largest]
        i = largest

在这里插入图片描述

堆的构建

堆有两种构建方法,分别是使用上虑操作的构建 ( O n log ⁡ ( n ) ) (On\log(n)) (Onlog(n))和使用下虑操作的构建 ( O n ) (On) (On)方法。下面我们以[2,7,26,25,19,17,1,90,3,36]大顶堆构建过程为例
上虑构建:对于一个给定的数组[2,7,26,25,19,17,1,90,3,36],我们从前向后查看,

  • 首先是2,作为根节点,因为他没有父节点,无法执行上虑操作
  • 接着是7,我们可以根据(2-1)//2计算其父节点为索引0,那么72大违反了大顶堆的特点,我们需要7执行上虑操作
  • 接着是26,我们可以根据(2-1)//2计算其父节点为索引0,那么267大违反了大顶堆的特点,我们需要26执行上虑操作
  • 依次类推,直到遍历完数组的所有元素,那么大顶堆的构建过程为遍历所有元素 O ( n ) O(n) O(n)乘以每个元素的上虑操作 O ( log ⁡ n ) O(\log n) O(logn),总的复杂度就是 O ( n log ⁡ n ) O(n\log n) O(nlogn)
def up_build(nums):
    for i in range(len(nums)):
        up_heapify(nums, i)
    return nums

在这里插入图片描述
下虑构建:与上虑构建相反,我们从数组的末尾向前查看,我们查看每个元素是否比其左右孩子节点还要小,如果小,我们就让该元素执行下虑操作

  • 首先36,3,90,1,17都没有孩子节点,跳过
  • 接着19有孩子节点36,19<36,执行下虑操作
  • 依次类推遍历完所有节点
def down_build(nums):
    for i in range(len(nums) // 2 -1, -1, -1):
        down_heapify(nums, len(nums), i)
    return nums

在这里插入图片描述

为什么复杂度是 O ( n ) O(n) O(n)而不是 O ( n log ⁡ n ) O(n\log n) O(nlogn),如果粗略的来看他确实是需要遍历n个节点,然后每个节点可能执行 log ⁡ n \log n logn的下虑操作,那么答案就是 O ( n log ⁡ n ) O(n\log n) O(nlogn)。然而并不是简单的想象那般,
好的,让我们使用LaTeX格式来完整表示这一推导过程。

假设我们的二叉树高度为 h h h

  • 树的第 i i i 层有 2 i 2^i 2i 个节点,层数 i i i 0 0 0 h h h
  • 树的总节点数 n n n为:
    n = 2 0 + 2 1 + 2 2 + … + 2 h = ∑ i = 0 h 2 i n = 2^0 + 2^1 + 2^2 + \ldots + 2^h = \sum_{i=0}^{h} 2^i n=20+21+22++2h=i=0h2i

由于这是一个等比数列,我们可以使用等比数列求和公式:

n = 2 h + 1 − 1 n = 2^{h+1} - 1 n=2h+11

从而可以得到高度 h h h

h = log ⁡ 2 ( n + 1 ) − 1 h = \log_2(n + 1) - 1 h=log2(n+1)1

各层节点数与堆化操作次数的关系 我们要对各层的“节点数量 × \times × 节点高度”求和,得到所有节点的堆化迭代次数的总和。

  • 高度为 h − i h-i hi 的节点数为 2 i 2^i 2i
  • 每个节点的高度是 h − i h-i hi

堆化操作总次数为(这里也可以体现出,如果是叶子节点,高度为 0 0 0,其操作次数也是 0 0 0):

∑ i = 0 h 2 i ⋅ ( h − i ) \sum_{i=0}^{h} 2^i \cdot (h - i) i=0h2i(hi)

我们将公式拆分并化简:

∑ i = 0 h 2 i ⋅ ( h − i ) = h ∑ i = 0 h 2 i − ∑ i = 0 h i ⋅ 2 i \sum_{i=0}^{h} 2^i \cdot (h - i) = h \sum_{i=0}^{h} 2^i - \sum_{i=0}^{h} i \cdot 2^i i=0h2i(hi)=hi=0h2ii=0hi2i

首先,计算 ∑ i = 0 h 2 i \sum_{i=0}^{h} 2^i i=0h2i

∑ i = 0 h 2 i = 2 h + 1 − 1 \sum_{i=0}^{h} 2^i = 2^{h+1} - 1 i=0h2i=2h+11

接下来,计算 ∑ i = 0 h i ⋅ 2 i \sum_{i=0}^{h} i \cdot 2^i i=0hi2i

定义 S = ∑ i = 0 h i ⋅ 2 i S = \sum_{i=0}^{h} i \cdot 2^i S=i=0hi2i

2 S = ∑ i = 0 h i ⋅ 2 i + 1 = ∑ i = 1 h + 1 ( i − 1 ) ⋅ 2 i = ∑ i = 1 h + 1 i ⋅ 2 i − ∑ i = 1 h + 1 2 i 2S = \sum_{i=0}^{h} i \cdot 2^{i+1} = \sum_{i=1}^{h+1} (i-1) \cdot 2^i = \sum_{i=1}^{h+1} i \cdot 2^i - \sum_{i=1}^{h+1} 2^i 2S=i=0hi2i+1=i=1h+1(i1)2i=i=1h+1i2ii=1h+12i

2 S = ∑ i = 1 h + 1 i ⋅ 2 i − ( 2 h + 2 − 2 ) 2S = \sum_{i=1}^{h+1} i \cdot 2^i - (2^{h+2} - 2) 2S=i=1h+1i2i(2h+22)

$$ 2S = \sum_{i=0}^{h+1} i \cdot 2^i - 0 \cdot 2^0 - (2^{h+2} - 2) = S

  • (h+1) \cdot 2^{h+1} - (2^{h+2} - 2) $$

S = ( h + 1 ) ⋅ 2 h + 1 − 2 h + 2 + 2 = ( h − 1 ) ⋅ 2 h + 1 + 2 S = (h+1) \cdot 2^{h+1} - 2^{h+2} + 2 = (h-1) \cdot 2^{h+1} + 2 S=(h+1)2h+12h+2+2=(h1)2h+1+2

S S S带入原公式:

∑ i = 0 h 2 i ⋅ ( h − i ) = h ⋅ ( 2 h + 1 − 1 ) − ( ( h − 1 ) ⋅ 2 h + 1 + 2 ) \sum_{i=0}^{h} 2^i \cdot (h - i) = h \cdot (2^{h+1} - 1) - ((h-1) \cdot 2^{h+1} + 2) i=0h2i(hi)=h(2h+11)((h1)2h+1+2)

= h ⋅ 2 h + 1 − h − ( h − 1 ) ⋅ 2 h + 1 − 2 = 2 h + 1 + h − 2 = h \cdot 2^{h+1} - h - (h-1) \cdot 2^{h+1} - 2 = 2^{h+1} + h - 2 =h2h+1h(h1)2h+12=2h+1+h2

进一步简化

由于我们知道 n ≈ 2 h + 1 n \approx 2^{h+1} n2h+1,则:

2 h + 1 ≈ n 2^{h+1} \approx n 2h+1n

所以我们可以得到:

∑ i = 0 h 2 i ⋅ ( h − i ) ≈ n \sum_{i=0}^{h} 2^i \cdot (h - i) \approx n i=0h2i(hi)n

因此,构建堆的时间复杂度为 O ( n ) O(n) O(n)

通过以上推导,我们证明了使用上虑操作构建堆的时间复杂度为 O ( n ) O(n) O(n),即堆化操作的总次数与节点数成线性关系,非常高效。

堆排序

明白了堆的操作和堆的构建后,堆排序就很简单

  • 首先使用下虑构建好堆
  • 依次将数组的第0个元素弹出,将末尾元素移动到0的位置,并执行下虑操作(为节省空间,弹出来的元素我们可以暂存在数组末尾i的位置(那么i-到len(nums)-1都是排好序的),那么下虑操作时不能超过i
  • 直到弹出所有元素弹出,那么弹出的顺序就是一个有序的序列,(将根节点放到末尾这种做法:如果是大顶堆就是递增顺序,小顶堆是递减序列,这与弹出的单调性相反)
def heap_sort(nums):
    nums = down_build(nums)
    for i in range(len(nums) - 1, 0, -1):
        nums[i], nums[0] = nums[0], nums[i] # 根节点依次移到末尾i中
        down_heapify(nums, i,0) #不能超过i
    return nums

在这里插入图片描述
在这里插入图片描述

heapq模块

这个API与教材的堆算法实现有所不同,具体区别有两方面:(a)我们使用了从零开始的索引。这使得节点和其孩子节点索引之间的关系不太直观但更加适合,因为 Python 使用从零开始的索引。(b)我们的 pop 方法返回最小的项而不是最大的项(这在教材中称为“最小堆”;而“最大堆”在教材中更为常见,因为它更适用于原地排序)。

基于这两方面,把堆看作原生的Python list也没什么奇怪的:heap[0] 表示最小的元素,同时 heap.sort() 维护了堆的不变性!

要创建一个堆,可以新建一个空列表 [],或者用函数 heapify() 把一个非空列表变为堆。

heapq.heappush(heap, item)

item 的值加入 heap 中,保持堆的不变性,默认是构建小顶堆。

>>> nums = []
>>> heapq.heappush(nums, 2)
>>> heapq.heappush(nums, 7)
>>> heapq.heappush(nums, 26)
>>> heapq.heappush(nums, 19)
>>> heapq.heappush(nums, 17)
>>> heapq.heappush(nums, 1)
>>> heapq.heappush(nums, 90)
>>> heapq.heappush(nums, 3)
>>> heapq.heappush(nums, 36)
>>> nums
[1, 3, 2, 7, 17, 26, 90, 19, 36]

heapq.heappop(heap)

弹出并返回 heap 的最小的元素,保持堆的不变性。如果堆为空,抛出 IndexError。使用 heap[0] ,可以只访问最小的元素而不弹出它。

>>> heapq.heappop(nums)
1
>>> nums
[2, 3, 26, 7, 17, 36, 90, 19]

heapq.heappushpop(heap, item)

item 放入堆中,然后弹出并返回 heap 的最小元素。该组合操作比先调用 heappush() 再调用 heappop() 运行起来更有效率。

>>> heapq.heappushpop(nums, 5)
2
>>> nums
[3, 5, 26, 7, 17, 36, 90, 19]

heapq.heapify(x)
将list x 转换成堆,原地,线性时间内。

>>> nums =  [2,7,26,25,19,17,1,90,3,36]
>>> heapq.heapify(nums)
>>> nums
[1, 3, 2, 7, 19, 17, 26, 90, 25, 36]

heapq.heapreplace(heap, item)

弹出并返回 heap 中最小的一项,同时推入新的 item。堆的大小不变。如果堆为空则引发 IndexError
这个单步骤操作比 heappop()heappush() 更高效,并且在使用固定大小的堆时更为适宜。pop/push 组合总是会从堆中返回一个元素并将其替换为 item
返回的值可能会比新加入的值大。如果不希望如此,可改用 heappushpop()。它的 push/pop 组合返回两个值中较小的一个,将较大的留在堆中。

>>> nums
[1, 3, 2, 7, 19, 17, 26, 90, 25, 36]
>>> a = nums
>>> heapq.heapreplace(a, 0) # 不用管弹出元素与插入元素它们两个的大小
1
>>> a
[0, 3, 2, 7, 19, 17, 26, 90, 25, 36]
>>> b = nums
>>> heapq.heappushpop(b,0) # 如果插入的比弹出的更小,那么直接返回插入元素,不弹出
0
>>> b
[0, 3, 2, 7, 19, 17, 26, 90, 25, 36]

该模块还提供了三个基于堆的通用目的函数。

heapq.merge(*iterables, key=None, reverse=False)

将多个已排序的输入合并为一个已排序的输出(例如,合并来自多个日志文件的带时间戳的条目)。返回已排序值的 iterator
类似于 sorted(itertools.chain(*iterables)) 但返回一个可迭代对象,不会一次性地将数据全部放入内存,并假定每个输入流都是已排序的(从小到大)。
具有两个可选参数,它们都必须指定为关键字参数。
key 指定带有单个参数的 key function,用于从每个输入元素中提取比较键。默认值为 None (直接比较元素)。
reverse 为一个布尔值。如果设为 True,则输入元素将按比较结果逆序进行合并。要达成与 sorted(itertools.chain(*iterables), reverse=True) 类似的行为,所有可迭代对象必须是已从大到小排序的。

>>> heapq.merge(a,b)
<generator object merge at 0x00000204065C9678>
>>> list(heapq.merge(a,b))
[0, 0, 3, 2, 3, 2, 7, 7, 19, 17, 19, 17, 26, 26, 90, 25, 36, 90, 25, 36]

在 3.5 版本发生变更: 添加了可选的 keyreverse 形参。

heapq.nlargest(n, iterable, key=None)

iterable 所定义的数据集中返回前 n 个最大元素组成的列表。如果提供了 key 则其应指定一个单参数的函数,用于从 iterable 的每个元素中提取比较键 (例如 key=str.lower)。等价于: sorted(iterable, key=key, reverse=True)[:n]

>>> heapq.nlargest(3, nums)
[90, 36, 26]
>>> nums
[0, 3, 2, 7, 19, 17, 26, 90, 25, 36]

heapq.nsmallest(n, iterable, key=None)

iterable 所定义的数据集中返回前 n 个最小元素组成的列表。如果提供了 key 则其应指定一个单参数的函数,用于从 iterable 的每个元素中提取比较键 (例如 key=str.lower)。等价于: sorted(iterable, key=key)[:n]
后两个函数在 n 值较小时性能最好。对于更大的值,使用 sorted() 函数会更有效率。此外,当 n==1 时,使用内置的 min()max() 函数会更有效率。如果需要重复使用这些函数,请考虑将可迭代对象转为真正的堆。

>>> heapq.nsmallest(3, nums)
[0, 2, 3]

TopK问题求解

我们有3种思路求解

遍历

k 轮遍历,分别在每轮中提取第 1,2,3...k 大的元素,时间复杂度为 O ( n k ) O(nk) O(nk) 。此方法只适用于k<<n的情况,因为当 kn 比较接近时,其时间复杂度趋向于$O(n) ,非常耗时。

排序

我们可以使用任何的排序手段,例如堆排、快排、归并等性能较好的排序算法然后取出前k哥元素,实现复杂度与排序算法的复杂度相同为 O ( n log ⁡ n ) O(n\log n) O(nlogn)

以求Topk个最大值为例:

  • 我们可以使用前k个值作为初始Topk值来初始化一个小顶堆,那么堆顶即为这k个值的最小值
  • 然后接着遍历k+1n的值,每个值与堆顶元素比较,如果大于堆顶元素,那么将堆顶元素剔除,让当前值入堆
  • 最后得到的值即为Topk的值,如果要求这k个值也要按顺序返回,直接将这k个元素依次弹出即可
def top_k_heap(nums, k):
    heap = []
    # 将数组的前 k 个元素入堆
    for i in range(k):
        heapq.heappush(heap, nums[i])
    # 从第 k+1 个元素开始,保持堆的长度为 k
    for i in range(k, len(nums)):
        # 若当前元素大于堆顶元素,则将堆顶元素出堆、当前元素入堆
        if nums[i] > heap[0]:
            heapq.heappop(heap)
            heapq.heappush(heap, nums[i])
    return heap

heapq的官方源码heapq.py

"""Heap queue algorithm (a.k.a. priority queue).

Heaps are arrays for which a[k] <= a[2*k+1] and a[k] <= a[2*k+2] for
all k, counting elements from 0.  For the sake of comparison,
non-existing elements are considered to be infinite.  The interesting
property of a heap is that a[0] is always its smallest element.

Usage:

heap = []            # creates an empty heap
heappush(heap, item) # pushes a new item on the heap
item = heappop(heap) # pops the smallest item from the heap
item = heap[0]       # smallest item on the heap without popping it
heapify(x)           # transforms list into a heap, in-place, in linear time
item = heappushpop(heap, item) # pushes a new item and then returns
                               # the smallest item; the heap size is unchanged
item = heapreplace(heap, item) # pops and returns smallest item, and adds
                               # new item; the heap size is unchanged

Our API differs from textbook heap algorithms as follows:

- We use 0-based indexing.  This makes the relationship between the
  index for a node and the indexes for its children slightly less
  obvious, but is more suitable since Python uses 0-based indexing.

- Our heappop() method returns the smallest item, not the largest.

These two make it possible to view the heap as a regular Python list
without surprises: heap[0] is the smallest item, and heap.sort()
maintains the heap invariant!
"""

# Original code by Kevin O'Connor, augmented by Tim Peters and Raymond Hettinger

__about__ = """Heap queues

[explanation by François Pinard]

Heaps are arrays for which a[k] <= a[2*k+1] and a[k] <= a[2*k+2] for
all k, counting elements from 0.  For the sake of comparison,
non-existing elements are considered to be infinite.  The interesting
property of a heap is that a[0] is always its smallest element.

The strange invariant above is meant to be an efficient memory
representation for a tournament.  The numbers below are `k', not a[k]:

                                   0

                  1                                 2

          3               4                5               6

      7       8       9       10      11      12      13      14

    15 16   17 18   19 20   21 22   23 24   25 26   27 28   29 30


In the tree above, each cell `k' is topping `2*k+1' and `2*k+2'.  In
a usual binary tournament we see in sports, each cell is the winner
over the two cells it tops, and we can trace the winner down the tree
to see all opponents s/he had.  However, in many computer applications
of such tournaments, we do not need to trace the history of a winner.
To be more memory efficient, when a winner is promoted, we try to
replace it by something else at a lower level, and the rule becomes
that a cell and the two cells it tops contain three different items,
but the top cell "wins" over the two topped cells.

If this heap invariant is protected at all time, index 0 is clearly
the overall winner.  The simplest algorithmic way to remove it and
find the "next" winner is to move some loser (let's say cell 30 in the
diagram above) into the 0 position, and then percolate this new 0 down
the tree, exchanging values, until the invariant is re-established.
This is clearly logarithmic on the total number of items in the tree.
By iterating over all items, you get an O(n ln n) sort.

A nice feature of this sort is that you can efficiently insert new
items while the sort is going on, provided that the inserted items are
not "better" than the last 0'th element you extracted.  This is
especially useful in simulation contexts, where the tree holds all
incoming events, and the "win" condition means the smallest scheduled
time.  When an event schedule other events for execution, they are
scheduled into the future, so they can easily go into the heap.  So, a
heap is a good structure for implementing schedulers (this is what I
used for my MIDI sequencer :-).

Various structures for implementing schedulers have been extensively
studied, and heaps are good for this, as they are reasonably speedy,
the speed is almost constant, and the worst case is not much different
than the average case.  However, there are other representations which
are more efficient overall, yet the worst cases might be terrible.

Heaps are also very useful in big disk sorts.  You most probably all
know that a big sort implies producing "runs" (which are pre-sorted
sequences, which size is usually related to the amount of CPU memory),
followed by a merging passes for these runs, which merging is often
very cleverly organised[1].  It is very important that the initial
sort produces the longest runs possible.  Tournaments are a good way
to that.  If, using all the memory available to hold a tournament, you
replace and percolate items that happen to fit the current run, you'll
produce runs which are twice the size of the memory for random input,
and much better for input fuzzily ordered.

Moreover, if you output the 0'th item on disk and get an input which
may not fit in the current tournament (because the value "wins" over
the last output value), it cannot fit in the heap, so the size of the
heap decreases.  The freed memory could be cleverly reused immediately
for progressively building a second heap, which grows at exactly the
same rate the first heap is melting.  When the first heap completely
vanishes, you switch heaps and start a new run.  Clever and quite
effective!

In a word, heaps are useful memory structures to know.  I use them in
a few applications, and I think it is good to keep a `heap' module
around. :-)

--------------------
[1] The disk balancing algorithms which are current, nowadays, are
more annoying than clever, and this is a consequence of the seeking
capabilities of the disks.  On devices which cannot seek, like big
tape drives, the story was quite different, and one had to be very
clever to ensure (far in advance) that each tape movement will be the
most effective possible (that is, will best participate at
"progressing" the merge).  Some tapes were even able to read
backwards, and this was also used to avoid the rewinding time.
Believe me, real good tape sorts were quite spectacular to watch!
From all times, sorting has always been a Great Art! :-)
"""

__all__ = ['heappush', 'heappop', 'heapify', 'heapreplace', 'merge',
           'nlargest', 'nsmallest', 'heappushpop']

def heappush(heap, item):
    """Push item onto heap, maintaining the heap invariant."""
    heap.append(item)
    _siftdown(heap, 0, len(heap)-1)

def heappop(heap):
    """Pop the smallest item off the heap, maintaining the heap invariant."""
    lastelt = heap.pop()    # raises appropriate IndexError if heap is empty
    if heap:
        returnitem = heap[0]
        heap[0] = lastelt
        _siftup(heap, 0)
        return returnitem
    return lastelt

def heapreplace(heap, item):
    """Pop and return the current smallest value, and add the new item.

    This is more efficient than heappop() followed by heappush(), and can be
    more appropriate when using a fixed-size heap.  Note that the value
    returned may be larger than item!  That constrains reasonable uses of
    this routine unless written as part of a conditional replacement:

        if item > heap[0]:
            item = heapreplace(heap, item)
    """
    returnitem = heap[0]    # raises appropriate IndexError if heap is empty
    heap[0] = item
    _siftup(heap, 0)
    return returnitem

def heappushpop(heap, item):
    """Fast version of a heappush followed by a heappop."""
    if heap and heap[0] < item:
        item, heap[0] = heap[0], item
        _siftup(heap, 0)
    return item

def heapify(x):
    """Transform list into a heap, in-place, in O(len(x)) time."""
    n = len(x)
    # Transform bottom-up.  The largest index there's any point to looking at
    # is the largest with a child index in-range, so must have 2*i + 1 < n,
    # or i < (n-1)/2.  If n is even = 2*j, this is (2*j-1)/2 = j-1/2 so
    # j-1 is the largest, which is n//2 - 1.  If n is odd = 2*j+1, this is
    # (2*j+1-1)/2 = j so j-1 is the largest, and that's again n//2-1.
    for i in reversed(range(n//2)):
        _siftup(x, i)

def _heappop_max(heap):
    """Maxheap version of a heappop."""
    lastelt = heap.pop()    # raises appropriate IndexError if heap is empty
    if heap:
        returnitem = heap[0]
        heap[0] = lastelt
        _siftup_max(heap, 0)
        return returnitem
    return lastelt

def _heapreplace_max(heap, item):
    """Maxheap version of a heappop followed by a heappush."""
    returnitem = heap[0]    # raises appropriate IndexError if heap is empty
    heap[0] = item
    _siftup_max(heap, 0)
    return returnitem

def _heapify_max(x):
    """Transform list into a maxheap, in-place, in O(len(x)) time."""
    n = len(x)
    for i in reversed(range(n//2)):
        _siftup_max(x, i)

# 'heap' is a heap at all indices >= startpos, except possibly for pos.  pos
# is the index of a leaf with a possibly out-of-order value.  Restore the
# heap invariant.
def _siftdown(heap, startpos, pos):
    newitem = heap[pos]
    # Follow the path to the root, moving parents down until finding a place
    # newitem fits.
    while pos > startpos:
        parentpos = (pos - 1) >> 1
        parent = heap[parentpos]
        if newitem < parent:
            heap[pos] = parent
            pos = parentpos
            continue
        break
    heap[pos] = newitem

# The child indices of heap index pos are already heaps, and we want to make
# a heap at index pos too.  We do this by bubbling the smaller child of
# pos up (and so on with that child's children, etc) until hitting a leaf,
# then using _siftdown to move the oddball originally at index pos into place.
#
# We *could* break out of the loop as soon as we find a pos where newitem <=
# both its children, but turns out that's not a good idea, and despite that
# many books write the algorithm that way.  During a heap pop, the last array
# element is sifted in, and that tends to be large, so that comparing it
# against values starting from the root usually doesn't pay (= usually doesn't
# get us out of the loop early).  See Knuth, Volume 3, where this is
# explained and quantified in an exercise.
#
# Cutting the # of comparisons is important, since these routines have no
# way to extract "the priority" from an array element, so that intelligence
# is likely to be hiding in custom comparison methods, or in array elements
# storing (priority, record) tuples.  Comparisons are thus potentially
# expensive.
#
# On random arrays of length 1000, making this change cut the number of
# comparisons made by heapify() a little, and those made by exhaustive
# heappop() a lot, in accord with theory.  Here are typical results from 3
# runs (3 just to demonstrate how small the variance is):
#
# Compares needed by heapify     Compares needed by 1000 heappops
# --------------------------     --------------------------------
# 1837 cut to 1663               14996 cut to 8680
# 1855 cut to 1659               14966 cut to 8678
# 1847 cut to 1660               15024 cut to 8703
#
# Building the heap by using heappush() 1000 times instead required
# 2198, 2148, and 2219 compares:  heapify() is more efficient, when
# you can use it.
#
# The total compares needed by list.sort() on the same lists were 8627,
# 8627, and 8632 (this should be compared to the sum of heapify() and
# heappop() compares):  list.sort() is (unsurprisingly!) more efficient
# for sorting.

def _siftup(heap, pos):
    endpos = len(heap)
    startpos = pos
    newitem = heap[pos]
    # Bubble up the smaller child until hitting a leaf.
    childpos = 2*pos + 1    # leftmost child position
    while childpos < endpos:
        # Set childpos to index of smaller child.
        rightpos = childpos + 1
        if rightpos < endpos and not heap[childpos] < heap[rightpos]:
            childpos = rightpos
        # Move the smaller child up.
        heap[pos] = heap[childpos]
        pos = childpos
        childpos = 2*pos + 1
    # The leaf at pos is empty now.  Put newitem there, and bubble it up
    # to its final resting place (by sifting its parents down).
    heap[pos] = newitem
    _siftdown(heap, startpos, pos)

def _siftdown_max(heap, startpos, pos):
    'Maxheap variant of _siftdown'
    newitem = heap[pos]
    # Follow the path to the root, moving parents down until finding a place
    # newitem fits.
    while pos > startpos:
        parentpos = (pos - 1) >> 1
        parent = heap[parentpos]
        if parent < newitem:
            heap[pos] = parent
            pos = parentpos
            continue
        break
    heap[pos] = newitem

def _siftup_max(heap, pos):
    'Maxheap variant of _siftup'
    endpos = len(heap)
    startpos = pos
    newitem = heap[pos]
    # Bubble up the larger child until hitting a leaf.
    childpos = 2*pos + 1    # leftmost child position
    while childpos < endpos:
        # Set childpos to index of larger child.
        rightpos = childpos + 1
        if rightpos < endpos and not heap[rightpos] < heap[childpos]:
            childpos = rightpos
        # Move the larger child up.
        heap[pos] = heap[childpos]
        pos = childpos
        childpos = 2*pos + 1
    # The leaf at pos is empty now.  Put newitem there, and bubble it up
    # to its final resting place (by sifting its parents down).
    heap[pos] = newitem
    _siftdown_max(heap, startpos, pos)

def merge(*iterables, key=None, reverse=False):
    '''Merge multiple sorted inputs into a single sorted output.

    Similar to sorted(itertools.chain(*iterables)) but returns a generator,
    does not pull the data into memory all at once, and assumes that each of
    the input streams is already sorted (smallest to largest).

    >>> list(merge([1,3,5,7], [0,2,4,8], [5,10,15,20], [], [25]))
    [0, 1, 2, 3, 4, 5, 5, 7, 8, 10, 15, 20, 25]

    If *key* is not None, applies a key function to each element to determine
    its sort order.

    >>> list(merge(['dog', 'horse'], ['cat', 'fish', 'kangaroo'], key=len))
    ['dog', 'cat', 'fish', 'horse', 'kangaroo']

    '''

    h = []
    h_append = h.append

    if reverse:
        _heapify = _heapify_max
        _heappop = _heappop_max
        _heapreplace = _heapreplace_max
        direction = -1
    else:
        _heapify = heapify
        _heappop = heappop
        _heapreplace = heapreplace
        direction = 1

    if key is None:
        for order, it in enumerate(map(iter, iterables)):
            try:
                next = it.__next__
                h_append([next(), order * direction, next])
            except StopIteration:
                pass
        _heapify(h)
        while len(h) > 1:
            try:
                while True:
                    value, order, next = s = h[0]
                    yield value
                    s[0] = next()           # raises StopIteration when exhausted
                    _heapreplace(h, s)      # restore heap condition
            except StopIteration:
                _heappop(h)                 # remove empty iterator
        if h:
            # fast case when only a single iterator remains
            value, order, next = h[0]
            yield value
            yield from next.__self__
        return

    for order, it in enumerate(map(iter, iterables)):
        try:
            next = it.__next__
            value = next()
            h_append([key(value), order * direction, value, next])
        except StopIteration:
            pass
    _heapify(h)
    while len(h) > 1:
        try:
            while True:
                key_value, order, value, next = s = h[0]
                yield value
                value = next()
                s[0] = key(value)
                s[2] = value
                _heapreplace(h, s)
        except StopIteration:
            _heappop(h)
    if h:
        key_value, order, value, next = h[0]
        yield value
        yield from next.__self__


# Algorithm notes for nlargest() and nsmallest()
# ==============================================
#
# Make a single pass over the data while keeping the k most extreme values
# in a heap.  Memory consumption is limited to keeping k values in a list.
#
# Measured performance for random inputs:
#
#                                   number of comparisons
#    n inputs     k-extreme values  (average of 5 trials)   % more than min()
# -------------   ----------------  ---------------------   -----------------
#      1,000           100                  3,317               231.7%
#     10,000           100                 14,046                40.5%
#    100,000           100                105,749                 5.7%
#  1,000,000           100              1,007,751                 0.8%
# 10,000,000           100             10,009,401                 0.1%
#
# Theoretical number of comparisons for k smallest of n random inputs:
#
# Step   Comparisons                  Action
# ----   --------------------------   ---------------------------
#  1     1.66 * k                     heapify the first k-inputs
#  2     n - k                        compare remaining elements to top of heap
#  3     k * (1 + lg2(k)) * ln(n/k)   replace the topmost value on the heap
#  4     k * lg2(k) - (k/2)           final sort of the k most extreme values
#
# Combining and simplifying for a rough estimate gives:
#
#        comparisons = n + k * (log(k, 2) * log(n/k) + log(k, 2) + log(n/k))
#
# Computing the number of comparisons for step 3:
# -----------------------------------------------
# * For the i-th new value from the iterable, the probability of being in the
#   k most extreme values is k/i.  For example, the probability of the 101st
#   value seen being in the 100 most extreme values is 100/101.
# * If the value is a new extreme value, the cost of inserting it into the
#   heap is 1 + log(k, 2).
# * The probability times the cost gives:
#            (k/i) * (1 + log(k, 2))
# * Summing across the remaining n-k elements gives:
#            sum((k/i) * (1 + log(k, 2)) for i in range(k+1, n+1))
# * This reduces to:
#            (H(n) - H(k)) * k * (1 + log(k, 2))
# * Where H(n) is the n-th harmonic number estimated by:
#            gamma = 0.5772156649
#            H(n) = log(n, e) + gamma + 1 / (2 * n)
#   http://en.wikipedia.org/wiki/Harmonic_series_(mathematics)#Rate_of_divergence
# * Substituting the H(n) formula:
#            comparisons = k * (1 + log(k, 2)) * (log(n/k, e) + (1/n - 1/k) / 2)
#
# Worst-case for step 3:
# ----------------------
# In the worst case, the input data is reversed sorted so that every new element
# must be inserted in the heap:
#
#             comparisons = 1.66 * k + log(k, 2) * (n - k)
#
# Alternative Algorithms
# ----------------------
# Other algorithms were not used because they:
# 1) Took much more auxiliary memory,
# 2) Made multiple passes over the data.
# 3) Made more comparisons in common cases (small k, large n, semi-random input).
# See the more detailed comparison of approach at:
# http://code.activestate.com/recipes/577573-compare-algorithms-for-heapqsmallest

def nsmallest(n, iterable, key=None):
    """Find the n smallest elements in a dataset.

    Equivalent to:  sorted(iterable, key=key)[:n]
    """

    # Short-cut for n==1 is to use min()
    if n == 1:
        it = iter(iterable)
        sentinel = object()
        result = min(it, default=sentinel, key=key)
        return [] if result is sentinel else [result]

    # When n>=size, it's faster to use sorted()
    try:
        size = len(iterable)
    except (TypeError, AttributeError):
        pass
    else:
        if n >= size:
            return sorted(iterable, key=key)[:n]

    # When key is none, use simpler decoration
    if key is None:
        it = iter(iterable)
        # put the range(n) first so that zip() doesn't
        # consume one too many elements from the iterator
        result = [(elem, i) for i, elem in zip(range(n), it)]
        if not result:
            return result
        _heapify_max(result)
        top = result[0][0]
        order = n
        _heapreplace = _heapreplace_max
        for elem in it:
            if elem < top:
                _heapreplace(result, (elem, order))
                top, _order = result[0]
                order += 1
        result.sort()
        return [elem for (elem, order) in result]

    # General case, slowest method
    it = iter(iterable)
    result = [(key(elem), i, elem) for i, elem in zip(range(n), it)]
    if not result:
        return result
    _heapify_max(result)
    top = result[0][0]
    order = n
    _heapreplace = _heapreplace_max
    for elem in it:
        k = key(elem)
        if k < top:
            _heapreplace(result, (k, order, elem))
            top, _order, _elem = result[0]
            order += 1
    result.sort()
    return [elem for (k, order, elem) in result]

def nlargest(n, iterable, key=None):
    """Find the n largest elements in a dataset.

    Equivalent to:  sorted(iterable, key=key, reverse=True)[:n]
    """

    # Short-cut for n==1 is to use max()
    if n == 1:
        it = iter(iterable)
        sentinel = object()
        result = max(it, default=sentinel, key=key)
        return [] if result is sentinel else [result]

    # When n>=size, it's faster to use sorted()
    try:
        size = len(iterable)
    except (TypeError, AttributeError):
        pass
    else:
        if n >= size:
            return sorted(iterable, key=key, reverse=True)[:n]

    # When key is none, use simpler decoration
    if key is None:
        it = iter(iterable)
        result = [(elem, i) for i, elem in zip(range(0, -n, -1), it)]
        if not result:
            return result
        heapify(result)
        top = result[0][0]
        order = -n
        _heapreplace = heapreplace
        for elem in it:
            if top < elem:
                _heapreplace(result, (elem, order))
                top, _order = result[0]
                order -= 1
        result.sort(reverse=True)
        return [elem for (elem, order) in result]

    # General case, slowest method
    it = iter(iterable)
    result = [(key(elem), i, elem) for i, elem in zip(range(0, -n, -1), it)]
    if not result:
        return result
    heapify(result)
    top = result[0][0]
    order = -n
    _heapreplace = heapreplace
    for elem in it:
        k = key(elem)
        if top < k:
            _heapreplace(result, (k, order, elem))
            top, _order, _elem = result[0]
            order -= 1
    result.sort(reverse=True)
    return [elem for (k, order, elem) in result]

# If available, use C implementation
try:
    from _heapq import *
except ImportError:
    pass
try:
    from _heapq import _heapreplace_max
except ImportError:
    pass
try:
    from _heapq import _heapify_max
except ImportError:
    pass
try:
    from _heapq import _heappop_max
except ImportError:
    pass


if __name__ == "__main__":

    import doctest # pragma: no cover
    print(doctest.testmod()) # pragma: no cover

参考

heapq可视化https://visualgo.net/zh/heap

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/659444.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【移除链表元素】python

目录 题目&#xff1a; 方法&#xff1a; 知识&#xff1a; 代码&#xff1a; 题目&#xff1a; 方法&#xff1a; 在头节点前增加一个虚拟头节点 知识&#xff1a; 链表中的每一个节点只包含当前值val和指向下一个next 代码&#xff1a; class Solution:def removeEle…

AI新时代——【深度学习】驱动的【AIGC大模型】与【机器学习】的创新融合

目录 1.机器学习与人工智能的基础 1.机器学习的基本原理 2.人工智能的广泛应用 2.深度学习的崛起 1.深度学习的概念和原理 2.卷积神经网络&#xff08;CNN&#xff09; 3.循环神经网络&#xff08;RNN&#xff09; 3.AIGC大模型的创新 1.AIGC的概念和应用 2.代表性AI…

网络侦察技术

网络侦察技术 收集的信息网络侦察步骤搜索引擎检索命令bing搜索引擎Baidu搜索引擎Shodan钟馗之眼(zoomeye) whois数据库&#xff1a;信息宝库查询注册资料 域名系统网络拓扑社交网络跨域拓展攻击 其它侦察手段社会工程学社会工程学常见形式Web网站查询 其它非技术侦察手段总结网…

连接远程的kafka【linux】

# 连接远程的kafka【linux】 前言版权推荐连接远程的kafka【linux】一、开放防火墙端口二、本地测试是否能访问端口三、远程kafka配置四、开启远程kakfa五、本地测试能否连接远程六、SpringBoot测试连接 遇到的问题最后 前言 2024-5-14 18:45:48 以下内容源自《【linux】》 仅…

如何使用宝塔面板搭建Tipask问答社区网站并发布公网远程访问

文章目录 前言1.Tipask网站搭建1.1 Tipask网站下载和安装1.2 Tipask网页测试1.3 cpolar的安装和注册 2. 本地网页发布2.1 Cpolar临时数据隧道2.2 Cpolar稳定隧道&#xff08;云端设置&#xff09;2.3 Cpolar稳定隧道&#xff08;本地设置&#xff09; 3. 公网访问测试4.结语 前…

S32K --- FLS MCAL配置

一、前言 二、MCAL配置 添加一个Mem_43_infls的模块, infls是访问内部flash, exfls是访问外部flash 2.1 General 这边暂时保持的默认,还没详细的去研究,等有空研究了,我再来更新 2.2 MemInstance 双金“index”的下标“0”可以进里面详细配置,这个是基本操作了。 2.2.1 G…

面试问到Spring中的@Autowired注解,可以这样答

前言 在Spring框架中&#xff0c;依赖注入是一个核心概念&#xff0c;它允许将一个对象的依赖关系外部化并由Spring容器来管理。Autowired注解是实现这一点的关键工具之一。当然&#xff0c;这块知识也是面试官们老生常谈的问题。 下面就跟着博主的步伐&#xff0c;一起来探讨…

Three.js是基于原生WebGL封装的三维引擎

Three.js: 基于原生WebGL封装的三维引擎 引言 随着互联网技术的发展&#xff0c;Web前端技术不断进步&#xff0c;用户对于网页交互体验的要求也越来越高。艾斯视觉前端开发&#xff1a;三维技术作为提升用户体验的重要手段之一&#xff0c;正在逐渐成为前端开发中的热门技术…

PyTorch张量索引用法速查

作为数据科学家或软件工程师&#xff0c;你可能经常处理大型数据集和复杂的数学运算&#xff0c;这些运算需要高效且可扩展的计算。PyTorch 是一个流行的开源机器学习库&#xff0c;它通过 GPU 加速提供快速灵活的张量计算。在本文中&#xff0c;我们将深入研究 PyTorch 张量索…

纷享销客当选江西省数字经济学会首席信息官专业委员会副主任委员

5月11日&#xff0c;江西省数字经济学会首席信息官(CIO)专业委员会成立大会暨“新质生产力”企业数字化转型论坛在南昌香格里拉大酒店隆重举行。 江西省工业和信息化厅作为指导单位&#xff0c;由江西省数字经济学会、南昌市中小企业服务局主办&#xff0c;金蝶软件&#xff0…

单值二叉树(oJ题)

一、题目连接&#xff1a;. - 力扣&#xff08;LeetCode&#xff09; 二、题目思路 遍历左右子树&#xff0c;如果左子树存在不为空并且根结点的值不等于左子树的值&#xff0c;返回false, 如果右子树存在不为空并且根结点的值不等于右子树的值&#xff0c;返回false, 每一个…

了解VS安全编译选项GS

缓冲区溢出攻击的基本原理就是溢出时覆盖了函数返回地址&#xff0c;之后就会去执行攻击者自己的函数&#xff1b; 针对缓冲区溢出时覆盖函数返回地址这一特征&#xff0c;微软在编译程序时使用了安全编译选项-GS&#xff1b; 目前版本的Visual Studio中默认启用了这个编译选项…

MT2075 礼物

思路&#xff1a; x,y为质数&#xff0c;若x2,y3&#xff0c;则xy的最小公倍数6既不能给A也不能给B。 所以假设共有V个数&#xff0c;在1-V中&#xff0c;可以选的个数为&#xff1a;V-⌊V/(x*y)⌋ 个。&#xff08;⌊V/(x*y)⌋为V个数中有多少个xy的公倍数&#xff09; 所以…

Kibana使用教程

Kibana使您能够轻松地向Elasticsearch发送请求&#xff0c;并以交互方式分析、可视化和管理数据。 1.安装 1.1 docker安装Kibana 如果你还没安装Elasticsearch&#xff0c;先执行docker安装Elasticsearch&#xff0c;下面是单机部署。 创建一个ES网络&#xff1a; docker n…

信息系统项目管理师0137:输出(8项目整合管理—8.9结束项目或阶段—8.9.3输出)

点击查看专栏目录 文章目录 8.9.3 输出8.9.3 输出 项目文件(更新)可在结束项目或阶段更新所有项目文件,并标记为最终版本。特别值得注意的是,经验教训登记册的最终版本要包含阶段或项目收尾的最终信息。最终版本的经验教训登记册可包含:效益管理、项目评估的准确性、项目和…

Spring和Servlet的整合

Servlet对象是谁创建的&#xff1f; 由服务器端创建的 程序启动调用加载spring配置文件代码 Web应用程序启动也需要加载Spring配置文件 Web开发中有三大组件&#xff1a; 1、servlet 2、filter 3、listener&#xff08;request&#xff0c;session&#xff0c;application&…

(4)医疗图像处理:MRI磁共振成像-成像技术--(杨正汉)

目录 一、特殊成像技术 1.水成像技术 2.化学位移成像技术 二、成像辅助技术 1.脂肪抑制技术 2.磁化转移技术 3.流动补偿技术 4.空间饱和空间标记技术 5.生理门控及导航回波技术 所有的这些技术最终就是为了使得K空间通过傅里叶变化之后得到的图片变的更为清晰。 一、…

React Hooks是如何保存的

React 函数式组件是没有状态的&#xff0c;需要 Hooks 进行状态的存储&#xff0c;那么状态是怎么存储的呢&#xff1f;Hooks是保存在 Fiber 树上的&#xff0c;多个状态是通过链表保存&#xff0c;本文将通过源代码分析 Hooks 的存储位置。 创建组件 首先我们在组件中添加两…

Nocobase快速上手 - 常见block的使用

在上一篇文章 Nocobase快速上手 -第一个collection 中&#xff0c;我们新建了一个collection ,并且通过在页面中配置block实现了数据的展示&#xff0c;本文继续探索block的使用。 Block类型 Block(区块)的整体架构如下图: 我们可以看到&#xff0c;block分为三个大类&#…

文心智能体平台 | 想象即现实

目录 文心智能体平台介绍平台简介通过平台能做什么平台的优势智能体介绍智能体类型AI 插件介绍 动手创建一个智能体访问平台并进行账号注册根据适合的方式选择智能体类型快速创建智能体智能体个性化模块配置 总结注意事项我的智能体 文心智能体平台介绍 平台简介 文心智能体平…