对于静态模式数据库(5-5-5),请参阅this(第290页和第283页)或下面有说明。对于What is 15-puzzle?
我正在创建一个静态模式数据库(5-5-5)。此代码将条目填入第一个表。我是通过递归函数insertInDB()
来完成的。递归函数的第一个输入是这个(实际上输入拼图包含一维数组。为了更好地理解,我将其表示为下面的二维)
1 2 3 4
0 6 0 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)是递归函数,首先从main函数调用十五个拼图参数的数组(puzzle
是整数数组字段等级FifteenPuzzle
)为 - 1,2,3,4,0,6,0,0,0,0,0,0,0,0,0,0
(与上面显示的矩阵相同)。在解释其他函数之前,我将解释静态模式数据库是什么;简要(由于下面的评论)
模式数据库是用于解决十五个谜题的启发式方法(可以是任何谜题。但在这里我将只讨论15-Puzzle)。启发式算法是用于确定接下来要扩展的状态的数字。我就像每个州的成本。这里的状态是15-Puzzle的排列。对于像8-Puzzle这样的简单谜题,启发式可以是曼哈顿距离。它为每个放错位置的磁贴提供最小移动次数,以达到它的目标位置。然后将所有瓷砖的曼哈顿距离相加以给出该瓷砖的成本。曼哈顿距离给出了达到目标状态所需的移动次数估计的下限,即你不能通过小于曼哈顿距离的移动达到目标状态。 但曼哈顿距离不是一个非常好的启发式,虽然可以接受,因为它不考虑附近的其他瓷砖。如果必须将图块移动到其目标位置,则还必须移动附近的图块并且移动的数量增加。因此,显然对于这些难题,实际成本要大得多
曼哈顿距离。
为了克服这个(曼哈顿距离)并考虑其他瓷砖,引入了模式数据库。
静态模式数据库保存子问题或一组瓦片达到其目标状态的启发式方法。因为,您正在计算使这些图块组达到其目标状态的移动次数,所以在移动图块时将考虑该组中的其他图块。因此,这是一种更好的启发式方法,并且大多数时间总是大于曼哈顿距离
5-5-5静态模式只是静态模式数据库的一种形式,其中组的数量为3,其中两个包含5个瓦片,第三个包含6个(第6个是空白瓦片)。
1 2 3 4
0 6 0 0
0 0 0 0
0 0 0 0
我正在计算此组的所有排列的启发式/ number_of_moves以达到上述配置并将它们插入我的数据库。
可能的组合总数(也就是db中的行数)
16!/(16-5)! = 524160
现在,其他函数 - updateIfNecessary(FifteenPuzzle)
- 此函数检查传递的FifteenPuzzle对象的数组是否已存在于数据库中。如果已存在于数据库中,它将检查当前对象的成本是否小于DB中的成本。如果是的话,它用当前的费用取而代之,否则什么都不做。函数 - insert(FifteenPuzzle)
插入一个新的permutaion与成本。
注意: 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次。这永远不会过去。
我需要一个实用的解决方案 - 有或没有递归。有可能吗?
答案 0 :(得分:2)
重要的部分是第一行:java.lang.StackOverflowError
。递归对堆栈要求很高。
尝试递归地仅执行算法部分,同时将DB访问放在一个额外的方法中。
答案 1 :(得分:2)
似乎你正在移动块来获得所有的排列。然后,检查DB中存在的每个排列;如果是,则在必要时更新移动次数。
它会生成一棵树。您正在以DFS样式生成它(通过递归调用)。如果以BFS样式执行,那么您将始终获得最少的移动次数。稍后生成的重复状态总是需要更大的移动。因此,您无需在数据库中进行比较。
在以下示例中,我们将转换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次)动作中到达。但这将在稍后公布,我们必须更新数据库。所以,显然这是浪费精力。
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
除了从数据库中检查状态之外,还有很多方法可以检查状态是否已经遍历。我在考虑哈希但不能出现。
您可以维护从模式字符串映射的布尔列表(例如traversed["1234060000000000"] = true or false
)。我不认为在主存储器中存储524160条目会产生任何问题。
答案 2 :(得分:1)
你不应该调用递归。这样做会在你的内存中堆叠另一个地址,如果这种情况经常发生,你的内存就会耗尽,这种情况会发生在你的情况下:StackOverflowError
尝试制作一个允许您输入一次数据的方法,然后循环调用该方法,直到所有数据都保存在数据库中。
答案 3 :(得分:1)
您正在每个方法调用中创建新的PreparedStatement
对象,这是一个递归方法。恩。 ps=connection.prepareStatement(read_statement);
和ps=connection.prepareStatement(insert_statement);
。为两者创建两个单独的PreparedStatement对象,并将它们移出方法,并在方法的开头调用ps.clearParameters();
(对于两个对象)。有了这个,你只需处理两个对象,在这里你创建了数千个对象。然后在不再需要时关注资源。 (即,在FifteenPuzzle temp=new FifteenPuzzle(sub.puzzle.clone(),2,sub.g_n);
之前)
答案 4 :(得分:0)
我猜你的问题与资源错误管理有关,而不是递归本身。此外,您试图使您的实现更加复杂,而不是您需要。我建议如下:
这更干净,因为您知道特定于数据库的方法会在返回递归之前关闭所有资源。但是,我建议您在调用此方法时重用连接对象。
此外,请确保关闭语句,结果集甚至连接作为finally块的一部分。对于初学者来说,我可以看到的一个问题是: