一个很酷的算法来检查数独场?

时间:2008-11-14 08:56:23

标签: algorithm sudoku

有没有人知道一个简单的算法来检查数独配置是否有效?我想出的最简单的算法是(对于一个大小为n的板)Pseudocode

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

但我确信必须有更好的(在更优雅的意义上)解决方案。效率是非常不重要的。

25 个答案:

答案 0 :(得分:23)

您需要检查Sudoku的所有约束:

  • 检查每一行的总和
  • 检查每列的总和
  • 检查每个方框的总和
  • 检查每行重复的数字
  • 检查每列上的重复数字
  • 检查每个方框上的重复数字

完全是6次检查..使用蛮力方法。

如果你知道电路板的尺寸(即3x3或9x9),可以使用某种数学优化

编辑:和约束的解释:首先检查总和(如果总和不是45则停止)比检查重复要快得多(并且更简单)。它提供了一种丢弃错误解决方案的简便方法。

答案 1 :(得分:23)

彼得·诺维格(Peter Norvig)有一篇关于解决数独谜题的精彩文章(使用python),

http://norvig.com/sudoku.html

也许这对你想做的事情来说太过分了,但无论如何这是一个很好的阅读

答案 2 :(得分:7)

检查每一行,每列和每个框,使其包含数字1-9,不重复。这里的大多数答案已经讨论过了。

但如何有效地做到这一点?答:使用像

这样的循环
result=0;
for each entry:
  result |= 1<<(value-1)
return (result==511);

每个数字都会设置一位结果。如果所有9个数字都是唯一的,则最低9 位将被设置。 所以“检查重复”测试只是检查所有9位是否设置,这与测试结果== 511相同。 您需要执行其中的27项检查。每行,每列和每个检查一次。

答案 3 :(得分:7)

只是一个想法:你不需要检查每个3x3平方的数字吗?

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

答案 4 :(得分:4)

这是我在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)        

答案 5 :(得分:4)

为每一行,每列和每个方块创建一个布尔数组。数组的索引表示放入该行,列或方块的。换句话说,如果向第二行第一列添加5,则将行[2] [5]设置为true,以及列[1] [5]和正方形[4] [5],以指示行,列和方块现在具有5值。

无论您的原始电路板如何表示,这都是一种简单而快速的方法,可以检查它的完整性和正确性。只需按照它们在电路板上显示的顺序获取数字,然后开始构建此数据结构。当您在板中放置数字时,它将成为O(1)操作,以确定是否在给定的行,列或方形中复制任何值。 (您还需要检查每个值是否为合法数字:如果它们给您一个空白或太高的数字,您就知道该板未完成。)当您到达董事会的最后时,会知道所有的值都是正确的,并且不再需要检查。

有人还指出你可以使用任何形式的Set来做到这一点。以这种方式排列的数组只是一个特别轻巧且高性能的Set形式,适用于小的,连续的,固定的数字集。如果你知道你的电路板的大小,你也可以选择进行位屏蔽,但考虑到效率对你来说并不是那么重要,这可能有点过于繁琐。

答案 6 :(得分:2)

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

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

答案 7 :(得分:2)

如果总和行/ col的乘法等于正确的数字45/362880

答案 8 :(得分:2)

我为一个课程项目做了一次。我总共使用了27套来表示每一行,每列和每一行。我检查了数字,因为我将它们添加到每个集合中(每个数字的位置导致数字被添加到3组,一行,一列和一个框)以确保用户只输入数字1- 9。一个集合可以填充的唯一方法是它是否正确填充了唯一的数字。如果所有27套都被填满,拼图就解决了。设置从用户界面到27集的映射有点单调乏味,但使其余的逻辑变得轻而易举。

答案 9 :(得分:2)

您可以将集合(行,列,框)中的所有值提取到列表中,对其进行排序,然后与'(1,2,3,4,5,6,7,8,9)进行比较

答案 10 :(得分:1)

前段时间,我写了一个数独检查器,检查每一行的重复数字,每列上的重复数字&amp;每个方框上的重复数字。我很乐意,如果有人可以用几行Linq代码来提出它。

