Java 中的自调用函数中的堆栈溢出错误(岛数)

Posted

技术标签:

【中文标题】Java 中的自调用函数中的堆栈溢出错误(岛数)【英文标题】:Stack Overflow Error in a Self Calling Function in Java (Number of Islands) 【发布时间】:2018-10-30 01:40:26 【问题描述】:

我对导致堆栈溢出错误的原因进行了一些研究,我可以得出结论,这是由程序中的递归函数引起的,该函数应该“计算数组中的岛数”。我了解导致问题的原因,但不确定为什么会发生这种情况,或者我的主要问题是实际该怎么做。我发现如果我通过让程序反复向控制台打印一些内容来减慢程序的速度,它可以工作,但需要很长时间才能完成。有没有办法可以保持程序速度而不会出错,或者有更好的方法来解决问题(搜索“岛屿数量”以找到问题)。此外,该数组是二维的,大小为 1050 x 800。

public class NumOfIslands 
  static boolean[][] dotMap = new boolean[1050][800];
  static boolean visited[][] = new boolean[1050][800];
  static int total = 0;
  public static void main(String args[]) 
    defineArrays();
    run();
  
  public static void findObjects(int xCord, int yCord) 
    for(int y = yCord - 1; y <= yCord + 1; y++) 
      for(int x = xCord - 1; x <= xCord + 1; x++) 
        if(x > -1 && y > -1 && x < dotMap[0].length && y < dotMap.length) 
          if((x != xCord || y != yCord) && dotMap[x][y] == true && visited[x][y] != true) 
            visited[x][y] = true;
            findObjects(x,y);
            //System.out.println("test");
          
        
      
    
  
  public static void defineArrays() 
    for(int y = 0; y < 800; y++) 
      for(int x = 0; x < 1050; x++) 
        dotMap[x][y] = true;
      
    
  

  public static int run() 
    //dotMap = DisplayImage.isYellow;
    System.out.println(dotMap.length + " " + dotMap[0].length);
    int objects = 0;
    for(int y = 439; y < 560/*dotMap[0].length*/; y++) 
      for(int x = 70; x < 300/*dotMap.length*/; x++) 
        if(dotMap[x][y] == true && visited[x][y] != true) 
          visited[x][y] = true;
          objects++;
          findObjects(x,y);
        
      
    
    System.out.println("total" + total);
    System.out.println(objects);
    return objects;

  

【问题讨论】:

你为什么要做recursivelooping @ScaryWombat 你是什么意思? 通常使用recursive而不是循环 @ScaryWombat for 循环用于检查错误中的每个值。如果使用递归函数达到任何值,则单独的数组存储它被访问过的值。我需要循环的原因是因为我正在计算数组中连接的对象的数量,而且我知道它会不止一个,所以我认为我需要循环和函数。可能有更好的方法来做到这一点,我错过了。 但是由于您似乎正在根据嵌套的 for 循环访问每个元素,您是否需要递归调用该方法? 【参考方案1】:

***Error reasons。在您的示例中,每次调用 findObjects 都会将 2 个变量添加到堆栈中的 int x 和 int y 循环中。


最快的解决方案之一:

class Solution 
    int m, n;
    public int numIslands(char[][] grid) 
        if (grid == null || grid.length == 0) 
            return 0;
        
        m = grid.length;
        n = grid[0].length;
        int counter = 0;
        for (int i = 0; i < m; i++) 
            for (int j = 0; j < n; j++) 
                if (grid[i][j] == '1') 
                    visit(grid, i, j);
                    counter++;
                
            
        
        return counter;
    

    public void visit(char[][] grid, int i, int j) 
        if (i < 0 || i >= m || j < 0 || j >= n) 
            return;
        
        if (grid[i][j] == '0') 
            return;
        
        grid[i][j] = '0';
        visit(grid, i - 1, j);
        visit(grid, i + 1, j);
        visit(grid, i, j - 1);
        visit(grid, i, j + 1);
    


所有递归算法都可以用循环来实现。示例之一如下。该解决方案实现了 BFS(广度优先搜索)算法,更多详情请参阅wikipedia。

