Files
hustoj/web/python-docs/library/heapq.html
2025-03-09 17:10:56 +08:00

447 lines
39 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" lang="zh_CN">
<head>
<meta charset="utf-8" /><meta name="generator" content="Docutils 0.17.1: http://docutils.sourceforge.net/" />
<title>heapq --- 堆队列算法 &#8212; Python 3.8.20 文档</title><meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="../_static/pydoctheme.css" type="text/css" />
<link rel="stylesheet" href="../_static/pygments.css" type="text/css" />
<script id="documentation_options" data-url_root="../" src="../_static/documentation_options.js"></script>
<script src="../_static/jquery.js"></script>
<script src="../_static/underscore.js"></script>
<script src="../_static/doctools.js"></script>
<script src="../_static/language_data.js"></script>
<script src="../_static/translations.js"></script>
<script src="../_static/sidebar.js"></script>
<link rel="search" type="application/opensearchdescription+xml"
title="在 Python 3.8.20 文档 中搜索"
href="../_static/opensearch.xml"/>
<link rel="author" title="关于这些文档" href="../about.html" />
<link rel="index" title="索引" href="../genindex.html" />
<link rel="search" title="搜索" href="../search.html" />
<link rel="copyright" title="版权所有" href="../copyright.html" />
<link rel="next" title="bisect --- 数组二分查找算法" href="bisect.html" />
<link rel="prev" title="collections.abc --- 容器的抽象基类" href="collections.abc.html" />
<link rel="canonical" href="https://docs.python.org/3/library/heapq.html" />
<style>
@media only screen {
table.full-width-table {
width: 100%;
}
}
</style>
<link rel="shortcut icon" type="image/png" href="../_static/py.svg" />
<script type="text/javascript" src="../_static/copybutton.js"></script>
<script type="text/javascript" src="../_static/menu.js"></script>
</head>
<body>
<div class="mobile-nav">
<input type="checkbox" id="menuToggler" class="toggler__input" aria-controls="navigation"
aria-pressed="false" aria-expanded="false" role="button" aria-label="Menu" />
<label for="menuToggler" class="toggler__label">
<span></span>
</label>
<nav class="nav-content" role="navigation">
<a href="https://www.python.org/" class="nav-logo">
<img src="../_static/py.svg" alt="Logo"/>
</a>
<div class="version_switcher_placeholder"></div>
<form role="search" class="search" action="../search.html" method="get">
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" class="search-icon">
<path fill-rule="nonzero"
d="M15.5 14h-.79l-.28-.27a6.5 6.5 0 001.48-5.34c-.47-2.78-2.79-5-5.59-5.34a6.505 6.505 0 00-7.27 7.27c.34 2.8 2.56 5.12 5.34 5.59a6.5 6.5 0 005.34-1.48l.27.28v.79l4.25 4.25c.41.41 1.08.41 1.49 0 .41-.41.41-1.08 0-1.49L15.5 14zm-6 0C7.01 14 5 11.99 5 9.5S7.01 5 9.5 5 14 7.01 14 9.5 11.99 14 9.5 14z" fill="#444"></path>
</svg>
<input type="text" name="q" aria-label="快速搜索"/>
<input type="submit" value="转向"/>
</form>
</nav>
<div class="menu-wrapper">
<nav class="menu" role="navigation" aria-label="main navigation">
<div class="language_switcher_placeholder"></div>
<h3><a href="../contents.html">目录</a></h3>
<ul>
<li><a class="reference internal" href="#"><code class="xref py py-mod docutils literal notranslate"><span class="pre">heapq</span></code> --- 堆队列算法</a><ul>
<li><a class="reference internal" href="#basic-examples">基本示例</a></li>
<li><a class="reference internal" href="#priority-queue-implementation-notes">优先队列实现说明</a></li>
<li><a class="reference internal" href="#theory">理论</a></li>
</ul>
</li>
</ul>
<h4>上一个主题</h4>
<p class="topless"><a href="collections.abc.html"
title="上一章"><code class="xref py py-mod docutils literal notranslate"><span class="pre">collections.abc</span></code> --- 容器的抽象基类</a></p>
<h4>下一个主题</h4>
<p class="topless"><a href="bisect.html"
title="下一章"><code class="xref py py-mod docutils literal notranslate"><span class="pre">bisect</span></code> --- 数组二分查找算法</a></p>
<div role="note" aria-label="source link">
<h3>本页</h3>
<ul class="this-page-menu">
<li><a href="../bugs.html">报告 Bug</a></li>
<li>
<a href="https://github.com/python/cpython/blob/3.8/Doc/library/heapq.rst"
rel="nofollow">显示源代码
</a>
</li>
</ul>
</div>
</nav>
</div>
</div>
<div id="outdated-warning" style="padding: .5em; text-align: center; background-color: #FFBABA; color: #6A0E0E;">
这个文档所针对的是一个已不再受支持的 Python 旧版本。
你应当升级版本,并阅读
<a href="/3/library/heapq.html"> Python 当前稳定版本的文档</a>.
</div>
<div class="related" role="navigation" aria-label="related navigation">
<h3>导航</h3>
<ul>
<li class="right" style="margin-right: 10px">
<a href="../genindex.html" title="总目录"
accesskey="I">索引</a></li>
<li class="right" >
<a href="../py-modindex.html" title="Python 模块索引"
>模块</a> |</li>
<li class="right" >
<a href="bisect.html" title="bisect --- 数组二分查找算法"
accesskey="N">下一页</a> |</li>
<li class="right" >
<a href="collections.abc.html" title="collections.abc --- 容器的抽象基类"
accesskey="P">上一页</a> |</li>
<li><img src="../_static/py.svg" alt="python logo" style="vertical-align: middle; margin-top: -1px"/></li>
<li><a href="https://www.python.org/">Python</a> &#187;</li>
<li class="switchers">
<div class="language_switcher_placeholder"></div>
<div class="version_switcher_placeholder"></div>
</li>
<li>
</li>
<li id="cpython-language-and-version">
<a href="../index.html">3.8.20 Documentation</a> &#187;
</li>
<li class="nav-item nav-item-1"><a href="index.html" >Python 标准库</a> &#187;</li>
<li class="nav-item nav-item-2"><a href="datatypes.html" accesskey="U">数据类型</a> &#187;</li>
<li class="right">
<div class="inline-search" role="search">
<form class="inline-search" action="../search.html" method="get">
<input placeholder="快速搜索" aria-label="快速搜索" type="text" name="q" />
<input type="submit" value="转向" />
<input type="hidden" name="check_keywords" value="yes" />
<input type="hidden" name="area" value="default" />
</form>
</div>
|
</li>
</ul>
</div>
<div class="document">
<div class="documentwrapper">
<div class="bodywrapper">
<div class="body" role="main">
<section id="module-heapq">
<span id="heapq-heap-queue-algorithm"></span><h1><a class="reference internal" href="#module-heapq" title="heapq: Heap queue algorithm (a.k.a. priority queue)."><code class="xref py py-mod docutils literal notranslate"><span class="pre">heapq</span></code></a> --- 堆队列算法<a class="headerlink" href="#module-heapq" title="永久链接至标题"></a></h1>
<p><strong>源码:</strong><a class="reference external" href="https://github.com/python/cpython/tree/3.8/Lib/heapq.py">Lib/heapq.py</a></p>
<hr class="docutils" />
<p>这个模块提供了堆队列算法的实现,也称为优先队列算法。</p>
<p>堆是一个二叉树,它的每个父节点的值都只会小于或等于所有孩子节点(的值)。 它使用了数组来实现:从零开始计数,对于所有的 <em>k</em> ,都有 <code class="docutils literal notranslate"><span class="pre">heap[k]</span> <span class="pre">&lt;=</span> <span class="pre">heap[2*k+1]</span></code><code class="docutils literal notranslate"><span class="pre">heap[k]</span> <span class="pre">&lt;=</span> <span class="pre">heap[2*k+2]</span></code>。 为了便于比较,不存在的元素被认为是无限大。 堆最有趣的特性在于最小的元素总是在根结点:<code class="docutils literal notranslate"><span class="pre">heap[0]</span></code></p>
<p>这个API与教材的堆算法实现有所不同具体区别有两方面a我们使用了从零开始的索引。这使得节点和其孩子节点索引之间的关系不太直观但更加适合因为 Python 使用从零开始的索引。 b我们的 pop 方法返回最小的项而不是最大的项(这在教材中称为“最小堆”;而“最大堆”在教材中更为常见,因为它更适用于原地排序)。</p>
<p>基于这两方面把堆看作原生的Python list也没什么奇怪的 <code class="docutils literal notranslate"><span class="pre">heap[0]</span></code> 表示最小的元素,同时 <code class="docutils literal notranslate"><span class="pre">heap.sort()</span></code> 维护了堆的不变性!</p>
<p>要创建一个堆可以使用list来初始化为 <code class="docutils literal notranslate"><span class="pre">[]</span></code> ,或者你可以通过一个函数 <a class="reference internal" href="#heapq.heapify" title="heapq.heapify"><code class="xref py py-func docutils literal notranslate"><span class="pre">heapify()</span></code></a> 来把一个list转换成堆。</p>
<p>定义了以下函数:</p>
<dl class="function">
<dt id="heapq.heappush">
<code class="sig-prename descclassname">heapq.</code><code class="sig-name descname">heappush</code><span class="sig-paren">(</span><em class="sig-param">heap</em>, <em class="sig-param">item</em><span class="sig-paren">)</span><a class="headerlink" href="#heapq.heappush" title="永久链接至目标"></a></dt>
<dd><p><em>item</em> 的值加入 <em>heap</em> 中,保持堆的不变性。</p>
</dd></dl>
<dl class="function">
<dt id="heapq.heappop">
<code class="sig-prename descclassname">heapq.</code><code class="sig-name descname">heappop</code><span class="sig-paren">(</span><em class="sig-param">heap</em><span class="sig-paren">)</span><a class="headerlink" href="#heapq.heappop" title="永久链接至目标"></a></dt>
<dd><p>弹出并返回 <em>heap</em> 的最小的元素,保持堆的不变性。如果堆为空,抛出 <a class="reference internal" href="exceptions.html#IndexError" title="IndexError"><code class="xref py py-exc docutils literal notranslate"><span class="pre">IndexError</span></code></a> 。使用 <code class="docutils literal notranslate"><span class="pre">heap[0]</span></code> ,可以只访问最小的元素而不弹出它。</p>
</dd></dl>
<dl class="function">
<dt id="heapq.heappushpop">
<code class="sig-prename descclassname">heapq.</code><code class="sig-name descname">heappushpop</code><span class="sig-paren">(</span><em class="sig-param">heap</em>, <em class="sig-param">item</em><span class="sig-paren">)</span><a class="headerlink" href="#heapq.heappushpop" title="永久链接至目标"></a></dt>
<dd><p><em>item</em> 放入堆中,然后弹出并返回 <em>heap</em> 的最小元素。该组合操作比先调用 <a class="reference internal" href="#heapq.heappush" title="heapq.heappush"><code class="xref py py-func docutils literal notranslate"><span class="pre">heappush()</span></code></a> 再调用 <a class="reference internal" href="#heapq.heappop" title="heapq.heappop"><code class="xref py py-func docutils literal notranslate"><span class="pre">heappop()</span></code></a> 运行起来更有效率。</p>
</dd></dl>
<dl class="function">
<dt id="heapq.heapify">
<code class="sig-prename descclassname">heapq.</code><code class="sig-name descname">heapify</code><span class="sig-paren">(</span><em class="sig-param">x</em><span class="sig-paren">)</span><a class="headerlink" href="#heapq.heapify" title="永久链接至目标"></a></dt>
<dd><p>将list <em>x</em> 转换成堆,原地,线性时间内。</p>
</dd></dl>
<dl class="function">
<dt id="heapq.heapreplace">
<code class="sig-prename descclassname">heapq.</code><code class="sig-name descname">heapreplace</code><span class="sig-paren">(</span><em class="sig-param">heap</em>, <em class="sig-param">item</em><span class="sig-paren">)</span><a class="headerlink" href="#heapq.heapreplace" title="永久链接至目标"></a></dt>
<dd><p>弹出并返回 <em>heap</em> 中最小的一项,同时推入新的 <em>item</em>。 堆的大小不变。 如果堆为空则引发 <a class="reference internal" href="exceptions.html#IndexError" title="IndexError"><code class="xref py py-exc docutils literal notranslate"><span class="pre">IndexError</span></code></a></p>
<p>这个单步骤操作比 <a class="reference internal" href="#heapq.heappop" title="heapq.heappop"><code class="xref py py-func docutils literal notranslate"><span class="pre">heappop()</span></code></a><a class="reference internal" href="#heapq.heappush" title="heapq.heappush"><code class="xref py py-func docutils literal notranslate"><span class="pre">heappush()</span></code></a> 更高效,并且在使用固定大小的堆时更为适宜。 pop/push 组合总是会从堆中返回一个元素并将其替换为 <em>item</em></p>
<p>返回的值可能会比添加的 <em>item</em> 更大。 如果不希望如此,可考虑改用 <a class="reference internal" href="#heapq.heappushpop" title="heapq.heappushpop"><code class="xref py py-func docutils literal notranslate"><span class="pre">heappushpop()</span></code></a>。 它的 push/pop 组合会返回两个值中较小的一个,将较大的值留在堆中。</p>
</dd></dl>
<p>该模块还提供了三个基于堆的通用功能函数。</p>
<dl class="function">
<dt id="heapq.merge">
<code class="sig-prename descclassname">heapq.</code><code class="sig-name descname">merge</code><span class="sig-paren">(</span><em class="sig-param">*iterables</em>, <em class="sig-param">key=None</em>, <em class="sig-param">reverse=False</em><span class="sig-paren">)</span><a class="headerlink" href="#heapq.merge" title="永久链接至目标"></a></dt>
<dd><p>将多个已排序的输入合并为一个已排序的输出(例如,合并来自多个日志文件的带时间戳的条目)。 返回已排序值的 <a class="reference internal" href="../glossary.html#term-iterator"><span class="xref std std-term">iterator</span></a></p>
<p>类似于 <code class="docutils literal notranslate"><span class="pre">sorted(itertools.chain(*iterables))</span></code> 但返回一个可迭代对象,不会一次性地将数据全部放入内存,并假定每个输入流都是已排序的(从小到大)。</p>
<p>具有两个可选参数,它们都必须指定为关键字参数。</p>
<p><em>key</em> 指定带有单个参数的 <a class="reference internal" href="../glossary.html#term-key-function"><span class="xref std std-term">key function</span></a>,用于从每个输入元素中提取比较键。 默认值为 <code class="docutils literal notranslate"><span class="pre">None</span></code> (直接比较元素)。</p>
<p><em>reverse</em> 为一个布尔值。 如果设为 <code class="docutils literal notranslate"><span class="pre">True</span></code>,则输入元素将按比较结果逆序进行合并。 要达成与 <code class="docutils literal notranslate"><span class="pre">sorted(itertools.chain(*iterables),</span> <span class="pre">reverse=True)</span></code> 类似的行为,所有可迭代对象必须是已从大到小排序的。</p>
<div class="versionchanged">
<p><span class="versionmodified changed">在 3.5 版更改: </span>添加了可选的 <em>key</em><em>reverse</em> 形参。</p>
</div>
</dd></dl>
<dl class="function">
<dt id="heapq.nlargest">
<code class="sig-prename descclassname">heapq.</code><code class="sig-name descname">nlargest</code><span class="sig-paren">(</span><em class="sig-param">n</em>, <em class="sig-param">iterable</em>, <em class="sig-param">key=None</em><span class="sig-paren">)</span><a class="headerlink" href="#heapq.nlargest" title="永久链接至目标"></a></dt>
<dd><p><em>iterable</em> 所定义的数据集中返回前 <em>n</em> 个最大元素组成的列表。 如果提供了 <em>key</em> 则其应指定一个单参数的函数,用于从 <em>iterable</em> 的每个元素中提取比较键 (例如 <code class="docutils literal notranslate"><span class="pre">key=str.lower</span></code>)。 等价于: <code class="docutils literal notranslate"><span class="pre">sorted(iterable,</span> <span class="pre">key=key,</span> <span class="pre">reverse=True)[:n]</span></code></p>
</dd></dl>
<dl class="function">
<dt id="heapq.nsmallest">
<code class="sig-prename descclassname">heapq.</code><code class="sig-name descname">nsmallest</code><span class="sig-paren">(</span><em class="sig-param">n</em>, <em class="sig-param">iterable</em>, <em class="sig-param">key=None</em><span class="sig-paren">)</span><a class="headerlink" href="#heapq.nsmallest" title="永久链接至目标"></a></dt>
<dd><p><em>iterable</em> 所定义的数据集中返回前 <em>n</em> 个最小元素组成的列表。 如果提供了 <em>key</em> 则其应指定一个单参数的函数,用于从 <em>iterable</em> 的每个元素中提取比较键 (例如 <code class="docutils literal notranslate"><span class="pre">key=str.lower</span></code>)。 等价于: <code class="docutils literal notranslate"><span class="pre">sorted(iterable,</span> <span class="pre">key=key)[:n]</span></code></p>
</dd></dl>
<p>后两个函数在 <em>n</em> 值较小时性能最好。 对于更大的值,使用 <a class="reference internal" href="functions.html#sorted" title="sorted"><code class="xref py py-func docutils literal notranslate"><span class="pre">sorted()</span></code></a> 函数会更有效率。 此外,当 <code class="docutils literal notranslate"><span class="pre">n==1</span></code> 时,使用内置的 <a class="reference internal" href="functions.html#min" title="min"><code class="xref py py-func docutils literal notranslate"><span class="pre">min()</span></code></a><a class="reference internal" href="functions.html#max" title="max"><code class="xref py py-func docutils literal notranslate"><span class="pre">max()</span></code></a> 函数会更有效率。 如果需要重复使用这些函数,请考虑将可迭代对象转为真正的堆。</p>
<section id="basic-examples">
<h2>基本示例<a class="headerlink" href="#basic-examples" title="永久链接至标题"></a></h2>
<p><a class="reference external" href="https://en.wikipedia.org/wiki/Heapsort">堆排序</a> 可以通过将所有值推入堆中然后每次弹出一个最小值项来实现。</p>
<div class="highlight-python3 notranslate"><div class="highlight"><pre><span></span><span class="gp">&gt;&gt;&gt; </span><span class="k">def</span> <span class="nf">heapsort</span><span class="p">(</span><span class="n">iterable</span><span class="p">):</span>
<span class="gp">... </span> <span class="n">h</span> <span class="o">=</span> <span class="p">[]</span>
<span class="gp">... </span> <span class="k">for</span> <span class="n">value</span> <span class="ow">in</span> <span class="n">iterable</span><span class="p">:</span>
<span class="gp">... </span> <span class="n">heappush</span><span class="p">(</span><span class="n">h</span><span class="p">,</span> <span class="n">value</span><span class="p">)</span>
<span class="gp">... </span> <span class="k">return</span> <span class="p">[</span><span class="n">heappop</span><span class="p">(</span><span class="n">h</span><span class="p">)</span> <span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="nb">len</span><span class="p">(</span><span class="n">h</span><span class="p">))]</span>
<span class="gp">...</span>
<span class="gp">&gt;&gt;&gt; </span><span class="n">heapsort</span><span class="p">([</span><span class="mi">1</span><span class="p">,</span> <span class="mi">3</span><span class="p">,</span> <span class="mi">5</span><span class="p">,</span> <span class="mi">7</span><span class="p">,</span> <span class="mi">9</span><span class="p">,</span> <span class="mi">2</span><span class="p">,</span> <span class="mi">4</span><span class="p">,</span> <span class="mi">6</span><span class="p">,</span> <span class="mi">8</span><span class="p">,</span> <span class="mi">0</span><span class="p">])</span>
<span class="go">[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]</span>
</pre></div>
</div>
<p>这类似于 <code class="docutils literal notranslate"><span class="pre">sorted(iterable)</span></code>,但与 <a class="reference internal" href="functions.html#sorted" title="sorted"><code class="xref py py-func docutils literal notranslate"><span class="pre">sorted()</span></code></a> 不同的是这个实现是不稳定的。</p>
<p>堆元素可以为元组。 这适用于将比较值(例如任务优先级)与跟踪的主记录进行赋值的场合:</p>
<div class="highlight-python3 notranslate"><div class="highlight"><pre><span></span><span class="gp">&gt;&gt;&gt; </span><span class="n">h</span> <span class="o">=</span> <span class="p">[]</span>
<span class="gp">&gt;&gt;&gt; </span><span class="n">heappush</span><span class="p">(</span><span class="n">h</span><span class="p">,</span> <span class="p">(</span><span class="mi">5</span><span class="p">,</span> <span class="s1">&#39;write code&#39;</span><span class="p">))</span>
<span class="gp">&gt;&gt;&gt; </span><span class="n">heappush</span><span class="p">(</span><span class="n">h</span><span class="p">,</span> <span class="p">(</span><span class="mi">7</span><span class="p">,</span> <span class="s1">&#39;release product&#39;</span><span class="p">))</span>
<span class="gp">&gt;&gt;&gt; </span><span class="n">heappush</span><span class="p">(</span><span class="n">h</span><span class="p">,</span> <span class="p">(</span><span class="mi">1</span><span class="p">,</span> <span class="s1">&#39;write spec&#39;</span><span class="p">))</span>
<span class="gp">&gt;&gt;&gt; </span><span class="n">heappush</span><span class="p">(</span><span class="n">h</span><span class="p">,</span> <span class="p">(</span><span class="mi">3</span><span class="p">,</span> <span class="s1">&#39;create tests&#39;</span><span class="p">))</span>
<span class="gp">&gt;&gt;&gt; </span><span class="n">heappop</span><span class="p">(</span><span class="n">h</span><span class="p">)</span>
<span class="go">(1, &#39;write spec&#39;)</span>
</pre></div>
</div>
</section>
<section id="priority-queue-implementation-notes">
<h2>优先队列实现说明<a class="headerlink" href="#priority-queue-implementation-notes" title="永久链接至标题"></a></h2>
<p><a class="reference external" href="https://en.wikipedia.org/wiki/Priority_queue">优先队列</a> 是堆的常用场合,并且它的实现包含了多个挑战:</p>
<ul class="simple">
<li><p>排序稳定性:你该如何令相同优先级的两个任务按它们最初被加入时的顺序返回?</p></li>
<li><p>如果优先级相同且任务没有默认比较顺序,则 (priority, task) 对的元组比较将会中断。</p></li>
<li><p>如果任务优先级发生改变,你该如何将其移至堆中的新位置?</p></li>
<li><p>或者如果一个挂起的任务需要被删除,你该如何找到它并将其移出队列?</p></li>
</ul>
<p>针对前两项挑战的一种解决方案是将条目保存为包含优先级、条目计数和任务对象 3 个元素的列表。 条目计数可用来打破平局,这样具有相同优先级的任务将按它们的添加顺序返回。 并且由于没有哪两个条目计数是相同的,元组比较将永远不会直接比较两个任务。</p>
<p>不可比较任务问题的另一种解决方案是创建一个忽略任务条目并且只比较优先级字段的包装器类:</p>
<div class="highlight-python3 notranslate"><div class="highlight"><pre><span></span><span class="kn">from</span> <span class="nn">dataclasses</span> <span class="kn">import</span> <span class="n">dataclass</span><span class="p">,</span> <span class="n">field</span>
<span class="kn">from</span> <span class="nn">typing</span> <span class="kn">import</span> <span class="n">Any</span>
<span class="nd">@dataclass</span><span class="p">(</span><span class="n">order</span><span class="o">=</span><span class="kc">True</span><span class="p">)</span>
<span class="k">class</span> <span class="nc">PrioritizedItem</span><span class="p">:</span>
<span class="n">priority</span><span class="p">:</span> <span class="nb">int</span>
<span class="n">item</span><span class="p">:</span> <span class="n">Any</span><span class="o">=</span><span class="n">field</span><span class="p">(</span><span class="n">compare</span><span class="o">=</span><span class="kc">False</span><span class="p">)</span>
</pre></div>
</div>
<p>其余的挑战主要包括找到挂起的任务并修改其优先级或将其完全移除。 找到一个任务可使用一个指向队列中条目的字典来实现。</p>
<p>移除条目或改变其优先级的操作实现起来更为困难,因为它会破坏堆结构不变量。 因此,一种可能的解决方案是将条目标记为已移除,再添加一个改变了优先级的新条目:</p>
<div class="highlight-python3 notranslate"><div class="highlight"><pre><span></span><span class="n">pq</span> <span class="o">=</span> <span class="p">[]</span> <span class="c1"># list of entries arranged in a heap</span>
<span class="n">entry_finder</span> <span class="o">=</span> <span class="p">{}</span> <span class="c1"># mapping of tasks to entries</span>
<span class="n">REMOVED</span> <span class="o">=</span> <span class="s1">&#39;&lt;removed-task&gt;&#39;</span> <span class="c1"># placeholder for a removed task</span>
<span class="n">counter</span> <span class="o">=</span> <span class="n">itertools</span><span class="o">.</span><span class="n">count</span><span class="p">()</span> <span class="c1"># unique sequence count</span>
<span class="k">def</span> <span class="nf">add_task</span><span class="p">(</span><span class="n">task</span><span class="p">,</span> <span class="n">priority</span><span class="o">=</span><span class="mi">0</span><span class="p">):</span>
<span class="s1">&#39;Add a new task or update the priority of an existing task&#39;</span>
<span class="k">if</span> <span class="n">task</span> <span class="ow">in</span> <span class="n">entry_finder</span><span class="p">:</span>
<span class="n">remove_task</span><span class="p">(</span><span class="n">task</span><span class="p">)</span>
<span class="n">count</span> <span class="o">=</span> <span class="nb">next</span><span class="p">(</span><span class="n">counter</span><span class="p">)</span>
<span class="n">entry</span> <span class="o">=</span> <span class="p">[</span><span class="n">priority</span><span class="p">,</span> <span class="n">count</span><span class="p">,</span> <span class="n">task</span><span class="p">]</span>
<span class="n">entry_finder</span><span class="p">[</span><span class="n">task</span><span class="p">]</span> <span class="o">=</span> <span class="n">entry</span>
<span class="n">heappush</span><span class="p">(</span><span class="n">pq</span><span class="p">,</span> <span class="n">entry</span><span class="p">)</span>
<span class="k">def</span> <span class="nf">remove_task</span><span class="p">(</span><span class="n">task</span><span class="p">):</span>
<span class="s1">&#39;Mark an existing task as REMOVED. Raise KeyError if not found.&#39;</span>
<span class="n">entry</span> <span class="o">=</span> <span class="n">entry_finder</span><span class="o">.</span><span class="n">pop</span><span class="p">(</span><span class="n">task</span><span class="p">)</span>
<span class="n">entry</span><span class="p">[</span><span class="o">-</span><span class="mi">1</span><span class="p">]</span> <span class="o">=</span> <span class="n">REMOVED</span>
<span class="k">def</span> <span class="nf">pop_task</span><span class="p">():</span>
<span class="s1">&#39;Remove and return the lowest priority task. Raise KeyError if empty.&#39;</span>
<span class="k">while</span> <span class="n">pq</span><span class="p">:</span>
<span class="n">priority</span><span class="p">,</span> <span class="n">count</span><span class="p">,</span> <span class="n">task</span> <span class="o">=</span> <span class="n">heappop</span><span class="p">(</span><span class="n">pq</span><span class="p">)</span>
<span class="k">if</span> <span class="n">task</span> <span class="ow">is</span> <span class="ow">not</span> <span class="n">REMOVED</span><span class="p">:</span>
<span class="k">del</span> <span class="n">entry_finder</span><span class="p">[</span><span class="n">task</span><span class="p">]</span>
<span class="k">return</span> <span class="n">task</span>
<span class="k">raise</span> <span class="ne">KeyError</span><span class="p">(</span><span class="s1">&#39;pop from an empty priority queue&#39;</span><span class="p">)</span>
</pre></div>
</div>
</section>
<section id="theory">
<h2>理论<a class="headerlink" href="#theory" title="永久链接至标题"></a></h2>
<p>堆是通过数组来实现的,其中的元素从 0 开始计数,对于所有的 <em>k</em> 都有 <code class="docutils literal notranslate"><span class="pre">a[k]</span> <span class="pre">&lt;=</span> <span class="pre">a[2*k+1]</span></code><code class="docutils literal notranslate"><span class="pre">a[k]</span> <span class="pre">&lt;=</span> <span class="pre">a[2*k+2]</span></code>。 为了便于比较,不存在的元素被视为无穷大。 堆最有趣的特性在于 <code class="docutils literal notranslate"><span class="pre">a[0]</span></code> 总是其中最小的元素。</p>
<p>上面的特殊不变量是用来作为一场锦标赛的高效内存表示。 下面的数字是 <em>k</em> 而不是 <code class="docutils literal notranslate"><span class="pre">a[k]</span></code>:</p>
<div class="highlight-python3 notranslate"><div class="highlight"><pre><span></span> <span class="mi">0</span>
<span class="mi">1</span> <span class="mi">2</span>
<span class="mi">3</span> <span class="mi">4</span> <span class="mi">5</span> <span class="mi">6</span>
<span class="mi">7</span> <span class="mi">8</span> <span class="mi">9</span> <span class="mi">10</span> <span class="mi">11</span> <span class="mi">12</span> <span class="mi">13</span> <span class="mi">14</span>
<span class="mi">15</span> <span class="mi">16</span> <span class="mi">17</span> <span class="mi">18</span> <span class="mi">19</span> <span class="mi">20</span> <span class="mi">21</span> <span class="mi">22</span> <span class="mi">23</span> <span class="mi">24</span> <span class="mi">25</span> <span class="mi">26</span> <span class="mi">27</span> <span class="mi">28</span> <span class="mi">29</span> <span class="mi">30</span>
</pre></div>
</div>
<p>在上面的树中,每个 <em>k</em> 单元都位于 <code class="docutils literal notranslate"><span class="pre">2*k+1</span></code><code class="docutils literal notranslate"><span class="pre">2*k+2</span></code> 之上。 体育运动中我们经常见到二元锦标赛模式,每个胜者单元都位于另两个单元之上,并且我们可以沿着树形图向下追溯胜者所遇到的所有对手。 但是,在许多采用这种锦标赛模式的计算机应用程序中,我们并不需要追溯胜者的历史。 为了获得更高的内存利用效率,当一个胜者晋级时,我们会用较低层级的另一条目来替代它,因此规则变为一个单元和它之下的两个单元包含三个不同条目,上方单元“胜过”了两个下方单元。</p>
<p>如果此堆的不变性质始终受到保护,则序号 0 显然是总的赢家。 删除它并找出“下一个”赢家的最简单算法方式是将某个输家(让我们假定是上图中的 30 号单元)移至 0 号位置,然后将这个新的 0 号沿树下行,不断进行值的交换,直到不变性质得到重建。 这显然会是树中条目总数的对数。 通过迭代所有条目,你将得到一个 O(n log n) 复杂度的排序。</p>
<p>此排序有一个很好的特性就是你可以在排序进行期间高效地插入新条目,前提是插入的条目不比你最近取出的 0 号元素“更好”。 这在模拟上下文时特别有用,在这种情况下树保存的是所有传入事件,“胜出”条件是最小调度时间。 当一个事件将其他事件排入执行计划时,它们的调试时间向未来方向延长,这样它们可方便地入堆。 因此,堆结构很适宜用来实现调度器,我的 MIDI 音序器就是用的这个 :-)。</p>
<p>用于实现调度器的各种结构都得到了充分的研究,堆是非常适宜的一种,因为它们的速度相当快,并且几乎是恒定的,最坏的情况与平均情况没有太大差别。 虽然还存在其他总体而言更高效的实现方式,但其最坏的情况却可能非常糟糕。</p>
<p>堆在大磁盘排序中也非常有用。 你应该已经了解大规模排序会有多个“运行轮次”(即预排序的序列,其大小通常与 CPU 内存容量相关),随后这些轮次会进入合并通道,轮次合并的组织往往非常巧妙 <a class="footnote-reference brackets" href="#id2" id="id1">1</a>。 非常重要的一点是初始排序应产生尽可能长的运行轮次。 锦标赛模式是达成此目标的好办法。 如果你使用全部有用内存来进行锦标赛,替换和安排恰好适合当前运行轮次的条目,你将可以对于随机输入生成两倍于内存大小的运行轮次,对于模糊排序的输入还会有更好的效果。</p>
<p>另外,如果你输出磁盘上的第 0 个条目并获得一个可能不适合当前锦标赛的输入(因为其值要“胜过”上一个输出值),它无法被放入堆中,因此堆的尺寸将缩小。 被释放的内存可以被巧妙地立即重用以逐步构建第二个堆,其增长速度与第一个堆的缩减速度正好相同。 当第一个堆完全消失时,你可以切换新堆并启动新的运行轮次。 这样做既聪明又高效!</p>
<p>总之,堆是值得了解的有用内存结构。 我在一些应用中用到了它们,并且认为保留一个 'heap' 模块是很有意义的。 :-)</p>
<p class="rubric">备注</p>
<dl class="footnote brackets">
<dt class="label" id="id2"><span class="brackets"><a class="fn-backref" href="#id1">1</a></span></dt>
<dd><p>当前时代的磁盘平衡算法与其说是巧妙,不如说是麻烦,这是由磁盘的寻址能力导致的结果。 在无法寻址的设备例如大型磁带机上,情况则相当不同,开发者必须非常聪明地(极为提前地)确保每次磁带转动都尽可能地高效(就是说能够最好地加入到合并“进程”中)。 有些磁带甚至能够反向读取,这也被用来避免倒带的耗时。 请相信我,真正优秀的磁带机排序看起来是极其壮观的,排序从来都是一门伟大的艺术! :-)</p>
</dd>
</dl>
</section>
</section>
</div>
</div>
</div>
<div class="sphinxsidebar" role="navigation" aria-label="main navigation">
<div class="sphinxsidebarwrapper">
<h3><a href="../contents.html">目录</a></h3>
<ul>
<li><a class="reference internal" href="#"><code class="xref py py-mod docutils literal notranslate"><span class="pre">heapq</span></code> --- 堆队列算法</a><ul>
<li><a class="reference internal" href="#basic-examples">基本示例</a></li>
<li><a class="reference internal" href="#priority-queue-implementation-notes">优先队列实现说明</a></li>
<li><a class="reference internal" href="#theory">理论</a></li>
</ul>
</li>
</ul>
<h4>上一个主题</h4>
<p class="topless"><a href="collections.abc.html"
title="上一章"><code class="xref py py-mod docutils literal notranslate"><span class="pre">collections.abc</span></code> --- 容器的抽象基类</a></p>
<h4>下一个主题</h4>
<p class="topless"><a href="bisect.html"
title="下一章"><code class="xref py py-mod docutils literal notranslate"><span class="pre">bisect</span></code> --- 数组二分查找算法</a></p>
<div role="note" aria-label="source link">
<h3>本页</h3>
<ul class="this-page-menu">
<li><a href="../bugs.html">报告 Bug</a></li>
<li>
<a href="https://github.com/python/cpython/blob/3.8/Doc/library/heapq.rst"
rel="nofollow">显示源代码
</a>
</li>
</ul>
</div>
</div>
</div>
<div class="clearer"></div>
</div>
<div class="related" role="navigation" aria-label="related navigation">
<h3>导航</h3>
<ul>
<li class="right" style="margin-right: 10px">
<a href="../genindex.html" title="总目录"
>索引</a></li>
<li class="right" >
<a href="../py-modindex.html" title="Python 模块索引"
>模块</a> |</li>
<li class="right" >
<a href="bisect.html" title="bisect --- 数组二分查找算法"
>下一页</a> |</li>
<li class="right" >
<a href="collections.abc.html" title="collections.abc --- 容器的抽象基类"
>上一页</a> |</li>
<li><img src="../_static/py.svg" alt="python logo" style="vertical-align: middle; margin-top: -1px"/></li>
<li><a href="https://www.python.org/">Python</a> &#187;</li>
<li class="switchers">
<div class="language_switcher_placeholder"></div>
<div class="version_switcher_placeholder"></div>
</li>
<li>
</li>
<li id="cpython-language-and-version">
<a href="../index.html">3.8.20 Documentation</a> &#187;
</li>
<li class="nav-item nav-item-1"><a href="index.html" >Python 标准库</a> &#187;</li>
<li class="nav-item nav-item-2"><a href="datatypes.html" >数据类型</a> &#187;</li>
<li class="right">
<div class="inline-search" role="search">
<form class="inline-search" action="../search.html" method="get">
<input placeholder="快速搜索" aria-label="快速搜索" type="text" name="q" />
<input type="submit" value="转向" />
<input type="hidden" name="check_keywords" value="yes" />
<input type="hidden" name="area" value="default" />
</form>
</div>
|
</li>
</ul>
</div>
<div class="footer">
&copy; <a href="../copyright.html">版权所有</a> 2001-2024, Python Software Foundation.
<br />
This page is licensed under the Python Software Foundation License Version 2.
<br />
Examples, recipes, and other code in the documentation are additionally licensed under the Zero Clause BSD License.
<br />
<br />
The Python Software Foundation is a non-profit corporation.
<a href="https://www.python.org/psf/donations/">Please donate.</a>
<br />
<br />
最后更新于 12月 09, 2024.
<a href="https://docs.python.org/3/bugs.html">Found a bug</a>?
<br />
Created using <a href="https://www.sphinx-doc.org/">Sphinx</a> 2.4.4.
</div>
</body>
</html>