您无法真正将基于k 的奇偶性更改交换行为的原因与算法作为一个整体工作的解释/理由分开,这是一个复杂的演示,但基本上,它与generate 对底层数组的不同副作用有关,具体取决于k 是偶数还是奇数,并以交替方式利用这些副作用来探索整个排列空间。这些副作用及其相互作用在数学上有点复杂,因此,为了简化讨论,首先我将把算法重新编写成一个完全等效的形式,这样更容易推理。我们没有将第一次调用 generate 放在 for 循环之外并从 0 迭代到 k-1,而是将它放在里面,从 0 迭代到 k,并提前退出最后一次迭代(以避免不必要的交换已经生成了当前k 值的所有输出)。
procedure generate(k : integer, A : array of any):
if k = 1:
output(A)
else:
for i := 0; i < k; i += 1 do:
generate(k - 1, A)
if i + 1 = k:
break
if k is even:
swap(A[i], A[k-1])
else:
swap(A[0], A[k-1])
但是,通过删除 break 语句让不必要的交换发生在最终循环迭代的尾端会产生相同的排列(尽管顺序不同),并使程序每次迭代的副作用显着更容易推理:
procedure generate(k : integer, A : array of any):
if k = 1:
output(A)
else:
for i := 0; i < k; i += 1 do:
generate(k - 1, A)
if k is even:
swap(A[i], A[k-1])
else:
swap(A[0], A[k-1])
除此之外,这里是堆算法的基本思想(任何形式):
使用给定的k 值调用 generate 函数意味着置换数组的前 k 个元素(从索引 k 开始的所有元素都必须是固定的,因为没有交换涉及超过 k-1 的索引)。为了置换这些前 k 个元素,我们对 generate(k-1, A) 进行 k 次递归调用,并且在每次进行这些递归调用之前,我们希望从要置换的段中交换一个 唯一元素它的结尾(即进入第 k 个位置:索引 k-1),因为将不同的元素放在所有大小为 k-1 的排列的后面有效地生成所有大小为 k 的排列。我们从哪里交换来选择这个唯一元素的条件选择完全取决于调用 generate(k-1, A) 对底层数组的不同副作用,这取决于 k-1 是偶数还是奇数,但要点是相同的:选择一个我们还没有在for循环中使用的元素,并在我们生成下一批大小为k-1的排列之前将它交换回数组的第k个位置。
这些交换在这个算法的修正版本中起作用的原因实际上非常简单。调用generate(k-1, A)(其中k-1 是偶数)的副作用是将数组的第一个k-1 元素向右旋转一个位置,并且调用generate(k-1, A) 和奇数k-1 实际上没有副作用元素顺序:数组的第一个 k-1 元素在调用之前完全位于它们所在的位置。
以偶数k为例,如果我们在数组[1,2,3,4,5,...]上连续调用generate(4, A),前4个元素会像这样循环(并且它们后面的所有内容都是固定的):
[1,2,3,4,...]
[4,1,2,3,...]
[3,4,1,2,...]
[2,3,4,1,...]
[1,2,3,4,...]
...
如果我们在 [1,2,3,4,5,6,...] 上调用 generate(5, A),则数组顺序方面的副作用是无操作(前 5 个元素的所有排列仍由递归调用生成,只是额外的当我们删除 break 语句时进行的交换消除了排序的副作用)。
[1,2,3,4,5,6,...]
[1,2,3,4,5,6,...]
...
条件互换策略直接源于以下事实:
当k 是偶数时,我们用奇数调用generate(k-1, A),它没有排序副作用,所以如果我们想在每次迭代中选择不同的元素交换到后面,我们可以使用@987654351 @,因为i 每次迭代都会递增,而其他元素不会移动。
另一方面,当k 为奇数时,我们用偶数调用generate(k-1, A),这具有将元素向右旋转一步的副作用。这就是为什么我们只是重复地从A[0] 中拉取:循环中对generate 的偶数调用的副作用为我们完成了循环元素的工作:我们每次都可以从第一个位置抓取元素,因为side-效果在每次迭代时将不同的元素放入该位置。
基本上,如果我们想在 for 循环中获取第一个 k 元素中的每一个并且它们是静态定位的,我们必须使用 i 的每个值,并且如果它们已经在循环,我们每次都必须从同一个位置抓取。它们是静态定位还是循环已经取决于generate 基于k 是偶数还是奇数的不同排序副作用。当您添加 break 时,偶数/奇数 k 的副作用的数学运算、这些副作用产生的循环以及这些特定规则起作用的原因会发生一些变化,但相同的规则仍然有效,并且你必须做的分析的一般形式来证明它也保持不变。