5

我正在尝试制作数独求解程序几天,但我坚持使用这些方法。我在这里找到了这个算法,但我不太明白:

  1. 从第一个空单元格开始,然后将 1 放入其中。
  2. 检查整个板子,看看有没有冲突
  3. 如果板上有冲突,则将当前单元格中的数字增加 1(因此将 1 更改为 2,将 2 更改为 3,等等)
  4. 如果棋盘是干净的移动,请再次从第一步开始。
  5. 如果给定单元格上的所有九个可能的数字都导致棋盘发生冲突,则将此单元格设置回空,返回上一个单元格,然后从第 3 步重新开始(这是“回溯”的来源)。

这是我的代码。我认为我的Help_Solve(...)函数有问题。你能帮我找出问题吗?

    #include <iostream>
#include <iomanip>
#include <time.h>
#include <cstdlib>
#include <windows.h>
using namespace std;

class Sudoku
  {
    private:
    int board[9][9];
    int change[9][9];
    public:
    Sudoku();
    void Print_Board();  
    void Add_First_Cord();  
    void Solve();
    void Help_Solve(int i, int j);
    bool Check_Conflicts(int p, int i, int j);
  };

Sudoku Game;  

void setcolor(unsigned short color)                 //The function that you'll use to
{                                                   //set the colour
    HANDLE hcon = GetStdHandle(STD_OUTPUT_HANDLE);
    SetConsoleTextAttribute(hcon,color);
}

Sudoku::Sudoku()
  {
    for(int i = 1; i <= 9; i++)
      for(int j = 1; j <= 9; j++)
        board[i][j] = 0;            
  }

void Sudoku::Print_Board()
  {
    for(int i = 1; i <= 9; i++)
      {
        for(int j = 1; j <= 9; j++)
          {
            if(change[i][j] == 1) 
              {
                setcolor(12);
                cout << board[i][j] << " ";
                setcolor(7);           
              }
              else cout << board[i][j] << " ";  
              if(j%3 == 0) cout << "| ";
          }
        cout << endl;
        if(i%3 == 0) cout << "------+-------+---------" << endl;

      }                    
  }

void Sudoku::Add_First_Cord()
  {
    board[1][1] = 5; change[1][1] = 1;
    board[1][2] = 3; change[1][2] = 1;     
    board[1][5] = 7; change[1][5] = 1;      
    board[2][1] = 6; change[2][1] = 1;  
    board[2][4] = 1; change[2][4] = 1;       
    board[2][5] = 9; change[2][5] = 1;  
    board[2][6] = 5; change[2][6] = 1; 
    board[3][2] = 9; change[3][2] = 1;      
    board[3][3] = 8; change[3][3] = 1;   
    board[3][8] = 6; change[3][8] = 1;     
    board[4][1] = 8; change[4][1] = 1;    
    board[4][5] = 6; change[4][5] = 1;    
    board[4][9] = 3; change[4][9] = 1;    
    board[5][1] = 4; change[5][1] = 1; 
    board[5][4] = 8; change[5][4] = 1;  
    board[5][6] = 3; change[5][6] = 1;  
    board[5][9] = 1; change[5][9] = 1;   
    board[6][1] = 7; change[6][1] = 1; 
    board[6][5] = 2; change[6][5] = 1;   
    board[6][9] = 6; change[6][9] = 1;  
    board[7][2] = 6; change[7][2] = 1;  
    board[7][7] = 2; change[7][7] = 1;  
    board[7][8] = 8; change[7][8] = 1;  
    board[8][4] = 4; change[8][4] = 1; 
    board[8][5] = 1; change[8][5] = 1;   
    board[8][6] = 9; change[8][6] = 1; 
    board[8][9] = 5; change[8][9] = 1;   
    board[9][5] = 8; change[9][5] = 1;  
    board[9][8] = 7; change[9][8] = 1;  
    board[9][9] = 9; change[9][9] = 1;  
  }

