解决类似TSP的拼图,也许是在Javascript中

时间:2011-02-18 07:20:30

标签: javascript algorithm math puzzle traveling-salesman

我创造了一个拼图,它是旅行商问题的衍生物,我称之为Trace Perfect。

它本质上是一个带加权边的无向图。目标是使用最小权重在任何方向上至少遍历每个边缘一次(与经典TSP不同,其目标是使用最小权重访问每个顶点)。

作为最后的扭曲,边缘被赋予两个权重,每个遍历一个方向。

我每天创建一个新的拼图实例,并通过JSON界面发布它。

现在我知道TSP是NP难的。但我的谜题通常只有很少的边缘和顶点。毕竟他们需要人性化的解决方案。因此,基本优化的蛮力可能就足够了。

我想开发一些(Javascript?)代码,从服务器中检索拼图,并在合理的时间内使用算法进行求解。此外,它甚至可以将解决方案发布到服务器以在引导板中注册。

我在服务器上使用我的后端Java模型在Java中为它编写了一个基本的暴力解算器,但代码太胖了,并且正如预期的那样快速耗尽堆空间。

Javascript求解器是否可行且可行?

JSON API很简单。您可以在http://service.traceperfect.com/api/stov?pdate=20110218找到它,其中pdate是yyyyMMdd格式的拼图日期。

基本上拼图有很多行。每行有两个顶点(A和B)。每行具有两个权重(当遍历A - > B时为timeA,在遍历B时为时间B - > A)。这应该是构建图形数据结构所需的全部内容。 JSON对象中的所有其他属性都是出于可视目的。

如果您想熟悉这个谜题,可以通过http://www.TracePerfect.com/

的Flash客户端进行播放

如果有人有兴趣为自己实现解算器,那么我将发布有关将解决方案提交给服务器的API的详细信息,这也非常简单。

感谢您阅读这篇冗长的帖子。我期待着听到你对这一点的看法。

3 个答案:

答案 0 :(得分:4)

如果Java中的堆空间不足,那么你的解决方法就错了。

解决此类问题的标准方法是进行广度优先搜索,并过滤掉重复项。为此,您需要三个数据结构。第一个是你的图表。接下来是一个名为“状态”的待办事项的队列,用于完成剩下的工作。最后一个是哈希,它将您所处的可能“状态”映射到该对(成本,最后状态)。

在这种情况下,“状态”是一对(当前节点,已经遍历的一组边)。

假设你有这些数据结构,这里是完整算法的伪代码,可以相当有效地解决这个问题。

foreach possible starting_point:
  new_state = state(starting_point, {no edges visited})
  todo.add(new_state)
  seen[new_state] = (0, null)

while todo.workleft():
  this_state = todo.get()
  (cost, edges) = seen[this_state]
  foreach directed_edge in graph.directededges(this_state.current_node()):
    new_cost = cost + directed_edge.cost()
    new_visited = directed_edge.to()
    new_edges = edges + directed_edge.edge()
    new_state = state(new_visited, new_edges)
    if not exists seen[new_state] or new_cost < seen[new_state][0]:
      seen[new_state] = (new_cost, this_state)
      queue.add(new_state)

best_cost = infinity
full_edges = {all possible edges}
best_state
foreach possible location:
  end_state = (location, full_edges)
  (cost, last_move) = seen[end_state]
  if cost < best_cost:
    best_state = end_state
    best_cost = cost

# Now trace back the final answer.
path_in_reverse = []
current_state = best_state
while current_state[1] is not empty:
    previous_state = seen[current_state][1]
    path_in_reverse.push(edge from previous_state[0] to current_state[0])
    current_state = previous_state

现在reverse(path_in_reverse)为您提供最佳路径。

请注意,哈希seen至关重要。这是阻止你进入无限循环的原因。

看看今天的谜题,这个算法最多会有一百万左右的状态你需要弄清楚。 (有2 + 16个可能的边缘集合,以及14个可能的节点。)这很可能适合RAM。但是大多数节点只连接了2个边。我强烈建议摧毁那些。这将减少4个节点和6个边缘,上限为256个状态。 (并非所有都可能,并注意多个边缘现在连接两个节点。)这应该能够在很少使用内存的情况下快速运行。

答案 1 :(得分:0)

对于图表的大多数部分,您可以应用http://en.wikipedia.org/wiki/Seven_Bridges_of_K%C3%B6nigsberg

通过这种方式,您可以获得应该重复的行数以便解决。

开始时,您不应该从具有短顶点的节点开始,您应该将其移动两次。 如果我总结一下:

  • 从节点whit奇数边开始。
  • 不要越过偶数位于偶数节点的线路。
  • 使用最短路径从一个奇数节点行进到另一个节点。

这种启发式的简单递归强力求解器可能是开始的好方法。

或者另一种方式 尝试找到最短的顶点,如果从图形提醒图中删除它们将只有两个奇数编号的节点,并且将被视为可解决为Koningsberg桥。解决方案是解决图形而不在此缩小的图形上拾取铅笔,一旦您点击节点“删除”边缘,您只需返回即可。

答案 2 :(得分:0)

在您的Java后端,您可以使用this TSP code(正在进行中)使用Drools Planner(开源,java)。