执行FloodFill的不同方法

时间:2011-08-02 15:36:48

标签: c# recursion queue stack-overflow flood-fill

确定每个人我有几种不同的方法来执行FloodFill。所有这些都会导致问题。我将列出3种方法并解释每种方法会发生什么。如果有人能给我一些很棒的指示。我看过一些类似的帖子,但没有一个是C#,java或VB.net(我知道的唯一语言)。

这个的用途是我有一个名为PixelData的类,它将一个Color存储在CellColor成员变量中。我有一个数量为50x50的PixelData对象,大小称为"像素"。我还有一个名为CANVAS_SIZE的常量,在这种情况下为50。以下是我尝试过的三种方法。

这是递归的。它非常容易出现堆栈溢出。我已经尝试设置一个计时器,在此方法完成后启用CanFill成员。这仍然无法防止溢出:

private void FloodFill(Point node, Color targetColor, Color replaceColor)
{
  //perform bounds checking X
  if ((node.X >= CANVAS_SIZE) || (node.X < 0))
    return; //outside of bounds

  //perform bounds checking Y
  if ((node.Y >= CANVAS_SIZE) || (node.Y < 0))
    return; //ouside of bounds

  //check to see if the node is the target color
  if (pixels[node.X, node.Y].CellColor != targetColor)
    return; //return and do nothing
  else
  {
    pixels[node.X, node.Y].CellColor = replaceColor;

    //recurse
    //try to fill one step to the right
    FloodFill(new Point(node.X + 1, node.Y), targetColor, replaceColor);
    //try to fill one step to the left
    FloodFill(new Point(node.X - 1, node.Y), targetColor, replaceColor);
    //try to fill one step to the north
    FloodFill(new Point(node.X, node.Y - 1), targetColor, replaceColor);
    //try to fill one step to the south
    FloodFill(new Point(node.X, node.Y + 1), targetColor, replaceColor);

    //exit method
    return;
  }
}

接下来,我有一个使用基于队列的填充的方法。此方法在运行时导致OutOfMemory异常,并且在填充整个画布时非常慢。如果只是填充画布的一小部分,它有点有效:

private void QueueFloodFill(Point node, Color targetColor, Color replaceColor)
{
  Queue<Point> points = new Queue<Point>();
  if (pixels[node.X, node.Y].CellColor != targetColor)
    return;

  points.Enqueue(node);

  while (points.Count > 0)
  {
    Point n = points.Dequeue();
    if (pixels[n.X, n.Y].CellColor == targetColor)
      pixels[n.X, n.Y].CellColor = replaceColor;

    if (n.X != 0)
    {
      if (pixels[n.X - 1, n.Y].CellColor == targetColor)
        points.Enqueue(new Point(n.X - 1, n.Y));
    }

    if (n.X != CANVAS_SIZE - 1)
    {
      if (pixels[n.X + 1, n.Y].CellColor == targetColor)
        points.Enqueue(new Point(n.X + 1, n.Y));
    }

    if (n.Y != 0)
    {
      if (pixels[n.X, n.Y - 1].CellColor == targetColor)
        points.Enqueue(new Point(n.X, n.Y - 1));
    }

    if (n.Y != CANVAS_SIZE - 1)
    {
      if (pixels[n.X, n.Y + 1].CellColor == targetColor)
        points.Enqueue(new Point(n.X, n.Y + 1));
    }
  }
  DrawCanvas();
  return;
}

我尝试的最后一种方法也使用基于队列的floodfill。此方法比先前基于队列的floodfill快得多,但最终也会在运行时导致OutOfMemory异常。同样,我尝试设置一个FillDelay计时器,以防止用户快速点击,但这仍然不能阻止异常发生。这个问题的另一个缺点是它很难适当地填充小区域。我认为解决这个问题没有意义,直到我能够让它不崩溃。

