25

Java で 9x9 グリッドの数独ソルバーをプログラミングしています。

次の方法があります。

  • グリッドの印刷

  • 指定された値でボードを初期化する

  • 競合のテスト (同じ番号が同じ行または 3x3 サブグリッドにある場合)

  • 数字を 1 つずつ配置する方法で、最も手間がかかります。

その方法について詳しく説明する前に、再帰とバックトラッキングを使用して解決する必要があることを覚えておいてください (例として、こちらのアプレットをご覧ください http://www.heimetli.ch/ffh/simplifiedsudoku.html ) 。

また、私はこの数独を、左上から始めて最初の列、次に 2 番目の列など、垂直に下に移動して解決しています。

これまでのところ、私は次のものを持っています:

public boolean placeNumber(int column){

    if (column == SUDOKU_SIZE){  // we have went through all the columns, game is over

        return true;

    }

    else
    {
        int row=0;  //takes you to the top of the row each time

        while (row < SUDOKU_SIZE)    loops through the column downwards, one by one
        {

            if (puzzle[row][column]==0){  //skips any entries already in there (the given values)

                puzzle[row][column]=1;   //starts with one

                while(conflictsTest(row,column)){   //conflictsTest is the method I wrote, which checks if the given parameters are in conflict with another number

                    puzzle[row][column] += 1;  

                }


           //BACK TRACKING 

                placeNumber(column);      //recursive call

            }
            else{
              row++;                  // row already has a number given, so skip it
            }
        }

        column++;              // move on to second column
        placeNumber(column);

    }
    return false; // no solutions to this puzzle
}

BACKTRACKING とラベルを付けた場所は、コードの残りの部分を移動する必要があると思われる場所です。

私は次の行に沿って何かを考え出しました:

  • 値が 10 の場合、その値を 0 に戻し、行を戻し、その値を 1 増やします

その後戻りの「戦略」は、いくつかの理由で正確には機能しません。

  1. 前の行が指定された値だった場合 (つまり、インクリメントしたり触れたりすることは想定されていませんが、代わりに、そこに配置した最後の値に戻ります)

  2. 前の値が 9 だった場合はどうなるでしょうか。それを 1 増やしたら、今度は 10 になり、うまくいきません。

誰かが私を助けてくれますか?

4

6 に答える 6

7

あなたが数独をどのように解こうとしているのかはわかりませんが、力ずくの方法を使用したとしても (そのため、あなたの説明のように聞こえます)、データ構造が適切ではないと考える必要があります。

つまり、すべてのセルは単なる数字ではなく、一連の数字 (そこに配置される可能性があります) であるべきです。

したがって、指定された数値はシングルトン セットとして表されますが、空の数値は {1,2,3,4,5,6,7,8,9} で初期化できます。そして、すべてのセルがシングルトンになるまで、非シングルトン セルを減らすことが目標です。

(鉛筆と紙で数独を解いている間、解いた範囲で可能な数を追跡するために、空白のセルに小さな数字を書き込むことがよくあることに注意してください。)

そして、「次の番号を試す」ときは、セットから次の番号を取得します。指定されたセルには次の番号がないため、変更できません。このようにして、あなたが説明する困難は(少なくとも少しは)消えます。

------ 総当たりが必要であることを知った後、編集します。

あなたの先生は明らかに再帰の素晴らしさをあなたに教えたいと思っています。とても良い!

その場合、どのセルが指定され、どのセルが指定されていないかを知る必要があります。

ここで使用できる特定の簡単な方法は、指定されたセルは定義により 1、2、3、4、5、6、7、8、9 のいずれかであるため、指定されていないセルに 0 を配置することです。

では、再帰的なブルート フォースを機能させる方法について考えてみましょう。

n個の空のセルで数独を解くという目標があります。n-1 個の空のセルで数独を解く (または解けないことを通知する) 関数があれば、このタスクは簡単です。

let c be some empty cell.
let f be the function that solves a sudoku with one empty cell less.
for i in 1..9
   check if i can be placed in c without conflict
   if not continue with next i
   place i in c
   if f() == SOLVED then return SOLVED
return NOTSOLVABLE

この疑似コードは、空のセルを選択し、そこに収まるすべての数値を試行します。数独には、定義上、ソリューションが 1 つしかないため、次のような場合しかありません。

  • 正しい番号を選択しました。次に、f() は残りの解を見つけて SOLVED を返します。
  • 間違った数字を選択しました: f() は、セル内のその間違った数字では数独が解けないことを通知します。
  • すべての数字をチェックしましたが、誰も正解ではありませんでした。次に、解決できない数独を自分で取得し、これを呼び出し元に通知します。

言うまでもなく、このアルゴリズムは、現在の状態と矛盾しない数字のみを配置するという前提に基づいています。たとえば9、同じ行、列、またはボックスに既に がある場合、そこには配置しません9

私たちの不思議で未知の機能がどのf()ように見えるかを今考えてみると、それは私たちがすでに持っているものとほとんど同じであることがわかります!
まだ検討していない唯一のケースは、空のセルが 0 の数独です。これは、空のセルがなくなったことがわかった場合、数独を解いたばかりであることがわかり、SOLVED を返すことを意味します。

これは、問題を解決することになっている再帰関数を書くときの一般的なトリックです。私たちは solve() を書いていますが、問題がまったく解決可能であることはわかっています。したがって、すべての再帰で問題が解決策に近づくことを確認する限り、今書いている関数を既に使用できます。最後に、いわゆる基本ケースに到達します。ここで、それ以上再帰せずに解を与えることができます。

私たちの場合、数独が解けること、さらに、その解が1 つだけであることもわかっています。空のセルにピースを配置することで、解決策 (または解決策がないという診断) に近づき、作成中の関数に新しい小さな問題を再帰的に与えます。基本ケースは、実際に解決策である「空セルが 0 の数独」です。

(考えられる解が多数ある場合は少し複雑になりますが、それは次のレッスンに譲ります。)

于 2012-02-22T23:23:51.640 に答える
4

まず、最適化の提案: セルに入力しようとしている数値が同じ行、列、またはミニグリッドに既に存在するかどうかを確認する間、ループなどを実行する必要はありません。配列のインデックス付けにより、インスタント チェックを実行できます。

3 つの 9x9 ブール値の 2 次元配列を考えてみましょう。

boolean row[9][9], col[9][9], minigrid[9][9]

最初の配列を使用して数値が同じ行に存在するかどうかを確認し、2 番目の配列を使用して数値が同じ列に存在するかどうかを確認し、3 番目の配列をミニ グリッドに使用します。

セルijに数値nを入れたいとします。row[i][n-1]が trueかどうかを確認します。はいの場合、i番目の行には既に n が含まれています。同様に、col[j][n-1]minigrid[gridnum][n-1]が true かどうかを確認します。

ここでgridnumは、数値を挿入するセルがあるミニ グリッドのインデックスです。セルi,jのミニ グリッド番号を計算するには、i と j を 3 で割り、前者の整数部分に 3 を掛けます。後者の不可欠な部分にそれを追加します。

これはどのように見えるか:

gridnum = (i/3)*3 + j/3

すべての i と j の i/3 と j/3 の値を見ると、これがどのように機能するかがわかります。また、セルに数値を入力した場合は、配列も更新します。例行[i][n-1] = true

理解できない部分がある場合は、コメントを投稿してください。回答を編集して説明します。

次に、再帰とバックトラッキングを使用してこれを解決するのは非常に簡単です。

boolean F( i, j) // invoke this function with i = j = 0
{
If i > 8: return true // solved

for n in 1..9
 {
 check if n exists in row, column, or mini grid by the method I described

 if it does: pass ( skip this iteration )

 if it doesn't
  {
   grid[i][j] = n
   update row[][], col[][], minigrid[][]

   if F( if j is 8 then i+1 else i, if j is 8 then 0 else j+1 ) return true // solved
  }
 }
 return false // no number could be entered in cell i,j
}
于 2012-04-09T04:22:42.217 に答える
1

現在の行と列の両方を再帰メソッドに渡してから、そのセルに許可されているすべての数値を見つけ、許可されている数値ごとに、次の列 (または最後の列の場合は次の行) のメソッドを再帰的に呼び出し、リードする場合は移動を元に戻すことをお勧めしますデッドトラックへ

public boolean fillCell(int r, int c) {
    if last row and last cell {
        //report success
        return true
    }
    for i 1 to 9 {
        if can place i on r, c {
            board[r][c] = i
            if !fillCell(next empty row, next empty column) { //DONT change r or c here or you will not be able to undo the move
                board[r][c] = 0
            }
            /*
            else {
                return true; //returning true here will make it stop after 1 solution is found, doing nothing will keep looking for other solutions also
            }
            */

        }
    }
    return false        
}
于 2012-02-22T23:47:27.450 に答える
0

各セルをチェックし、解決策が見つからない場合は再帰ステップに戻ります。

詳細: 次のセルに移動します。値 x == 0 の場合、x+1 が有効かどうかを確認し、真の場合は、次の可能なセルでメソッドを再帰的に呼び出して次のセルに移動します。番号が有効でない場合は x+2 をチェックし、有効な番号がない場合は false を返し、前の呼び出しで x+1 の手順を繰り返します。数字が入ったセルにヒットした場合、再帰を呼び出さずに直接次のセルに移動します。したがって、事前に入力されたセルにフラグを立てる必要はありません。

擬似コード:

fillcell cell
 while cell is not 0
  cell = next cell
 while cell value < 10
  increase cell value by one
  if cell is valid 
    if fillcell next cell is true
      return true
return false

これが正しいかどうかはわかりませんが、アイデアを示す必要があります。

于 2012-02-23T02:00:23.917 に答える