Flood Fill算法的非递归实现?

时间:2014-02-18 21:36:15

标签: algorithm flood-fill non-recursive

我正在使用Java开发一个小型绘图应用程序。我正在尝试通过实施Flood Fill算法来创建“桶填充”工具。

我尝试使用递归实现,但这是有问题的。无论如何,我在网上搜索,似乎为此目的,建议使用非递归实现此算法。

所以我问你:

您能描述一下Flood Fill算法的 非递归实现吗?一个实际的代码示例,一些伪代码,甚至一般的解释都将受到欢迎。

我正在寻找最简单,您能想到的最有效的实施方案。

(不必特定于Java)。

谢谢

3 个答案:

答案 0 :(得分:17)

我假设你有某种网格,你可以从中获得你希望填充该区域的位置坐标。

递归填充算法是DFS。您可以执行BFS将其转换为非递归。

基本上这两种算法的想法都相似。你有一个包,其中保存了尚未看到的节点。您从包中删除节点并将节点的有效邻居放回包中。 如果包是一个堆栈,你会得到一个DFS。如果它是一个队列你得到一个BFS。

伪代码大致是这个。

flood_fill(x,y, check_validity)
   //here check_validity is a function that given coordinates of the point tells you whether
   //the point should be colored or not
   Queue q
   q.push((x,y))
   while (q is not empty)
       (x1,y1) = q.pop()
       color(x1,y1)

       if (check_validity(x1+1,y1))
            q.push(x1+1,y1)
       if (check_validity(x1-1,y1))
            q.push(x1-1,y1)
       if (check_validity(x1,y1+1))
            q.push(x1,y1+1)
       if (check_validity(x1,y1-1))
            q.push(x1,y1-1)

注意:确保check_validity考虑该点是否已着色。


  • DFS:深度优先搜索
  • BFS:广度优先搜索

答案 1 :(得分:6)

您基本上有两种方法可以非递归地实现泛洪填充算法。 sukunrt已经清楚地解释了第一种方法,其中您使用队列来实现广度优先搜索。

或者,您可以使用隐式堆栈非递归地实现递归DFS。例如,以下代码在具有整数节点的图上实现非递归DFS。在此代码中,您使用Iterator数组来跟踪每个节点的邻接列表中的已处理邻居。可以访问完整的代码here

public NonrecursiveDFS(Graph G, int s) {
        marked = new boolean[G.V()];

        // to be able to iterate over each adjacency list, keeping track of which
        // vertex in each adjacency list needs to be explored next
        Iterator<Integer>[] adj = (Iterator<Integer>[]) new Iterator[G.V()];
        for (int v = 0; v < G.V(); v++)
            adj[v] = G.adj(v).iterator();

        // depth-first search using an explicit stack
        Stack<Integer> stack = new Stack<Integer>();
        marked[s] = true;
        stack.push(s);
        while (!stack.isEmpty()) {
            int v = stack.peek();
            if (adj[v].hasNext()) {
                int w = adj[v].next();
                if (!marked[w]) {
                    // discovered vertex w for the first time
                    marked[w] = true;
                    // edgeTo[v] = w;
                    stack.push(w);
                }
            }
            else {
                // v's adjacency list is exhausted
                stack.pop();
            }
        }
    }

答案 2 :(得分:0)

我非常擅长洪水填充algorythms,因为我将所有可用的代码扫描成比较序列,然后我根据它们编写了一个新版本。

因此,您将阅读的这些信息是您可以在任何编程平台上实现的最佳实现。

所有基于递归和堆栈的实现都不使用有组织的存储器或数组来存储它们处理的像素的步骤。

使用显式声明但不依赖于堆栈的数组,您可以重写当前已知的扫描图像和扫描像素/体素(如果您使用的是2d或3d),代码的任何一行以及任何订购数组。

最简单,最快的版本就变成了递归:

你递归并写入三个作为你正在编写的2d / 3d源数组/图像版本复制的数组。

一个用于产生满溢的填充,一个用于源图像,一个用于“检查下一个空间”...

对于使用递归的1兆位源图片,您的内存占用量就像4兆。

接下来检查这个空间......是魔术阵列。您将任何额外的递归覆盖到相同的检查空间,从而将1PowerN的递归内存印记展开为1PowerX + Y.

我个人使用这种理念在单核上实现了每秒2千兆像素。它是通过可覆盖数组递归的艺术。简单。弄清楚,你会知道最简单,最快,最简单的可写版本。

当您处理100万像素左右的源文件时,通过计算机内存递归并创建每个递归结果的新副本是荒谬的。声明您需要编写和读取的所有内容,将递归作为您正在读取和写入的映射XY内容的数组,它只是简化为三个数组sytaxed和一个基本递归。