所以我为Sokoban游戏实施了2个不同的求解器。
如果初始状态是目标状态然后返回结果,则解算器很简单,给定起始状态(位置)。否则生成子状态并将它们存储到与算法相对应的任何数据结构中。 (BFS的队列和A *的优先级队列)然后从数据结构中弹出第一个子状态以检查它是否为目标状态,否则生成子状态并存储到结构中,重复此过程直到找到目标状态。
目前,A *算法确实比BFS更好,因此在找到结果之前生成的节点更少。但是,我的问题是A *算法需要更长的时间来计算。例如,在其中一个级别中,BFS算法生成26000个节点,而A *仅生成3488个节点,但A *花费的时间比完成BFS的时间长一个。
从使用时间分析器我得出结论,用于存储节点的优先级队列数据结构是造成这种减速的原因。因为每次将节点插入队列时,优先级队列必须运行启发式函数算法以确定新状态是否应该是与目标状态最接近的状态(因此它将该节点放在队列中的第一个节点中)。
现在我的问题是,你们认为这是我的实现的问题,或者这是正常的,因为在计算启发式函数时会产生开销。
答案 0 :(得分:1)
另一个假设要考虑(假设是我们可以做的最好而不用你的代码可以看):也许你正在重新计算你的启发式分数(我认为这是一个相对昂贵的事情来计算你的情况)经常不必要地。
您的代码中的哪个位置可以计算启发式分数?你
Node
对象中,以便您可以在需要时立即检索它?Comparator
函数/仿函数/等内的启发式分数。您的优先级队列使用它来确定节点的排序吗?在第二种情况下,您将经常重新计算您之前已经完成的节点的启发式分数。例如,假设您当前在优先级队列中有100个节点,并且您尝试插入新节点。在第二种情况下,插入新节点可能需要在找出新节点所属的节点之前与这100个现有节点中的一些节点进行比较,因此可能需要一堆额外的启发式分数计算。
如果你选择了第一个选项(这是我推荐的),那100个现有节点和要添加的新节点都将计算出他们的启发式分数(恰好一次),并作为成员存储在内存中变量。与一堆现有节点进行一系列比较将非常便宜,它只需要获取已经计算的启发式分数而不需要重新计算它们。
实际上,根据你问题中的文字,我怀疑你确实正在使用(低效)第二种实现。否则,您的优先级队列根本不会调用启发式功能。
如果使用上面描述的更高效的第一个实现,您仍然在努力处理过于昂贵的启发式函数,那么您可以尝试调查是否有可能在生成后继状态期间编写更有效的增量实现。这是否可行取决于您的启发式功能究竟在做什么。但是,我可以想象,在某些情况下,如果你已经拥有
s
h(s)
s'
您可能能够推导出一种有效的增量算法,该算法根据所做的移动计算h(s)
如何逐步修改以确定h(s')
(然后您可以立即将其存储在s'
)的节点
答案 1 :(得分:0)
你确定你实际上是在使用堆栈而不是BFS的队列吗?如果您正在使用堆栈,那么您正在做的是DFS,并且可能并不总能为您提供到达目标的最短路径。
至于为什么你的A *较慢,我认为很难说不知道你的启发式函数的复杂性,假设你的优先级队列正在进行O(Log(N))插入/删除(如果不是,这是你的问题)。如果你的启发式工作做了很多工作,那么它可能会主导所需的计算,并且可以通过查看更少的节点来抵消任何收益。
如果启发式函数确实是罪魁祸首,那么可能的解决方案是使用更简单的启发式算法。
答案 2 :(得分:0)
您的实施存在问题。当您想要解决问题faster而不是基本(但可能是确切的)方法时,会使用启发式方法。他们以最快的速度交易:你有可能获得一个非完美的解决方案,以换取快速获得。使用一种使你的方法比它的确切版本更慢的启发式是没有意义的。
当然,如果不了解更多关于实现的内容,就不可能提供更多帮助,但我可以向您保证,如果您的启发式操作使得求解器变慢,则使用它是不值得的。