private void RevisedQueueFloodFill(Point node, Color targetColor, Color replaceColor)
{
  Queue<Point> q = new Queue<Point>();
  if (pixels[node.X, node.Y].CellColor != targetColor)
    return;

  q.Enqueue(node);
  while (q.Count > 0)
  {
    Point n = q.Dequeue();
    if (pixels[n.X, n.Y].CellColor == targetColor)
    {
      Point e = n;
      Point w = n;
      while ((w.X != 0) && (pixels[w.X, w.Y].CellColor == targetColor))
      {
        pixels[w.X, w.Y].CellColor = replaceColor;
        w = new Point(w.X - 1, w.Y);
      }

      while ((e.X != CANVAS_SIZE - 1) && (pixels[e.X, e.Y].CellColor == targetColor))
      {
        pixels[e.X, e.Y].CellColor = replaceColor;
        e = new Point(e.X + 1, e.Y);
      }

      for (int i = w.X; i <= e.X; i++)
      {
        Point x = new Point(i, e.Y);
        if (e.Y + 1 != CANVAS_SIZE - 1)
        {
          if (pixels[x.X, x.Y + 1].CellColor == targetColor)
            q.Enqueue(new Point(x.X, x.Y + 1));
        }
        if (e.Y - 1 != -1)
        {
          if (pixels[x.X, x.Y - 1].CellColor == targetColor)
            q.Enqueue(new Point(x.X, x.Y - 1));
        }
      }
    }
  }
}

感谢大家的帮助!所有这些方法都基于维基百科上的伪代码。

编辑:

我选择了RevisedQueueFloodFill并按建议修改,以便在循环中不声明变量。仍然生成OutOfMemory。即使使用filldelay计时器。

private void RevisedQueueFloodFill(Point node, Color targetColor, Color replaceColor)
{
  Queue<Point> q = new Queue<Point>();

  if (pixels[node.X, node.Y].CellColor != targetColor)
    return;

  q.Enqueue(node);

  Point n, e, w, x;
  while (q.Count > 0)
  {
    n = q.Dequeue();
    if (pixels[n.X, n.Y].CellColor == targetColor)
    {
      e = n;
      w = n;
      while ((w.X != 0) && (pixels[w.X, w.Y].CellColor == targetColor))
      {
        pixels[w.X, w.Y].CellColor = replaceColor;
        w = new Point(w.X - 1, w.Y);
      }

      while ((e.X != CANVAS_SIZE - 1) && (pixels[e.X, e.Y].CellColor == targetColor))
      {
        pixels[e.X, e.Y].CellColor = replaceColor;
        e = new Point(e.X + 1, e.Y);
      }

      for (int i = w.X; i <= e.X; i++)
      {
        x = new Point(i, e.Y);
        if (e.Y + 1 != CANVAS_SIZE - 1)
        {
          if (pixels[x.X, x.Y + 1].CellColor == targetColor)
            q.Enqueue(new Point(x.X, x.Y + 1));
        }
        if (e.Y - 1 != -1)
        {
          if (pixels[x.X, x.Y - 1].CellColor == targetColor)
            q.Enqueue(new Point(x.X, x.Y - 1));
        }
      }
    }
  }
}

4 个答案:

答案 0 :(得分:1)

好几件事:

  1. C#具有几千个深度的递归限制(由堆栈大小决定)。 这意味着你不能向下递归DEEPER而不会导致堆栈溢出。一旦方法返回,其指针就会从堆栈中弹出。您的问题与OutOfMemoryException不同。堆栈保存指针而非实际内存,因此并不意味着拥有数千个指针。

  2. 垃圾收集是导致内存不足的原因。您需要停止在循环内声明变量。垃圾收集器将这些视为“仍在范围内”,并且在循环完成所有迭代之前不会释放内存空间。但是如果你使用相同的内存地址,它每次都会覆盖它,几乎不使用任何内存。

  3. 要明确:

    for (int i = w.X; i <= e.X; i++)
    {
        Point x = new Point(i, e.Y);
    }
    

    应该是这样的:

    Point x;
    
    for(int i = w.X; i<= e.X; i++)
    {
       x = new Point(i, e.Y);
    }
    

    这将重用你想要的内存地址。

    希望有所帮助!

