【问题标题】:Overflowing stack with recursion that should terminate?应终止的递归堆栈溢出?
【发布时间】:2019-05-31 09:25:54
【问题描述】:

我正在尝试使用 Java 和递归回溯算法制作一个随机迷宫生成器。当我尝试运行此代码时出现堆栈溢出。我对堆栈有所了解,我认为这不是无限递归。我的猜测是我有一个很大的逻辑错误。我必须分配更多内存吗?

堆栈跟踪:

Exception in thread "main" java.lang.StackOverflowError
    at java.base/java.util.Vector.elementAt(Vector.java:499)
    at java.base/java.util.Stack.peek(Stack.java:103)
    at java.base/java.util.Stack.pop(Stack.java:84)
    at mazeMaker.Maze.generateMaze(Maze.java:115)
    at mazeMaker.Maze.generateMaze(Maze.java:115)
...
    at mazeMaker.Maze.generateMaze(Maze.java:115)
    at mazeMaker.Maze.generateMaze(Maze.java:115)

Main.java

package mazeMaker;

public class Main 
{

    public static void main(String[] args) 
    {
        Maze mainMaze = new Maze(20, 30);
    }

}

迷宫.java

package mazeMaker;

import java.util.Random;
import java.util.Stack;

public class Maze 
{
    public int xSize = 0;
    public int ySize = 0;
    public int totalDimensions = 0;

    Random randomGenerator = new Random();

    public Cell[][] cellData;

    public Stack<Cell> cellStack = new Stack<Cell>();

    Cell tempCell; // Temporary variable used for maze generation

    public Maze(int xSize, int ySize) 
    {
        cellData = new Cell[xSize][ySize];
        this.xSize = xSize;
        this.ySize = ySize;
        this.totalDimensions = this.xSize * this.ySize;

        // Initialize array objects
        for (int i = 0; i < this.xSize; i++) 
        {
            for (int j = 0; j < this.ySize; j++) 
            {
                cellData[i][j] = new Cell();
            }
        }

        // Assign x and y positions
        for (int i = 0; i < this.xSize; i++) 
        {
            for (int j = 0; j < this.ySize; j++) 
            {
                cellData[i][j].xPos = i;
                cellData[i][j].yPos = j;
            }
        }

        initBoundries();
        generateMaze();
    }

    private void initBoundries() 
    {
        // Initialize the border cells as visited so we don't go out of bounds
        int m = this.xSize;
        int n = this.ySize;

        for (int i = 0; i < m; i++) 
        { 
            for (int j = 0; j < n; j++) 
            { 
                if (i == 0 || j == 0 || i == n - 1 || j == n - 1) 
                    cellData[i][j].hasBeenVisited = true;
            } 
        } 
    }

    private void generateMaze(int x, int y) 
    {
        // Set current cell as visited
        cellData[x][y].hasBeenVisited = true;

        // While there are unvisited neighbors
        while (!cellData[x][y+1].hasBeenVisited || !cellData[x+1][y].hasBeenVisited || !cellData[x][y-1].hasBeenVisited || !cellData[x-1][y].hasBeenVisited) 
        {
            // Select a random neighbor
            while (true) 
            {
                int r = randomGenerator.nextInt(4);
                if (r == 0 && !cellData[x][y+1].hasBeenVisited) 
                {
                    cellStack.push(cellData[x][y]);
                    cellData[x][y].hasNorthWall = false;
                    cellData[x][y+1].hasSouthWall = false;
                    generateMaze(x, y + 1);
                    break;
                }
                else if (r == 1 && !cellData[x+1][y].hasBeenVisited) 
                {
                    cellStack.push(cellData[x][y]);
                    cellData[x][y].hasEastWall = false;
                    cellData[x+1][y].hasWestWall = false;
                    generateMaze(x+1, y);
                    break;
                }
                else if (r == 2 && !cellData[x][y-1].hasBeenVisited) 
                {
                    cellStack.push(cellData[x][y]);
                    cellData[x][y].hasSouthWall = false;
                    cellData[x][y-1].hasNorthWall = false;
                    generateMaze(x, y-1);
                    break;
                }
                else if (r == 3 && !cellData[x-1][y].hasBeenVisited) 
                {
                    cellStack.push(cellData[x][y]);
                    cellData[x][y].hasWestWall = false;
                    cellData[x-1][y].hasEastWall = false;
                    generateMaze(x-1, y);
                    break;
                }
            }
        }

        // There are no unvisited neighbors
        tempCell = cellStack.pop();
        generateMaze(tempCell.xPos, tempCell.yPos);

    }