char VerifySudoku(char grid[81])
{
    for (char r = 0; r < 9; ++r)
    {
        unsigned int bigFlags = 0;

        for (char c = 0; c < 9; ++c)
        {
            unsigned short buffer = r/3*3+c/3;

                        // check horizontally
            bitFlags |= 1 << (27-grid[(r<<3)+r+c]) 
                        // check vertically
                     |  1 << (18-grid[(c<<3)+c+r])
                        // check subgrids
                     |  1 << (9-grid[(buffer<<3)+buffer+r%3*3+c%3]);

        }

        if (bitFlags != 0x7ffffff)
            return 0; // invalid
    }

    return 1; // valid
}

答案 11 :(得分:1)

检查是否非常有趣:

when the sum of each row/column/box equals n*(n+1)/2
and the product equals n!
with n = number of rows or columns

这足以满足数独的规则。因为这将允许算法O(n ^ 2),求和并乘以正确的单元格。

看n = 9,总和应为45,产品为362880。

您可以这样做:

for i = 0 to n-1 do
  boxsum[i] := 0;
  colsum[i] := 0;
  rowsum[i] := 0;
  boxprod[i] := 1;
  colprod[i] := 1;
  rowprod[i] := 1;    
end;

for i = 0 to n-1 do
  for j = 0 to n-1 do
    box := (i div n^1/2) + (j div n^1/2)*n^1/2;
    boxsum[box] := boxsum[box] + cell[i,j];
    boxprod[box] := boxprod[box] * cell[i,j];
    colsum[i] := colsum[i] + cell[i,j];
    colprod[i] := colprod[i] * cell[i,j];
    rowsum[j] := colsum[j] + cell[i,j];
    rowprod[j] := colprod[j] * cell[i,j];
   end;
end;

for i = 0 to n-1 do
  if boxsum[i] <> 45
  or colsum[i] <> 45
  or rowsum[i] <> 45
  or boxprod[i] <> 362880
  or colprod[i] <> 362880
  or rowprod[i] <> 362880
   return false;

答案 12 :(得分:1)

首先,你需要制作一个布尔值,“正确”。然后,如前所述,进行for循环。循环的代码和之后的所有内容(在java中)如前所述,其中field是具有相等边的2D数组,col是具有相同尺寸的另一个,l是1D的:

for(int i=0; i<field.length(); i++){
    for(int j=0; j<field[i].length; j++){
        if(field[i][j]>9||field[i][j]<1){
            checking=false;
            break;
        }
        else{
            col[field[i].length()-j][i]=field[i][j];
        }
    }
}

我不知道检查3x3框的确切算法,但是您应该检查字段中的所有行,并使用“/*array name goes here*/[i].contains(1)&&/*array name goes here*/[i].contains(2)”(继续直到达到行的长度)检查另一个< em> for loop。

答案 13 :(得分:1)

这是Python中一个很好的可读方法:

from itertools import chain                                                                                            

def valid(puzzle):                                                                                                     
    def get_block(x,y):                                                                                                
        return chain(*[puzzle[i][3*x:3*x+3] for i in range(3*y, 3*y+3)])                                               
    rows = [set(row) for row in puzzle]                                                                                
    columns = [set(column) for column in zip(*puzzle)]                                                                 
    blocks = [set(get_block(x,y)) for x in range(0,3) for y in range(0,3)]                                             
    return all(map(lambda s: s == set([1,2,3,4,5,6,7,8,9]), rows + columns + blocks))         

每个3x3正方形被称为一个块,其中有9个在3x3网格中。假设拼图是作为列表列表输入的,每个内部列表都是一行。

答案 14 :(得分:0)

array = [1,2,3,4,5,6,7,8,9]  
sudoku = int [][]
puzzle = 9 #9x9
columns = map []
units = map [] # box    
unit_l = 3 # box width/height
check_puzzle()    


def strike_numbers(line, line_num, columns, units, unit_l):
    count = 0
    for n in line:
        # check which unit we're in
        unit = ceil(n / unit_l) + ceil(line_num / unit_l) # this line is wrong - rushed
        if units[unit].contains(n): #is n in unit already?
             return columns, units, 1
        units[unit].add(n)
        if columns[count].contains(n): #is n in column already?
            return columns, units, 1
        columns[count].add(n)
        line.remove(n) #remove num from temp row
    return columns, units, line.length # was a number not eliminated?

def check_puzzle(columns, sudoku, puzzle, array, units):
    for (i=0;i< puzzle;i++):
        columns, units, left_over = strike_numbers(sudoku[i], i, columns, units) # iterate through rows
        if (left_over > 0): return false

