1

静的パターン データベース (5-5-5) については、こちら(290 ページと 283 ページ) を参照してください。または、以下に説明があります。15パズルとは
静的パターン データベースを作成しています(5-5-5)。このコードは、最初のテーブルにエントリを入力します。私は再帰関数を介してそれをやっていますinsertInDB()。再帰関数への最初の入力はこれです (実際には、入力パズルには 1 次元配列で含まれています。理解を深めるために、以下では 2 次元として表しています)


1 2 3 4
0 6 0 0
0 0 0 0
0 0 0


これは私のコードです:

class DBClass
{
    public Connection connection;
     public ResultSet rs;
      public PreparedStatement ps1;
    public PreparedStatement ps2;
    public int k;
      String read_statement,insert_statement;

    public DBClass()
    {
        try {
            Class.forName("com.mysql.jdbc.Driver");
        } catch (ClassNotFoundException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
            try {
                connection = DriverManager
                    .getConnection("jdbc:mysql://localhost/feedback?"
                        + "user=ashwin&password=ashwin&autoReconnect=true&useUnicode=true&characterEncoding=utf8&validationQuery=Select 1");
                insert_statement="insert into staticpdb1(hash,permutation,cost) values(?,?,?)";
                read_statement="select SQL_NO_CACHE * from staticpdb1 where hash=? and permutation= ? LIMIT 1";
                 ps1=connection.prepareStatement(read_statement, ResultSet.TYPE_SCROLL_SENSITIVE, 
                            ResultSet.CONCUR_UPDATABLE);
                ps2=connection.prepareStatement(insert_statement);
                k=0;
            } catch (SQLException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
    }
    public int updateIfNecessary(FifteenPuzzle sub) 
       {
           String str=sub.toDBString();
           try
           {

               ps1.setInt(1, sub.hashcode());
               ps1.setString(2,str);
               rs=ps1.executeQuery();
           if(rs.next())
              {
                  //if a row exists, check if the cost is greater than sub's
                  int cost=rs.getInt(3);
                  if(sub.g_n<cost)  //if the cost of sub is less than db row's cost
                  {
                      //replace the cost
                      rs.updateInt(3, sub.g_n);
                      rs.updateRow();
                      return 1;   //only examine - do not insert
                  }
                  else
                      return 0;   //dont examine - return

              }
           else
               return 2;      //insert and examine
           }
           catch(SQLException e)
           {

               System.out.println("here1"+e);
               System.err.println("reported recursion level was "+e.getStackTrace().length);
               return 0;
           }
           finally{

               try{
                   rs.close();}
               catch(final Exception e1)
               {
                   System.out.println("here2"+e1);
               }

           }


       }
    public void insert(FifteenPuzzle sub)
    {

        try{
        String str=sub.toDBString();


         ps2.setInt(1,sub.hashcode());
         ps2.setString(2, str);
         ps2.setInt(3,sub.g_n);
         ps2.executeUpdate();
         ps2.clearParameters();
        }catch(SQLException e)
        {
            System.out.println("here3"+e);
        }
    }

    public void InsertInDB(FifteenPuzzle sub) throws SQLException
       {

           System.out.println(k++);

           int i;

           int p=updateIfNecessary(sub);
          if(p==0)
          {
              System.out.println("returning");
           return;
          }
          if(p==2)
          {
          insert(sub);
          System.out.println("inserted");
          }


           //FifteenPuzzle temp=new FifteenPuzzle(sub.puzzle.clone(),2,sub.g_n);
           for(i=0;i<sub.puzzle.length;i++)
           {
               if(sub.puzzle[i]!=0)
               {

                   //check the positions it can be moved to
                   if(i%4!=0 && sub.puzzle[i-1]==0)  //left
                   {
                       //create another clone and increment the moves
                       FifteenPuzzle temp_inner=new FifteenPuzzle(sub.puzzle.clone(),2,sub.g_n+1);
                       //exchange positions
                        int t=temp_inner.puzzle[i];
                        temp_inner.puzzle[i]=temp_inner.puzzle[i-1];
                        temp_inner.puzzle[i-1]=t;
                        InsertInDB(temp_inner);
                   }
                   if(i%4!=3 && sub.puzzle[i+1]==0)  //right
                   {
                       //create another clone and increment the moves
                       FifteenPuzzle temp_inner=new FifteenPuzzle(sub.puzzle.clone(),2,sub.g_n+1);
                       //exchange positions
                        int t=temp_inner.puzzle[i];
                        temp_inner.puzzle[i]=temp_inner.puzzle[i+1];
                        temp_inner.puzzle[i+1]=t;
                        InsertInDB(temp_inner);
                   }
                   if(i/4!=0 && sub.puzzle[i-4]==0)  //up
                   {
                       //create another clone and increment the moves
                       FifteenPuzzle temp_inner=new FifteenPuzzle(sub.puzzle.clone(),2,sub.g_n+1);
                       //exchange positions
                        int t=temp_inner.puzzle[i];
                        temp_inner.puzzle[i]=temp_inner.puzzle[i-4];
                        temp_inner.puzzle[i-4]=t;
                        InsertInDB(temp_inner);
                   }
                   if(i/4!=3 && sub.puzzle[i+4]==0)  //down
                   {
                       //create another clone and increment the moves
                       FifteenPuzzle temp_inner=new FifteenPuzzle(sub.puzzle.clone(),2,sub.g_n+1);
                       //exchange positions
                        int t=temp_inner.puzzle[i];
                        temp_inner.puzzle[i]=temp_inner.puzzle[i+4];
                        temp_inner.puzzle[i+4]=t;
                        InsertInDB(temp_inner);

                  }
             }   
       }



クラスの関数insertInDB(FifteenPuzzle fp)は再帰関数であり、メイン関数から最初に呼び出され、15 個のパズル引数の配列 (puzzleは Class の整数配列フィールドFifteenPuzzle) が - 1,2,3,4,0,6,0,0,0,0,0,0,0,0,0,0(上記の行列と同じ) です。 . 他の機能を説明する前に、静的パターン データベースとは何かを説明します。簡単に(以下のコメントのため)

15パズルの(5-5-5)静的パターンデータベースとは?

パターン データベースは、15 のパズル (どんなパズルでもかまいません。ただし、ここでは 15 のパズルについてのみ説明します) を解くために使用されるヒューリスティックです。ヒューリスティックとは、次に展開する状態を決定するために使用される数値です。私は各状態のコストのようなものです。こちらの状態は15パズルの順列です。8-Puzzle のような単純なパズルの場合、ヒューリスティックはマンハッタン距離になります。見当違いのタイルごとに、目標位置に到達するための最小移動数を示します。次に、すべてのタイルのマンハッタン距離を合計して、そのタイルのコストを算出します。マンハッタン距離は、目標状態に到達するために必要な移動数の推定値の下限を示します。つまり、マンハッタン距離未満の移動では目標状態に到達できません。しかしマンハッタン距離は、近くにある他のタイルを考慮していないため、許容できますが、あまり良いヒューリスティックではありません。タイルをゴール位置に移動する必要がある場合、近くのタイルも移動する必要があり、移動回数が増加します。したがって、明らかにこれらのパズルの実際のコストは、マンハッタンの距離よりもはるかに大きくなります。
克服するこれ(マンハッタン距離)と他のタイルを考慮して、パターン データベースが導入されました。静的パターン データベースには、サブ問題またはタイルのグループが目標状態に到達するためのヒューリスティックが保持されます。これらのタイルのグループが目標状態に到達するための移動数を計算しているため、タイルが移動されている場合、そのグループ内の他のタイルが考慮されます。したがって、これはより優れたヒューリスティックであり、ほとんどの場合、常にマンハッタン距離よりも大きくなります。
5-5-5 静的パターンは、グループの数が 3 である静的パターン データベースの形式であり、そのうちの 2 つはそれぞれ 5 つのタイルを含み、3 つ目は 6 つを含みます (6 つ目は空白のタイル)。

グループの 1 つがこのマトリックスです。

1 2 3 4
0 6 0 0
0 0 0 0
0 0 0


このグループのすべての順列のヒューリスティック/number_of_moves を計算して、上記の構成に到達し、それらをデータベースに挿入しています
可能な組み合わせの総数(dbの行数も)は

16!/(16-5)! = 524160


さて、他の関数 - updateIfNecessary(FifteenPuzzle)- この関数は、渡された FifteenPuzzle オブジェクトの配列がデータベースに既に存在するかどうかをチェックします。データベースに既に存在する場合は、現在のオブジェクトのコストが DB のコストよりも小さいかどうかを確認します。はいの場合、それを現在のコストに置き換えます。そうでない場合は何もしません。関数 -insert(FifteenPuzzle)コストで新しい順列を挿入します。

注 : fifteenuzzle.g_nはパズルの費用です。上記のマトリックスを表す最初のパズルの場合、コストは0であり、各移動のコストはincremented by1です。

実行構成のスタック サイズのスタック サイズを - Xss128m(1024、512、および 256 で致命的なエラーが発生しました) に設定しました。
現在、再帰数または深さは7,500,000であり、カウント( の値System.out.println(k++);) です。
可能な組み合わせの総数は

16!/(16-5)! = 524160


しかし、深さはすでに7,500,000に達しています。これは、重複した状態が生成されるためです。現在、データベースのエントリ数は513423です。現在、10,000 エントリしか入力されていないと思われるかもしれません。しかし、現在、エントリが作成される速度は、30 分ごとに 1 エントリ程度大幅に減少しています。これでは絶対に治りません。再帰の有無にかかわらず、

実用的なソリューションが必要です。出来ますか?

4

5 に答える 5

2

重要な部分は最初の行です: java.lang.StackOverflowError. 再帰はスタックを必要とします。

アルゴリズム部分だけを再帰的に実行し、DB アクセスは別のメソッドに入れてみてください。

于 2012-11-05T10:08:06.950 に答える
2

すべての順列を取得するためにブロックを移動しているようです。次に、各順列が DB に存在することを確認します。はいの場合、必要に応じて移動数を更新します。

それは木を生成します。DFS スタイルで (再帰呼び出しによって) 生成しています。BFS スタイルで行うと、常に最小数の移動が得られます。後で生成された複製状態には、常により大きな移動が必要です。したがって、DB で比較する必要はありません。

次の例では、シフト6してから移動回数を確認します。

Priority: Left, Right, Up, Down (as you gave)

DFS スタイル

1 2 3 4    1 2 3 4
0 6 0 0    6 0 0 0
0 0 0 0    0 0 0 0
0 0 0 0    0 0 0 0
  (0)        (1)

一番左の位置に達しました。ここで、(元の場所から) 右に移動することを確認します。その位置はDBにすでにあるので、続けてください。さらに、上がることもできません。それで下ります。

1 2 3 4    1 2 3 4
0 0 0 0    0 0 0 0
6 0 0 0    0 0 0 0
0 0 0 0    6 0 0 0
  (2)        (3)

さぁ、右へ

           State-1    State-2    State-3
1 2 3 4    1 2 3 4    1 2 3 4    1 2 3 4
0 0 0 0    0 0 0 0    0 0 0 0    0 0 0 0
0 0 0 0    0 0 0 0    0 0 0 0    0 0 0 0
6 0 0 0    0 6 0 0    0 0 6 0    0 0 0 6
  (3)        (4)        (5)        (6)

ここでState-1は、わずか 2 回の移動 (4 回ではなく) で到達できることがわかります。しかし、それは後で明らかになり、DB を更新する必要があります。したがって、明らかに努力の無駄です。

BFS スタイル

1 2 3 4    1 2 3 4
0 6 0 0    6 0 0 0
0 0 0 0    0 0 0 0
0 0 0 0    0 0 0 0
  (0)        (1)

一番左の位置に達したので、右に移動します

1 2 3 4    1 2 3 4
0 0 6 0    0 0 0 0
0 0 0 0    0 6 0 0
0 0 0 0    0 0 0 0
  (1)        (1)

6これは、すべての辺を均等に広げていると考えることができます。ここでも、状態が重複しますが、DB の最初のエントリよりも大きな最小移動数が必要になります。

これを実装するには、単純なキューを使用できます。

擬似コード:

Initialize no_of_moves by 0
enqueue(startingPosition)
insertIntoDB(startingPattern, no_of_moves)
while((currentPattern = dequeue()) is not null)
    if(currentPattern is not already traversed)
        insertIntoDB(currentPattern);
        list<Pattern> allPatterns = patterns which can be reached from currentPattern in just 1 move
        no_of_moves++
        enqueue(allPatterns, no_of_moves)
    end if
end while

DB から確認する以外にも、状態が既にトラバースされているかどうかを確認する方法は多数あります。ハッシュ化を考えていましたが思いつきませんでした。

パターン文字列からマッピングされたブール値のリストを維持できます (たとえばtraversed["1234060000000000"] = true or false)。524160 エントリをメイン メモリに保存しても問題はないと思います。

于 2012-11-16T15:29:39.923 に答える
1

再帰的に呼び出すべきではありません。そうすることで、メモリ内のスタックに別のアドレスが配置され、これが頻繁に発生すると、メモリが不足します。これは、あなたの場合に発生します。StackOverflowError
データを一度入力してから、そのメソッドをループで呼び出すまですべてのデータはデータベースに保存されます。

于 2012-11-05T10:07:09.373 に答える
1

各メソッド呼び出しで新しいPreparedStatementオブジェクトを作成しています。これはここでは再帰メソッドです。元。ps=connection.prepareStatement(read_statement);ps=connection.prepareStatement(insert_statement);。両方に対して 2 つの個別の PreparedStatement オブジェクトを作成し、それらをメソッドの外に移動して、メソッドps.clearParameters();の開始時に呼び出します (両方のオブジェクトに対して)。これにより、ここでは何千ものオブジェクトを作成しているのに、2 つのオブジェクトだけを処理する必要があります。そして、不要になったときにリソースを閉じるように注意してください。(つまり、前FifteenPuzzle temp=new FifteenPuzzle(sub.puzzle.clone(),2,sub.g_n);)

于 2012-11-05T10:44:43.267 に答える
0

あなたの問題は、再帰自体ではなく、リソースの管理ミスに関係していると思います。また、必要以上に実装を複雑にしようとしています。次のことをお勧めします。

  • データベースにデータを挿入/更新するメソッドを作成します。ここでリソースを管理します。これは再帰的である必要はありません。
  • 再帰メソッドからこのメソッドを呼び出して、データベースにデータを挿入/更新します。

データベース固有のメソッドが再帰に戻る前にすべてのリソースを閉じることがわかっているため、これはよりクリーンです。ただし、このメソッドを呼び出すときに接続オブジェクトを再利用することをお勧めします。

また、finally ブロックの一部として、ステートメント、結果セット、または接続を必ず閉じてください。手始めに、私が見ることができる1つの問題はこれです:

  • try ブロックまたは if ブロックで準備済みステートメントを閉じようとしています。If ブロックにヒットせず、リソースをクローズする機会を得る前に例外にヒットした場合はどうなるでしょうか? PreparedStatement およびその他のリソースが閉じられることはありません。
于 2012-11-05T10:04:42.163 に答える