    // Begin generating maze at top left corner
    private void generateMaze() 
    {
        generateMaze(1,1);
    }

}

Cell.java

package mazeMaker;

public class Cell 
{
    public boolean isCurrentCell;
    public boolean hasBeenVisited;
    public boolean hasNorthWall;
    public boolean hasSouthWall;
    public boolean hasEastWall;
    public boolean hasWestWall;
    public int xPos;
    public int yPos;
}

【问题讨论】:

  • 不是 generateMaze 称自己为无限吗?检查您的逐行调试器。

标签: java recursion stack-overflow maze


【解决方案1】:

我尝试在自己的环境中运行您的项目,但不幸的是,我无法重现您的问题。

但是,我在方法 generateMaze 中遇到了IndexOutOfBound 异常。在解决这个问题时,我发现initBoudaries 方法存在问题。

确实,当您将布尔值 hasBeenVisited 设置为 true 时,您不会在 IF 子句中使用正确的变量。这是我尝试过的版本:

private void initBoundries() 
    {
        // Initialize the border cells as visited so we don't go out of bounds

        for (int i = 0; i < this.xSize; i++) 
        { 
            for (int j = 0; j < ySize; j++) 
            { 
                if (i == 0 || j == 0 || i == xSize - 1 || j == ySize - 1) 
                    cellData[i][j].hasBeenVisited = true;
            } 
        } 
    }

现在关于emptyStackException,我认为如果这个堆栈是空的,这意味着没有更多的单元格可以处理(正如你在评论中提到的那样)并且程序必须结束。如果我是对的,请确保在调用 pop() 方法之前测试您的堆栈是否为空,如下所示:

// There are no unvisited neighbors
        if (!cellStack.isEmpty()) {
            tempCell = cellStack.pop();
            generateMaze(tempCell.xPos, tempCell.yPos);
        }

希望它会有所帮助。

【讨论】:

  • 您是否能够编译一个工作版本,我尝试了您推荐的更改,但仍然出现堆栈溢出?
  • 我认为 emptyStackException 已按照您的建议修复,但堆栈溢出仍然存在。
  • 如果我让迷宫变小,比如 30x30,我不会溢出,这告诉我我认为我的堆栈内存不足是合法的。
  • @MichaelSimanski 当我尝试生成比 30x30 更大的迷宫时,我也遇到了堆栈溢出问题。触发此异常是因为方法 generateMaze 被调用堆栈调用太多次。可能您应该尝试增加调用堆栈的大小或查看代码,以免递归完成。
【解决方案2】:

方法generateMaze 永远不会因为一些简单的原因而终止:

要终止 generateMaze 方法需要完成它的执行 - 它必须返回。

该方法中没有return语句,因此它必须通过while循环,然后继续执行,直到执行到并完成该方法的最后一条语句。

但是最后一条语句是generateMaze(tempCell.xPos, tempCell.yPos);,它启动了一个新的递归,因此您的代码永远不会终止!

【讨论】:

  • 我想我明白你在说什么。因此,通过添加一个 if 语句检查是否在 while 循环之后访问了所有周围的单元格,并将递归调用放入其中可以解决它?
  • @Michael 也许 - 试试看。您还应该考虑当您的算法结束时cellStack 应该为空,目前您不考虑这种情况。
  • 刚刚尝试过,现在我得到一个空堆栈异常。这和你说的案子有关系吗?
猜你喜欢
  • 2015-04-04
  • 2017-01-20
  • 2018-12-02
  • 2017-09-06
  • 2019-07-08
  • 2015-08-05
  • 2017-09-29
  • 1970-01-01
  • 2018-03-10
相关资源
最近更新 更多