如果没有彻底检查,请从头顶开始,这应该可以工作(通过一些调试),而只能循环两次。 O(n ^ 2)代替O(3(n ^ 2))

答案 15 :(得分:0)

以下是数学教授J.F. Crook撰写的论文:A Pencil-and-Paper Algorithm for Solving Sudoku Puzzles

本文发表于2009年4月,作为明确的数独解决方案获得了大量的宣传(请查看谷歌“J.F.Crook Sudoku”)。

除了算法之外,还有一个算法有效的数学证明(教授承认他没有发现数独非常有趣,所以他在纸上扔了一些数学以使它更有趣。)

答案 16 :(得分:0)

我编写的接口具有接收数据字段的函数,如果它是解决方案,则返回true / false。 然后将约束实现为每个约束的单个验证类。

验证只是迭代所有约束类,并且当所有传递数据时都是正确的。加速将那些最有可能失败的东西放在第一个指向无效区域的结果中并停在前面。

非常通用的模式。 ; - )

您当然可以对此进行增强,以提供哪些字段可能错误等提示。

第一个约束,只需检查是否填写了所有字段。 (简单循环) 再次检查每个块中是否都有所有数字(嵌套循环) 第三次检查完整的行和列(几乎与上面相同但不同的访问方案)

答案 17 :(得分:0)

让我们假设您的电路板从1 - n开始。

我们将创建一个验证阵列,填充它然后验证它。

grid [0-(n-1)][0-(n-1)]; //this is the input grid
//each verification takes n^2 bits, so three verifications gives us 3n^2
boolean VArray (3*n*n) //make sure this is initialized to false


for i = 0 to n
 for j = 0 to n
  /*
   each coordinate consists of three parts
   row/col/box start pos, index offset, val offset 
  */

  //to validate rows
  VArray( (0)     + (j*n)                             + (grid[i][j]-1) ) = 1
  //to validate cols
  VArray( (n*n)   + (i*n)                             + (grid[i][j]-1) ) = 1
  //to validate boxes
  VArray( (2*n*n) + (3*(floor (i/3)*n)+ floor(j/3)*n) + (grid[i][j]-1) ) = 1
 next    
next

if every array value is true then the solution is correct. 

我认为这样做会有所作为,虽然我确信我在那里犯了几个愚蠢的错误。我甚至可能完全错过了这条船。

答案 18 :(得分:0)

让我们说int sudoku [0..8,0..8]是数独字段。

bool CheckSudoku(int[,] sudoku)
{
    int flag = 0;

// Check rows
for(int row = 0; row < 9; row++)
{
    flag = 0;
    for (int col = 0; col < 9; col++)
    {
        // edited : check range step (see comments)
        if ((sudoku[row, col] < 1)||(sudoku[row, col] > 9)) 
        {
            return false;
        }

        // if n-th bit is set.. but you can use a bool array for readability
        if ((flag & (1 << sudoku[row, col])) != 0) 
        {
            return false;
        }

        // set the n-th bit
        flag |= (1 << sudoku[row, col]); 
    }
}

// Check columns
for(int col= 0; col < 9; col++)
{
    flag = 0;
    for (int row = 0; row < 9; row++)
    {
        if ((flag & (1 << sudoku[row, col])) != 0)
        {
            return false;
        }
        flag |= (1 << sudoku[row, col]);
    }
}

// Check 3x3 boxes
for(int box= 0; box < 9; box++)
{
    flag = 0;
    for (int ofs = 0; ofs < 9; ofs++)
    {
        int col = (box % 3) * 3;
        int row = ((int)(box / 3)) * 3;

        if ((flag & (1 << sudoku[row, col])) != 0)
        {
            return false;
        }
        flag |= (1 << sudoku[row, col]);
    }
}
return true;

}

答案 19 :(得分:0)

这是我的C.只通过每个广场一次。

int checkSudoku(int board[]) {
  int i;
  int check[13] = { 0 };

  for (i = 0; i < 81; i++) {
    if (i % 9 == 0) {
      check[9] = 0;
      if (i % 27 == 0) {
        check[10] = 0;
        check[11] = 0;
        check[12] = 0;
      }
    }

    if (check[i % 9] & (1 << board[i])) {
      return 0;
    }
    check[i % 9] |= (1 << board[i]);

    if (check[9] & (1 << board[i])) {
      return 0;
    }
    check[9] |= (1 << board[i]);

    if (i % 9 < 3) {
      if (check[10] & (1 << board[i])) {
        return 0;
      }
      check[10] |= (1 << board[i]);
    } else if (i % 9 < 6) {
      if (check[11] & (1 << board[i])) {
        return 0;
      }
      check[11] |= (1 << board[i]);
    } else {
      if (check[12] & (1 << board[i])) {
        return 0;
      }
      check[12] |= (1 << board[i]);
    }
  }
}

