我找到了一篇文章,试图在这里解释它:
Heap算法为什么有效?
然而,我认为这篇文章很难理解,所以想出了一个希望更容易理解的解释:
请暂时假设这些陈述是正确的(稍后我会证明):
每次“generate”函数的调用
(I) 当n为奇数时,在结束时元素的顺序保持不变。
(II) 当n为偶数时,将元素向右旋转,例如ABCD变成DABC。
因此,在“for i”的循环中,
当n为偶数时,“generate(n - 1, A)”的递归调用不会改变顺序。因此,for循环可以迭代地交换i = 0..(n-1)处的元素与(n - 1)处的元素,并且每次都会调用“generate(n - 1, A)”并且缺少另一个元素。
当n为奇数时,“generate(n - 1, A)”的递归调用已将元素向右旋转。因此,索引0处的元素将始终自动成为另一个元素。只需在每次迭代中交换0和(n-1)处的元素即可生成唯一的元素集。
最后,让我们看看为什么初始陈述是正确的:
旋转到右侧
(III) 这些交换的系列导致向右旋转一个位置:
A[0] <-> A[n - 1]
A[1] <-> A[n - 1]
A[2] <-> A[n - 1]
...
A[n - 2] <-> A[n - 1]
例如,尝试使用序列ABCD:
A[0] <-> A[3]: DBCA
A[1] <-> A[3]: DACB
A[2] <-> A[3]: DABC
无操作
(IV) 这一系列步骤使得序列的顺序与之前完全相同:
Repeat n times:
Rotate the sub-sequence a[0...(n-2)] to the right
Swap: a[0] <-> a[n - 1]
直观上讲,这是正确的:
如果您有一个长度为5的序列,然后将其旋转5次,它最终保持不变。
在旋转之前取出0号元素,然后在旋转后将其与新的0号元素交换,不会改变结果(如果旋转n次)。
归纳法
现在我们可以看到为什么(I)和(II)是正确的:
如果n为1:
很明显,在调用函数后,排序不会改变。
如果n为2:
递归调用“generate(n - 1, A)”不会改变排序(因为它调用第一个参数为1的generate)。
所以我们可以忽略那些调用。
在此调用中执行的交换导致右旋,参见(III)。
如果n为3:
递归调用“generate(n - 1, A)”导致右旋。
因此,此调用中的总步骤等于(IV) => 序列未改变。
重复n = 4、5、6、...的操作。