缘起
在网上看到了字典序全排列生成算法,不懂为什么会这样可以算法可以生成当前序列的下一个字典序,于是便在思考能不能证明一下。
step1:
对于排列 a[0...n−1] ,找到所有满足 a[k]<a[k+1](−1<k<n−2) 的k的最大值,如果这样的k不存在,则说明当前排列已经是a的所有排列中字典序最大者,所有排列输出完毕。
step2:
在 a[k+1...n] 中,寻找满足这样条件的元素l,使得在所有 a[l]>a[k] 的元素中,a[l]取得最小值。也就是说 a[l]>a[k] ,但是小于所有其他大于a[k]的元素。
step3:
交换 a[l] 与 a[k] .
step4:
对于 a[k+1...n] ,反转该区间内元素的顺序。也就是说a[k+1]与a[n]交换,a[k+2]与a[n-1]交换,……,这样就得到了a[1…n]在字典序中的下一个排列。
预备知识
首先要了解什么是字典序:
设想一本英语字典里的单词,何者在前何者在后?
显然的做法是先按照第一个字母、以 a、b、c……z 的顺序排列;如果第一个字母一样,那么比较第二个、第三个乃至后面的字母。如果比到最后两个单词不一样长(比如,sigh 和 sight),那么把短者排在前。(维基百科)
如果你不满足于上面的简单理解,可以看在wikipedia的严格定义。
开始证明
首先对于一个字符串,每一个字符可能为c1、c2、c3……cm的m个有序字符中的一个。我们定义:
例如c3 - c1 = 第3个 - 第1个= 2.
假设我们现在又有两个长度为n的字符串A、B,其中A、B的每个字符有m种可能。定义:
显然,我们要证明字典序全排列生成算法有效,即此算法能生成的A的下一个字典序全排列,则需证明
证明step1
要使(*)成立,则A的某一位 A k 则必须被替换为 A j , 其中 A j >A k 且j>k 这样生成的 A next 才会使 d(A next ,A)>0 .
又因为 d(A next ,A) 尽量小,所以 A k 必须是序列A中满足 A i <A i+1 的下标最大(即最右边)的一个。这用反证法根据 d(A,B) 的定义可以证明。如找不到这样的 A i 说明A已经没有下一个更大的字典序全排列了。
证明step2和step3
step1后, A next 0 A next 1 ……A next k−1 都确定了,即 A 0 A 1 ……A k−1 ,否则 d(A next ,A) 将变大。我们此时能动的是 A k A k+1 ……A n−1 。
已知 A k 要被调换,为了使 d(A next ,A) 尽量小, A k 应该被调换为 A k+1 ……A n−1 中最小的一个。这用反证法根据 d(A,B) 的定义可以证明。
证明step4
step2和step3后, A next k 位置也被固定。还能动的是 A k+1 ……A n−1
我们注意到,在step1中:因为
A k
必须是序列A中满足
A i <A i+1
的下标最大(即最右边)的一个,
A k+1 到A n−2
都不符合这个性质,所以序列
A k+1 ……A n−1
是非递增的
显然,经过step2和step3后的
A k+1 ……A n−1
仍然是非递增的。
由于step2,step3已经保证
d(A next ,A)>0
,我们只需要重排
A i+1 ……A n−1
来使
d(A next ,A)
尽量小,即
上面说了:序列 A k+1 ……A n−1 是非递增的,所以我们只要将序列 A k+1 ……A n−1 逆转,便可以使上式最小。这样子权重越大,系数越小,从而保证上式最小。
这样
A next k+1 A next k+2 ……A next n−1 也确定了,它们是 A n−1 A n−2 ……A k+1 。
结论
最终 A next 的每一个字符都被确定下来,并且(*)成立,这说明上述算法确实是有效的。
思考
证明的时候进一步发现这个算法的思路是,在保证(*)成立的前提下,逐渐确定
A next
的每一个字符,最终确定了
A next
的所有字符,所得出得字典序全排列便是我们想要的。
这是局部最优达到全局最优的一个典型例子。
实现
具体实现在我的另一篇博客中。