“Python/docs/3.9/library/heapq”的版本间差异

来自菜鸟教程
Python/docs/3.9/library/heapq
跳转至:导航、​搜索
(autoload)
 
(Page commit)
 
第1行: 第1行:
 +
{{DISPLAYTITLE:heapq — 堆队列算法 — Python 文档}}
 
<div id="module-heapq" class="section">
 
<div id="module-heapq" class="section">
  
 
<span id="heapq-heap-queue-algorithm"></span>
 
<span id="heapq-heap-queue-algorithm"></span>
= [[#module-heapq|<code>heapq</code>]] --- Heap queue algorithm =
+
= heapq — 堆队列算法 =
  
'''Source code:''' [https://github.com/python/cpython/tree/3.9/Lib/heapq.py Lib/heapq.py]
+
'''源代码:''' [[#id1|<span id="id2" class="problematic">:source:`Lib/heapq.py`</span>]]
  
This module provides an implementation of the heap queue algorithm, also known
 
as the priority queue algorithm.
 
  
Heaps are binary trees for which every parent node has a value less than or
+
-----
equal to any of its children. This implementation uses arrays for which
 
<code>heap[k] &lt;= heap[2*k+1]</code> and <code>heap[k] &lt;= heap[2*k+2]</code> for all ''k'', counting
 
elements from zero. For the sake of comparison, non-existing elements are
 
considered to be infinite. The interesting property of a heap is that its
 
smallest element is always the root, <code>heap[0]</code>.
 
  
The API below differs from textbook heap algorithms in two aspects: (a) We use
+
该模块提供了堆队列算法的实现,也称为优先队列算法。
zero-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 zero-based indexing. (b) Our pop method returns the smallest
 
item, not the largest (called a &quot;min heap&quot; in textbooks; a &quot;max heap&quot; is more
 
common in texts because of its suitability for in-place sorting).
 
  
These two make it possible to view the heap as a regular Python list without
+
堆是二叉树,其每个父节点的值都小于或等于其任何子节点。 此实现使用数组,其中 <code>heap[k] &lt;= heap[2*k+1]</code> 和 <code>heap[k] &lt;= heap[2*k+2]</code> 用于所有 ''k'',从零开始计算元素。 为了比较,不存在的元素被认为是无限的。 堆的有趣特性是它的最小元素总是根,<code>heap[0]</code>
surprises: <code>heap[0]</code> is the smallest item, and <code>heap.sort()</code> maintains the
 
heap invariant!
 
  
To create a heap, use a list initialized to <code>[]</code>, or you can transform a
+
下面的 API 在两个方面不同于教科书的堆算法: (a) 我们使用从零开始的索引。 这使得节点的索引与其子节点的索引之间的关系稍微不那么明显,但更合适,因为 Python 使用从零开始的索引。 (b) 我们的 pop 方法返回最小的项目,而不是最大的项目(在教科书中称为“最小堆”;“最大堆”在文本中更常见,因为它适用于就地排序)。
populated list into a heap via function [[#heapq.heapify|<code>heapify()</code>]].
 
  
The following functions are provided:
+
这两个使得可以毫无意外地将堆视为常规 Python 列表:<code>heap[0]</code> 是最小的项,而 <code>heap.sort()</code> 保持堆不变!
  
; <code>heapq.</code><code>heappush</code><span class="sig-paren">(</span>''<span class="n">heap</span>'', ''<span class="n">item</span>''<span class="sig-paren">)</span>
+
要创建堆,请使用初始化为 <code>[]</code> 的列表,或者您可以通过函数 [[#heapq.heapify|heapify()]] 将填充列表转换为堆。
: Push the value ''item'' onto the ''heap'', maintaining the heap invariant.
 
  
; <code>heapq.</code><code>heappop</code><span class="sig-paren">(</span>''<span class="n">heap</span>''<span class="sig-paren">)</span>
+
提供以下功能:
: Pop and return the smallest item from the ''heap'', maintaining the heap invariant. If the heap is empty, [[../exceptions#IndexError|<code>IndexError</code>]] is raised. To access the smallest item without popping it, use <code>heap[0]</code>.
 
  
; <code>heapq.</code><code>heappushpop</code><span class="sig-paren">(</span>''<span class="n">heap</span>'', ''<span class="n">item</span>''<span class="sig-paren">)</span>
+
; <span class="sig-prename descclassname"><span class="pre">heapq.</span></span><span class="sig-name descname"><span class="pre">heappush</span></span><span class="sig-paren">(</span>''<span class="n"><span class="pre">heap</span></span>'', ''<span class="n"><span class="pre">item</span></span>''<span class="sig-paren">)</span>
: Push ''item'' on the heap, then pop and return the smallest item from the ''heap''. The combined action runs more efficiently than [[#heapq.heappush|<code>heappush()</code>]] followed by a separate call to [[#heapq.heappop|<code>heappop()</code>]].
+
: 将值 ''item'' 推送到 ''heap'',保持堆不变。
  
; <code>heapq.</code><code>heapify</code><span class="sig-paren">(</span>''<span class="n">x</span>''<span class="sig-paren">)</span>
+
; <span class="sig-prename descclassname"><span class="pre">heapq.</span></span><span class="sig-name descname"><span class="pre">heappop</span></span><span class="sig-paren">(</span>''<span class="n"><span class="pre">heap</span></span>''<span class="sig-paren">)</span>
: Transform list ''x'' into a heap, in-place, in linear time.
+
: 从 ''堆'' 弹出并返回最小的项目,保持堆不变。 如果堆为空,则会引发 [[../exceptions#IndexError|IndexError]]。 要访问最小的项目而不弹出它,请使用 <code>heap[0]</code>。
 +
 
 +
; <span class="sig-prename descclassname"><span class="pre">heapq.</span></span><span class="sig-name descname"><span class="pre">heappushpop</span></span><span class="sig-paren">(</span>''<span class="n"><span class="pre">heap</span></span>'', ''<span class="n"><span class="pre">item</span></span>''<span class="sig-paren">)</span>
 +
: 将''项''推入堆,然后从''堆''中弹出并返回最小的项。 组合操作比 [[#heapq.heappush|heappush()]] 更有效地运行,然后单独调用 [[#heapq.heappop|heappop()]]。
 +
 
 +
; <span class="sig-prename descclassname"><span class="pre">heapq.</span></span><span class="sig-name descname"><span class="pre">heapify</span></span><span class="sig-paren">(</span>''<span class="n"><span class="pre">x</span></span>''<span class="sig-paren">)</span>
 +
: 将列表 ''x'' 在线性时间内就地转换为堆。
  
 
<dl>
 
<dl>
<dt><code>heapq.</code><code>heapreplace</code><span class="sig-paren">(</span>''<span class="n">heap</span>'', ''<span class="n">item</span>''<span class="sig-paren">)</span></dt>
+
<dt><span class="sig-prename descclassname"><span class="pre">heapq.</span></span><span class="sig-name descname"><span class="pre">heapreplace</span></span><span class="sig-paren">(</span>''<span class="n"><span class="pre">heap</span></span>'', ''<span class="n"><span class="pre">item</span></span>''<span class="sig-paren">)</span></dt>
<dd><p>Pop and return the smallest item from the ''heap'', and also push the new ''item''.
+
<dd><p>''''中弹出并返回最小的项,同时推送新的''''。 堆大小不会改变。 如果堆为空,则引发 [[../exceptions#IndexError|IndexError]]</p>
The heap size doesn't change. If the heap is empty, [[../exceptions#IndexError|<code>IndexError</code>]] is raised.</p>
+
<p>这一步操作比 [[#heapq.heappop|heappop()]] 后接 [[#heapq.heappush|heappush()]] 更有效,并且在使用固定大小的堆时更合适。 pop/push 组合总是从堆中返回一个元素并将其替换为 ''item''</p>
<p>This one step operation is more efficient than a [[#heapq.heappop|<code>heappop()</code>]] followed by
+
<p>返回的值可能大于添加的 '''' 。 如果不需要,请考虑使用 [[#heapq.heappushpop|heappushpop()]] 代替。 它的 push/pop 组合返回两个值中较小的一个,将较大的值留在堆上。</p></dd></dl>
[[#heapq.heappush|<code>heappush()</code>]] and can be more appropriate when using a fixed-size heap.
 
The pop/push combination always returns an element from the heap and replaces
 
it with ''item''.</p>
 
<p>The value returned may be larger than the ''item'' added. If that isn't
 
desired, consider using [[#heapq.heappushpop|<code>heappushpop()</code>]] instead. Its push/pop
 
combination returns the smaller of the two values, leaving the larger value
 
on the heap.</p></dd></dl>
 
  
The module also offers three general purpose functions based on heaps.
+
该模块还提供了三个基于堆的通用函数。
  
 
<dl>
 
<dl>
<dt><code>heapq.</code><code>merge</code><span class="sig-paren">(</span>''<span class="o">*</span><span class="n">iterables</span>'', ''<span class="n">key</span><span class="o">=</span><span class="default_value">None</span>'', ''<span class="n">reverse</span><span class="o">=</span><span class="default_value">False</span>''<span class="sig-paren">)</span></dt>
+
<dt><span class="sig-prename descclassname"><span class="pre">heapq.</span></span><span class="sig-name descname"><span class="pre">merge</span></span><span class="sig-paren">(</span>''<span class="o"><span class="pre">*</span></span><span class="n"><span class="pre">iterables</span></span>'', ''<span class="n"><span class="pre">key</span></span><span class="o"><span class="pre">=</span></span><span class="default_value"><span class="pre">None</span></span>'', ''<span class="n"><span class="pre">reverse</span></span><span class="o"><span class="pre">=</span></span><span class="default_value"><span class="pre">False</span></span>''<span class="sig-paren">)</span></dt>
<dd><p>Merge multiple sorted inputs into a single sorted output (for example, merge
+
<dd><p>将多个排序的输入合并为一个排序的输出(例如,合并来自多个日志文件的带时间戳的条目)。 在排序后的值上返回一个 [[../../glossary#term-iterator|迭代器]] </p>
timestamped entries from multiple log files). Returns an [[../../glossary#term-iterator|<span class="xref std std-term">iterator</span>]]
+
<p><code>sorted(itertools.chain(*iterables))</code> 类似,但返回一个可迭代对象,不会一次将数据全部拉入内存,并假设每个输入流都已经排序(从小到大)。</p>
over the sorted values.</p>
+
<p>有两个可选参数,必须指定为关键字参数。</p>
<p>Similar to <code>sorted(itertools.chain(*iterables))</code> but returns an iterable, does
+
<p>''key'' 指定一个参数的 [[../../glossary#term-key-function|key 函数]] ,用于从每个输入元素中提取比较键。 默认值为 <code>None</code>(直接比较元素)。</p>
not pull the data into memory all at once, and assumes that each of the input
+
<p>''reverse'' 是一个布尔值。 如果设置为 <code>True</code>,则输入元素将被合并,就像每个比较都被反转一样。 要实现类似于 <code>sorted(itertools.chain(*iterables), reverse=True)</code> 的行为,所有可迭代对象必须从大到小排序。</p>
streams is already sorted (smallest to largest).</p>
 
<p>Has two optional arguments which must be specified as keyword arguments.</p>
 
<p>''key'' specifies a [[../../glossary#term-key-function|<span class="xref std std-term">key function</span>]] of one argument that is used to
 
extract a comparison key from each input element. The default value is
 
<code>None</code> (compare the elements directly).</p>
 
<p>''reverse'' is a boolean value. If set to <code>True</code>, then the input elements
 
are merged as if each comparison were reversed. To achieve behavior similar
 
to <code>sorted(itertools.chain(*iterables), reverse=True)</code>, all iterables must
 
be sorted from largest to smallest.</p>
 
 
<div class="versionchanged">
 
<div class="versionchanged">
  
<p><span class="versionmodified changed">3.5 版更改: </span>Added the optional ''key'' and ''reverse'' parameters.</p>
+
<p><span class="versionmodified changed"> 3.5 版本变更: </span> 增加了可选的 ''key'' ''reverse'' 参数。</p>
  
 
</div></dd></dl>
 
</div></dd></dl>
  
; <code>heapq.</code><code>nlargest</code><span class="sig-paren">(</span>''<span class="n">n</span>'', ''<span class="n">iterable</span>'', ''<span class="n">key</span><span class="o">=</span><span class="default_value">None</span>''<span class="sig-paren">)</span>
+
; <span class="sig-prename descclassname"><span class="pre">heapq.</span></span><span class="sig-name descname"><span class="pre">nlargest</span></span><span class="sig-paren">(</span>''<span class="n"><span class="pre">n</span></span>'', ''<span class="n"><span class="pre">iterable</span></span>'', ''<span class="n"><span class="pre">key</span></span><span class="o"><span class="pre">=</span></span><span class="default_value"><span class="pre">None</span></span>''<span class="sig-paren">)</span>
: Return a list with the ''n'' largest elements from the dataset defined by ''iterable''. ''key'', if provided, specifies a function of one argument that is used to extract a comparison key from each element in ''iterable'' (for example, <code>key=str.lower</code>). Equivalent to: <code>sorted(iterable, key=key, reverse=True)[:n]</code>.
+
: ''iterable'' 定义的数据集中返回一个包含 ''n'' 个最大元素的列表。 ''key'',如果提供,指定一个参数的函数,用于从 ''iterable'' 中的每个元素提取比较键(例如,<code>key=str.lower</code>)。 相当于:<code>sorted(iterable, key=key, reverse=True)[:n]</code>
  
; <code>heapq.</code><code>nsmallest</code><span class="sig-paren">(</span>''<span class="n">n</span>'', ''<span class="n">iterable</span>'', ''<span class="n">key</span><span class="o">=</span><span class="default_value">None</span>''<span class="sig-paren">)</span>
+
; <span class="sig-prename descclassname"><span class="pre">heapq.</span></span><span class="sig-name descname"><span class="pre">nsmallest</span></span><span class="sig-paren">(</span>''<span class="n"><span class="pre">n</span></span>'', ''<span class="n"><span class="pre">iterable</span></span>'', ''<span class="n"><span class="pre">key</span></span><span class="o"><span class="pre">=</span></span><span class="default_value"><span class="pre">None</span></span>''<span class="sig-paren">)</span>
: Return a list with the ''n'' smallest elements from the dataset defined by ''iterable''. ''key'', if provided, specifies a function of one argument that is used to extract a comparison key from each element in ''iterable'' (for example, <code>key=str.lower</code>). Equivalent to: <code>sorted(iterable, key=key)[:n]</code>.
+
: ''iterable'' 定义的数据集中返回一个包含 ''n'' 个最小元素的列表。 ''key'',如果提供,指定一个参数的函数,用于从 ''iterable'' 中的每个元素提取比较键(例如,<code>key=str.lower</code>)。 相当于:<code>sorted(iterable, key=key)[:n]</code>
  
The latter two functions perform best for smaller values of ''n''. For larger
+
后两个函数在 ''n'' 的较小值下表现最佳。 对于较大的值,使用 [[../functions#sorted|sorted()]] 函数效率更高。 此外,当 <code>n==1</code> 时,使用内置的 [[../functions#min|min()]] [[../functions#max|max()]] 函数效率更高。 如果需要重复使用这些函数,请考虑将可迭代对象转换为实际的堆。
values, it is more efficient to use the [[../functions#sorted|<code>sorted()</code>]] function. Also, when
 
<code>n==1</code>, it is more efficient to use the built-in [[../functions#min|<code>min()</code>]] and [[../functions#max|<code>max()</code>]]
 
functions. If repeated usage of these functions is required, consider turning
 
the iterable into an actual heap.
 
  
 
<div id="basic-examples" class="section">
 
<div id="basic-examples" class="section">
  
== Basic Examples ==
+
== 基本示例 ==
  
A [https://en.wikipedia.org/wiki/Heapsort heapsort] can be implemented by
+
[https://en.wikipedia.org/wiki/Heapsort heapsort] 可以通过将所有值推送到堆上,然后一次弹出一个最小值来实现:
pushing all values onto a heap and then popping off the smallest values one at a
 
time:
 
  
 
<div class="highlight-python3 notranslate">
 
<div class="highlight-python3 notranslate">
第105行: 第73行:
 
<div class="highlight">
 
<div class="highlight">
  
<pre>&gt;&gt;&gt; def heapsort(iterable):
+
<syntaxhighlight lang="python3">>>> def heapsort(iterable):
 
...    h = []
 
...    h = []
 
...    for value in iterable:
 
...    for value in iterable:
第111行: 第79行:
 
...    return [heappop(h) for i in range(len(h))]
 
...    return [heappop(h) for i in range(len(h))]
 
...
 
...
&gt;&gt;&gt; heapsort([1, 3, 5, 7, 9, 2, 4, 6, 8, 0])
+
>>> heapsort([1, 3, 5, 7, 9, 2, 4, 6, 8, 0])
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]</pre>
+
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]</syntaxhighlight>
  
 
</div>
 
</div>
  
 
</div>
 
</div>
This is similar to <code>sorted(iterable)</code>, but unlike [[../functions#sorted|<code>sorted()</code>]], this
+
这与 <code>sorted(iterable)</code> 类似,但与 [[../functions#sorted|sorted()]] 不同的是,此实现并不稳定。
implementation is not stable.
 
  
Heap elements can be tuples. This is useful for assigning comparison values
+
堆元素可以是元组。 这对于在被跟踪的主记录旁边分配比较值(例如任务优先级)很有用:
(such as task priorities) alongside the main record being tracked:
 
  
 
<div class="highlight-python3 notranslate">
 
<div class="highlight-python3 notranslate">
第127行: 第93行:
 
<div class="highlight">
 
<div class="highlight">
  
<pre>&gt;&gt;&gt; h = []
+
<syntaxhighlight lang="python3">>>> h = []
&gt;&gt;&gt; heappush(h, (5, 'write code'))
+
>>> heappush(h, (5, 'write code'))
&gt;&gt;&gt; heappush(h, (7, 'release product'))
+
>>> heappush(h, (7, 'release product'))
&gt;&gt;&gt; heappush(h, (1, 'write spec'))
+
>>> heappush(h, (1, 'write spec'))
&gt;&gt;&gt; heappush(h, (3, 'create tests'))
+
>>> heappush(h, (3, 'create tests'))
&gt;&gt;&gt; heappop(h)
+
>>> heappop(h)
(1, 'write spec')</pre>
+
(1, 'write spec')</syntaxhighlight>
  
 
</div>
 
</div>
第142行: 第108行:
 
<div id="priority-queue-implementation-notes" class="section">
 
<div id="priority-queue-implementation-notes" class="section">
  
== Priority Queue Implementation Notes ==
+
== 优先队列实施注意事项 ==
  
A [https://en.wikipedia.org/wiki/Priority_queue priority queue] is common use
+
[https://en.wikipedia.org/wiki/Priority_queue 优先级队列] 是堆的常见用途,它提出了几个实现挑战:
for a heap, and it presents several implementation challenges:
 
  
* Sort stability: how do you get two tasks with equal priorities to be returned in the order they were originally added?
+
* 排序稳定性:如何让两个优先级相同的任务按照最初添加的顺序返回?
* Tuple comparison breaks for (priority, task) pairs if the priorities are equal and the tasks do not have a default comparison order.
+
* 如果优先级相等并且任务没有默认的比较顺序,则元组比较会中断(优先级,任务)对。
* If the priority of a task changes, how do you move it to a new position in the heap?
+
* 如果任务的优先级发生变化,如何将其移动到堆中的新位置?
* Or if a pending task needs to be deleted, how do you find it and remove it from the queue?
+
* 或者如果一个挂起的任务需要删除,你如何找到它并将其从队列中删除?
  
A solution to the first two challenges is to store entries as 3-element list
+
前两个挑战的解决方案是将条目存储为 3 元素列表,包括优先级、条目计数和任务。 条目计数用作决胜局,以便具有相同优先级的两个任务按添加顺序返回。 并且由于没有两个条目计数相同,元组比较永远不会尝试直接比较两个任务。
including the priority, an entry count, and the task. The entry count serves as
 
a tie-breaker so that two tasks with the same priority are returned in the order
 
they were added. And since no two entry counts are the same, the tuple
 
comparison will never attempt to directly compare two tasks.
 
  
Another solution to the problem of non-comparable tasks is to create a wrapper
+
另一个解决不可比较任务问题的方法是创建一个包装类,忽略任务项,只比较优先级字段:
class that ignores the task item and only compares the priority field:
 
  
 
<div class="highlight-python3 notranslate">
 
<div class="highlight-python3 notranslate">
第165行: 第125行:
 
<div class="highlight">
 
<div class="highlight">
  
<pre>from dataclasses import dataclass, field
+
<syntaxhighlight lang="python3">from dataclasses import dataclass, field
 
from typing import Any
 
from typing import Any
  
第171行: 第131行:
 
class PrioritizedItem:
 
class PrioritizedItem:
 
     priority: int
 
     priority: int
     item: Any=field(compare=False)</pre>
+
     item: Any=field(compare=False)</syntaxhighlight>
  
 
</div>
 
</div>
  
 
</div>
 
</div>
The remaining challenges revolve around finding a pending task and making
+
剩下的挑战围绕着找到一个待处理的任务并更改其优先级或完全删除它。 可以使用指向队列中条目的字典来查找任务。
changes to its priority or removing it entirely. Finding a task can be done
 
with a dictionary pointing to an entry in the queue.
 
  
Removing the entry or changing its priority is more difficult because it would
+
删除条目或更改其优先级更加困难,因为它会破坏堆结构不变量。 因此,一个可能的解决方案是将条目标记为已删除并添加一个具有修改后的优先级的新条目:
break the heap structure invariants. So, a possible solution is to mark the
 
entry as removed and add a new entry with the revised priority:
 
  
 
<div class="highlight-python3 notranslate">
 
<div class="highlight-python3 notranslate">
第188行: 第144行:
 
<div class="highlight">
 
<div class="highlight">
  
<pre>pq = []                        # list of entries arranged in a heap
+
<syntaxhighlight lang="python3">pq = []                        # list of entries arranged in a heap
 
entry_finder = {}              # mapping of tasks to entries
 
entry_finder = {}              # mapping of tasks to entries
REMOVED = '&lt;removed-task&gt;'      # placeholder for a removed task
+
REMOVED = '<removed-task>'      # placeholder for a removed task
 
counter = itertools.count()    # unique sequence count
 
counter = itertools.count()    # unique sequence count
  
第214行: 第170行:
 
             del entry_finder[task]
 
             del entry_finder[task]
 
             return task
 
             return task
     raise KeyError('pop from an empty priority queue')</pre>
+
     raise KeyError('pop from an empty priority queue')</syntaxhighlight>
  
 
</div>
 
</div>
第223行: 第179行:
 
<div id="theory" class="section">
 
<div id="theory" class="section">
  
== Theory ==
+
== 理论 ==
  
Heaps are arrays for which <code>a[k] &lt;= a[2*k+1]</code> and <code>a[k] &lt;= a[2*k+2]</code> for all
+
堆是数组,其中 <code>a[k] &lt;= a[2*k+1]</code> <code>a[k] &lt;= a[2*k+2]</code> 用于所有 ''k'',从 0 开始计数元素。 为了比较,不存在的元素被认为是无限的。 堆的有趣特性是 <code>a[0]</code> 总是它的最小元素。
''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 <code>a[0]</code> is always its smallest element.
 
  
The strange invariant above is meant to be an efficient memory representation
+
上面奇怪的不变量旨在成为锦标赛的有效记忆表示。 下面的数字是 ''k'',而不是 <code>a[k]</code>
for a tournament. The numbers below are ''k'', not <code>a[k]</code>:
 
  
 
<div class="highlight-python3 notranslate">
 
<div class="highlight-python3 notranslate">
第237行: 第189行:
 
<div class="highlight">
 
<div class="highlight">
  
<pre>                              0
+
<syntaxhighlight lang="python3">                              0
  
 
               1                                2
 
               1                                2
第245行: 第197行:
 
   7      8      9      10      11      12      13      14
 
   7      8      9      10      11      12      13      14
  
15 16  17 18  19 20  21 22  23 24  25 26  27 28  29 30</pre>
+
15 16  17 18  19 20  21 22  23 24  25 26  27 28  29 30</syntaxhighlight>
  
 
</div>
 
</div>
  
 
</div>
 
</div>
In the tree above, each cell ''k'' is topping <code>2*k+1</code> and <code>2*k+2</code>. In a usual
+
在上面的树中,每个单元格 ''k'' 位于 <code>2*k+1</code> <code>2*k+2</code> 的顶部。 在我们在体育运动中看到的通常的二元锦标赛中,每个单元格都是它顶部的两个单元格的获胜者,我们可以沿着树追踪获胜者以查看他/她的所有对手。 然而,在此类锦标赛的许多计算机应用程序中,我们不需要追踪获胜者的历史。 为了提高内存效率,当获胜者被提升时,我们尝试用较低级别的其他东西替换它,规则变成一个单元格和它顶部的两个单元格包含三个不同的项目,但顶部单元格“获胜”在两个顶部的单元格上。
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
+
如果此堆不变量始终受到保护,则索引 0 显然是总体赢家。 删除它并找到“下一个”赢家的最简单算法方法是将一些输家(假设上图中的单元格 30)移动到 0 位置,然后将这个新的 0 沿树向下渗透,交换值,直到不变量被重新建立。 这显然是树中项目总数的对数。 通过迭代所有项目,你得到一个 O(n log n) 排序。
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 &quot;wins&quot; over the two topped cells.
 
  
If this heap invariant is protected at all time, index 0 is clearly the overall
+
这种排序的一个很好的功能是,您可以在排序进行时有效地插入新项目,前提是插入的项目不比您提取的最后一个第 0 元素“更好”。 这在模拟上下文中特别有用,其中树包含所有传入事件,并且“获胜”条件意味着最小的预定时间。 当一个事件调度其他事件执行时,它们被调度到未来,所以它们可以很容易地进入堆。 因此,堆是用于实现调度程序的良好结构(这是我用于 MIDI 音序器的 :-)。
winner. The simplest algorithmic way to remove it and find the &quot;next&quot; 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 log 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 &quot;better&quot; than the
 
last 0'th element you extracted. This is especially useful in simulation
 
contexts, where the tree holds all incoming events, and the &quot;win&quot; condition
 
means the smallest scheduled time. When an event schedules 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,
+
堆在大磁盘排序中也非常有用。 你很可能都知道大排序意味着产生“运行”(这是预先排序的序列,其大小通常与 CPU 内存量有关),然后是这些运行的合并通道,这种合并通常非常巧妙有组织的 [[#id4|1]]。 初始排序产生尽可能长的运行是非常重要的。 锦标赛是实现这一目标的好方法。 如果使用所有可用内存来举办锦标赛,您替换和渗透恰好适合当前运行的项目,您将产生两倍于随机输入内存大小的运行,并且对于模糊排序的输入要好得多。
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
+
此外,如果您在磁盘上输出第 0 个项目并获得一个可能不适合当前锦标赛的输入(因为该值“胜”于最后一个输出值),则它无法放入堆中,因此堆减少。 释放的内存可以立即巧妙地重用,以逐步构建第二个堆,其增长速度与第一个堆融化的速度完全相同。 当第一个堆完全消失时,您切换堆并开始新的运行。 聪明而且相当有效!
big sort implies producing &quot;runs&quot; (which are pre-sorted sequences, whose 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 [[#id2|1]]. It is very
 
important that the initial sort produces the longest runs possible. Tournaments
 
are a good way to achieve 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 &quot;wins&quot; 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. :-)
 
  
Footnotes
+
; <span class="brackets">[[#id3|1]]</span>
 +
: 现在的磁盘平衡算法比智能更烦人,这是磁盘搜索能力的结果。 在无法搜索的设备上,如大型磁带驱动器,情况就大不相同了,必须非常聪明地确保(提前)每个磁带移动都是最有效的(即,将最好地参与“进行”合并)。 有些磁带甚至可以倒读,这也是为了避免倒带时间。 相信我,真正好的磁带种类非常值得观看! 从古至今,排序一直是一门伟大的艺术! :-)
  
; <span class="brackets">[[#id1|1]]</span>
 
: 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 &quot;progressing&quot; 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! :-)
 
  
 +
</div>
  
 
</div>
 
</div>
 +
<div class="clearer">
 +
 +
  
 
</div>
 
</div>
  
[[Category:Python 3.9 中文文档]]
+
[[Category:Python 3.9 文档]]

2021年10月31日 (日) 04:52的最新版本

heapq — 堆队列算法

源代码: :source:`Lib/heapq.py`



该模块提供了堆队列算法的实现,也称为优先队列算法。

堆是二叉树,其每个父节点的值都小于或等于其任何子节点。 此实现使用数组,其中 heap[k] <= heap[2*k+1]heap[k] <= heap[2*k+2] 用于所有 k,从零开始计算元素。 为了比较,不存在的元素被认为是无限的。 堆的有趣特性是它的最小元素总是根,heap[0]

下面的 API 在两个方面不同于教科书的堆算法: (a) 我们使用从零开始的索引。 这使得节点的索引与其子节点的索引之间的关系稍微不那么明显,但更合适,因为 Python 使用从零开始的索引。 (b) 我们的 pop 方法返回最小的项目,而不是最大的项目(在教科书中称为“最小堆”;“最大堆”在文本中更常见,因为它适用于就地排序)。

这两个使得可以毫无意外地将堆视为常规 Python 列表:heap[0] 是最小的项,而 heap.sort() 保持堆不变!

要创建堆,请使用初始化为 [] 的列表,或者您可以通过函数 heapify() 将填充列表转换为堆。

提供以下功能:

heapq.heappush(heap, item)
将值 item 推送到 heap,保持堆不变。
heapq.heappop(heap)
弹出并返回最小的项目,保持堆不变。 如果堆为空,则会引发 IndexError。 要访问最小的项目而不弹出它,请使用 heap[0]
heapq.heappushpop(heap, item)
推入堆,然后从中弹出并返回最小的项。 组合操作比 heappush() 更有效地运行,然后单独调用 heappop()
heapq.heapify(x)
将列表 x 在线性时间内就地转换为堆。
heapq.heapreplace(heap, item)

中弹出并返回最小的项,同时推送新的。 堆大小不会改变。 如果堆为空,则引发 IndexError

这一步操作比 heappop() 后接 heappush() 更有效,并且在使用固定大小的堆时更合适。 pop/push 组合总是从堆中返回一个元素并将其替换为 item

返回的值可能大于添加的 。 如果不需要,请考虑使用 heappushpop() 代替。 它的 push/pop 组合返回两个值中较小的一个,将较大的值留在堆上。

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

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

将多个排序的输入合并为一个排序的输出(例如,合并来自多个日志文件的带时间戳的条目)。 在排序后的值上返回一个 迭代器

sorted(itertools.chain(*iterables)) 类似,但返回一个可迭代对象,不会一次将数据全部拉入内存,并假设每个输入流都已经排序(从小到大)。

有两个可选参数,必须指定为关键字参数。

key 指定一个参数的 key 函数 ,用于从每个输入元素中提取比较键。 默认值为 None(直接比较元素)。

reverse 是一个布尔值。 如果设置为 True,则输入元素将被合并,就像每个比较都被反转一样。 要实现类似于 sorted(itertools.chain(*iterables), reverse=True) 的行为,所有可迭代对象必须从大到小排序。

3.5 版本变更: 增加了可选的 keyreverse 参数。

heapq.nlargest(n, iterable, key=None)
iterable 定义的数据集中返回一个包含 n 个最大元素的列表。 key,如果提供,指定一个参数的函数,用于从 iterable 中的每个元素提取比较键(例如,key=str.lower)。 相当于:sorted(iterable, key=key, reverse=True)[:n]
heapq.nsmallest(n, iterable, key=None)
iterable 定义的数据集中返回一个包含 n 个最小元素的列表。 key,如果提供,指定一个参数的函数,用于从 iterable 中的每个元素提取比较键(例如,key=str.lower)。 相当于:sorted(iterable, key=key)[:n]

后两个函数在 n 的较小值下表现最佳。 对于较大的值,使用 sorted() 函数效率更高。 此外,当 n==1 时,使用内置的 min()max() 函数效率更高。 如果需要重复使用这些函数,请考虑将可迭代对象转换为实际的堆。

基本示例

heapsort 可以通过将所有值推送到堆上,然后一次弹出一个最小值来实现:

>>> def heapsort(iterable):
...     h = []
...     for value in iterable:
...         heappush(h, value)
...     return [heappop(h) for i in range(len(h))]
...
>>> heapsort([1, 3, 5, 7, 9, 2, 4, 6, 8, 0])
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

这与 sorted(iterable) 类似,但与 sorted() 不同的是,此实现并不稳定。

堆元素可以是元组。 这对于在被跟踪的主记录旁边分配比较值(例如任务优先级)很有用:

>>> h = []
>>> heappush(h, (5, 'write code'))
>>> heappush(h, (7, 'release product'))
>>> heappush(h, (1, 'write spec'))
>>> heappush(h, (3, 'create tests'))
>>> heappop(h)
(1, 'write spec')

优先队列实施注意事项

优先级队列 是堆的常见用途,它提出了几个实现挑战:

  • 排序稳定性:如何让两个优先级相同的任务按照最初添加的顺序返回?
  • 如果优先级相等并且任务没有默认的比较顺序,则元组比较会中断(优先级,任务)对。
  • 如果任务的优先级发生变化,如何将其移动到堆中的新位置?
  • 或者如果一个挂起的任务需要删除,你如何找到它并将其从队列中删除?

前两个挑战的解决方案是将条目存储为 3 元素列表,包括优先级、条目计数和任务。 条目计数用作决胜局,以便具有相同优先级的两个任务按添加顺序返回。 并且由于没有两个条目计数相同,元组比较永远不会尝试直接比较两个任务。

另一个解决不可比较任务问题的方法是创建一个包装类,忽略任务项,只比较优先级字段:

from dataclasses import dataclass, field
from typing import Any

@dataclass(order=True)
class PrioritizedItem:
    priority: int
    item: Any=field(compare=False)

剩下的挑战围绕着找到一个待处理的任务并更改其优先级或完全删除它。 可以使用指向队列中条目的字典来查找任务。

删除条目或更改其优先级更加困难,因为它会破坏堆结构不变量。 因此,一个可能的解决方案是将条目标记为已删除并添加一个具有修改后的优先级的新条目:

pq = []                         # list of entries arranged in a heap
entry_finder = {}               # mapping of tasks to entries
REMOVED = '<removed-task>'      # placeholder for a removed task
counter = itertools.count()     # unique sequence count

def add_task(task, priority=0):
    'Add a new task or update the priority of an existing task'
    if task in entry_finder:
        remove_task(task)
    count = next(counter)
    entry = [priority, count, task]
    entry_finder[task] = entry
    heappush(pq, entry)

def remove_task(task):
    'Mark an existing task as REMOVED.  Raise KeyError if not found.'
    entry = entry_finder.pop(task)
    entry[-1] = REMOVED

def pop_task():
    'Remove and return the lowest priority task. Raise KeyError if empty.'
    while pq:
        priority, count, task = heappop(pq)
        if task is not REMOVED:
            del entry_finder[task]
            return task
    raise KeyError('pop from an empty priority queue')

理论

堆是数组,其中 a[k] <= a[2*k+1]a[k] <= a[2*k+2] 用于所有 k,从 0 开始计数元素。 为了比较,不存在的元素被认为是无限的。 堆的有趣特性是 a[0] 总是它的最小元素。

上面奇怪的不变量旨在成为锦标赛的有效记忆表示。 下面的数字是 k,而不是 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

在上面的树中,每个单元格 k 位于 2*k+12*k+2 的顶部。 在我们在体育运动中看到的通常的二元锦标赛中,每个单元格都是它顶部的两个单元格的获胜者,我们可以沿着树追踪获胜者以查看他/她的所有对手。 然而,在此类锦标赛的许多计算机应用程序中,我们不需要追踪获胜者的历史。 为了提高内存效率,当获胜者被提升时,我们尝试用较低级别的其他东西替换它,规则变成一个单元格和它顶部的两个单元格包含三个不同的项目,但顶部单元格“获胜”在两个顶部的单元格上。

如果此堆不变量始终受到保护,则索引 0 显然是总体赢家。 删除它并找到“下一个”赢家的最简单算法方法是将一些输家(假设上图中的单元格 30)移动到 0 位置,然后将这个新的 0 沿树向下渗透,交换值,直到不变量被重新建立。 这显然是树中项目总数的对数。 通过迭代所有项目,你得到一个 O(n log n) 排序。

这种排序的一个很好的功能是,您可以在排序进行时有效地插入新项目,前提是插入的项目不比您提取的最后一个第 0 元素“更好”。 这在模拟上下文中特别有用,其中树包含所有传入事件,并且“获胜”条件意味着最小的预定时间。 当一个事件调度其他事件执行时,它们被调度到未来,所以它们可以很容易地进入堆。 因此,堆是用于实现调度程序的良好结构(这是我用于 MIDI 音序器的 :-)。

已经广泛研究了用于实现调度器的各种结构,堆对此有好处,因为它们相当快,速度几乎恒定,最坏情况与平均情况没有太大区别。 但是,还有其他表示总体上更有效,但最坏的情况可能会很糟糕。

堆在大磁盘排序中也非常有用。 你很可能都知道大排序意味着产生“运行”(这是预先排序的序列,其大小通常与 CPU 内存量有关),然后是这些运行的合并通道,这种合并通常非常巧妙有组织的 1。 初始排序产生尽可能长的运行是非常重要的。 锦标赛是实现这一目标的好方法。 如果使用所有可用内存来举办锦标赛,您替换和渗透恰好适合当前运行的项目,您将产生两倍于随机输入内存大小的运行,并且对于模糊排序的输入要好得多。

此外,如果您在磁盘上输出第 0 个项目并获得一个可能不适合当前锦标赛的输入(因为该值“胜”于最后一个输出值),则它无法放入堆中,因此堆减少。 释放的内存可以立即巧妙地重用,以逐步构建第二个堆,其增长速度与第一个堆融化的速度完全相同。 当第一个堆完全消失时,您切换堆并开始新的运行。 聪明而且相当有效!

总之,堆是需要了解的有用内存结构。 我在一些应用程序中使用它们,我认为保留一个“堆”模块很好。 :-)

脚注

1
现在的磁盘平衡算法比智能更烦人,这是磁盘搜索能力的结果。 在无法搜索的设备上,如大型磁带驱动器,情况就大不相同了,必须非常聪明地确保(提前)每个磁带移动都是最有效的(即,将最好地参与“进行”合并)。 有些磁带甚至可以倒读,这也是为了避免倒带时间。 相信我,真正好的磁带种类非常值得观看! 从古至今,排序一直是一门伟大的艺术! :-)