摩天大楼拼图算法

时间:2013-09-18 12:40:58

标签: python algorithm recursion puzzle

我正在编写一个算法来解决skyscrapers puzzles

  

摩天大楼的谜题将数独的行和列限制与外部线索值相结合,外部线索值将每行或每列数字重新设想为一条充满不同高度的摩天大楼的道路。数字越大表示建筑物越高。

     

要解决摩天大楼之谜,你必须将1到5或1放置到任何大小的拼图中,每个拼版块放入每一行和每列,同时还要解决每个给定的摩天大楼线索。

     

要了解摩天大楼的谜题,您必须想象您放入网格的每个值都代表了这个楼层的摩天大楼。因此,1楼是1层楼的摩天大楼,4楼是4层楼的摩天大楼。现在想象一下,你站在网格之外,其中一个线索数字是回到网格中。该线索数字告诉您从该点可以看到多少个摩天大楼,仅沿着线索所在的行或列,以及从线索的角度来看。较高的建筑物总是掩盖了较低的建筑物,因此换句话说,较高的数字总是掩盖较低的数字。

所有基本技术都已实现并正常工作,但我意识到,对于更大的谜题(5x5>),我需要某种递归算法。我找到了一个体面的工作python script,但我并没有真正关注它除了解决基本线索之外的实际做法。

有谁知道解决这些谜题的正确方法,还是有人能透露上面代码中的要点?

2 个答案:

答案 0 :(得分:5)

Misha向你展示了蛮力的方式。可以基于constraint propagation进行更快的递归算法。 Peter Norvig(Google Research的负责人)写了一篇excellent article关于如何使用这种技术用python解决数独的问题。阅读它并尝试了解每一个细节,你会学到很多东西,保证。由于摩天大楼拼图与Sudoku有很多共同之处(没有3X3块,但是边缘上的数字给出了一些额外的限制),你可能会偷走他的很多代码。

您可以像Sudoku一样开始,其中每个字段都包含1..N中所有可能数字的列表。之后,您一次查​​看一条水平/垂直线或边缘线索,并删除非法选项。例如。在5x5的情况下,3的边缘从前两个中排除5,从第一个方格中排除4。约束传播应该完成其余的工作。保持循环超过边缘约束,直到它们满足为止,或者在循环通过所有约束后卡住。如Norvig所示,您可以在出现矛盾的情况下开始猜测并删除数字。

在Sudoku的情况下,给定的线索只需要处理一次,因为一旦你将一个数字分配给一个方格(你删除了所有其他的可能性),就会使用线索的所有信息。但是,对于摩天大楼,您可能需要多次应用给定线索,直到完全满意为止(例如,当完整线路被解决时)。

答案 1 :(得分:4)

如果你绝望,你可以蛮力拼图。我通常这样做是为了熟悉这个谜题的第一步。基本上,您需要使用从1到N的整数填充NxN正方形,遵循以下约束:

  • 每个整数只出现在每一行
  • 每个整数只出现一次
  • 满足“线索”行
  • 满足“线索”栏目

蛮力解决方案会像这样工作。首先,将板表示为整数的2D数组。然后编写一个函数is_valid_solution,如果电路板满足上述约束,则返回True,否则返回False。这部分在O(N^2)中相对容易。

最后,迭代可能的电路板排列,并为每个排列调用is_valid_solution。当返回True时,您已找到解决方案。总共有N^(NxN)个可能的安排,因此您的完整解决方案将是O(N^(NxN))。您可以通过使用上述约束来减少搜索空间。

上述方法运行需要相对较长时间(O(N^(NxN))对于算法而言非常可怕),但您(最终)会得到一个解决方案。当你有了这个工作,试着想出一个更好的方法来实现它;如果你遇到困难,那就回到这里。

编辑

稍微更好的替代方案是从空板开始执行搜索(例如深度优先)。在搜索的每次迭代中,您将使用数字填充表格的一个单元格(同时不违反任何约束)。一旦你碰巧填满了董事会,你就完成了。

这是递归暴力深度优先搜索的伪代码。搜索将是NxN个节点深,每个节点的分支因子最多为N。这意味着您需要检查最多1 + N + N^2 + ... + N^(N-1)(N^N-1)/(N-1)个节点。对于每个节点,您需要在最坏的情况下(当电路板已满时)调用is_valid_board,即O(N ^ 2)。

def fill_square(board, row, column):
  if row == column == N-1: # the board is full, we're done
    print board
    return
  next_row, next_col = calculate_next_position(row, col)
  for value in range(1, N+1):
    next_board = copy.deepcopy(board)
    next_board[row][col] = value
    if is_valid_board(next_board):
      fill_square(next_board, next_row, next_col)

board = initialize_board()
fill_square(board, 0, 0)

函数calculate_next_position选择要填充的下一个方块。最简单的方法是扫描电路板的遍历。更聪明的方法是交替填充行和列。