class Solution 
  public int numIslands(char[][] grid) 
    if (grid == null || grid.length == 0) 
      return 0;
    

    int nr = grid.length;
    int nc = grid[0].length;
    int num_islands = 0;

    for (int r = 0; r < nr; ++r) 
      for (int c = 0; c < nc; ++c) 
        if (grid[r][c] == '1') 
          ++num_islands;
          grid[r][c] = '0'; // mark as visited
          Queue<Integer> neighbors = new LinkedList<>();
          neighbors.add(r * nc + c);
          while (!neighbors.isEmpty()) 
            int id = neighbors.remove();
            int row = id / nc;
            int col = id % nc;
            if (row - 1 >= 0 && grid[row-1][col] == '1') 
              neighbors.add((row-1) * nc + col);
              grid[row-1][col] = '0';
            
            if (row + 1 < nr && grid[row+1][col] == '1') 
              neighbors.add((row+1) * nc + col);
              grid[row+1][col] = '0';
            
            if (col - 1 >= 0 && grid[row][col-1] == '1') 
              neighbors.add(row * nc + col-1);
              grid[row][col-1] = '0';
            
            if (col + 1 < nc && grid[row][col+1] == '1') 
              neighbors.add(row * nc + col+1);
              grid[row][col+1] = '0';
            
          
        
      
    
    return num_islands;
  

【讨论】:

我尝试运行您的代码,但遇到了相同的堆栈溢出错误。我正在使用 Dr Java,因为这是一台学校计算机,我无法获得更强大的功能。不确定像 android studios 或 eclipse 这样的程序是否还会遇到这种情况。因为这用于图像上的每个像素,所以它是一个很大的 for 循环(x = 1050 和 y = 800)。 用较低的资源试了一下,发现了你的错误)。添加了迭代示例。诀窍是使用队列(LinkedList)来存储发现的邻居,在下一个while循环中访问它们。 我修改了您放入我的代码的第二个程序,它似乎运行良好。我不太了解链表是如何工作的,所以我会对此做更多的研究。感谢您的回复。【参考方案2】:

问题出在这个函数上

public static void findObjects(int xCord, int yCord) 


        for(int y = yCord - 1; y <= yCord + 1; y++) 
          for(int x = xCord - 1; x <= xCord + 1; x++) 
            if(x > -1 && y > -1 && x < dotMap[0].length && y < dotMap.length) 
              if((x != xCord || y != yCord) && dotMap[x][y] == true && visited[x][y] != true) 
                visited[x][y] = true;
                 findObjects(x,y);
                //System.out.println("test");
              
            
          
        
      `

在这里,您正在构建一个对 findobjects 的递归调用堆栈,最终它没有终止条件,因此它最终以无限的 findobjects 堆栈结束,所以我的解决方案是如果您只是检查 x 和 y 变量是否不相等并且visited[x][y] 不正确,则无需调用递归只需注释递归调用,因为您的循环已经做了您想要的递归打电话去做。

 public static void findObjects(int xCord, int yCord) 


        for(int y = yCord - 1; y <= yCord + 1; y++) 
          for(int x = xCord - 1; x <= xCord + 1; x++) 
            if(x > -1 && y > -1 && x < dotMap[0].length && y < dotMap.length) 
              if((x != xCord || y != yCord) && dotMap[x][y] == true && visited[x][y] != true) 
                visited[x][y] = true;
                //findObjects(x,y);
                //System.out.println("test");
              
            
          
        
      

【讨论】:

你错了。首先,findObjects 具有终止条件if(x &gt; -1 &amp;&amp; y &gt; -1 &amp;&amp; x &lt; dotMap[0].length &amp;&amp; y &lt; dotMap.length)if((x != xCord || y != yCord) &amp;&amp; dotMap[x][y] == true &amp;&amp; visited[x][y] != true)。其次,您的更改不会标记整个岛,并导致 objectsrun() 函数中的计数不正确。 在方法 findobjects 中,您正在检查是否访问了访问数组的每个位置,因此您的内部和外部循环已经在方法 findobjects 那么为什么需要递归调用 findobjects 呢? findObjects 的目标是标记整个岛(所有连接的单元格)。 findObjects 方法中的循环仅检查半径为 1 个单元格的单元格。 run 函数中的循环从数组的中间开始,但是 island 占据了整个数组。

以上是关于Java 中的自调用函数中的堆栈溢出错误(岛数)的主要内容,如果未能解决你的问题,请参考以下文章

如何解决递归调用中的堆栈溢出错误?

递归过程中的第一个过程调用发生堆栈溢出

函数 堆栈溢出

用例子解释编程中的栈溢出和堆溢出? [复制]

为啥我的代码会导致 java 中的堆栈溢出错误?它最终应该终止。 for循环版本不会导致错误

内核栈溢出