bool Sudoku::Check_Conflicts(int p, int i, int j)
  {
    for(int k = 1; k <= 9; k++)
      if(board[i][k] == p) return false;

    for(int q = 1; q <= 9; q++)
      if(board[q][j] == p) return false;

    /*
      *00
      000
      000
    */
    if((j == 1 || j == 4 || j == 7) && (i == 1 || i == 4 || i == 7))
      {
         if(board[i][j+1] == p || board[i][j+2] == p || board[i+1][j] == p || 
             board[i+2][j] == p || board[i+1][j+1] == p || board[i+1][j+2] == p || 
                 board[i+2][j+1] == p || board[i+2][j+2] == p)return false;     
      } 


    /*
      000
      000
      *00
    */  
    if((j == 1 || j == 4 || j == 7) && (i == 3 || i == 6 || i == 9))
      {
         if(board[i-1][j] == p || board[i-2][j] == p || board[i][j+1] == p || 
             board[i][j+2] == p || board[i-1][j+1] == p || board[i-1][j+2] == p || 
                 board[i-2][j+1] == p || board[i-2][j+2] == p)return false;   
      }

    /*
      000
      *00
      000
    */            
    if((j == 1 || j == 4 || j == 7) && (i == 2 || i == 5 || i == 8))
      {
         if(board[i-1][j] == p || board[i+1][j] == p || board[i-1][j+1] == p || 
             board[i][j+1] == p || board[i+1][j+1] == p || board[i+1][j+2] == p || 
                 board[i][j+2] == p || board[i+1][j+2] == p)return false;  
      } 


    /*
      0*0
      000
      000
    */            
    if((j == 2 || j == 5 || j == 8) && (i == 1 || i == 5 || i == 7))
      {
         if(board[i-1][j] == p || board[i+1][j] == p || board[i-1][j+1] == p || 
             board[i][j+1] == p || board[i+1][j+1] == p || board[i+1][j+2] == p || 
                 board[i][j+2] == p || board[i+1][j+2] == p)return false;  
      }

    /*
      000
      0*0
      000
    */            
    if((j == 2 || j == 5 || j == 8) && (i == 2 || i == 5 || i == 8))
      {
         if(board[i-1][j] == p || board[i-1][j-1] == p || board[i-1][j+1] == p || 
             board[i][j+1] == p || board[i][j-1] == p || board[i+1][j+1] == p || 
                 board[i][j] == p || board[i+1][j-1] == p)return false;  
      }


    /*
      000
      000
      0*0
    */            
    if((j == 2 || j == 5 || j == 8) && (i == 3 || i == 6 || i == 9))
      {
         if(board[i][j-1] == p || board[i][j+1] == p || board[i-1][j] == p || 
             board[i-1][j+1] == p || board[i-1][j-1] == p || board[i-2][j] == p || 
                 board[i-1][j+1] == p || board[i-2][j-1] == p) return false;  
      }  

    /*
      00*
      000
      000
    */            
    if((j == 3 || j == 6 || j == 9) && (i == 1 || i == 4 || i == 7))
      {
         if(board[i][j-1] == p || board[i][j-2] == p || board[i+1][j] == p || 
             board[i+1][j-1] == p || board[i+1][j-2] == p || board[i+2][j] == p || 
                 board[i+2][j-1] == p || board[i+2][j-2] == p) return false;  
      } 

    /*
      000
      00*
      000
    */            
    if((j == 3 || j == 6 || j == 9) && (i == 2 || i == 5 || i == 8))
      {
         if(board[i-1][j] == p || board[i-1][j-1] == p || board[i-1][j-2] == p || 
             board[i][j-1] == p || board[i][j-2] == p || board[i+1][j] == p || 
                 board[i+1][j-1] == p || board[i+1][j-2] == p) return false;  
      }

    /*
      000
      000
      00*
    */            
    if((j == 3 || j == 6 || j == 9) && (i == 3 || i == 6 || i == 9))
      {
         if(board[i][j-1] == p || board[i][j-1] == p || board[i-1][j] == p || 
             board[i-1][j-1] == p || board[i-1][j-2] == p || board[i-2][j] == p || 
                 board[i-2][j-1] == p || board[i-2][j-2] == p) return false;  
      }      

    return true;                          
  }

void Sudoku::Help_Solve(int i, int j)
  {
    if(j <= 0) 
      {
        i = i-1;
        j = 9;
      }
    if(change[i][j] == 1) return Game.Help_Solve(i, j-1);
    for(int p = 1; p <= 9; p++)
      if(Game.Check_Conflicts(p, i, j)) 
        {
          board[i][j] = p;
          return;
        }
    return Game.Help_Solve(i, j-1);

  }

void Sudoku::Solve()
  {                          
      for(int i = 1; i <= 9; i++)
        {
          for(int j = 1; j <= 9; j++)
            {
              if(board[i][j] == 0 && change[i][j] == 0)
                {
                  Game.Help_Solve(i, j);           
                }      
            }      
        }

      for(int i = 1; i <= 9; i++)
        for(int j = 1; j <= 9; j++)
          if(board[i][j] == 0) Game.Help_Solve(i, j);

  }


int main()
{
  Game.Add_First_Cord();
  Game.Solve();
  Game.Print_Board();  

  system("pause");
  return 0;
}

