一个酷炫的算法来检测数独场?

25

有没有人知道一个简单的算法来检查数独配置是否有效?我想到的最简单的算法是(对于大小为n的棋盘)伪代码如下:

有没有人知道一个简单的算法来检查数独配置是否有效?我想到的最简单的算法是(对于大小为n的棋盘)伪代码如下:

for each row
  for each number k in 1..n
    if k is not in the row (using another for-loop)
      return not-a-solution

..do the same for each column

但我相信一定有更好(更优雅的)解决方案。效率并不是非常重要。


在这里的所有算法中添加:检查没有数字比你的正方形边数更高。 - StriplingWarrior
25个回答

25

您需要检查数独的所有约束:

  • 检查每行上的总和
  • 检查每列上的总和
  • 检查每个盒子上的总和
  • 检查每行上是否有重复数字
  • 检查每列上是否有重复数字
  • 检查每个盒子上是否有重复数字

总共需要进行6个检查,可以使用暴力方法。

如果您知道棋盘的大小(例如3x3或9x9),则可以使用某种类型的数学优化。

编辑:关于总和约束的解释:首先检查总和(如果总和不是45,则停止)比检查重复项更快速(更简单)。它提供了一种舍弃错误解决方案的简单方法。


15
数独不涉及求和。 - cjm
14
任意一行或一列的总和相同,即1+2+3+4+5+6+7+8+9 = 45,重复检查是因为如果存在重复数字,也可以通过其他方式得到45。上述规则是获取有效配置所必需的限制条件。 - Tristan Warner-Smith
25
这个答案是错误的。求和限制条件是不必要的,因为除了违反三个实际限制之外,没有其他方法可以获得总和为45的结果。 - sykora
24
预先检查总和可能比简单的重复检查性能更好,但这并不是必需的。仅检查重复即可。 - Svante
13
先检查和是否为45(如果和不是45,则停止检查)比检查重复项要快得多。那些没上过数学课的人能停止贬低这个观点吗? - Radu094
显示剩余8条评论

25

Peter Norvig撰写了一篇关于用Python解决数独难题的精彩文章,

https://norvig.com/sudoku.html

也许对于您想做的事情来说有点过头了,但不管怎样,这是一篇很棒的阅读材料。


10
解决问题和检查问题是两回事,对吗? - ckarbass
给看到这篇帖子的任何人:请注意,问题是在寻求推荐。 - fcdt

10

检查每行、每列和每个方块,确保它们都包含数字1-9且没有重复。此处已经有大量答案探讨了这一点。

但如何高效地实现呢?答案是:使用循环,例如

result=0;
for each entry:
  result |= 1<<(value-1)
return (result==511);
每个数字将设置结果的一个位。如果所有9个数字都是唯一的,则将设置最低的9位。 因此,“检查重复项”的测试只是检查所有9位是否设置,这与测试result==511相同。 您需要执行27个这样的检查..每个行、列和宫格各一个。

7

仅供参考:你是否需要检查每个3x3的方格中的数字?

我在尝试弄清楚,在没有正确的数独的情况下,是否可以满足行和列的条件。


3
我不确定这是否正确:第一行1..9,第二行2..9,1,第三行3..9,1,2... 可以产生正确的行和列,但不是3x3的方块。或者我有什么遗漏吗? - malach
1
@malach:你说得完全正确。如果没有盒子限制,数独就是拉丁方阵。因此,一个大小的有效完成数独集合是该大小所有拉丁方阵的子集。例如,行为[1234] [2143] [3412] [4321]的4x4网格是拉丁方阵,但不是有效的2x2数独。 - Michael Madsen
你确实需要检查所有三个方面(行、列和宫)。 - Bill the Lizard

5

这是我的Python解决方案,很高兴看到它是迄今为止最短的 :)

代码如下:

def check(sud):
    zippedsud = zip(*sud)

    boxedsud=[]
    for li,line in enumerate(sud):
        for box in range(3):
            if not li % 3: boxedsud.append([])    # build a new box every 3 lines
            boxedsud[box + li/3*3].extend(line[box*3:box*3+3])

    for li in range(9):
        if [x for x in [set(sud[li]), set(zippedsud[li]), set(boxedsud[li])] if x != set(range(1,10))]:
            return False
    return True  

