【问题标题】:Why does this flood-fill algorithm cause a stack overflow?为什么这种洪水填充算法会导致堆栈溢出?
【发布时间】:2010-01-17 14:22:07
【问题描述】:
void FloodFill(int layer, int x, int y, int target, int replacement)
{
    if (x < 0) return;
    if (y < 0) return;
    if (x >= _mapWidth) return;
    if (y >= _mapHeight) return;

    if (_mapLayers[layer, x, y] != target) return;

    _mapLayers[layer, x, y] = replacement;

    FloodFill(layer, x - 1, y, target, replacement);
    FloodFill(layer, x + 1, y, target, replacement);
    FloodFill(layer, x, y - 1, target, replacement);
    FloodFill(layer, x, y + 1, target, replacement);

    return;
}

到目前为止,这是我的代码,但是当它到达地图的末尾时会导致堆栈溢出,有人知道如何解决这个问题(可能是一个棘手的情况)吗?

【问题讨论】:

    标签: c# algorithm map tiles


    【解决方案1】:

    注意这个调用路径存在:

    (x, y) -> (x+1, y) -> (x+1-1, y) -> (x+1-1+1, y) -> ...
    

    这是一个无限递归,所以你有堆栈溢出。你的支票无法解决这个问题。您必须执行一项额外检查:

    if (_mapLayers[layer, x, y] == replacement) return;
    

    即使您已包含上述额外检查,请注意最大递归深度为 (_mapWidth * _mapHeight),即使对于小位图(例如 100 x 100)也可能非常深。

    【讨论】:

    • 最大递归深度实际上是_mapWidth * _mapHeight。 200 不是很深(我猜是错字):)。 100*100 也不应该导致溢出(除非内存中已经有很多其他的东西),但是像 10,000*10,000 这样的东西会。但额外的检查可能是 OP 需要的,+1。
    • -1,这个答案在不止一个方面是错误的。 “线性搜索”不符合点的邻域。测试 _mapLayers[layer, x, y] == 如果有测试目标,则不需要替换!=在首先调用“FloodFill”之前进行替换。并且最大递归深度不会超过 _mapWidth*_mapHeight/2 (对于某种螺旋图像)。
    • @DocBrown: 'if (_mapLayers[layer, x, y] != target) return;'不处理 target==replacement 的(不可否认的)情况。是的,(_mapWidth * _mapHeight) 不是确切的深度,但深度仍然是 O(_mapWidth * _mapHeight),所以提出的点仍然有效。唯一真正错误的是关于线性搜索的部分。
    • @MAK:我没有写 'if (_mapLayers[layer, x, y] != target)' 处理承认的情况,我写了 'one should add the missing test for 'target==替换”(但不在递归例程中,测试应该由“FloodFill”的调用者完成)。并且在当前答案中没有大 O。对于 target!=replacement,给定的调用路径将不会如图所示执行,因为递归将在之前停止。恕我直言,答案只是部分正确,而且有点误导。
    【解决方案2】:

    首先,你应该确保target!=replacement(可以在'FloodFill'的初始调用之前完成一次)。然后,您的算法可能会起作用,只要 _mapWidth 和 _mapHeight 不是特别大(这在很大程度上取决于您的 _mapLayers 数组的内容)。如果这是一个问题,您应该尝试使用非递归算法。创建一个

    class Point
    { 
        public int x, y;
        public Point(int newX, int newY)
        {
             x=newX;
             y=newY;
        }
    }
    

    还有一个

     List<Point> pointList;
    

    将初始点放入此列表并运行某种循环,直到 pointList 为空:从列表中取出一个点,像上面一样处理它,而不是上面的原始递归调用,将四个邻居再次放入列表中。

    编辑:这是完整的示例,但没有测试:

        void FloodFill2(int layer, int xStart, int yStart, int target, int replacement)
        {
            if(target==replacement)
                return;
            List<Point> pointList = new List<Point>();
    
            pointList.Add(new Point(xStart,yStart));
            while(pointList.Count>0)
            {
                Point p = pointList[pointList.Count-1];
                pointList.RemoveAt(pointList.Count-1);
                if (p.x < 0) continue;
                if (p.y < 0) continue;
                if (p.x >= _mapWidth) continue;
                if (p.y >= _mapHeight) continue;
                if (_mapLayers[layer, p.x, p.y] != target) continue;
                _mapLayers[layer, p.x, p.y] = replacement;
    
                pointList.Add(new Point(p.x - 1, p.y));
                pointList.Add(new Point(p.x + 1, p.y));
                pointList.Add(new Point(p.x, p.y - 1));
                pointList.Add(new Point(p.x, p.y + 1));
            }
        }
    

    EDIT2:事实上,这里有一个优化例程的建议:如果插入变得毫无意义,则避免插入到列表中,所以:

                if(p.x>=0) 
                     pointList.Add(new Point(p.x - 1, p.y));
                if(p.x<_mapWidth-1) 
                     pointList.Add(new Point(p.x + 1, p.y));
                if(p.y>=0) 
                     pointList.Add(new Point(p.x, p.y - 1));
                if(p.y<_mapHeight-1) 
                     pointList.Add(new Point(p.x, p.y + 1));
    

    【讨论】:

      猜你喜欢
      • 2014-12-20
      • 1970-01-01
      • 1970-01-01
      • 2011-01-13
      • 1970-01-01
      • 1970-01-01
      • 2018-07-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多