【问题标题】:Java - StackOverflow Error on recursive 2D boolean array method that shouldn't happenJava - 不应该发生的递归 2D 布尔数组方法的 StackOverflow 错误
【发布时间】:2011-06-09 11:00:21
【问题描述】:

我正在开发一个可运行的 Java 小程序,它具有与 Microsoft Paint 等绘图程序中的填充方法非常相似的填充功能。

这是我的填充方法的工作原理:

  1. 小程序使用.getRGB获取用户点击的颜色

  2. 小程序创建窗口中所有像素的二维布尔数组,如果该像素与单击的颜色相同,则值为“true”,否则为“false”。此步骤的重点是将.getRGB 方法排除在递归方法之外,以希望防止此错误。

  3. 小程序递归搜索用户单击的二维布尔数组,并在 ArrayList 中记录每个“真”的相邻点。然后该方法将其记录的每个点更改为 false 并继续。

  4. 小程序将存储在ArrayList 中的每个点绘制成用户选择的颜色。

如果用户在一个只有几千像素左右颜色改变的小区域内点击,上述所有步骤都可以完美运行。然而,如果用户选择了一个很大的区域(例如大约 360,000 / 小程序窗口的大小),小程序会进入递归阶段,然后输出此错误:

Exception in thread "AWT-EventQueue-1" java.lang.StackOverflowError
 at java.util.ArrayList.add(ArrayList.java:351)
 at paint.recursiveSearch(paint.java:185)
 at paint.recursiveSearch(paint.java:190)
 at paint.recursiveSearch(paint.java:190)
 at paint.recursiveSearch(paint.java:190)
 at paint.recursiveSearch(paint.java:190)
 at paint.recursiveSearch(paint.java:190)
 at paint.recursiveSearch(paint.java:190)  
 (continues for a few pages) 

这是我的递归代码:

public void recursiveSearch(boolean [][] list, Point p){
    if(isValid(p)){
        if(list[(int)p.y][(int)p.x]){
            fillPoints.add(p);
            list[(int)p.y][(int)p.x] = false;

            recursiveSearch(list, new Point(p.x-1,p.y));//Checks to the left
            recursiveSearch(list, new Point(p.x,p.y-1));//Checks above
            recursiveSearch(list, new Point(p.x+1,p.y));//Checks to the right
            recursiveSearch(list, new Point(p.x,p.y+1));//Checks below
            }
        }
    }

有什么办法可以解决这样的错误吗?我知道循环永远不会永远持续下去,它可能会花费很多时间。

【问题讨论】:

    标签: java arrays recursion stack-overflow


    【解决方案1】:

    您可以增加 Java 进程的堆栈空间,例如:

    java -Xss10m MyProgram
    

    会给线程堆栈10兆。但是,我建议不要这样做,并考虑编写算法的迭代版本,特别是如果递归调用的数量取决于某种用户驱动的行为。

    编辑:对于小程序,我认为您不能指定 -X 标志值。

    【讨论】:

      【解决方案2】:

      我不明白你为什么要递归。如果您曾经将list 中的任何点更改回true,那将是一回事,但显然您没有。那么为什么不只是迭代呢?

      事实上,您可能会多次检查每个点,而每个点只检查一次就足够了。

      另一个想法:如果我没记错的话,除非list[0, 0] == true,否则您的算法不会将任何点添加到fillPoints 向量。这是你想要的行为吗?


      编辑:好的,现在我对你想要做什么有了更好的了解。我建议您查看Flood Fill 算法上的维基百科页面的this part

      【讨论】:

      • 非常感谢您提供维基百科链接,该页面上解释的算法帮助我解决了我的错误。
      【解决方案3】:

      基本问题是您对每个像素进行了太多检查。我建议您在方法中添加一个“级别”参数,该参数最初为 0,但在递归调用时会增加。然后添加一个初始打印语句,显示当前递归调用的级别。

      我想你会惊讶于你的代码有多深!

      【讨论】:

        【解决方案4】:

        如果您想处理任意大小的图像,则必须以非递归方式重写它。没有其他方法可以保证您的堆栈足够大。

        【讨论】:

          【解决方案5】:

          您的算法在纸面上是有意义的,但您发现它的扩展性不好。

          我怀疑任何图形程序都会使用这种方法。

          【讨论】:

            【解决方案6】:

            如果您想避免检查窗口上的所有像素,递归将是一个好主意。我建议更改所选像素的颜色并递归检查是否有任何相邻像素也需要更改,如果没有则返回,依此类推。这样可以避免必须检查窗口中的每个像素。 (想象一个 800 x 800px 的窗口,你必须填充一个 4x4px 的区域。在那里检查每个像素都是多余的。)

            【讨论】:

              【解决方案7】:

              您需要的是广度优先搜索。您将有一个“未处理”像素队列。起初,队列由用户点击的一个像素组成。现在,当队列不为空时,重复以下步骤:从队列中取出下一个像素,对其进行处理(绘制为您需要的颜色或其他颜色),并且对于每个相邻的相同颜色的未访问像素将其标记为已访问和添加到队列中。如果该区域由 360,000 像素组成,它应该不会及时运行。

              【讨论】:

              • 非常感谢,使用队列我能够让它工作。
              【解决方案8】:

              从我的快速浏览。我认为您会多次遍历区域。您应该更新方法以包括调用不应返回的方向。像这样:

              public void recursiveSearch(boolean [][] list, Point p, String directionFrom){
                  if(isValid(p)){
                      if(list[(int)p.y][(int)p.x]){
                          fillPoints.add(p);
                          list[(int)p.y][(int)p.x] = false;
                          //Add a check for which direction it came from and dont go that way.
                          if (string.equals(right)){     
                               recursiveSearch(list, new Point(p.x,p.y-1),down);//Checks above
                               recursiveSearch(list, new Point(p.x+1,p.y),left);//Checks to the right
                               recursiveSearch(list, new Point(p.x,p.y+1),right);//Checks below
                          }else if(string.equals(left){
                            //... and so on
                          }
                      }
                  }
              }
              

              【讨论】:

              • 我试过了,但它并没有减少递归调用的数量来防止错误。不过谢谢!
              【解决方案9】:

              应该发生在一张大图上。你不应该使用递归。也许是这样的:

              LinkedList<Point> frontier = new ...
              frontier.add(starting_point);
              
              while(frontier is not empty)
                  point = frontier.removeLast();
                  point.state = (point.color == the_color)
                  if(point.state==true)
                     // expand frontier
                     for(neighbor : neighbor_points)
                         if(neighbor.visited==false)
                             frontier.add(neighbor)
                             neighbor.visited=true;
              

              【讨论】:

                猜你喜欢
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 2011-12-26
                • 1970-01-01
                • 1970-01-01
                相关资源
                最近更新 更多