答案 1 :(得分:1)

我不知道这是否可行,但我自己怀疑由于所有“新”运算符而使用的内存比必要的多得多,并且可能由于算法的密集性,垃圾收集器没有机会参加比赛?

我已经重写了算法,所以所有的Point变量都被重用了,但我目前还没有办法测试它。

我也冒昧地改变了前几行代码,但这是因为我的一个小小的烦恼,你发现大多数泛洪填充算法需要用户指定目标颜色,当事实上,您可以从参数中给出的点自动获取目标颜色。

无论如何,尝试使用它,或者只是嘲笑它:

private void RevisedQueueFloodFill(Point node, Color replaceColor)
{
    Color targetColor = pixels[node.X, node.Y].CellColor;
    if (targetColor == replaceColor) return;

    Queue<Point> q = new Queue<Point>();
    q.Enqueue(node);

    Point n, t, u;

    while (q.Count > 0)
    {
        n = q.Dequeue();
        if (pixels[n.X, n.Y].CellColor == targetColor)
        {

            t = n;
            while ((t.X > 0) && (pixels[t.X, t.Y].CellColor == targetColor))
            {
                pixels[t.X, t.Y].CellColor = replaceColor;
                t.X--;
            }
            int XMin = t.X + 1;


            t = n;
            t.X++;
            while ((t.X < CANVAS_SIZE - 1) &&
                   (pixels[t.X, t.Y].CellColor == targetColor))
            {
                pixels[t.X, t.Y].CellColor = replaceColor;
                t.X++;
            }
            int XMax = t.X - 1;

            t = n;
            t.Y++;

            u = n;
            u.Y--;

            for (int i = XMin; i <= XMax; i++)
            {
                t.X = i;
                u.X = i;

                if ((t.Y < CANVAS_SIZE - 1) &&
                    (pixels[t.X, t.Y].CellColor == targetColor)) q.Enqueue(t);

                if ((u.Y >= 0) &&
                    (pixels[u.X, u.Y].CellColor == targetColor)) q.Enqueue(u);
            }
        }
    }
}

答案 2 :(得分:0)

在第三种方法中,您应该检查当前点的西边和东边的像素。您应该检查pixels[w.X, w.Y]而不是pixels[w.X - 1, w.Y]而不是pixels[e.X, e.Y],而不是pixels[e.X + 1, e.Y]。以下是我对你的第三种方法的看法:

private void RevisedQueueFloodFill(Point node, Color targetColor, Color replaceColor)
{
    if (pixels[node.X, node.Y].CellColor != targetColor) return;

    Queue<Point> Q = new Queue<Point>();
    Q.Enqueue(node);

    while (Q.Count != 0)
    {
        Point n = Q.Dequeue();
        if (pixels[n.X, n.Y].CellColor == targetColor)
        {
            int y = n.Y;
            int w = n.X;
            int e = n.X;
            while (w > 0 && pixels[w - 1, y].CellColor == targetColor) w--;
            while (e < CANVAS_SIZE - 1 && pixels[e + 1, y].CellColor == targetColor) e++;

            for (int x = w; x <= e; x++)
            {
                pixels[x, y].CellColor = replaceColor;
                if (y > 0 && pixels[x, y - 1].CellColor == targetColor)
                {
                    Q.Enqueue(new Point(x, y - 1));
                }
                if (y < CANVAS_SIZE - 1 && pixels[x, y + 1].CellColor == targetColor)
                {
                    Q.Enqueue(new Point(x, y + 1));
                }
            }
        }
    }
}

答案 3 :(得分:0)

这里使用基本算法的问题是您将多次访问排队到一个点并进行广度优先搜索。这意味着您在每次传递期间创建同一点的多个副本。这会以指数方式累积,因为每个点都可以传播(排队更多点),即使它不是目标颜色(已被替换)。

在您将它们排队的同时设置颜色(而不是在Dequeue上),这样您就不会最终将它们添加到队列中两次。