<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>算法 on Nekonull's Garden</title><link>https://nekonull.me/tags/%E7%AE%97%E6%B3%95/</link><description>Recent content in 算法 on Nekonull's Garden</description><generator>Hugo</generator><language>zh-CN</language><copyright>CC-BY-SA-4.0</copyright><lastBuildDate>Tue, 10 May 2022 22:54:00 +0800</lastBuildDate><atom:link href="https://nekonull.me/tags/%E7%AE%97%E6%B3%95/index.xml" rel="self" type="application/rss+xml"/><item><title>Python `itertools.permutations` 使用的排列算法</title><link>https://nekonull.me/posts/py-itertools-permutation-algo/</link><pubDate>Tue, 10 May 2022 22:54:00 +0800</pubDate><guid>https://nekonull.me/posts/py-itertools-permutation-algo/</guid><description>&lt;p&gt;&lt;strong&gt;TL;DR&lt;/strong&gt;：这本质上是一个基于回溯，利用元素交换的递归排列生成算法，但被重写成了循环形式（可能出于效率考量）。&lt;/p&gt;
&lt;h2 id="引子"&gt;引子&lt;a class="td-heading-self-link" href="#%e5%bc%95%e5%ad%90" aria-label="标题锚点"&gt;&lt;/a&gt;
&lt;/h2&gt;
&lt;p&gt;最近在算法复健，刷到了排列相关的题目。恰巧 Python 内置了一个非常实用的工具库 &lt;code&gt;itertools&lt;/code&gt;，其中有一个 &lt;code&gt;permutations(iterable, r)&lt;/code&gt; 方法，可以对一个给定的 iterable 生成所有大小为 &lt;code&gt;r&lt;/code&gt; 的排列，且输出按照字典序排列。&lt;/p&gt;
&lt;blockquote&gt;
&lt;div class="td-code td-code--untitled" id="td-code-9daaf0de-fence-0" data-td-code data-td-code-auto-id
data-td-language="Python" data-td-line-count="5"&gt;
&lt;div class="td-code__viewport" id="td-code-9daaf0de-fence-0-viewport" data-td-code-viewport&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-Python" data-lang="Python"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;permutations&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;ABCD&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;[(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;A&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;B&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;A&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;C&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;A&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;D&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;B&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;A&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;B&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;C&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;B&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;D&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;C&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;A&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;C&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;B&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;C&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;D&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;D&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;A&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;D&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;B&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;D&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;C&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/blockquote&gt;
&lt;p&gt;在我之前所接触的算法中，排列生成要么是基于回溯，要么是基于字典序，但无论哪种都只能生成全排列，而无法生成这样的部分排列（指生成的排列长度 &lt;code&gt;r&lt;/code&gt; 和原输入长度 &lt;code&gt;n&lt;/code&gt; 不同）。另一条思路是先生成所有长度为 &lt;code&gt;r&lt;/code&gt; 的组合，然后再在每个组合内生成全排列，但这样无法保证输出按字典序（除非先手动收集再排序）。&lt;/p&gt;
&lt;p&gt;于是我打开了 Python 的 &lt;code&gt;itertools&lt;/code&gt; 的官方文档，其中提供了与 CPython 实现等价的 Python 代码，&lt;code&gt;permutations&lt;/code&gt; 方法的代码如下（&lt;a href="https://docs.python.org/zh-cn/3/library/itertools.html#itertools.permutations"&gt;🔗&lt;/a&gt;）：&lt;/p&gt;
&lt;div class="td-code td-code--untitled" id="td-code-9daaf0de-fence-1" data-td-code data-td-code-auto-id
data-td-language="python" data-td-line-count="24"&gt;
&lt;div class="td-code__viewport" id="td-code-9daaf0de-fence-1-viewport" data-td-code-viewport&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;permutations&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;iterable&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;None&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;# permutations(&amp;#39;ABCD&amp;#39;, 2) --&amp;gt; AB AC AD BA BC BD CA CB CD DA DB DC&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;# permutations(range(3)) --&amp;gt; 012 021 102 120 201 210&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;pool&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;tuple&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;iterable&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pool&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;indices&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;cycles&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;yield&lt;/span&gt; &lt;span class="nb"&gt;tuple&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pool&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;indices&lt;/span&gt;&lt;span class="p"&gt;[:&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nb"&gt;reversed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;)):&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;cycles&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;-=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;cycles&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;indices&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;:]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;indices&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;:]&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;indices&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;cycles&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;j&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;cycles&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;indices&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;indices&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;j&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;indices&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;j&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;indices&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;yield&lt;/span&gt; &lt;span class="nb"&gt;tuple&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pool&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;indices&lt;/span&gt;&lt;span class="p"&gt;[:&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;break&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;h3 id="尝试初步理解"&gt;尝试初步理解&lt;a class="td-heading-self-link" href="#%e5%b0%9d%e8%af%95%e5%88%9d%e6%ad%a5%e7%90%86%e8%a7%a3" aria-label="标题锚点"&gt;&lt;/a&gt;
&lt;/h3&gt;
&lt;p&gt;虽然顶部有两行注释，不过这也只是描述了这个方法的效果，对其原理并没有任何解释。往后继续看，可以发现算法首先构造了两个 list：&lt;code&gt;indices&lt;/code&gt; 和 &lt;code&gt;cycles&lt;/code&gt;，且之后每次输出结果（&lt;code&gt;yield&lt;/code&gt;）实际上都是将 &lt;code&gt;indices&lt;/code&gt; 中的前 &lt;code&gt;r&lt;/code&gt; 个输出。再往后算法进入了一个神秘的 &lt;code&gt;for&lt;/code&gt; 循环，对 &lt;code&gt;cycles&lt;/code&gt; 中的元素做了一些修改，用 &lt;code&gt;cycles&lt;/code&gt; 的值对 &lt;code&gt;indices&lt;/code&gt; 中的一些元素做了交换。仅从代码层面出发，对算法的理解似乎也就止步于此了。然而这并没有回答一个重要问题：为什么这个算法能工作？&lt;/p&gt;
&lt;h3 id="寻找相关信息"&gt;寻找相关信息&lt;a class="td-heading-self-link" href="#%e5%af%bb%e6%89%be%e7%9b%b8%e5%85%b3%e4%bf%a1%e6%81%af" aria-label="标题锚点"&gt;&lt;/a&gt;
&lt;/h3&gt;
&lt;p&gt;考虑到 &lt;code&gt;itertools&lt;/code&gt; 库是在 Python 2.3 （2003 年 7 月）被引入标准库的，这个算法已经算得上历史悠久了。再加之 &lt;code&gt;itertools&lt;/code&gt; 库的广泛使用，这个算法的原理应该是早已被详尽记录的。带着这样的期望，我开始用各种关键词组合搜索相关信息。可惜的是，除了 Stack Overflow 上一个 2010 年的问题（&lt;a href="https://stackoverflow.com/questions/2565619/algorithm-for-python-itertools-permutations"&gt;🔗&lt;/a&gt;），和一个知乎提问（&lt;a href="https://www.zhihu.com/question/440688033"&gt;🔗&lt;/a&gt;），就没有任何其他相关的网页了，甚至连当年的提交记录都找不到。&lt;/p&gt;
&lt;p&gt;既然如此，那就只能从这两个链接入手了。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Stack Overflow 的答主 Alex 写了一篇长文回答，但一开头就说「你需要理解 Permutation Cycle 的数学理论，才能理解这个算法」。于是我又回头恶补了一番组合数学，然而就算我大概知道 Permutation Cycle 是怎么一回事，对这个算法依然毫无头绪。（评论中也有人自称有 6 年抽象代数经验，但不认为这个算法与数学相关。）不过 Alex 的回答提供了一些有用的直觉，至少读完这个回答我大概理解 &lt;code&gt;cycles&lt;/code&gt; 在算法执行过程中是如何变化的了。具体而言，&lt;code&gt;cycles&lt;/code&gt; 的变化类似于「倒计时」，或者说「带借位的减法」，每次最后一位到 0 时，就会触发前面的一位减 1。然而我对&lt;code&gt;indices&lt;/code&gt; 的变化依然云里雾里。&lt;/li&gt;
&lt;li&gt;知乎提问中，答主「杨个毛」提供了一个看起来很有说服力的回答（剧透：而且也的确如此）：「那个代码可以看成下列递归程序的强行非递归版」，指出了 &lt;code&gt;cycles&lt;/code&gt; 是&lt;strong&gt;变进制数&lt;/strong&gt;，并提供了一个原始递归版本。可惜的是，对于这个算法为什么可以输出正确结果依然没有解释（也有可能是我没有完全理解）。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="自己来"&gt;自己来&lt;a class="td-heading-self-link" href="#%e8%87%aa%e5%b7%b1%e6%9d%a5" aria-label="标题锚点"&gt;&lt;/a&gt;
&lt;/h3&gt;
&lt;p&gt;已有的信息似乎不是很充分。看来我只能自己来了。在加了一堆 &lt;code&gt;print&lt;/code&gt; 并在纸上手动模拟了多次这个算法之后，我认为我可能大概理解它的工作原理，并且可以证明其正确性了。下文将详述我的理解。&lt;/p&gt;
&lt;h2 id="算法"&gt;算法&lt;a class="td-heading-self-link" href="#%e7%ae%97%e6%b3%95" aria-label="标题锚点"&gt;&lt;/a&gt;
&lt;/h2&gt;
&lt;h3 id="起步"&gt;起步&lt;a class="td-heading-self-link" href="#%e8%b5%b7%e6%ad%a5" aria-label="标题锚点"&gt;&lt;/a&gt;
&lt;/h3&gt;
&lt;p&gt;开始前，需要统一一下后文使用的记号：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;n&lt;/code&gt; 代表输入 iterable 的长度&lt;/li&gt;
&lt;li&gt;&lt;code&gt;r&lt;/code&gt; 代表输出的排列元组的长度&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;并回顾我们先前阅读算法得到的理解：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;对输入的 &lt;code&gt;iterable&lt;/code&gt; 会遍历其所有元素并放在 &lt;code&gt;pool&lt;/code&gt; 中&lt;/li&gt;
&lt;li&gt;每次算法输出结果（&lt;code&gt;yield&lt;/code&gt;），实际上只是取了 &lt;code&gt;indices&lt;/code&gt; 列表的前 &lt;code&gt;r&lt;/code&gt; 个 index，并输出 &lt;code&gt;pool&lt;/code&gt; 中的对应元素。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;我们将按照如下步骤理解这个算法：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;理解 &lt;code&gt;cycles&lt;/code&gt; 的变化&lt;/li&gt;
&lt;li&gt;理解 &lt;code&gt;indices&lt;/code&gt; 的变化，并尝试说明这个算法的正确性&lt;/li&gt;
&lt;li&gt;尝试重新实现这个算法的「原始」递归版本&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id="cycles"&gt;&lt;code&gt;cycles&lt;/code&gt;&lt;a class="td-heading-self-link" href="#cycles" aria-label="标题锚点"&gt;&lt;/a&gt;
&lt;/h3&gt;
&lt;p&gt;我们首先从 &lt;code&gt;cycles&lt;/code&gt; 变量入手，理解它在这个算法中是如何变化的。这个阶段我们暂时先不考虑 &lt;code&gt;indices&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;可以先指定一些具体的输入，然后尝试加一些 &lt;code&gt;print&lt;/code&gt; 语句。以 &lt;code&gt;iterable=&amp;quot;ABCD&amp;quot;, r=2&lt;/code&gt; 作为输入，在 &lt;code&gt;if&lt;/code&gt; 和 &lt;code&gt;else&lt;/code&gt; 两个分支执行前后中都插入 &lt;code&gt;print&lt;/code&gt;，可以得到如下结果：（其中中括号说明算法有输出 &lt;code&gt;yield&lt;/code&gt;，大括号部分算法无输出）&lt;/p&gt;
&lt;div class="td-code td-code--untitled" id="td-code-9daaf0de-fence-2" data-td-code data-td-code-auto-id
data-td-language="" data-td-line-count="4"&gt;
&lt;div class="td-code__viewport" id="td-code-9daaf0de-fence-2-viewport" data-td-code-viewport&gt;&lt;pre tabindex="0"&gt;&lt;code&gt;[4,3] -&amp;gt; [4,2] -&amp;gt; [4,1] -&amp;gt; {4,0} -&amp;gt; {4,3} -&amp;gt;
[3,3] -&amp;gt; [3,2] -&amp;gt; [3,1] -&amp;gt; {3,0} -&amp;gt; {3,3} -&amp;gt;
[2,3] -&amp;gt; [2,2] -&amp;gt; [2,1] -&amp;gt; {2,0} -&amp;gt; {2,3} -&amp;gt;
[1,3] -&amp;gt; [1,2] -&amp;gt; [1,1] -&amp;gt; {1,0} -&amp;gt; {1,3} -&amp;gt; {0,3} -&amp;gt; {4,3}&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;我们可以直观感受到，似乎 &lt;code&gt;cycles&lt;/code&gt; 变量就像一个「倒计时」，或者说「带借位的减法」。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;一开始 &lt;code&gt;cycles[0]&lt;/code&gt; 被初始化为 4，&lt;code&gt;cycles[1]&lt;/code&gt; 被初始化为 3。&lt;/li&gt;
&lt;li&gt;之后的循环中，一般是 &lt;code&gt;cycles[1]&lt;/code&gt; 不断被减 1。当 &lt;code&gt;cycles[1] != 0&lt;/code&gt; 时，算法会产生一次输出。&lt;/li&gt;
&lt;li&gt;如果 &lt;code&gt;cycles[1] == 0&lt;/code&gt;，会导致 &lt;code&gt;cycles[0]&lt;/code&gt; （前一位）减 1，并将 &lt;code&gt;cycles[1]&lt;/code&gt; 重设回其初始值 3。&lt;/li&gt;
&lt;li&gt;最后当 &lt;code&gt;cycles[0] == 0&lt;/code&gt; 时，算法结束。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;从这个具体的示例出发，我们可以这样理解 &lt;code&gt;cycles&lt;/code&gt; 的变化：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;一开始 &lt;code&gt;cycles&lt;/code&gt; 被初始化为 &lt;code&gt;range(n, n-r, -1)&lt;/code&gt;，即 &lt;code&gt;cycles[0]=n&lt;/code&gt;，&lt;code&gt;cycles[1]=n-1&lt;/code&gt;，&amp;hellip;，&lt;code&gt;cycles[i]=n-i&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;之后的循环中，通常只有最后一个元素 &lt;code&gt;cycles[r-1]&lt;/code&gt; 在不断递减。如果 &lt;code&gt;cycles[r-1]&lt;/code&gt; 递减后值不为 0，则算法会产生一次输出。用我们之前提到的「倒计时」类比，可以将这种情况称之为 tick。&lt;/li&gt;
&lt;li&gt;如果 &lt;code&gt;cycles&lt;/code&gt; 中的某个元素（如 &lt;code&gt;cycles[i]&lt;/code&gt;）为 0，会导致其前面的元素被（&lt;code&gt;cycles[i-1]&lt;/code&gt;）减 1，并将这个元素（&lt;code&gt;cycles[i]&lt;/code&gt;）重设回其初始值（&lt;code&gt;n-i&lt;/code&gt;）。这一行为类似于倒计时中秒为 0 时会导致分减 1，并将秒重设回 0。继续用「倒计时」类比，可以将这种情况称之为 reset。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;有了这一直观感受，就可以为 &lt;code&gt;cycles&lt;/code&gt; 找出一个可能的解释（「物理含义」）了。我认为，&lt;code&gt;cycles&lt;/code&gt; 代表的是 &lt;strong&gt;「每个位置上剩余的可用选择数」&lt;/strong&gt; 。如果将 &lt;code&gt;cycles&lt;/code&gt; 视作一个变进制数，则 &lt;code&gt;cycles&lt;/code&gt; 也代表 &lt;strong&gt;「总体剩余还没有输出的排列数」&lt;/strong&gt; 。理由如下：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;一开始 &lt;code&gt;cycles&lt;/code&gt; 被初始化为 &lt;code&gt;range(n, n-r, -1)&lt;/code&gt;，而可以计算出对给定的 &lt;code&gt;n&lt;/code&gt;, &lt;code&gt;r&lt;/code&gt; 所有的排列有 &lt;code&gt;P(n,r) = n*(n-1)*...*(n-r-1)&lt;/code&gt; 种。&lt;/li&gt;
&lt;li&gt;每次 tick，算法生成一个排列，消耗一个选择，&lt;code&gt;cycles[i]&lt;/code&gt; 也减 1&lt;/li&gt;
&lt;li&gt;每次 reset，实际上就是在 &lt;code&gt;cycles&lt;/code&gt; 这个变进制数上的借位减法&lt;/li&gt;
&lt;li&gt;最后在 &lt;code&gt;cycles&lt;/code&gt; 上首位为 0 时算法结束，代表所有排列的选择都已经被消耗（输出）了，已经没有更多的排列了。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;其实 &lt;code&gt;cycles&lt;/code&gt; 的变化，无论是 Stack Overflow 上的回答，还是知乎上的回答，都有相对详尽的描述。在此我只是尝试以自己的语言重述了一次而已。但接下来对 &lt;code&gt;indices&lt;/code&gt; 的理解就大部分是我自己的了。&lt;/p&gt;
&lt;h3 id="indices"&gt;&lt;code&gt;indices&lt;/code&gt;&lt;a class="td-heading-self-link" href="#indices" aria-label="标题锚点"&gt;&lt;/a&gt;
&lt;/h3&gt;
&lt;p&gt;现在我们来看看 &lt;code&gt;indices&lt;/code&gt; 是如何变化的。和之前对 &lt;code&gt;cycles&lt;/code&gt; 的探索一样，我们也先从一个具体的例子开始：&lt;code&gt;iterable=&amp;quot;ABCDE&amp;quot;,r=3&lt;/code&gt;，并关注一个子问题：前 3 个输出（ABC, ABD, ABE）是如何产生的。为便于展示，这里我直接使用具体元素（字母）代替 index。加了一些 &lt;code&gt;print&lt;/code&gt; 后，我们可以得到如下的变化过程。&lt;/p&gt;
&lt;img class="td-image" src="https://nekonull.me/img/py_permutation.png" alt="py_permutation" loading="lazy" decoding="async"&gt;&lt;p&gt;这个图稍微有些复杂。以下是进一步解释。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;图的阅读顺序是从上至下，按照时间顺序展示了&lt;code&gt;indices&lt;/code&gt; 列表的变化。左侧为各阶段具体行为的标注，右侧为算法输出。&lt;/li&gt;
&lt;li&gt;这里选择展示前 3 次输出（即 &lt;code&gt;i=2&lt;/code&gt;），对应 &lt;code&gt;cycles[2]&lt;/code&gt; 从 3 到 0 的过程。别忘了每次进入循环前 &lt;code&gt;cycles[2]&lt;/code&gt; 会被递减。&lt;/li&gt;
&lt;li&gt;为方便描述，我们可以把 &lt;code&gt;indices&lt;/code&gt; 列表拆成 3 个部分：
&lt;ul&gt;
&lt;li&gt;fixed：&lt;code&gt;indices[0:1]&lt;/code&gt;，在执行时不会变化&lt;/li&gt;
&lt;li&gt;changing：&lt;code&gt;indices[2]&lt;/code&gt;，这是在不断被交换的元素，和 fixed 一起组成了算法输出（&lt;code&gt;indices[0:2]&lt;/code&gt;）&lt;/li&gt;
&lt;li&gt;backlog：&lt;code&gt;indices[3:4]&lt;/code&gt;，这里存放着已经被使用的元素和还未被使用的元素&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;在每个 tick 中，changing 位置上的元素和 backlog 中的一个元素交换（&lt;code&gt;swap&lt;/code&gt;），并触发一次输出。可以注意到，交换前后 backlog 中元素依然维持其相对顺序。
&lt;ul&gt;
&lt;li&gt;ABC{DE} -&amp;gt; ABD{CE} -&amp;gt; ABE{CD}&lt;/li&gt;
&lt;li&gt;注：这里将算法开始时的首次输出也视作一个 tick，因为进入循环前依然触发了递减，消耗了一个可能性。&lt;/li&gt;
&lt;li&gt;相关代码：&lt;code&gt;indices[i], indices[-j] = indices[-j], indices[i]&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;算法结束前，一共触发了 3 次输出。这已经穷尽了固定前缀为 &lt;code&gt;AB&lt;/code&gt;，最后一个可选项为 &lt;code&gt;C&lt;/code&gt;, &lt;code&gt;D&lt;/code&gt;, &lt;code&gt;E&lt;/code&gt; 的排列。&lt;/li&gt;
&lt;li&gt;在最后 reset 时，changing 位置上的元素被移到了 backlog 后（&lt;code&gt;move_to_last&lt;/code&gt;）。这一步完成后，changing + backlog 这个子列表（&lt;code&gt;indices[2:4]&lt;/code&gt;）恢复了和算法开始时一样的顺序。
&lt;ul&gt;
&lt;li&gt;AB{E}CD -&amp;gt; ABCD{E}&lt;/li&gt;
&lt;li&gt;相关代码：&lt;code&gt;indices[i:] = indices[i+1:] + indices[i:i+1]&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;可以发现，这部分执行过程，恰好满足了回溯算法的正确性要求：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;分步解决一个问题，每步中固定之前步骤的选择不变&lt;/li&gt;
&lt;li&gt;算法执行中，遍历当前步的所有可能性&lt;/li&gt;
&lt;li&gt;算法执行后，取消当前步的计算，退回上一步，选择下一个可选项&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;虽然图中仅描述了一个子问题（&lt;code&gt;i=2&lt;/code&gt;，或者说&lt;code&gt;i=r-1&lt;/code&gt;），但不难发现对于其他的 &lt;code&gt;i&lt;/code&gt;∈&lt;code&gt;[0, r-1]&lt;/code&gt; 这一讨论都是成立的。这也（不严格地）说明了这一算法的确可以遍历所有的可能排列。输出顺序为字典序，则是因为每个 tick 中交换元素时都维护了 backlog 中的相对顺序。&lt;/p&gt;
&lt;p&gt;这部分讨论有些复杂，如果不太理解（或者不完全信服）的话，可以自己多加点 &lt;code&gt;print&lt;/code&gt; ，或者手动在纸上执行感受一下。&lt;/p&gt;
&lt;h3 id="重新实现"&gt;重新实现&lt;a class="td-heading-self-link" href="#%e9%87%8d%e6%96%b0%e5%ae%9e%e7%8e%b0" aria-label="标题锚点"&gt;&lt;/a&gt;
&lt;/h3&gt;
&lt;p&gt;现在我们已经了解了这个算法的原理，重新实现其原始递归版本也就不难了。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;递归调用时，需要一个参数指定当前修改的元素（changing）的 index&lt;/li&gt;
&lt;li&gt;在每一层调用中，交换 changing 和 backlog 中的所有项，并在每次交换（做出选择）后触发下一层&lt;/li&gt;
&lt;li&gt;如果 backlog 中的所有可选项都已经被选择（消耗）了，就把当前的 changing 移到 backlog 后，以撤销这一层的选择&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;以下是一个可能的 Python 重新实现。&lt;/p&gt;
&lt;div class="td-code td-code--untitled" id="td-code-9daaf0de-fence-3" data-td-code data-td-code-auto-id
data-td-language="python" data-td-line-count="47"&gt;
&lt;div class="td-code__viewport" id="td-code-9daaf0de-fence-3-viewport" data-td-code-viewport&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;## a reimplementation of `itertools.permutation`&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# helpers&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;swap&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;j&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;j&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;j&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;move_to_last&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;:]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;:]&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;]]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;print_first_n_element&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&amp;#34;&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;[:&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;]))&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# backtracking dfs&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;permutations&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;changing_index&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;changing_index&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;# we&amp;#39;ve reached the deepest level&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;print_first_n_element&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;# a pseudo `tick`&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;# process initial permutation&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;# which is just doing nothing (using the initial value)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;permutations&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;changing_index&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;# note: initial permutaion has been outputed, thus the minus 1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;remaining_choices&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;changing_index&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;# for (i=1;i&amp;lt;=remaining_choices;i++)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nb"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;remaining_choices&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;# `tick` phases&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;# make one swap&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;swap_idx&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;changing_index&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;swap&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;changing_index&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;swap_idx&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;# finished one move at current level, now go deeper&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;permutations&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;changing_index&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;# `reset` phase&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;move_to_last&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;changing_index&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# wrapper&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;permutations_wrapper&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;permutations&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# main&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="vm"&gt;__name__&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;__main__&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;my_list&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;A&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;B&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;C&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;D&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;permutations_wrapper&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;my_list&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;h3 id="递归转循环优化"&gt;递归转循环优化&lt;a class="td-heading-self-link" href="#%e9%80%92%e5%bd%92%e8%bd%ac%e5%be%aa%e7%8e%af%e4%bc%98%e5%8c%96" aria-label="标题锚点"&gt;&lt;/a&gt;
&lt;/h3&gt;
&lt;p&gt;出于性能和安全（防止爆栈）的考量，我们会想将这个算法的递归版本转换成循环版本。这需要我们用栈手动维护每一层递归的相关状态，包括递归中的变量和下一次执行的开始位置。幸运的是，对这个算法而言，我们需要维护的状态并不多。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;r&lt;/code&gt; 是已知且固定的，即栈的最大深度为 &lt;code&gt;r&lt;/code&gt;，因此可以用一个固定大小的列表来表示栈中（每一层递归）的状态。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;changing_idx&lt;/code&gt; 是每层递归的输入，代表当前层变化的元素。&lt;code&gt;changing_idx&lt;/code&gt; 可以从栈的深度计算出来，栈底为 0，再上一层为 1&amp;hellip; 因此实际上不需要维护。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;i&lt;/code&gt; 或 &lt;code&gt;swap_idx&lt;/code&gt; 为下一次交换的目标元素 index。这无法从栈本身的信息计算得到，需要我们维护。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;进入或重新回到当前层递归的开始位置，可以用 &lt;code&gt;i&lt;/code&gt; 或 &lt;code&gt;swap_idx&lt;/code&gt; 推算得到：如果还有剩余可选项，则跳转到 tick；反之跳转到 reset&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;基于上文分析，可以发现我们需要维护的栈有两个特点：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;长度固定为 &lt;code&gt;r&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;每个元素上维护下一次交换的目标元素 index&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;回头看看，这实际上就是 &lt;code&gt;cycles&lt;/code&gt;。在「剩余可能数」的身份之外，&lt;code&gt;cycles&lt;/code&gt; 也承担起了维护递归状态的职责。而作者巧妙利用了 Python 列表索引可以为负数从后往前的特性，统一了 &lt;code&gt;cycles&lt;/code&gt; 的两面。&lt;/p&gt;
&lt;p&gt;至此，我们完成了对这一算法的分析。🎉&lt;/p&gt;
&lt;h2 id="相关链接"&gt;相关链接&lt;a class="td-heading-self-link" href="#%e7%9b%b8%e5%85%b3%e9%93%be%e6%8e%a5" aria-label="标题锚点"&gt;&lt;/a&gt;
&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Python &lt;code&gt;itertools.permutation&lt;/code&gt; 文档：&lt;a href="https://docs.python.org/zh-cn/3/library/itertools.html#itertools.permutations"&gt;itertools &amp;mdash; 为高效循环而创建迭代器的函数 — Python 3.10.4 文档&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;CPython &lt;code&gt;itertools.permutation&lt;/code&gt; 实现：&lt;a href="https://github.com/python/cpython/blob/main/Modules/itertoolsmodule.c#L3212"&gt;cpython/itertoolsmodule.c at main · python/cpython (github.com)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Stack Overflow 相关问题：&lt;a href="https://stackoverflow.com/questions/2565619/algorithm-for-python-itertools-permutations"&gt;algorithm for python itertools.permutations - Stack Overflow&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;知乎相关问题：&lt;a href="https://www.zhihu.com/question/440688033"&gt;如何理解Python itertools.permutations中的全排列算法？ - 知乎 (zhihu.com)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;我给 Stack Overflow 问题写的回答：&lt;a href="https://stackoverflow.com/questions/2565619/algorithm-for-python-itertools-permutations/72177891#72177891"&gt;algorithm for python itertools.permutations - Stack Overflow&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description></item></channel></rss>