数独求解器回溯算法不起作用

Posted

技术标签:

【中文标题】数独求解器回溯算法不起作用【英文标题】:Sudoko Solver Backtracking Algorithm Not Working 【发布时间】:2020-10-13 14:29:08 【问题描述】:

我对编程很陌生,我最近开始研究 sudoko 求解器,除了求解算法本身之外,一切都很好,我从互联网上获得了一些帮助,但是因为我自己编写了方法,所以我可以'找不到我的代码的准确解决方案,任何帮助将不胜感激!

问题:代码一直运行,直到它无法在空 (0) 索引之一中输入值,而不是回溯,它只是停止。

如果你能让我知道我做错了什么,或者提出可能的方法来完全改进代码,那么任何事情都会有很大的帮助!

我的代码在这里:

    import time
    start_time = time.time()

    grid = [
         [7, 8, 0, 4, 0, 0, 1, 2, 0],
         [6, 0, 0, 0, 7, 5, 0, 0, 9],
         [0, 0, 0, 6, 0, 1, 0, 7, 8],
         [0, 0, 7, 0, 4, 0, 2, 6, 0],
         [0, 0, 1, 0, 5, 0, 9, 3, 0],
         [9, 0, 4, 0, 6, 0, 0, 0, 5],
         [0, 7, 0, 3, 0, 0, 0, 1, 2],
         [1, 2, 0, 0, 0, 7, 4, 0, 0],
         [0, 4, 9, 2, 0, 6, 0, 0, 7]
     ] 


    def printBoard():
        print("Suduko Board")
        for row in grid:
            for elem in row:
                print(elem, end=' ')
            print()

    def checkInRow(grid,row, num):
        for i in range(0,9):
            if(grid[row][i] == num):
                return True
        return False

    def checkInCol(grid,col, num):
        for i in range(0,9):
            if(grid[i][col] == num):
                return True
        return False

    def checkInBox(grid, row, col, num):
        #This will give the starting point for the box
        boxX = (row // 3) * 3
        boxY = (col // 3) * 3   
        for i in range(3):
            for j in range(3):
                if((grid[boxY + j][boxX + i]) == num):
                    return True
        return False

    def findNextEmpty(grid, num):
        for row in range(9):
            for col in range(9):
                if(grid[row][col] == num):
                    return row, col
        return None


    def checkSafe(grid, row, col, num): #Returns true if safe returns false if unsafe
        return not checkInRow(grid,row,num) and not checkInCol(grid,col,num) and not checkInBox(grid,row,col,num)


    #PROBLEM IS HERE \/ 

    def solveSudoko(grid, i,j):
        if not findNextEmpty(grid,0):
            return True
        else:
            i = findNextEmpty(grid,0)[0] # Finds Row #
            j = findNextEmpty(grid,0)[1] # Finds Col #
            print(i, j)
            for value in range(1,10):
                if checkSafe(grid,i,j,value) == True:
                    grid[i][j] = value

                    printBoard()
                    print("--- %s seconds ---" % (time.time() - start_time))

                    if(solveSudoko(grid,i,j)):
                        return True

                    grid[i][j] = 0

            return False



    #print(str(value) + " can go in grid[" + str(i) + "]" + "[" + str(j) + "]")


    printBoard()

    solveSudoko(grid,0,0)

【问题讨论】:

How to debug small programs. | What is a debugger and how can it help me diagnose problems? 请使用这些链接中描述的技巧将您的代码压缩为 minimal reproducible example。也请拨打tour,并阅读How to Ask 和what's on-topic here。欢迎使用 Stack Overflow! 【参考方案1】:

问题说明

我认为问题在于您的网格是一个列表,即可变的。这意味着如果您调用solveSudoko(grid,i,j),它不会复制网格,但您的内存中只有一个网格。因此,在solveSudoko 遇到问题后,它无法回滚,因为您更改了原始网格。可能的解决方案是使用非可变数据类型,如元组或有点丑陋,但也可能是制作网格的副本。

例子:

grid = [1,2,3]
def test(grid):
    grid[0] = 0
test(grid)
print(grid)
---> [0,2,3]

快速但丑陋的解决方案

您可能可以在代码开头添加import copy 并替换

                    if(solveSudoko(grid,i,j)):

通过

                    if(solveSudoko(copy.deepcopy(grid),i,j)):

例子:

grid = [1,2,3]
def test(grid):
    grid[0] = 0
test(copy.deepcopy(grid))
print(grid)
---> [1,2,3]

请注意,使用 copy.copy 而不是 copy.deepcopy 在示例中也可以工作,因为整数是不可变的,但您需要 copy.deepcopy 代码中的列表列表。

很好的解决方案

例子:

grid = (1,2,3)
def test(grid):
    grid = (0,2,3)
test(grid)
print(grid)
---> (1,2,3)

元组的问题是获得一个新的网格并不好玩。我可能会使用一个小类来为我管理网格:

class Grid:
    def __init__(self, grid):
        assert type(grid) == tuple
        self.grid = grid

    @staticmethod
    def gird_from_list(list_):
        return Grid(tuple(list_[i][j] for i in range(len(list_)) for j in range(len(list_[0]))))

    def __repr__(self):
        return "\n".join(str(self.grid[i*9:(i+1)*9]) for i in range(9))

    def set(self, i, j, value):
        return Grid(tuple(value if i+j*9==k else x for k,x in enumerate(self.grid)))

tuple_grid = Grid.gird_from_list(gird)
tuple_grid2 = tuple_grid.set(8,8,0)

这里grid_from_list 只是将您的列表转换为我的格式,__repr__ 为您提供了一个很好的二维网格表示,即使我实际上只保留一个一维元组。如果您不知道在打印该类的实例时总是会调用 __repr__

现在打印 tuple_grid 会产生

(7, 8, 0, 4, 0, 0, 1, 2, 0)
(6, 0, 0, 0, 7, 5, 0, 0, 9)
(0, 0, 0, 6, 0, 1, 0, 7, 8)
(0, 0, 7, 0, 4, 0, 2, 6, 0)
(0, 0, 1, 0, 5, 0, 9, 3, 0)
(9, 0, 4, 0, 6, 0, 0, 0, 5)
(0, 7, 0, 3, 0, 0, 0, 1, 2)
(1, 2, 0, 0, 0, 7, 4, 0, 0)
(0, 4, 9, 2, 0, 6, 0, 0, 7)

打印 tuple_grid2 会产生

(7, 8, 0, 4, 0, 0, 1, 2, 0)
(6, 0, 0, 0, 7, 5, 0, 0, 9)
(0, 0, 0, 6, 0, 1, 0, 7, 8)
(0, 0, 7, 0, 4, 0, 2, 6, 0)
(0, 0, 1, 0, 5, 0, 9, 3, 0)
(9, 0, 4, 0, 6, 0, 0, 0, 5)
(0, 7, 0, 3, 0, 0, 0, 1, 2)
(1, 2, 0, 0, 0, 7, 4, 0, 0)
(0, 4, 9, 2, 0, 6, 0, 0, 0)

因此,当您调用 set 时,网格不会改变,而是会为您提供一个具有新值的全新网格。

【讨论】:

@Ryanjewbo 我刚刚在我的答案中添加了一个更简单的版本,如何解决您的数独问题。如果您有任何问题,请随时在 cmets 中提问。

以上是关于数独求解器回溯算法不起作用的主要内容,如果未能解决你的问题,请参考以下文章

回溯数独求解器不起作用

具有回溯的数独求解器不能总是检测到多个解决方案

为啥这个数独求解器返回相同的板而不解决任何问题?

无法回溯以使用递归 javascript 数独求解器

优化回溯算法求解数独

Swift递归回溯算法不起作用