使用递归回溯时耗尽堆
我目前正在研究递归回溯这个美丽的话题。 我已经尝试过经典的例子,例如寻找走出迷宫的最短路径或 n 皇后问题。但我现在正在解决的问题确实让我很困惑: 事实上,我认为解决一个简单的拼图游戏可能是一个简单的练习: 我有一块尺寸为 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 > 。 100?我做错了什么?或者我采取了完全错误的方法?
非常感谢您提前提供的帮助。
I am currently studying the beautiful topic of recursive backtracking.
I've already tried classical examples like finding the shortest path out of a maze, or the n-queens problem. But the problem I'm working on right now really keeps me confused:
Actually I thought it might be an easy exercise to solve a simple jigsaw-puzzle:
I have a board with the size of n = a * b and exactly that much (n) pieces.
In the end I want to have all the pieces to be put on the board in a certain order where they obey certain restrictions (like matching their neighbours). Fairly easy, I thought and I came up with the following algorithm:
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;
}
Well basically, this algorithm does what it should do - but unfortunately it only works for "small" board sizes (n < 100).
Because if I have a board like 10 x 10 squares and 100 pieces, the function searches and searches and just doesn't come to an end until JVM crashes due to insufficient heap space.
I even tried setting eclipse's memory size limit up to 1.2g which made the function work longer but still was not enough.
So my question is: Is it possible to optimize the algorithm above to make it work for board sizes n > 100? What am I doing wrong? Or am I taking the entirely wrong approach?
Thank your very much for you help in advance.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(3)
看来您的程序中的主要堆使用情况确实是您怀疑的地方:初始化
sizepieces.length -1
的新数组时。请注意,这里您确实可以节省大量空间!因为您实际上只使用“最深”的集合。
如果您仍然想使用数组,您可能需要传递一个额外的参数:
start
,并实现swap(arr,i,k)
来交换第 i 个数组和 arr 中的第 k 个元素,并且在每个步骤中,不是分配新数组,而是swap(pieces,start,i)
,并传递给新函数start+1
code> 在递归步骤中。请注意,由于您总是交换最后一个元素,因此接下来的步骤不关心交换,因为它们位于数组的start
位置之后。所以基本上,因为算法永远不会“回头看”,所以交换这些元素不会有任何问题...应该看起来像这样:
您可能知道回溯算法非常耗时[指数!],所以即使对于空间优化版本,算法可能会运行很长时间,直到找到答案。
It seems the main heap usage in your program is indeed where you suspect: when initializing the new array of
size pieces.length -1
.Note that you can indeed save a lot of space here! since you actually use only the 'deepest' set.
If you still want to use an array, you might want to pass an extra parameter:
start
, and implementswap(arr,i,k)
which swaps the i'th and k'th elements in arr, and in each step, instead of allocating a new array,swap(pieces,start,i)
, and pass to the new functionstart+1
in the recursive step. note that since you always swap the last element, the next steps do no care of the swaps, becasue they are after thestart
position of the array. So basically, because the algorithm never 'looks back', you don't have any problems swapping these elements around...Should look something like that:
You are probably aware that backtracking algorithms are time-consuming [exponential!], so even with space-optimized version, the algorithm might run for a very long time until an answer is found.
函数式语言将通过使用尾递归来帮助这里,这将节省堆。不幸的是,JVM 似乎不支持尾递归(至少对于 Java 而言),请参阅
您可以尝试手动模拟尾递归。
Functional languages will help here by employing tail recursion which would save heap. Unfortunately, it seems JVM don't support tail recursion (at least, for Java), see this SO question
You can try to emulate tail recursion by hand.
由于棋盘有一种方法可以告诉您piece[i]在某个位置上是否有效,因此在继续之前迭代位置并尝试该位置中的每个(剩余)棋子不是更有意义吗?它不会使用递归(这将解决您的堆空间问题),但如果您特别追求递归解决方案,那么它显然不适合。
为了更有效地做到这一点,我建议将这些片段放在一个列表中,并在放置时删除它。像这样的事情:
Since the board has a method to tell you whether piece[i] is valid in a position, doesn't it make more sense to iterate over the positions and try every (remaining) piece in that location before moving on? It wouldn't be using recursion (which would solve your heap space issue), but if you're after a recursive solution specifically it's obviously not a fit.
In order to do this more effectively I would suggest placing the pieces in a List however and remove a piece as you place it. Something like this: