如何摆脱最大递归深度误差或更好地解决这个问题?

yas*_*sar 6 python algorithm recursion

问题:我们有一个5行4列的正方形网格.我们需要使用这些数字来填充网格; 1,2,3,4,5,6,7,8,9,10,12,18,20,21,24,27,30,35,36,40.我们需要以这样的方式填充网格,即每个水平和垂直邻居应该在没有余数的情况下划分其他邻居.例如,12并且3可以是邻居因为12 % 3 == 0,但是512不能.网格2x2被赋予10.

我尝试使用集合列表来解决问题.每组代表每个网格的可能值.当每个集合只有一个元素时,问题就解决了.以下是我用来尝试解决这个问题的函数(为了以防万一,我添加了整个函数,但我认为我的问题在于求解函数.);

class CannotSolveError(Exception):
    pass

def suitable_neighbor(a,b):
    "return True if a and b can be neighbors."
    return (a > b) and (a % b == 0) or (b % a == 0)

def equalize_tables(table1, table2):
    "Make two tables equal, by changing first one in-place"
    for i in range(len(table1)):
        table1[i] = table2[i]


def remove_possibility(table, row, column, value):
    """Remove possibilities that can't be neighbors with value in rowxcolumn grid."""

    index = ((row - 1) * num_cols) + column - 1

    if len(table[index]) == 1:
        return # This is a solved grid, do nothing.

    remaining_possibilities = set(
        filter(lambda x: suitable_neighbor(x, value), table[index])
                                )

    if not remaining_possibilities:
        raise ValueError("Invalid move")

    if len(remaining_possibilities) == 1:
        "Only one possibility remains, try to put it!"
        copy_table = table[:]
        try:
            "Try it on copy"
            put(copy_table, row, column, remaining_possibilities.pop())
        except ValueError:
            "Cannot put, re-raise and do nothing.."
            raise
        else:
            "Putting is successfull, update original table"
            equalize_tables(table, copy_table)
    else:
        table[index] = remaining_possibilities


def put(table, row, column, value):
    """Put a value on a grid, modifies given table. use with care!"""

    index = ((row - 1) * num_cols) + column - 1

    "Is this move possible?"
    if value not in table[index]:
        raise ValueError("Cannot put %d on %dx%d" % (value, row, column))

    "Remove possibilities from left neighbor"
    if column > 1:
        remove_possibility(table, row, column - 1, value)

    "Remove possibilities from right neighbor"
    if column < num_cols:
        remove_possibility(table, row, column + 1, value)

    "Remove possibilities from upper neighbor"
    if row > 1:
        remove_possibility(table, row - 1, column, value)

    "Remove possibilities from lower neighbor"
    if row < num_rows:
        remove_possibility(table, row + 1, column, value)

    "Remove this value from every other set."
    for i in range(num_rows * num_cols):
        if i == index:
            continue

        table[i].discard(value)

    "Put one-item set in place. Have to do this last."
    table[index] = set([value])



def solve(table):
    "Try to solve the table by trying possible values on grids."

    to_try = [(i,len(table[i])) for i in range(num_rows * num_cols) if len(table[i]) > 1]

    "Grid with least remaining possibilities will be tried first."
    to_try.sort(key = lambda x: x[1])

    for index, _ in to_try:
        for value in table[index]:
            row = index / num_cols + 1
            column = index % num_cols + 1
            copy_table = table[:]
            put(copy_table, row, column, value)
            try:
                solve(copy_table)
                equalize_tables(table, copy_table)
                return
            except CannotSolveError:
                continue
            except ValueError:
                continue
    raise CannotSolveError
Run Code Online (Sandbox Code Playgroud)

我认为这个算法应该解决这个问题.但我超出最大递归深度.任何想法如何解决这个问题,或者我应该如何在Python中更好地解决这个问题?

这不是一个家庭作业问题.我自己正在研究这个问题.

ale*_*xis 6

为了避免炸毁堆栈,更强大的方法是为部分解决方案(部分填充板)设计编码,并自己实现回溯.这将需要比依赖python的堆栈少得多的内存.

谷歌的彼得诺维格写了一篇很有启发性的文章,描述了他如何利用这些技术建立一个有效的回溯数独求解器.它使用一种他称之为"约束传播"的技术来限制选项的空间,这样就可以通过强力回溯搜索快速找到解决方案(也就是说,不检查每个可能的数字网格,但只追求可能仍然存在的部分网格导致解决方案).我认为你会发现它非常适用,不仅适用于一般的想法,也适用于细节:你的问题,正如你已接近它,非常接近数独求解器.

  • 这就是我想写的.一个链接更多,以及python中的回溯示例:http://code.activestate.com/recipes/577777-backtracking-template-method/ (2认同)

and*_*oke 0

今天是下雨天,所以我写了一个解决方案。如果您愿意,我可以发布,但也许您宁愿自己找到?

