使用递归回溯时耗尽堆

时间:2011-10-21 15:17:22

标签: java algorithm recursion backtracking

我目前正在研究递归回溯的漂亮话题。 我已经尝试过一些经典的例子,比如找出迷宫中的最短路径,或者是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?我究竟做错了什么?或者我采取了完全错误的做法?

非常感谢您的帮助。

3 个答案:

答案 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;
}

你可能知道回溯算法是耗时的[指数!],因此即使使用空间优化版本,算法也可能运行很长时间,直到找到答案。