我一直在编写解决各种数字难题的程序,但我一直在设计无法合理的复杂搜索算法。
例如,在一个谜题中,您将获得一个3x3网格的数字1到9:
123
456
789
您可以在任何方向上循环任何行或列中的数字。以下是将顶行数字移至右侧的示例。如果它们位于网格的边缘,则数字将循环。
123 -> 312
456 456
789 789
您必须以这种方式移动数字,直到您创建魔方,其中每列,行和对角线中的数字总和为15。
我已经编写了一个DFS强力算法来测试所有可能的移动序列,尽管每个回合的可用移动次数呈指数级增长(大约12 ^ [当前转弯]),使其无用。
看起来BFS最适合找到正确的动作,但这需要我存储数百甚至数千个副本的网格才能回溯!
我一直遇到这些问题。 BFS和DFS算法分别使用太多的内存和时间。我需要帮助优化这些算法,以便它们更快更有效地运行。也许识别数字的模式和关系或者让算法逻辑朝着目标努力会有所帮助吗? (我不知道会带来什么)。
修改
我的固定算法就像魅力一样。学习如何对我的排列进行编号是至关重要的。谢谢大家!
答案 0 :(得分:9)
我建议查找memoization(根据输入缓存函数调用的结果,以便不会为相同的后续调用重新计算函数)。理解了memoization后,我会查找动态编程(仍然保存函数的结果,但也重新排序计算以消除不必要的调用)。动态编程的一些解释使用斐波那契的递归定义,然后使用斐波纳契+ memoization,并完成计算重新排序。
对于一般的DFS和BFS问题,可能会对称为分支和绑定的技术感兴趣。边界部分可以在一些问题上给你带来实质性的收益。修剪比使用不太复杂的边界高一代的子树消除了搜索树中的许多新分支(替代措辞:因为树随着深度呈指数级增长,所以提前修剪搜索很重要。)
对于您的特定问题,我认为可以进行优化。
首先,让我们考虑一下DFS。我相信您的电路板的所有排列都可以从电路板的任何配置中获得。作为结果。 DFS可以在没有回溯的情况下实现(尽管我猜你知道这一点)。深度只搜索? (编辑:根据Daniel Fischer的说法,这是错误的。有一半州可以到达,但它不会影响无回溯声明,因为回溯不会帮助你达到无法到达的状态)
但是,你可能会发现你不想仅仅为了发现你还没有解决问题而通过许多排列。回溯可能实际上有所帮助。还是......
考虑一下你的最终目标。魔术方块具有一些特定的属性,您可以利用这些属性来更仔细地选择操作。例如,由于行和列必须总和为15,因此您知道9,8和7不能相互共享行或列。 9和6都不能与8和1或7和2一起使用.6不能与5和4共用一列/行,即使它们总和为15,因为鸽子洞原则(每行/每列包含9个) ,8或7)。实际上,您可能会发现您的问题有一个独特的解决方案,在所有行,全列,反射和转置中模拟某种循环置换。对角线要求进一步限制了有效的解决方案。
除此之外:前一段中使用的逻辑与基于约束的编程没有什么不同。它实际上并不是一种优化技术(尽管如果不是运行时间,它可能被认为是对实现时间的优化),但也可能对您感兴趣(还要注意魔术方块和数独库经常用于说明基于约束的编程)
现在你有一个目标:
这是一种根本不同的方法,而不是搜索各种排列直到问题得到解决。我试着找到一个动态的编程解决方案。对于使用增量操作从开始状态移动到目标状态的稍微容易的动态编程问题,请查看Levenshtein编辑距离问题。
答案 1 :(得分:4)
除了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
诀窍是在factorial base中编写索引,
n = a_1 * 1! + a_2 * 2! + a_3 * 3! + a_4 * 4! + ...
其中0 <= a_k <= k
。如果您有s
个符号,则索引范围从0到s!-1,因此您在n,s-1
的阶乘基扩展中有(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
因为那不是特别清楚,一个例子。假设我们寻找{1,2,3,4,5,6,7,8}的索引4231的排列。首先,我们在阶乘基础上扩展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)),但你可以轻松快速地进行比较和查找(我之前看过状态吗?)。在每种情况下,是否仍然需要决定收益。
现在,针对特定情况,我们进一步限制了搜索空间。
使用约束和对称性减少搜索空间。如果您使用具有所有可能状态的结构的搜索策略,这将通过相应的因子减少内存需求。如果您的搜索策略仅触及可达状态,则约束不会减少步骤数,但由于内存占用较少,它们仍然可以加快搜索速度。对称性的使用可以通过识别等效状态来减少步骤数。
在示例问题中,我们有一个更好的情况,即5已经在正确的位置,并且最佳解决方案不会移动它。所以我们只需要考虑8个符号的排列,将搜索空间减少到8!/ 2 = 20160个可能的状态。 (虽然这并不明显。)
但是,一般情况下,很难证明最优解决方案永远不会留下可能状态的特定子集,因此您很少直接对搜索施加此类限制。
但通常情况下,您可以使用此类限制找到问题的良好解决方案,然后使用好的解决方案在搜索无限制搜索空间中的最佳解决方案时尽早修剪。 / p>
一个经常使用的变体是通过贪婪的策略找到一个近似值,并将其作为在穷举搜索中尽早修剪的约束。
答案 2 :(得分:3)
如果问题是如何使用行和列旋转来生成3x3魔方,则应该从known solutions to generating a 3x3 magic square(or this animated one)开始。您也可以简单地消除某些旋转类别,例如旋转中心行或列的旋转类型。
事实上,有一种解决方案只需要4次旋转。
如果DFS或BFS导致指数搜索空间,您通常会通过利用问题的结构获得巨大的胜利。在魔术方案中,它知道你不能旋转中间行或列来获得有效答案。
答案 3 :(得分:1)
尝试使用A *。您可以使用曼哈顿距离的启发式算法,从数字所在的位置到应该的位置。我假设你已经有了一个魔术广场作为目标状态。
答案 4 :(得分:1)
这个整体问题没有一般性答案。对特定案例有具体的答案 - 但也有一些具体案例可以在数学上证明没有答案明显优于蛮力算法所要求的答案。
在许多情况下,找到最佳算法的问题是一个活跃的研究问题,非常聪明的人正在努力,但收效甚微。
你可能会发现关于“NP完整性”的阅读很有意思,因为它只是这个问题的一个小角落,而是一个经过充分研究的问题。