这里有一些提示:

  • 你的代码似乎不是以 (2,2) 处的 10 开头

  • 当尝试新值时,您可以将其添加到任何空白处。最好的尝试空间是有很多邻居的空间,因为这样可以让你快速测试和拒绝不良值。

  • 上面假设的,或者说同一件事的不同方式 - 我的搜索超过了值。所以我选择了“下一步”的位置并尝试了那里的每个值。相反的方法是搜索位置(选择“下一个值”并在每个位置使用该值进行搜索),但这效率不高(见上文)。

  • 当回溯和重试时,始终遵循相同的位置模式。例如,(2,2) 是 10,那么 (2,3) 可能是 40,那么您可能会发现没有任何内容适合 (2,4)。所以你回溯并删除 40 并在 (2,3) 处尝试不同的数字。但您尝试的第二个数字(10 之后和 (2,2) 处的某个数字)始终为 (2,3)。如果您不小心这样做,您最终可能会测试许多重复的组合。抱歉,不确定这是否很清楚。基本上 - 选择一条您填充的“路径”,并在搜索和回溯时坚持它。因为选择这条路径是为了最大化邻居的数量(上面的点),所以我一边继续一边构建它,但保留了回溯时使用的路径位置的缓存。通过显示代码更容易解​​释......

  • 对于表,我使用了数组的数组。复制时我重新使用了未更改的列。这应该减少内存使用(我不知道这是否重要)。

  • 搜索只需要递归 40 次(每个值一次),因此堆栈足够大。

  • 在 python 中进行一个简单的搜索,依次尝试每个值,失败时回溯,在我的笔记本电脑上运行约 4 分钟(假设您使用上面的提示)(不打印稍微修改的版本只需 8 秒)。

  • 我发现有一个 python 函数很有用,给定一个网格和一个位置,它返回yield邻居坐标的列表(好吧,一个生成器,带有 )。这使得编写其他函数(例如测试一步是否正确的函数)变得更加简单。

无论如何,如果您想要代码或解决方案(我更改了代码以打印所有内容,但只有一个),请询问,我会发布。当然,它也可能有一个错误:o)

解决方案

我对此进行了一些调整,现在它打印出 (2,2)=10 解决方案,然后搜索所有解决方案(仍在为我运行):

#!/usr/bin/python3

nx, ny = 4, 5
values = [1,2,3,4,5,6,7,8,9,10,12,18,20,21,24,27,30,35,36,40]
# grid[x][y] so it is a list of columns (prints misleadingly!)
grid = [[0 for _ in range(ny)] for _ in range(nx)]
# cache these to avoid re-calculating
xy_moves = {}
debug = False

def neighbours(grid, x, y):
    'coordinates of vertical/horizontal neighbours'
    for (xx, yy) in [(x-1,y),(x+1,y),(x,y-1),(x,y+1)]:
        if xx > -1 and xx < nx and yy > -1 and yy < ny:
            yield xx, yy

def filled_neighbours(grid, x, y):
    'filter "neighbours" to give only filled cells'
    return filter(lambda xy: grid[xy[0]][xy[1]], neighbours(grid, x, y))

def count_neighbours(grid, x, y):
    'use this to find most-constrained location'
    return sum(1 for _ in filled_neighbours(grid, x, y))

def next_xy(grid, depth):
    '''given a certain depth in the search, where should we move next?  
       choose a place with lots of neighbours so that we have good 
       constraints (and so can reject bad moves)'''
    if depth not in xy_moves:
        best, x, y = 0, nx // 2, ny // 2 # default to centre
        for xx in range(nx):
            for yy in range(ny):
                if not grid[xx][yy]:
                    count = count_neighbours(grid, xx, yy)
                    if count > best:
                        best, x, y = count, xx, yy
        xy_moves[depth] = (x, y)
        if debug: print('next move for %d is %d,%d' % (depth, x, y))
    return xy_moves[depth]

def drop_value(value, values):
    'remove value from the values'
    return [v for v in values if v != value]

def copy_grid(grid, x, y, value):
    'copy grid, replacing the value at x,y'
    return [[value if j == y else grid[i][j] for j in range(ny)]
            if x == i else grid[i]
            for i in range(nx)]

def move_ok(grid, x, y, value):
    'are all neighbours multiples?'
    for (xx, yy) in filled_neighbours(grid, x, y):
        g = grid[xx][yy]
        if (g > value and g % value) or (g < value and value % g):
            if debug: 
                print('fail: %d at %d,%d in %s' % (value, x, y, grid))
            return False
    return True

def search(grid, values, depth=0):
    'search over all values, backtracking on failure'
    if values:
        (x, y) = next_xy(grid, depth)
        for value in values:
            if move_ok(grid, x, y, value):
                if debug: print('add %d to %d,%d' % (value, x, y))
                for result in search(copy_grid(grid, x, y, value),
                                     drop_value(value, values), 
                                     depth+1):
                    yield result
    else:
        yield grid


# run the search, knowing that (2,2) (which is (1,1) for zero-indexing)
# has the value 10.
for result in search(copy_grid(grid, 1, 1, 10), drop_value(10, values)):
    print(result)

# how many solutions in total?
#xy_moves = {} # reset cache
#for (n, solution) in enumerate(search(grid, values)):
#    print('%d: %s' % (n, solution))
Run Code Online (Sandbox Code Playgroud)

它首先选择将使用 来添加下一个数字的正方形next_xy()。它会选择尽可能靠近现有数字的位置,以便可以有效地测试和拒绝数字(该位置被保存,xy_moves以便在回溯时不需要重新找到)。对于每个值,它都会使用 来检查将该值放在该位置是否有效move_ok。如果是这样,它会计算一个新的网格(添加了值)和一个新的值列表(删除了使用的值)并递归。当没有可添加的值时,递归结束。

这是结果(每个内部列表都是一):

> time ./grid.py 
[[4, 20, 5, 35, 7], [40, 10, 30, 1, 21], [8, 2, 6, 18, 3], [24, 12, 36, 9, 27]]
real    0m5.909s
Run Code Online (Sandbox Code Playgroud)

[删除有关递归和生成器的错误评论]

更新

它完成了全局搜索 - 如果您在开始时不修复 (2,2),则似乎总共有 12 个解决方案(如果您忽略简单的对称性,则有 3 个不同的解决方案)。

更新2

最终代码,包括搜索没有对称重复的所有解决方案