答案 20 :(得分:0)

以下是我刚刚为此所做的事情:

boolean checkers=true;
String checking="";
    if(a.length/3==1){}
    else{
       for(int l=1; l<a.length/3; l++){
            for(int n=0;n<3*l;n++){
                for(int lm=1; lm<a[n].length/3; lm++){
                    for(int m=0;m<3*l;m++){
                    System.out.print("    "+a[n][m]);
                    if(a[n][m]<=0){
                    System.out.print("        (Values must be positive!)        ");
                    }
                    if(n==0){
                        if(m!=0){
                        checking+=", "+a[n][m];
                    }
                    else{
                        checking+=a[n][m];
                    }
                }
                else{
                    checking+=", "+a[n][m];
                }
            }
                    }
            System.out.print("        "+checking);
            System.out.println();
                }
       }
            for (int i=1;i<=a.length*a[1].length;i++){
        if(checking.contains(Integer.toString(i))){

        }
        else{
            checkers=false;
        }
            }
        }
    checkers=checkCol(a);
    if(checking.contains("-")&&!checking.contains("--")){
        checkers=false;
    }
    System.out.println();
    if(checkers==true){
        System.out.println("This is correct! YAY!");
    }
    else{
        System.out.println("Sorry, it's not right. :-(");
    }
}
private static boolean checkCol(int[][]a){
    boolean checkers=true;
    int[][]col=new int[][]{{0,0,0},{0,0,0},{0,0,0}};
    for(int i=0; i<a.length; i++){
        for(int j=0; j<a[i].length; j++){
            if(a[i][j]>9||a[i][j]<1){
                checkers=false;
                break;
            }
            else{
                col[a[i].length-j][i]=a[i][j];
            }
        }
    }
    String alia="";
    for(int i=0; i<col.length; i++){
        for(int j=1; j<=col[i].length; j++){
            alia=a[i].toString();
            if(alia.contains(""+j)){
                alia=col[i].toString();
                if(alia.contains(""+j)){}
                else{
                    checkers=false;
                }   
            }
            else{
                checkers=false;
            }
        }
    }
    return checkers;
}

答案 21 :(得分:0)

您可以通过以下两种类似方式检查数据是否已解决:

  • 检查每个行,列和块中的数字是否唯一。

一个天真的解决方案是迭代每个方块并检查该数字在该行所占的列块中是否唯一。

但还有更好的方法。

  • 如果每一行,每列和每个块都包含数字的排列(1槽9),则解散数独

这只需要检查每一行,每列和每个数字,而不是每个数字。一个简单的实现是将数字1的位域设置为9到9,并在迭代列,行和块时将其删除。如果您尝试删除丢失的数字,或者当您完成时该字段不为空,则数据未正确解决。

答案 22 :(得分:0)

def solution(board):
    for i in board:
        if sum(i) != 45:
            return "Incorrect"

    for i in range(9):
        temp2 = []
        for x in range(9):
            temp2.append(board[i][x])

        if sum(temp2) != 45:
            return "Incorrect"

    return "Correct"

board = []
for i in range(9):
    inp = raw_input()
    temp = [int(i) for i in inp]
    board.append(temp)

print solution(board)

答案 23 :(得分:0)

这是Swift中一个非常简洁的版本,只使用Ints数组来跟踪9个数字的组,并且只迭代数独一次。

chrome.commands.onCommand.addListener(function(command) {
    console.log("I am a stupid script.", command);
});

答案 24 :(得分:0)

您可以做的一个次要优化是,您可以检查O(n)时间而不是O(n ^ 2)的行,列或框中的重复项:在遍历一组数字时,您可以添加每个到一个哈希集。根据语言的不同,您实际上可能可以使用真正的哈希集,即固定时间的查找和插入。然后可以通过查看插入是否成功来在同一步骤中检查重复项。这是对代码的微小改进,但是从O(n ^ 2)变为O(n)是一个重大的优化。