执行结果如下:

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

print check(sudoku)        

如果您有任何改进或问题,请随意评论! - Kami
对于 Python 3,请使用 list(zip(**)) - Otieno Rowland

4
创建一个布尔数组,为每一行、每一列和每一个方格分别创建一个数组。该数组的索引表示已放置在该行、列或方格中的值。换句话说,如果您将5添加到第二行第一列,则将rows[2][5]、columns[1][5]和squares[4][5]设置为true,以表示该行、列和方格现在具有5值。
无论您的原始棋盘是如何表示的,这都是一种简单而非常快速的检查完整性和正确性的方法。只需按照它们在板上出现的顺序取出数字,并开始构建此数据结构。当您将数字放入棋盘时,确定任何值是否在给定的行、列或方格中重复变成了O(1)操作。(您还需要检查每个值是否是合法的数字:如果他们给您空白或太高的数字,则知道该板未完成。)当您到达棋盘的末尾时,您将知道所有的值都是正确的,不需要进行更多的检查。
还有人指出,您可以使用任何形式的Set来执行此操作。以这种方式排列的数组只是一种特别轻量级且性能良好的Set形式,适用于一个小的、连续的、固定的数字集。如果您知道您的棋盘的大小,您也可以选择进行位掩码,但考虑到效率并不是那么重要,这可能有点过于繁琐。

有点不太清楚您是如何填充这些方格(行和列似乎已经很明显了)。您是将其视为一个 9x9 的矩阵,并将每个 3x3 的方块布置成一行吗?这肯定不可能是正确的方法,不是吗? - ForeverLearning
@JoranBeasley:你理解StriplingWarrior如何设想这些方块吗? - ForeverLearning
在数独中,每个3x3的方格只能出现一次。因此,如果你为每个3x3的方格分配一个索引,那么你可以通过找到该索引处的数组,然后找到对应于你正在检查的数字的布尔值来检查给定的方格是否已经有数字。无论你如何计算每个3x3方格的索引——从上到下,从左到右,或者其他方式——只要保持一致即可。 - StriplingWarrior
这可能不适合于楼主,但这是我在这里寻找的答案。 - Tyler
@Tyler:很高兴它对你有用。只是出于好奇,你能解释一下它为什么可能不适合楼主吗? - StriplingWarrior
1
@StriplingWarrior 看起来他想要最简单、最易读的算法,我认为这将是通过迭代每个组(行、列、框)来实现。我试图找到一种更有效的算法,作为计算机科学的练习而不是实际实现。这实际上变得相当可读,这是一个额外的好处。 - Tyler

2

创建单元格集,每个集合包含9个单元格,并为垂直列、水平行和3x3正方形创建集合。

然后,对于每个单元格,只需识别它所属的集合并分析这些集合。


这是我过去写的方式。 - jmucchiello

2
你可以将一个集合(行、列、宫)中的所有值提取到一个列表中,进行排序,然后与'(1, 2, 3, 4, 5, 6, 7, 8, 9)'进行比较。

2

我曾经为一个班级项目做过这件事。我使用了27个集合来代表每一行、每一列和每一个框。我在将数字添加到每个集合中时(每个数字的放置会导致该数字被添加到3个集合中,即一行、一列和一个框),会检查数字以确保用户只输入了1-9的数字。只有当一个集合被正确填充了独特的数字后,它才能被填满。如果所有27个集合都被填满,那么谜题就被解决了。将用户界面映射到27个集合上是有点繁琐的,但使得其他逻辑的实现变得轻松自如。


1
def solution(board):
    for i in board:
        if sum(i) != 45:
            return "不正确"
for i in range(9): temp2 = [] for x in range(9): temp2.append(board[i][x])
if sum(temp2) != 45: return "不正确"
return "正确"
board = [] for i in range(9): inp = input() temp = [int(i) for i in inp] board.append(temp) print(solution(board))

这里需要写成 temp2.append(board[x][i]),否则可能会出现一些错误,我认为是这样的,对吗? - Otieno Rowland

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接