web-dev-qa-db-ja.com

数独解法アルゴリズムC ++

数独解決プログラムを数日間作成しようとしていますが、その方法に固執しています。私はここでこのアルゴリズムを見つけましたが、私はそれを本当に理解していません:

  1. 最初の空のセルから開始し、その中に1を入れます。
  2. ボード全体をチェックし、競合がないかどうかを確認します
  3. ボード上に競合がある場合は、現在のセルの数を1増やします(したがって、1を2に、2を3に変更します)。
  4. ボードがきれいに動く場合は、ステップ1からやり直してください。
  5. 特定のセルで考えられる9つの数字すべてがボードで競合を引き起こす場合は、このセルを空に戻し、前のセルに戻って、手順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 。ウィキペディアに表示されているように、解決された数独が表示されることを期待しています。しかし、解決された値の中には正しいものもあれば、そうでないものもあります。これが私がコンソールに表示するものです enter image description here

5
Sinan Zikri

推奨されるアプローチ

  1. 一般的なグラフ検索アルゴリズムを実装します
    • [〜#〜] idfs [〜#〜] または A *グラフ検索 のいずれかを使用できます。
      • 私は2番目を好む
    • 一般有向グラフに対してこれを行います。
      • ノードタイプTNode
      • ノード後継関数TNode => vector<TNode>
  2. 数独の状態を定義します
    • 状態は、番号1、2、...、または9の9x9配列、または各位置に空白があります
  3. 数独の目標を定義する
    • 81個​​のセルすべてが入力されました
    • 9行すべてに番号{1、2、...、9}が含まれています
    • 9つの列すべてに番号{1、2、...、9}が含まれています
    • 9つの3x3の正方形すべてに、数字{1、2、...、9}が含まれています
  4. 有効な数独状態の後継関数を定義します
    • 状態Sは、行N、列Iに番号Jを追加できます:
      • セル(I,J)は空です
      • Nに他のIはありません
      • Nに他のJはありません
      • (I,J)を含む3x3の正方形には他のNはありません。
    • 状態後続関数は、状態Sをこれらのルールを満たす状態のvectorにマップします。
  5. 一般的なグラフ検索アルゴリズム(1)を数独状態グラフ(2-4)に適用します
  6. (オプション)A *グラフ検索を使用することを選択した場合は、数独状態空間にヒューリスティックを定義して、パフォーマンスを大幅に向上させることもできます[.____]。
    • ヒューリスティックを設計する方法は別の全体的な問題であり、それは科学というよりは芸術です

現在のアプローチ

現在のアプローチでは、検索するグラフの仕様と検索アルゴリズムの実装。これら2つを混ぜると、多くの困難が生じます。この問題は自然に2つの異なる部分(アルゴリズムとグラフ)に分かれるので、実装でそれを活用することができ、活用する必要があります。それはそれをはるかに簡単にします。

この分離を使用すると得られる他の利点は、膨大な数の問題に対してグラフ検索アルゴリズムを再利用できることです-非常にクールです!!

20
Timothy Shields

以下は、パズルを生成するのではなく、特定のボードを解決しようとしていることを前提としています。

基本的な(単純な)アプローチ

オブジェクトがボードを保持できるクラスを作成します(ここでは_board_t_と呼びます)。このクラスは内部で配列を使用できますが、ボードのコピーをサポートする必要があります。

nごとに以下を繰り返す関数void solve(board_t const& board);があります。

  • 入力をコピーします
  • コピーしたボードの最初の空のセルにnを入力します
  • コピーしたボードがソリューションの場合は、ソリューションとreturnを印刷します。
  • それ以外の場合、ボードがまだ実行可能である場合(たとえば、競合がない場合):
    • solve(copied_board)を呼び出す

パフォーマンス

これは再帰的なバックトラッキングソリューションであり、難しい問題に対してひどく実行されます。適切な剪定または演繹的な手順を実行することで、大幅に高速化できます(たとえば、1つを挿入した後に8つの数字が連続して表示される場合は、検索を行わなくてもすぐに9番目の数字を入力できます)。

推論

確かに印象的な手法ではありませんが、コピーを変更して単一の値を追加するだけなので、正しく機能する可能性が高くなります。これにより、データ構造の破損が防止されます(バックトラック時に検出された数値が破壊されるという問題があります。必ずしも挿入したばかりの数値ではありませんが、最初のパズルの一部である可能性があります)。

パフォーマンスの向上は非常に簡単です。よりインテリジェントなヒューリスティックを選択し始めると(たとえば、正方形を順番にテストする代わりに、残りの動きが最も少ないものを選択して、邪魔にならないようにするか、またはその逆を行うことができます... )または、少しの控除と剪定を開始します。

注: アルゴリズム設計マニュアル は、数独ソルバーを使用して、これらの手法がバックトラッキングに与える影響を示しています。

4
gha.st

再帰的アルゴリズムには非常に重要な変更が1つあります。それは、最も制約のある最初のアプローチを使用することです。これは、最初に可能な候補の数が最も少ないセルを解決することを意味します(直接の行/列/ブロックの競合が削除された場合)。

もう1つの変更は次のとおりです。ボードを所定の位置に変更します。コピーしないでください。各再帰呼び出しでは、ボード上の1つのセルのみを変更し、そのセルは以前は空でした。その呼び出しが再帰呼び出しツリーのどこかで解決されたボードに到達しない場合は、戻る前にセルを再度クリアするだけです。これにより、ボードが元の状態に戻ります。

あなたはアドレスのC#で非常に短くて速い解決策を見つけることができます: 数独ソルバー 。最も制約のある最初のヒューリスティックのおかげで、任意の数独ボードを約100ステップで解決します。

1
Zoran Horvat

これは古典的な制約充足問題です。成功する戦略を理解するために、このトピックについて調査することをお勧めします。問題を解決するには、バックトラッキング手法とともにAC-3(Arc Consistency 3)アルゴリズムを使用する必要があります。

0
s.n