数独は紙とペンを使って遊ぶ論理ゲームです。プレイヤーは、9×9 のボード上の既知の数字に基づいて残りのすべてのスペースの数字を推測し、各行、列、太線の宮殿の数字に重複なく 1 ~ 9 が含まれていることを確認する必要があります。 認定された数独パズルには必ず 1 つの答えがあり、推論方法はこれに基づいています。 解答が 1 つもないか、複数の解答がある問題は認定されません。 下の図に示すように、これは数独の問題です。 数独の詳しい紹介については、「Baidu 百科事典 - 数独」を参照してください。 数独の基本的な解法はルール消去法を使うことである いくつかの定義 各行は「数独行」 、各列は「数独列」 、各小さな 9 マスのグリッドは「数独マス」と呼ばれます。数独の基本的なルールは、各行、各列、各マスに 1 から 9 までの数字が 1 回だけ現れるというものです。 上図のセルを表すには (行, 列) を使用します。たとえば、(1, 1) は 1 行目と 1 列目のセルを表し、(2, 4) は 2 行目と 4 列目のセルを表します。 上の図に示すように、各空白セルに入力できる数には制限があります。 たとえば、(1, 1) には 7 と 8 しか入れられません。また、(6, 4) には 8 しか入れられません。 1つの数字しか入れられない空白のセルをユニーク数セルと呼びます。上の図では、(6, 4) がユニーク数セルです。 問題を解く順番は、固有の番号を持つセルから始めます。固有の番号を持つセルには 1 つの番号しか入力できないため、まずこのセルに番号を入力します。このセルに数字を入力してください。ルールの定義により、このセルが配置されている行、列、宮殿内の他のセルにこの数字を入力することはできません。これらのセルに入力できる数字の数は少なくなります。新しい一意の番号セルが生成される可能性もあります。 多くの数独パズルでは、一意の数字が書かれたセルから始めて、一意の数字が書かれたセルを埋めていくことで数独を解くことができます。 問題を解く過程で、空白のセルに数字を記入する必要がないことがわかった場合、そのようなセルは「解けないセル」と呼ばれます。これは、数独パズルに解答がないか、前の問題解決プロセスに問題があり、前の問題解決プロセスに戻って確認する必要があることを意味します。 しかし、数独の問題の中には、解くときに空白のセルはあるものの、一意の数字を持つセルが見つからないものも多くあります。つまり、各空白のセルに埋めることができる数字が少なくとも 2 つあるということです。これを、セルの数が一意ではない状況と呼びます こういう時どうすればいいでしょうか?最も小さい数字の空白セルを 1 つ見つけます (これには明確な答えはありません。最も小さい数字の空白セルの場合もあれば、最初の空白セルの場合もあれば、最も大きい数字の空白セルの場合もあります。どの空白セルを選択するかがその後の問題解決に影響するかどうかは証明されていないため、結論を出すことは困難です。感覚に基づいて最も小さい数字の空白セルを選択するのが最善の選択です)。記入できる数字は複数あるため、まず現在の状態を保存し、次に記入可能な数字から数字を選択し (最小から最大まで選択)、問題を解き続けます。最終結果が解けた場合、現在の選択が正しいことを意味します。その後の解決プロセスで問題が発生した場合、現在の数字の選択に問題があることを意味します。その場合は、別の数字を選択して入力し、解決を続けます。どの選択肢でも最終的な答えが得られない場合は、数独パズルに解答がないか、または前の解答プロセスに問題があり、戻って前の解答プロセスを確認する必要があることを意味します。最終的な答えが得られるまでこのプロセスを繰り返します。 極端な状況が発生する(可能性は低い)。つまり、現在の空白セル内のすべての可能な数字が選択されており、解はありません。そして、これまでに固有の数値セルが存在しない状況は一度もありませんでした。つまり、この数独にはまったく解法がないということです。 次の図は数独を解くフローチャートです。 このアルゴリズムの具体的な実装についてお話ししましょう 1. 数独の状態の表現 コンピュータを使用して数独を解きます。基本的なポイントは、数独の状態をどのように表現するかです。 整数1次元配列を使用して数独の状態を表す Num(80)は数独の状態を表すために使用されます(配列の添え字は0から始まります)。数独は2次元の表ですが、配列は1次元の配列です。次に、1次元と2次元の間の変換があります 1次元配列の添字インデックス(添字は0から始まる)と2次元の添字X、Y(添字は0から始まる)間の変換式
配列内の各整数は、数独パズル内の対応するセルの状態を表します。 正の数は、空白のセルに入力できる数値の組み合わせを 2 進数で表します。ビットを使用して、セルを対応する数字で埋めることができるかどうかを示します。1 は埋めることができることを意味し、0 は埋めることができないことを意味します。 例えば、記事の冒頭にある数独のセル(1, 1)には7と8が記入されているので、7番目と8番目の数字は1(数字は後ろから前に数えます)で、残りは0です。整数形式では、Num(0)= 011000000 2 = 192です。 セルに数字が入力されるたびに、対応する行、列、または四角形の残りのセルからその数字が削除されます。 ビット演算をフル活用して、数値を 10 進数化するプロセスを簡素化できます。たとえば、セルから数字 7 の可能性を削除します。まず、7 に対応する 2 進数 001000000 2の逆数を取り、 110111111 2を取得します。次に、ターゲット セルの値とビット単位の AND 演算を実行して、セルから数字 7 の可能性を排除します (ビット単位の演算の利便性により、セルに元々数字 7 が含まれていたかどうかを考慮する必要はありません)。 たとえば、(1, 1) = 011000000 2 AND 110111111 2 = 010000000 2の場合、7 の可能性を除外すると 8 のみが残り、これは一意の数値セルになることを意味します。 たとえば、(1, 9) = 010000010 2 AND 110111111 2 = 010000010 2です。元のセルには 7 の可能性はありません。ビット操作を実行した後も、可能性は同じままで変化しません。 負の数は、セルにすでに特定の数字が入力されていることを示します。たとえば、(1, 2) = -6 は、セルに数字 6 が入力されていることを意味します。 0 は、セルに明確な数字がなく、数字が入力される可能性もないことを意味します。これが、上で述べた解決不可能なセルです。 アルゴリズムでの計算を容易にするために、これらの 2 進数は事前にキャッシュされ、 1 次元配列で表されます。 各ビットに対応する数値を表すために配列Vを使用する
7に対応する2進数はV(6)=001000000 2 =64であり、7の逆数はV(9)-V(6)=110111111 2 =447である。 各セルの初期値はV(9) = 1111111111 2 = 511 2. セルに入力できる数字の数を取得する方法 セルの状態は 2 進数で表されているため、埋めることができる数字の数は、数字内の 1 の数になります。これまで、数値内の 1 の数を素早く計算する非常に便利な方法がありました。強力なアルゴリズムをご覧ください - 正の 2 進整数内の 1 の数を素早く計算します。 3. 状態キャッシュ 前述の説明によれば、セルの数が一意ではない状況に遭遇した場合、現在の状態をキャッシュする必要があります。実際の状況を考慮すると、アルゴリズムの観点からは、スタック(先入れ後出し)データ構造を使用して実装する方が適切です。独自のスタック実装を記述できます。ただし、現在多くのプログラミング言語は基本的なデータ構造を実装し、呼び出すための基本的なデータ構造クラスとメソッドを提供しています。 Visual Studio を例にとると、基本的なスタック操作を実装する Stack クラスがあります。スタック メソッドには 2 つあります: プッシュ - スタックにデータを書き込む; ポップ - スタックからデータを取り出し、スタック内のデータを削除する。 4. コードの説明 基本変数
_Num 配列は数独パズルの状態を表します。_V 配列は、よく使用される 2 進数をキャッシュする補助配列です。 _S は、数独を解くプロセスを保存するテキスト オブジェクトです。_HasString は、解くプロセスを記録するかどうかを示すスイッチ変数です。これら 2 つの変数は補助変数であり、記録としてのみ機能します。 クラスの初期化
コードの前半は配列V、_V(9)=511を生成します。後半では、数独配列が初期化されます。空白の数独配列なので、各セルの値は_V(9)です。 特定のセルの数字を削除する可能性のあるコード
3 つのパラメータ、Row は行、Col は列 (すべての添え字は 0 から始まります)、Num2 は削除する数値の 2 の補数を 2 進数で表します。 たとえば、セル(1, 1)の7の可能性を削除するには、RemoveNum(0, 0, 110111111 2 )を呼び出します。 戻り値はセルの状態値です。0を返す場合、セルが解決不可能なセルになったことを意味し、後続のコードで適切な処理を行う必要があります。 指定されたセルに数字を入力する
パラメータは 3 つあります。Row は行、Col は列、Num は埋める数値 (添字は 0 から始まります) を表します。このメソッドは、クラス内の内部呼び出しに使用されます。プログラムの観点からは、1 から始まる添字よりも 0 から始まる添字の方がプログラムが処理しやすくなります。 たとえば、(1, 1)に数字7を入力するには、SetNumPri (0, 0, 6)を呼び出します。 コードの最初の行では、まずビット演算を使用して、現在のセルに指定された数値を入力できるかどうかを判断します。入力できない場合は、False を返します。 2 行目のコードでは、現在のセルを指定された数値に設定します。前述のように、負の数値は塗りつぶされた数値を表すために使用されます。 コードの 3 行目は、セルが配置されている行、列、および宮殿内の他のセルから数字を削除する準備として、現在の数字の逆コードを取得します。 その後に 2 つのループがあります。最初のループは行と列の他のセルの数字を削除します。2 番目の二重ループは宮殿の他のセルの数字を削除します。 RomoveNum メソッドを呼び出したとき、戻り値が 0 の場合は、解決できないセルが存在することを意味し、このセルに数値を入力することは不可能であることを意味するため、False を返します。 すべてのコードが正常に完了すると、このセルに入力された数値が妥当であることを意味し、Trueを返します。 このメソッドの別のオーバーロード形式
これも内部呼び出しのメソッドで、2 つのパラメータを持ちます。Index は 1 次元配列の添字、Num2 は数値の 2 進形式です。メソッド全体はパラメータを変換し、前のメソッドを呼び出すことです 外部通話には2つの方法があります
最初の方法は、外部呼び出しに公開される数値を入力する方法です。外部的には、添え字を 1 から始める方が直感的ですが、内部的には 0 から始める方が適切です。 たとえば、(1, 1)に7が入力されると、SetNum(1, 1, 7)が呼び出され、このメソッドはSetNumPri(0, 0, 6)を呼び出します。 このメソッドは通常、Sudoku を初期化するときに呼び出されます。 2 番目の方法も外部に公開されています。一度に 1 行ずつ入力し、空白のセルがある場合は 0 に置き換えます。 たとえば、この記事の冒頭にある数独パズルでは、コードの最初の行はSetLine(1, 0, 6, 0, 5, 9, 3, 0, 0, 0)です。 #p# いくつかの補助的な方法
L のデータを Sudoku 配列に復元します。ここで、L は以前にキャッシュされたデータです。 AppendString は、テキスト オブジェクトにデータを記録するメソッドです。
数字の中の1の数、つまり空白のセルに入力できる数字の数を取得します。 たとえば、(1, 1) = 011000000 2 、Get1Count (011000000 2 ) = 2 は、セル (1, 1) に 2 つの数字を入力できることを示します。
指定された数値NumのIndex番目の位置で利用可能なフィルの数を取得します(バイナリ形式) 上記の例を引き続きとると、(1, 1) = 011000000 2 、 GetIndexOfNum(011000000 2 ,1)=7、つまり最初の利用可能な数字は7である。 GetIndexOfNum(011000000 2 ,2)=8、つまり2番目に利用可能な数字は8である。 GetIndexOfNum(011000000 2 ,3)=-1、3番目の数字が入力されていないことを示す 補助録音機能 これらの関数はアルゴリズムを解くのにはあまり役立ちません。将来の研究の参考のために、解のプロセスをテキストで記録するだけです。
数値のテキスト形式を返します。空白セルの場合は、セルに入力できるすべての数値を返します。入力済みのセルの場合は、#+数値の文字列を返します。返される文字列は整列されます。
数独パズル全体のステータステキストを返します
テキストオブジェクトにテキストを追加し、AppendMatrixパラメータに基づいて、数独の状態全体をテキストオブジェクトに追加するかどうかを決定します。
テキストオブジェクトで使用される指定されたインデックスの座標と塗りつぶされた番号を返します。
このメソッドは公開されており、将来の研究の参考のために以前に記録された解決プロセスであるテキストオブジェクトを返します。 メインソルバー関数 - アルゴリズムの核 次の3つの関数がアルゴリズムの中核となる。
この関数は、最小の数値を持つセルを取得します(2より大きい数値を持つ空白セルは埋めることができます) この関数には 3 つの戻り値があります。 戻り値: -1、そのようなセルが見つからない場合、関数は一意の数値セルから数値の入力を開始し、すべての空白セルが埋められるまで順番に数値を入力し続けます。これはソリューションが完了したことを意味します。 戻り値: -2、そのようなセルは見つからない場合、関数は一意の数値セルから数値の入力を開始し、数値を 1 つずつ入力していき、解のないセルが生成されます。これは、前の解決プロセスにエラーがあるか、数独に解決法がないことを意味します。 戻り値: 0-80、そのようなセルが見つかり、現在の数独配列に一意の数字セルがありません (関数は一意の数字セルに数字を直接入力します)
公開されている主なソリューション関数は、最終結果の整数配列を返します。 まず、スタックオブジェクト Q について説明します。スタック Q は一度に 1 つのオブジェクトしかプッシュできないため、セルの数が一意ではない状況が発生すると、現在のデータをキャッシュする必要があります。キャッシュする必要があるコンテンツには、数独配列、見つかった最小の数字のセルの下付き文字、および最小の数字のセルを埋めるために選択された数字の 3 つの部分があります。したがって、前の 3 つのコンテンツをキャッシュするには、List (Integer) オブジェクトが使用されます。 L(0)-L(80)は数独配列を表し、L(81)は最小の数字が含まれるセルの下付き文字であり、L(82)は最小の数字が含まれるセルを埋めるために選択された数字です。 この関数の主な目的は、K の値を決定することです。前の関数で説明したように、K には主に 3 つの値があります。 K=-1は空白のセルがないことを示し、数独は完全に解かれ、結果が直接返されます。 K=-2 は、解セルがあるかどうかを示し、スタック Q 内のデータを確認します。スタック Q にデータがない場合、数独に解がないことを意味します。スタック Q にデータがある場合は、データを取り出し、数独の状態を以前の状態に戻します。そして、前回キャッシュされた最小の数字を持つセルから次の入力可能な数字を抽出して、試行を続行します。 たとえば、0 と 1 がキャッシュされます。これは、最後の試行が最初のセルの最初の利用可能な番号を使用して行われたことを意味します (下付き文字は 0 から始まります)。解けないセルがあるので、最初の可能な数字が間違っているということになります。そのため、2 番目の可能な数字を試し続けます。呼び出されるメソッドは FindNextK(Q, L, K, I) です。ここで、I は 1 増加されます。 K=0-80、可能な限り最小の数字を持つセルの下付き文字を取得します。まず、セル内の最初の可能な数字を試します。呼び出されたメソッド: FindNextK(Q, L, K, 1) 可能な数字を試す関数は FindNextK で、戻り値も -1、-2、0~80 の 3 種類に分かれています。意味は上記と同じです
可能な数値を試した結果を得るための補助関数 まず、GetIndexOfNum を使用して現在使用可能なフィールドの数を取得します。戻り値が-1の場合は、記入する数字がなく、解けないセルがあることを意味するので、戻り値はそのまま-2になります。 次に、SetNumPri(K, _V(J - 1)) を呼び出して、現在のセルに数字を入力しようとします。True を返すと、数字を入力できることを意味し、現在の状態をスタック Q にキャッシュし、FindMinCell 関数を使用して次の可能な K 値を取得して返します。False を返すと、数字を入力できないことを意味し、データを Sudoku 配列に復元し、次の数字を試し続けます。 これまで、このアルゴリズム クラスのコードは完全に説明されました。 このアルゴリズムでは、最も基本的な解決方法である消去法のみが使用されます。一意の番号を持つセルに遭遇した場合は、その番号を直接入力します。一意の番号を持たないセルに遭遇した場合は、データをキャッシュし、数独が解けるまでそのセルのすべての可能な番号を試します。 スタック Q を使用してデータをキャッシュすると、システム リソースが大幅に占有され、問題を解決できなくなるのではないかと疑問に思う人もいるかもしれません。現状を踏まえて、私はこのアルゴリズムを使って「プログラマーは世間に理解されない真の天才か? - この数独の解法を見てください」の最も難しい数独を解き、解法をファイルに保存して解析のために開きました。スタック Q のキャッシュは 20 ステップを超えないことが分かりました。20 ステップを例にとると、各ステップは 83*4 バイトなので、合計は 20*83*4=6640 バイト < 7K バイトです。システムの許容範囲をはるかに下回ります。したがって、システムの耐久性について心配する必要はありません。 優れた数独アルゴリズムをお持ちの方がいらっしゃいましたら、ぜひその経験を私と共有してください。 実際に結果を見てみましょう。このアルゴリズムを使用して、この記事の冒頭にある数独を解いてみましょう。コードは次のとおりです。
この数独は比較的シンプルで、最後まで1 つのセルしかありません。 結果は次のとおりです。 計算完了!!!!
#p# 以下はアルゴリズムクラスの完全なコードです
オリジナルリンク: http://www.cnblogs.com/grenet/p/3138654.html |
<<: OSPFはSPFアルゴリズムを使用してルートを伝播します
[[319322]]この記事では、一般的に使用されている機械学習アルゴリズムの概要と、一般的に使用さ...
人工知能はテクノロジー界でホットな話題となっている。それは人々の生活を変えただけでなく、考えられるあ...
2023 年は、世界中の政府、公共部門、企業、さらには一般大衆の生活を大きく変えるテクノロジーの急...
2016年7月12日から9月5日まで、北京TalkingData Technology Co., ...
科学研究機関の世界総合ランキングでは、中国科学院、中国科学技術大学、北京大学がトップ10にランクイン...
人工知能は、消費者と組織にとって大きな革命的な進歩です。その結果、さらに重要かつ緊急性の高い発見がい...
今日、人工知能技術は社会のあらゆる分野にますます大きな影響を及ぼしており、教育も例外ではありません。...
McKinsey & Company の画期的なレポートでは、AI を含むデジタル調達ソリュ...
緊急時のメモとしても使える、コレクションする価値のあるAI写真を8枚シェアします。最初の RTF フ...
グーグルやフェイスブックなどのテクノロジー大手は長年にわたり、人工知能(AI)に数十億ドルを投資し、...
8月29日、OpenAIは、企業ユーザーのニーズを満たし、より高いセキュリティとプライバシー保護を提...
顔認識は、AI 研究が世界にもたらした数多くの驚異のうちの 1 つです。これは多くの技術者にとって興...