编辑:我需要使用递归吗?但也许我给函数的参数是错误的。我真的不知道。在Add_First_Cord()中,我声明了每个数独在开始时的起始值。以下是我使用的值:http ://bg.wikipedia.org/wiki/%D0%A4%D0%B0%D0%B9%D0%BB:Sudoku-by-L2G-20050714.gif 。我希望看到已解决的数独,如维基百科中所示。但是一些解决的值是正确的,而另一些则不是。这是我在控制台中得到的在此处输入图像描述

4

4 回答 4

20

建议的方法

  1. 实现一个通用的图搜索算法
    • 可以使用IDFSA* 图搜索
      • 我更喜欢第二个
    • 一般有向图执行此操作
      • 节点类型TNode
      • 节点后继函数TNode => vector<TNode>
  2. 定义你的数独状态
    • 状态是一个 9x9 数组,每个位置都有数字 1、2、... 或 9 或空白
  3. 定义目标数独状态是什么
    • 所有 81 个单元格均已填写
    • 所有 9 行都有数字 {1, 2, ..., 9}
    • 所有 9 列中都有数字 {1, 2, ..., 9}
    • 所有 9 个 3x3 方格中都有数字 {1, 2, ..., 9}
  4. 定义您的有效数独状态后继函数
    • 一个状态S可以N在 row I, column添加数字,J如果:
      • 单元格(I,J)为空
      • 没有其他NI
      • N列中没有其他内容J
      • N3x3 正方形中没有其他包含(I,J)
    • 状态后继函数将一个状态映射Svector满足这些规则的状态
  5. 将您的通用图搜索算法 (1) 应用于数独状态图 (2-4)
  6. (可选)如果您确实选择使用 A* 图搜索,您还可以在数独状态空间上定义启发式算法,以潜在地显着提高性能
    • 如何设计启发式是另一个完整的问题,这更像是一门艺术而不是一门科学

当前方法

您当前的方法混合了要搜索的图形的规范搜索算法的实现。如果你把这两者混合起来,你会遇到很多困难。这个问题自然地分成两个不同的部分——算法和图表——所以你可以并且应该在你的实现中利用它。它会让事情变得简单得多。

如果您采用这种分离,您将获得的另一个好处是您将能够在大量问题上重用您的图搜索算法 - 非常酷!

于 2013-05-21T16:53:54.097 回答
4

以下假设您正在尝试解决给定的棋盘,而不是生成谜题。

基本(简单)方法

创建一个类,其对象可以容纳一块板(这里称为board_t)。这个类可以内部使用数组,但必须支持抄板。

具有void solve(board_t const& board);对每个数字重复以下内容的功能n

  • 复制您的输入
  • 进入n复制板的第一个空单元格
  • 如果复制的板是解决方案,请打印解决方案和return.
  • Else 如果董事会仍然可行(例如没有冲突):
    • 称呼solve(copied_board)

表现

这是一个递归回溯解决方案,它在解决难题时表现得非常糟糕。您可以通过适当的修剪或演绎步骤显着加快它的速度(例如,如果您在插入一个后连续出现 8 个数字,您可以立即输入第 9 个而无需任何搜索)。

推理

虽然肯定不是一个令人印象深刻的技术,但它很有可能正确工作,因为您只会修改副本以添加单个值。这可以防止您的数据结构损坏(您的想法存在的一个问题是它会破坏回溯时找到的数字,不一定是您刚刚插入的数字,但可能是初始难题的一部分)。

提高性能非常简单,一旦您开始选择更智能的启发式方法(例如,您可以选择剩余移动最少的那些,并尝试将它们排除在外 - 或者反过来...... ) 或者开始做一些推演和修剪。

注意:算法设计手册使用 Soduko 求解器来展示这些技术对回溯的影响。

于 2013-05-21T17:28:09.583 回答
1

递归算法有一个非常重要的修改:使用最受约束的第一种方法。这意味着首先解决可能候选者数量最少的单元格(当直接行/列/块冲突被删除时)。

另一个修改是:就地换板;不要复制它。在每个递归调用中,您只修改板上的一个单元格,而那个单元格曾经是空的。如果该调用没有在递归调用树下某个已解决的板上结束,则只需在返回之前再次清除单元格 - 这会将板返回到原始状态。

您可以在 C# 中找到一个非常简短且快速的解决方案,地址为:Sudoku Solver。它只需要大约 100 步就可以解决任意数独板,这一切都归功于最受限制的第一启发式。

于 2013-05-22T07:13:56.680 回答
0

这是一个经典的约束满足问题。我建议对该主题进行一些研究,以找出成功的策略。您将需要使用 AC-3(Arc Consistency 3)算法以及回溯技术来解决问题。

于 2016-10-01T02:17:35.127 回答