数独解決プログラムを数日間作成しようとしていますが、その方法に固執しています。私はここでこのアルゴリズムを見つけましたが、私はそれを本当に理解していません:
- 最初の空のセルから開始し、その中に1を入れます。
- ボード全体をチェックし、競合がないかどうかを確認します
- ボード上に競合がある場合は、現在のセルの数を1増やします(したがって、1を2に、2を3に変更します)。
- ボードがきれいに動く場合は、ステップ1からやり直してください。
- 特定のセルで考えられる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 。ウィキペディアに表示されているように、解決された数独が表示されることを期待しています。しかし、解決された値の中には正しいものもあれば、そうでないものもあります。これが私がコンソールに表示するものです
TNode
TNode => vector<TNode>
S
は、行N
、列I
に番号J
を追加できます:(I,J)
は空ですN
に他のI
はありませんN
に他のJ
はありません(I,J)
を含む3x3の正方形には他のN
はありません。S
をこれらのルールを満たす状態のvector
にマップします。現在のアプローチでは、検索するグラフの仕様と検索アルゴリズムの実装。これら2つを混ぜると、多くの困難が生じます。この問題は自然に2つの異なる部分(アルゴリズムとグラフ)に分かれるので、実装でそれを活用することができ、活用する必要があります。それはそれをはるかに簡単にします。
この分離を使用すると得られる他の利点は、膨大な数の問題に対してグラフ検索アルゴリズムを再利用できることです-非常にクールです!!
以下は、パズルを生成するのではなく、特定のボードを解決しようとしていることを前提としています。
オブジェクトがボードを保持できるクラスを作成します(ここでは_board_t
_と呼びます)。このクラスは内部で配列を使用できますが、ボードのコピーをサポートする必要があります。
n
ごとに以下を繰り返す関数void solve(board_t const& board);
があります。
n
を入力しますreturn
を印刷します。solve(copied_board)
を呼び出すこれは再帰的なバックトラッキングソリューションであり、難しい問題に対してひどく実行されます。適切な剪定または演繹的な手順を実行することで、大幅に高速化できます(たとえば、1つを挿入した後に8つの数字が連続して表示される場合は、検索を行わなくてもすぐに9番目の数字を入力できます)。
確かに印象的な手法ではありませんが、コピーを変更して単一の値を追加するだけなので、正しく機能する可能性が高くなります。これにより、データ構造の破損が防止されます(バックトラック時に検出された数値が破壊されるという問題があります。必ずしも挿入したばかりの数値ではありませんが、最初のパズルの一部である可能性があります)。
パフォーマンスの向上は非常に簡単です。よりインテリジェントなヒューリスティックを選択し始めると(たとえば、正方形を順番にテストする代わりに、残りの動きが最も少ないものを選択して、邪魔にならないようにするか、またはその逆を行うことができます... )または、少しの控除と剪定を開始します。
注: アルゴリズム設計マニュアル は、数独ソルバーを使用して、これらの手法がバックトラッキングに与える影響を示しています。
再帰的アルゴリズムには非常に重要な変更が1つあります。それは、最も制約のある最初のアプローチを使用することです。これは、最初に可能な候補の数が最も少ないセルを解決することを意味します(直接の行/列/ブロックの競合が削除された場合)。
もう1つの変更は次のとおりです。ボードを所定の位置に変更します。コピーしないでください。各再帰呼び出しでは、ボード上の1つのセルのみを変更し、そのセルは以前は空でした。その呼び出しが再帰呼び出しツリーのどこかで解決されたボードに到達しない場合は、戻る前にセルを再度クリアするだけです。これにより、ボードが元の状態に戻ります。
あなたはアドレスのC#で非常に短くて速い解決策を見つけることができます: 数独ソルバー 。最も制約のある最初のヒューリスティックのおかげで、任意の数独ボードを約100ステップで解決します。
これは古典的な制約充足問題です。成功する戦略を理解するために、このトピックについて調査することをお勧めします。問題を解決するには、バックトラッキング手法とともにAC-3(Arc Consistency 3)アルゴリズムを使用する必要があります。