我目前正在研究递归回溯的漂亮话题。 我已经尝试过一些经典的例子,比如找出迷宫中的最短路径,或者是n-queens问题。但是我现在正在处理的问题让我感到困惑: 实际上我认为解决一个简单的拼图游戏可能是一个简单的练习: 我有一块大小为n = a * b的板子和那么多(n)件 最后,我希望按照一定的顺序将所有部件放在电路板上,以便遵守某些限制(例如匹配他们的邻居)。相当简单,我想,我想出了以下算法:
public board recursiveSolve(Board board, Piece[] pieces, int position){
// base case
if(position == board.getLength())
return board;
else{
// For each possible piece
for(int i = 0; i < pieces.length; i++){
// if the piece is placeable at that position
// place it and search on recursively
if(board.isValid(piece[i], position)){
board.putPiece(pieces[i], position);
// THIS IS THE FISHY PART!!
// Now I need to pass a set of pieces to the function recursively
// that does not contain the current one (pieces[i])
// But I fear my (following) approach is too heap-intensive
Piece[] subPieces = new Piece[pieces.length - 1];
// fill subPieces with all elements from pieces except from the one
// at position i
for (int j = 0; j < subPieces.length; j++) {
if(j >= i)
subPieces[j] = pieces[j+1];
else
subPieces[j] = pieces[j];
}
if(recursiveSolve(board, subPieces, position + 1) != null)
return board;
else
board.removePiece(position);
}
}
// If this is reached, none of the pieces have worked -> go back
return null;
}
基本上,这个算法做了它应该做的事情 - 但不幸的是它只适用于“小”板尺寸(n <100)。
因为如果我有一个像10 x 10正方形和100块的板,函数搜索和搜索,直到JVM由于堆空间不足而崩溃才结束。
我甚至尝试将eclipse的内存大小限制设置为1.2g,这使得该功能工作时间更长但仍然不够。
所以我的问题是:是否有可能优化上面的算法,使其适用于电路板尺寸n&gt; 100?我究竟做错了什么?或者我采取了完全错误的做法?
非常感谢您的帮助。
答案 0 :(得分:1)
通过使用可以节省堆的尾递归,函数式语言将有助于此。不幸的是,似乎JVM不支持尾递归(至少对于Java而言),请参阅this SO question
您可以尝试手动模拟尾递归。
答案 1 :(得分:1)
由于董事会有一种方法可以告诉你piece [i]在一个位置是否有效,在继续之前迭代这些位置并尝试该位置的每个(剩余)部分是否更有意义?它不会使用递归(这会解决你的堆空间问题),但是如果你专注于递归解决方案,那么它显然不合适。
为了更有效地做到这一点,我建议将这些碎片放在List中,然后在放置时取出一块。像这样:
List<Piece> remainingPieces = new ArrayList<Piece>(Arrays.asList(pieces));
int numberOfPositions = // I assume you have some way of finding this.
for (int position = 0; position < numberOfPositions; position++) {
Iterator<Piece> it = remainingPieces.iterator();
while (it.hasNext()) {
Piece temp = it.next();
if (board.isValid(temp, position)) {
board.putPiece(temp, position);
it.remove();
break;
}
}
}
答案 2 :(得分:1)
您的程序中的主要堆使用似乎确实在您怀疑的地方:初始化size pieces.length -1
的新数组时。
请注意,确实可以节省大量空间!因为你实际上只使用了“最深”的设置。
如果您仍想使用数组,则可能需要传递一个额外的参数:start
,并实现swap(arr,i,k)
,它会交换arr中的第i和第k个元素,并且每个步骤,而不是分配新数组swap(pieces,start,i)
,并在递归步骤中传递给新函数start+1
。请注意,由于您总是交换最后一个元素,因此接下来的步骤不关心交换,因为它们位于数组的start
位置之后。所以基本上,因为算法永远不会'回头',所以你在交换这些元素时没有任何问题......
看起来应该是这样的:
public board recursiveSolve(Board board, Piece[] pieces, int position,int start){
if(position == board.getLength())
return board;
else {
//starting from start instead from 0
for(int i = start; i < pieces.length; i++){
if(board.isValid(piece[i], position)){
board.putPiece(pieces[i], position);
swap(pieces,start,i); //the swap() method I mentioned above
//sending start+1:
if(recursiveSolve(board, subPieces, position + 1,start+1) != null)
return board;
else
board.removePiece(position);
}
}
return null;
}
你可能知道回溯算法是耗时的[指数!],因此即使使用空间优化版本,算法也可能运行很长时间,直到找到答案。