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

文章目录

    • 堆的定义
    • 堆的主要操作
    • 堆的构建
    • 堆排序
    • 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)`
  • 参考

堆的定义

堆队列算法,即优先队列算法。

  • 堆是一种完全二叉树,这个性质保证了堆使用数组表示就不会出现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]

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/659142.html

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

相关文章

赛氪网与武汉外语外事职业学院签署校企合作,共创职业教育新篇章

5月23日下午14:00&#xff0c;武汉外语外事职业学院在藏龙岛校区食堂三楼报告厅隆重举行了2024年职业教育活动周优秀校外实习基地表彰仪式。本次活动旨在表彰在职业教育领域作出突出贡献的校外实习基地&#xff0c;同时加强校企合作&#xff0c;共同推动职业教育的发展。作为重…

gitlab之docker-compose汉化离线安装

目录 概述离线资源docker-compose结束 概述 gitlab可以去 hub 上拉取最新版本&#xff0c;在此我选择汉化 gitlab &#xff0c;版本 11.x 离线资源 想自制离线安装镜像&#xff0c;请稳步参考 docker镜像的导入导出 &#xff0c;无兴趣的直接使用在此提供离线资源 百度网盘(链…

经典文献阅读之--RepViT-SAM(利用语义分割提高NDT地图压缩和描述能力的框架)

0. 简介 Segment Anything Model (SAM) 最近在各种计算机视觉任务上展现了令人瞩目的零样本迁移性能 。然而&#xff0c;其高昂的计算成本对于实际应用仍然具有挑战性。MobileSAM 提出通过使用蒸馏替换 SAM 中的重图像编码器&#xff0c;使用 TinyViT&#xff0c;从而显著降低了…

认识K8s集群的声明式资源管理方法

前言 Kubernetes 集群的声明式资源管理方法是当今云原生领域中的核心概念之一&#xff0c;使得容器化应用程序的部署和管理变得更加高效和可靠。本文将认识了解 Kubernetes 中声明式管理的相关理念、实际应用以及优势。 目录 一、管理方法介绍 1. 概述 2. 语法格式 2.1 管…

AI图书推荐:用ChatGPT和Python搭建AI应用来变现

《用ChatGPT和Python搭建AI应用来变现》&#xff08;Building AI Applications with ChatGPT API&#xff09;将ChatGPT API与Python结合使用&#xff0c;可以开启构建非凡AI应用的大门。通过利用这些API&#xff0c;你可以专注于应用逻辑和用户体验&#xff0c;而ChatGPT强大的…

适合学生党的蓝牙耳机有哪些?盘点四大性价比蓝牙耳机品牌

对于追求高品质音乐体验而又预算有限的学生党来说&#xff0c;一款性价比高的蓝牙耳机无疑是最佳选择&#xff0c;在众多品牌和型号中&#xff0c;如何挑选到既适合自己需求又价格亲民的蓝牙耳机&#xff0c;确实是一个值得思考的问题&#xff0c;作为一个蓝牙耳机大户&#xf…

台灯护眼是真的吗?警惕这六大问题!

在当今社会&#xff0c;随着电子设备的普及和长时间的用眼&#xff0c;大多数人面临着严重的视觉疲劳问题。长时间盯着屏幕或学习&#xff0c;眼睛需要不断调节焦距&#xff0c;导致眼睛肌肉疲劳&#xff0c;进而引发视力下降。这种现象在年轻一代甚至青少年中尤为普遍&#xf…

半导体测试基础 - 功能测试

功能测试(Functional Test)主要是验证逻辑功能,是运用测试矢量和测试命令来进行的一种测试,相比于纯 DC 测试而言,组合步骤相对复杂且耦合度高。 在功能测试阶段时,测试系统会以周期为单位,将测试矢量输入 DUT,提供预测的结果并与输出的数据相比较,如果实际的结果与测…

图论(五)-最短路

一、Bellman-Ford算法 算法思想&#xff1a;通过 n 次循环&#xff0c;每次循环都遍历每条边&#xff08;共 m 条边&#xff09;&#xff0c;进而更新节点的距离&#xff0c;每次循环至少可以确定一个点的最短路&#xff0c;循环 n 次&#xff0c;求出 n 个点的最短路 时间复杂…

opencascade V3d_RectangularGrid 源码学习

类V3d_RectangularGrid V3d_RectangularGrid() V3d_RectangularGrid::V3d_RectangularGrid(const V3d_ViewerPointer &aViewer, const Quantity_Color &aColor, const Quantity_Color &aTenthColor) // 构造函数 ◆ ~V3d_RectangularGrid() virtual V3d_Rectang…

YOLOv10最详细全面讲解1- 目标检测-准备自己的数据集(YOLOv5,YOLOv8均适用)

YOLOv10没想到出来的如此之快&#xff0c;作为一名YOLO的爱好者&#xff0c;以YOLOv5和YOLOv8的经验&#xff0c;打算出一套从数据集装备->环境配置->训练->验证->目标追踪全系列教程。请大家多多点赞和收藏&#xff01;&#xff01;&#xff01;YOLOv5和YOLOv8亲测…

Simulink从0搭建模型06-P7模型中结构体的使用

Simulink从0搭建模型06-P7模型中结构体的使用 本节课学习内容1. 结构体的创建 Bus Creator&#xff08;多输入单输出&#xff09;2. 结构体的引用 Bus Selector&#xff08;单输入多输出&#xff09;3. 结构体的赋值 Bus Assignment4. 结构体对象的创建 Bus object5. 结构体数组…

10分钟掌握FL Studio21中文版,音乐制作更高效!

FL Studio 21中文版是Image Line公司推出的一款深受欢迎的数字音频工作站软件&#xff0c;在音乐制作领域享有盛誉。这个版本特别针对中文用户进行了本地化处理&#xff0c;旨在提供更加便捷的用户体验和操作界面。本次评测将深入探讨FL Studio 21中文版的功能特点、使用体验及…

Java RMI

RMI - 安全篇 RMI分为三个主体部分&#xff1a; *Client-客户端*&#xff1a;客户端调用服务端的方法 *Server-服务端*&#xff1a;远程调用方法对象的提供者&#xff0c;也是代码真正执行的地方&#xff0c;执行结束会返回给客户端一个方法执行的结果。 *Registry-注册中心…

防火墙技术基础篇:配置主备备份的双机热备

防火墙技术基础篇&#xff1a;配置主备备份的双机热备 防火墙双机热备&#xff08;High Availability, HA&#xff09;技术是网络安全中的一个关键组成部分&#xff0c;通过它&#xff0c;我们可以确保网络环境的高可靠性和高可用性。下面我们一起来了解防火墙双机热备的基本原…

第二十三届中国科学家论坛盛大开幕,星医联董事长杨星荣获“十四五”科技创新先锋人物

2024年5月25-26日&#xff0c;第二十三届中国科学家论坛在北京召开&#xff0c;北京星医联科技有限公司&#xff08;以下简称“星医联”&#xff09;董事长杨星女士受邀出席并荣获“十四五科技创新先锋人物”称号。同时星医联专利“一种靶向协同降脂的纳米双药制备及应用”荣获…

[CVPR-24] HUGS: Human Gaussian Splats

本文提出一种新的数字人表征Human Gaussian Splats (HUGS)&#xff0c;可以实现新姿态和新视角生成&#xff1b;本文提出一种新的前向形变模块&#xff08;forward deformation module&#xff09;&#xff0c;在标定空间基于Gaussians表征数字人&#xff0c;并基于LBS学习如何…

从 ASCII 到 UTF-8 - Unicode 码的诞生与实现

前言&#xff1a;最近我在整理过往笔记时&#xff0c;发现涉及到了 UTF-8、Unicode 的相关内容&#xff0c;相信大家中的很多人和之前的我一样&#xff0c;在过去的很长一段时间里&#xff0c;并没有搞清楚什么是 Unicode、什么是 UTF-8&#xff0c;于是就有了这篇文章&#xf…

SSL证书:守护个人信息安全的坚固盾牌

在数字化浪潮汹涌的今天&#xff0c;我们的个人信息如同一座座宝藏&#xff0c;吸引着不法分子的贪婪目光。数据泄露事件频发&#xff0c;让信息安全问题日益凸显。而在这个信息爆炸的时代&#xff0c;如何保护我们的个人信息安全&#xff0c;成为了一个亟待解决的问题。幸运的…

【第三节】类的构造和析构函数

目录 一、数据成员的初始化 二、构造函数 2.1 什么是构造函数 2.2 构造函数的注意事项 三、析构函数 四、带参数的构造函数 五、缺省构造函数 六、构造函数初始化列表 七、拷贝构造函数和调用规则 八、深拷贝和浅拷贝 九、总结 一、数据成员的初始化 定义普通变量&am…