除了 ccoakley 的好答案和 stubbscroll 的评论之外,还有一些关于具体示例和一些一般原则的评论。
关于 stubbscroll 说这个特定问题只有 9 个!= 362880 种不同的状态:
将排列编码为数字的一种(相当简单的)方法是通过字典顺序对排列进行索引。例如
0 1 2 3 --> 0
0 1 3 2 --> 1
0 2 1 3 --> 2
...
1 0 2 3 --> 6
1 0 3 2 --> 7
...
3 1 2 0 --> 21
3 2 0 1 --> 22
3 2 1 0 --> 23
诀窍是在阶乘基数中编写索引,
n = a_1 * 1! + a_2 * 2! + a_3 * 3! + a_4 * 4! + ...
哪里0 <= a_k <= k
。如果您有s
符号,则索引范围从 0 到 s!-1,因此您s-1
在 n, 的阶乘基展开式中有系数(a_1,a_2,...,a_(s-1))
。然后找到索引为 n 的排列如下
for i = 1 to s-1
the i-th symbol becomes the (a_(s-i)+1)-th smallest unused symbol
the last symbol is the left over one
既然不是特别清楚,举个例子。假设我们寻找索引 4231 为 {1,2,3,4,5,6,7,8} 的排列。首先我们将 4231 扩展为阶乘基数
4231 = 1 + 2*2115 : a_1 = 1
2115 = 0 + 3* 705 : a_2 = 0
705 = 1 + 4* 176 : a_3 = 1
176 = 1 + 5* 35 : a_4 = 1
35 = 5 + 6* 5 : a_5 = 5
5 = 5 + 7* 0 : a_6 = 5
所有进一步的系数(这里只是 a_7)都是 0。最好按照相反的顺序编写 a_i,(a_7,a_6,...a_1),所以
coefficients symbols choice
0,5,5,1,1,0,1 1,2,3,4,5,6,7,8 1
5,5,1,1,0,1 2,3,4,5,6,7,8 7
5,1,1,0,1 2,3,4,5,6,8 8
1,1,0,1 2,3,4,5,6 3
1,0,1 2,4,5,6 4
0,1 2,5,6 2
1 5,6 6
- 5 5
结果:17834265。
找到 246351 的索引:
symbols count perm index(head)
1,2,3,4,5,6 6 2,4,6,3,5,1 1 a_5 = 1
1,3,4,5,6 5 4,6,3,5,1 2 a_4 = 2
1,3,5,6 4 6,3,5,1 3 a_3 = 3
1,3,5 3 3,5,1 1 a_2 = 1
1,5 2 5,1 1 a_1 = 1
索引是`1 * 5!+ 2 * 4!+ 3 * 3!+ 1*2!+ 1 * 1!= 187。
所以现在我们有了一种相当简单的方法来在排列和它们的索引之间进行转换。转换速度不是很快 (O(s^2)),但您可以轻松快速地进行比较和查找(我以前见过这种状态吗?)。在每种情况下,这是否是一种收益仍有待决定。
现在,对于手头的特定情况,我们有一些进一步的限制来减少搜索空间。
因此,这些移动的所有组合也是偶数排列,这意味着一半的可能状态是不可达的。我们(最多)剩下 9!/2 = 181440 个可达状态。通过字典顺序索引甚至排列只是稍微复杂一些。关键点是,当且仅当其索引的阶乘展开中的系数 a_k 之和为偶数时,置换是偶数。
使用约束和对称性减少搜索空间。如果您使用具有所有可能状态的结构的搜索策略,这将相应地减少内存需求。如果您的搜索策略仅涉及可达状态,则约束不会减少步骤数,但由于内存占用较少,它们仍然可以加快搜索速度。对称性的使用可以通过识别等效状态来减少步骤数。
在示例问题中,我们有更好的情况,即 5 已经在正确的位置,并且最优解永远不会移动它。所以我们只需要考虑 8 个符号的偶数排列,将搜索空间减少到 8!/2 = 20160 个可能的状态。(虽然这并不明显。)
然而,一般来说,很难证明最优解永远不会留下可能状态的特定子集,因此您很少可以直接对搜索施加这样的限制。
但通常情况下,您可以使用这样的限制找到问题的良好解决方案,然后在您搜索无限制搜索空间中的最佳解决方案时使用该良好解决方案进行早期修剪。
人们经常使用的一种变体是通过贪心策略找到近似值,并将其用作穷举搜索早期修剪的界限。