代码之家  ›  专栏  ›  技术社区  ›  horseyguy

用C语言编写的一种有效的非递归洪水填充算法?

  •  17
  • horseyguy  · 技术社区  · 16 年前

    我一直在尝试寻找一种有效的洪水填充算法。在我尝试过的许多算法中,只有“递归行填充”算法的行为与它应该的完全一样,主要警告是它偶尔会破坏堆栈。:(

    我尝试过许多我发现的非递归实现,它们都异常冷静:要么在奇怪的地方留下空白,要么淹没整个区域(当它们应该被封闭时)。

    任何人都有一个用C(或C++)编写的非递归的FooRoad工作源代码,它不是太重OOP,我可以很容易地解开)?

    10 回复  |  直到 7 年前
        1
  •  11
  •   rlbond    16 年前

    这里有一些C++代码,可以实现你想要的。它使用一个队列,并且在插入队列时效率更高。

    connectedRegion(const Point& source, RegionType& region, const Color target)
    {
        Color src_color = color_of(source, region);
        if (region.count(source) == 0 || src_color == target)
            return;
        std::queue<Point> analyze_queue;
        analyze_queue.push(source);
    
        while (!analyze_queue.empty())
        {
            if (color_of(analyze_queue.front()) != src_color)
            {
                analyze_queue.pop();
                continue;
            }
            Point leftmost_pt = analyze_queue.front();
                leftmost_pt.col -= 1;
            analyze_queue.pop();
            Point rightmost_pt = leftmost_pt;
                rightmost_pt.col += 2;
            while (color_of(leftmost_pt, region) == src_color)
                --leftmost_pt.col;
    
            while (color_of(rightmost_pt, region) == src_color)
                ++rightmost_pt.col;
    
            bool check_above = true;
            bool check_below = true;
                Point pt = leftmost_pt;
                ++pt.col;
            for (; pt.col < rightmost_pt.col; ++pt.col)
            {
                set_color(pt, region, target);
    
                Point pt_above = pt;
                        --pt_above.row;
                if (check_above)
                {
                    if (color_of(pt_above, region) == src_color)
                    {
                        analyze_queue.push(pt_above);
                        check_above = false;
                    }
                }
                else // !check_above
                {
                    check_above = (color_of(pt_above, region) != src_color);
                }
    
                Point pt_below = pt;
                        ++pt_below.row;
                if (check_below)
                {
                    if (color_of(pt_below, region) == src_color)
                    {
                        analyze_queue.push(pt_below);
                        check_below = false;
                    }
                }
                else // !check_below
                {
                    check_below = (color_of(pt_below, region) != src_color);
                }
            } // for 
        } // while queue not empty
        return connected;
    }
    
        2
  •  24
  •   Jared Updike    16 年前

    只需使用一个固定大小的数组(例如,图像的像素大小或平方根大小)实现一个int对堆栈,并使用int跟踪顶部。

    下面是一些实现非递归洪水填充的C代码:

    private static void Floodfill(byte[,] vals, Point q, byte SEED_COLOR, byte COLOR)
    {
        int h = vals.GetLength(0);
        int w = vals.GetLength(1);
    
        if (q.Y < 0 || q.Y > h - 1 || q.X < 0 || q.X > w - 1)
            return;
    
        Stack<Point> stack = new Stack<Point>();
        stack.Push(q);
        while (stack.Count > 0)
        {
            Point p = stack.Pop();
            int x = p.X;
            int y = p.Y;
            if (y < 0 || y > h - 1 || x < 0 || x > w - 1)
                continue;
            byte val = vals[y, x];
            if (val == SEED_COLOR)
            {
                vals[y, x] = COLOR;
                stack.Push(new Point(x + 1, y));
                stack.Push(new Point(x - 1, y));
                stack.Push(new Point(x, y + 1));
                stack.Push(new Point(x, y - 1));
            }
        }
    }
    
        3
  •  9
  •   user7116    16 年前

    快速的谷歌搜索会显示 Wikipedia article on Flood Fill 其中包括非递归的伪代码实现。下面是一些可以帮助您开始的代码,这是C语言中的基本队列实现:

    typedef struct queue_ { struct queue_ *next; } queue_t;
    typedef struct ffnode_ { queue_t node; int x, y; } ffnode_t;
    
    /* returns the new head of the queue after adding node to the queue */
    queue_t* enqueue(queue_t *queue, queue_t *node) {
        if (node) {
            node->next = queue;
            return node;
        }
        return NULL;
    }
    
    /* returns the head of the queue and modifies queue to be the new head */
    queue_t* dequeue(queue_t **queue) {
        if (queue) {
            queue_t *node = (*queue);
            (*queue) = node->next;
            node->next = NULL;
            return node;
        }
        return NULL;
    }
    
    ffnode_t* new_ffnode(int x, int y) {
        ffnode_t *node = (ffnode_t*)malloc(sizeof(ffnode_t));
        node->x = x; node->y = y;
        node->node.next = NULL;
        return node;
    }
    
    void flood_fill(image_t *image, int startx, int starty, 
                    color_t target, color_t replacement) {
        queue_t *head = NULL;
        ffnode_t *node = NULL;
    
        if (!is_color(image, startx, starty, target)) return;
    
        node = new_ffnode(startx, starty);
        for ( ; node != NULL; node = (ffnode_t*)dequeue(&head)) {
            if (is_color(image, node->x, node->y, target)) {
                ffnode_t *west = node, *east = node;
    
                recolor(image, node->x, node->y, replacement);
                /* 1. move w to the west until the color of the node to the west
                   no longer matches target */
                ...
            }
        }
    }
    
        4
  •  7
  •   Jim Buck    16 年前

    难道没有证据证明所有递归函数都可以通过使用本地数据模拟堆栈实现为迭代函数吗?您可能可以使用std::vector创建算法的类似堆栈的行为,而不必刷新堆栈,因为它将使用堆栈。

    编辑:我注意到您使用的是C,所以您可以通过realloc来实现类似的行为,而不是std::vector,因为您需要向您将要使用的任何数据结构的本地“堆栈”中添加更多元素。

        5
  •  3
  •   dmckee --- ex-moderator kitten    16 年前

    您可以通过创建一个显式的堆栈或队列并将工作加载到它上面/将其拉出,将任何递归算法转换为迭代算法。

    你所需要的是选择一个好的,紧凑的工作表示要完成。最坏情况:创建 struct 保存通常传递给递归版本的参数…

        6
  •  2
  •   Sayan    15 年前

    我们注意到,我们在3D卷上的泛光填充实现占用了大量内存;因此,我们以以下方式修改了代码(有了很大的改进):

    1. 围绕起点创建半径为10个体素的球体,并将该半径内的所有体素标记为“待访问”。

    2. 如果当前体素阈值为,则插入1。

    3. 转到邻居[+1,-1,0](还要检查是否没有重新访问任何体素),如果neighbor.getvoxval=0(目标卷的初始化值),那么它将落在球体的边界,将坐标插入不同的堆栈中。(这将是我们下一个球体的起点)

    4. 半径=半径+10(体素)

    所以有一段时间,我们的洪泛填充是在一个同心球体上工作的,它填充了整个卷的一部分,正如我所说,这大大减少了内存消耗,但是我仍然在寻找一个更好的实现/想法。

        7
  •  1
  •   Norman Ramsey    16 年前

    我有一个非递归的洪泛填充,但我不会发布它,因为它是家庭作业的解决方案。但这里有一个提示:深度优先搜索,这是自然算法,使用 远的 比广度优先搜索更多的辅助空间。以下是我当时写的(适当删去):

    我不敢用简单的递归来尝试深度优先搜索;递归深度只受redacted的限制,我的实验表明redacted的问题可能需要超过一百万的堆栈深度。所以我把栈放在一个辅助数据结构中。使用显式堆栈实际上也可以很容易地尝试宽度优先搜索,结果表明宽度优先搜索比深度优先搜索使用的空间少40倍。

    对于我的数据结构,我使用 Seq_T 戴夫·汉森的 C Interfaces and Implementations ;从深度优先更改为宽度优先只需要更改一个函数调用。

        8
  •  1
  •   Paolo Fassin    8 年前

    我不知道我的答案是否与你提出的问题完全相关,但在后面我提出了我的C版本的洪泛填充算法,它不使用递归调用。

    1-11-2017:新版本;成功测试了两个位图。

    它只使用新点的偏移队列,它在窗口上工作:双缓冲区的winnoffs(windimx,windimy):*vBuffer(屏幕或图像的副本),还可以编写一个洪水填充结果的掩码(*extravBuff)。 ExtravBuff必须在调用前用0填充(如果不需要遮罩,可以将ExtravBuff设置为空);调用后使用它可以进行渐变泛光填充或其他绘制效果。新的泛光填充每像素32位,它是一个C函数。我在1991年重新发明了这个算法(我在Pascal中写了他的),但现在它在C语言中工作,每像素32位;也不使用任何函数调用,只在队列中的每个“pop”之后执行一个除法,并且从不溢出队列,如果它的大小是正确的(大约是图像像素的1/4),它允许始终正确地填充任何区域;我在C函数(ffill.c)之前显示,在测试程序(test.c)之后显示:

    #define IMAGE_WIDTH 1024
    #define IMAGE_HEIGHT 768
    #define IMAGE_SIZE IMAGE_WIDTH*IMAGE_HEIGHT
    #define QUEUE_MAX IMAGE_SIZE/4
    
    typedef int T_Queue[QUEUE_MAX];
    typedef int T_Image[IMAGE_SIZE];
    
    void NewFloodFill(int X,
                      int Y,
                      int Color,
                      int BuffDimX,
                      int WinOffS,
                      int WinDimX,
                      int WinDimY,
                      T_Image VBuffer,
                      T_Image ExtraVBuff,
                      T_Queue MyQueue)
    
    /* Replaces all pixels adjacent to the first pixel and equal to this;   */
    /* if ExtraVBuff == NULL writes to *VBuffer (eg BUFFER of 786432 Pixel),*/
    /* otherwise prepare a mask by writing on *ExtraVBuff (such BUFFER must */
    /* always have the same size as *VBuffer (it must be initialized to 0)).*/
    
    /*         X,Y: Point coordinates' of origin of the flood-fill.         */
    /*     WinOffS: Writing start offset on *VBuffer and *ExtraVBuff.       */
    /*    BuffDimX: Width, in number of Pixel (int), of each buffer.        */
    /*     WinDimX: Width, in number of Pixel (int), of the window.         */
    /*       Color: New color that replace all_Pixel == origin's_point.     */
    /*     WinDimY: Height, in number of Pixel (int), of the window.        */
    /*     VBuffer: Pointer to the primary buffer.                          */
    /*  ExtraVBuff: Pointer to the mask buffer (can be = NULL).             */
    /*     MyQueue: Pointer to the queue, containing the new-points' offsets*/
    
    {
     int VBuffCurrOffs=WinOffS+X+Y*BuffDimX;
     int PixelIn=VBuffer[VBuffCurrOffs];
     int QueuePnt=0;
     int *TempAddr=((ExtraVBuff) ? ExtraVBuff : VBuffer);
     int TempOffs1;
     int TempX1;
     int TempX2;
     char FLAG;
    
     if (0<=X && X<WinDimX && 0<=Y && Y<WinDimY) do
      {
       /* Fill to left the current line */
       TempX2=X;
       while (X>=0 && PixelIn==VBuffer[VBuffCurrOffs])
        {
         TempAddr[VBuffCurrOffs--]=Color;
         --X;
        }
       TempOffs1=VBuffCurrOffs+1;
       TempX1=X+1;
    
       /* Fill to right the current line */
       VBuffCurrOffs+=TempX2-X;
       X=TempX2;
       while (X+1<WinDimX && PixelIn==VBuffer[VBuffCurrOffs+1])
        {
         ++X;
         TempAddr[++VBuffCurrOffs]=Color;
        }
       TempX2=X;
    
       /* Backward scan of the previous line; puts new points offset in Queue[] */
       if (Y>0)
        {
         FLAG=1;
         VBuffCurrOffs-=BuffDimX;
         while (X-->=TempX1)
          {
           if (PixelIn!=VBuffer[VBuffCurrOffs] ||
               ExtraVBuff && Color==ExtraVBuff[VBuffCurrOffs])
            FLAG=1;
           else
           if (FLAG)
            {
             FLAG=0;
             if (QueuePnt<QUEUE_MAX)
              MyQueue[QueuePnt++]=VBuffCurrOffs;
            } 
           --VBuffCurrOffs;
          }
        }
    
       /* Forward scan of the next line; puts new points offset in Queue[] */
       if (Y<WinDimY-1)
        {
         FLAG=1;
         VBuffCurrOffs=TempOffs1+BuffDimX;
         X=TempX1;
         while (X++<=TempX2)
          {
           if (PixelIn!=VBuffer[VBuffCurrOffs] ||
               ExtraVBuff && Color==ExtraVBuff[VBuffCurrOffs])
            FLAG=1;
           else
           if (FLAG)
            {
             FLAG=0;
             if (QueuePnt<QUEUE_MAX)
              MyQueue[QueuePnt++]=VBuffCurrOffs;
            }
           ++VBuffCurrOffs;
          }
        }
    
       /* Gets a new point offset from Queue[] */ 
       if (--QueuePnt>=0)
        {
         VBuffCurrOffs=MyQueue[QueuePnt];
         TempOffs1=VBuffCurrOffs-WinOffS;
         X=TempOffs1%BuffDimX;
         Y=TempOffs1/BuffDimX;
        }
    
      /* Repeat the main cycle until the Queue[] is not empty */
      } while (QueuePnt>=0);
    }
    

    这里有测试程序:

    #include <stdio.h>
    #include <malloc.h>
    #include "ffill.c"
    
    #define RED_COL 0xFFFF0000
    #define WIN_LEFT 52
    #define WIN_TOP 48
    #define WIN_WIDTH 920
    #define WIN_HEIGHT 672
    #define START_LEFT 0
    #define START_TOP 671
    
    #define BMP_HEADER_SIZE 54
    
    typedef char T_Image_Header[BMP_HEADER_SIZE];
    void main(void)
    {
    
     T_Image_Header bmpheader;
     T_Image *image;
     T_Image *mask;
     T_Queue *MyQueue;
    
     FILE *stream;
     char *filename1="ffill1.bmp";
     char *filename2="ffill2.bmp";
     char *filename3="ffill3.bmp";
     int bwritten;
     int bread;
    
     image=malloc(sizeof(*image));
     mask=malloc(sizeof(*mask));
     MyQueue=malloc(sizeof(*MyQueue));
    
     stream=fopen(filename1,"rb");
     bread=fread(&bmpheader, 1, BMP_HEADER_SIZE, stream);
     bread=fread((char *)image, 1, IMAGE_SIZE<<2, stream);
     fclose(stream);
    
     memset(mask,0,IMAGE_SIZE<<2);
    
     NewFloodFill(START_LEFT,
                  START_TOP,
                  RED_COL,
                  IMAGE_WIDTH,
                  IMAGE_WIDTH*WIN_TOP+WIN_LEFT,
                  WIN_WIDTH,
                  WIN_HEIGHT,
                  *image,
                  NULL,
                  *MyQueue);
    
     stream=fopen(filename2,"wb+");
     bwritten=fwrite(&bmpheader, 1, BMP_HEADER_SIZE, stream);
     bwritten=fwrite((char *)image, 1, IMAGE_SIZE<<2, stream);
     fclose(stream);
    
     stream=fopen(filename3,"wb+");
     bwritten=fwrite(&bmpheader, 1, BMP_HEADER_SIZE, stream);
     bwritten=fwrite((char *)mask, 1, IMAGE_SIZE<<2, stream);
     fclose(stream);
    
     free(MyQueue);
     free(mask);
     free(image);
    }
    

    对于所示测试程序的输入,我使用了以下Windows Uncompressed.bmp图像(ffill1.bmp):

    enter image description here

    由所示测试程序填充,如下所示(ffill2.bmp):

    enter image description here

    使用“mask”而不是空,输出位图为(ffill3.bmp):

    enter image description here

        9
  •  0
  •   erol yeniaras    8 年前

    下面是我的BFS基于迭代C++的洪水填充问题的方法:

    // M is the input matrix, with every entry(pixel) have a color
    // represented with an integer value.
    // (x, y) row and column of seed point respectively
    // k: The new color to fill the seed and its adjacent pixels
    void floodFill(vector<vector<int>> &M, int x, int y, int k) {
        queue<pair<int, int>> nodeQ;
        nodeQ.push({x, y});
        int oldCol = M[x][y];
        while(!nodeQ.empty()) {
            pair<int, int> currNode = nodeQ.front();
            nodeQ.pop();
            if(M[currNode.first][currNode.second] == oldCol) {
                M[currNode.first][currNode.second] = k;
                if(currNode.first > 0) nodeQ.push({currNode.first-1, currNode.second});
                if(currNode.first < (M.size()-1)) nodeQ.push({currNode.first+1, currNode.second});
                if(currNode.second > 0) nodeQ.push({currNode.first, currNode.second-1});
                if(currNode.second < (M[0].size()-1)) nodeQ.push({currNode.first, currNode.second+1});
            }
        }
    }
    
        10
  •  0
  •   bandybabboon    7 年前

    您可以快速地将递归洪泛填充转换为超性能的伪递归…不编辑行,只添加新行: 将递归函数放入xy循环中以添加结构。 将找到的邻居记录到“找到的邻居阵列” 而不是内存,因此您将开始将4-16-64样式的递归树打包到xy数组中。内存使用从1 Gygabyte变为2兆字节。 还可以使用一个称为“填充邻居数组”的二维数组…中止“填充邻居数组”中标记为填充的任何像素的函数,这对每个副本使用2条指令,对每个填充操作使用20条指令,并且它像多米诺骨牌一样反复向上和向左填充,速度极快。

    1024x1024使用大约100万*20条指令,单核0.1秒。

    我以这种方式在i7上每秒获得900万个填充像素,我有一个视频作为证明,还有一个包含代码和理论解释的博客页面:

    www.youtube.com/watch?v=4hQ1wA4Sl4c

    这里有一页我试图解释它是如何工作的。 http://unity3dmc.blogspot.com/2017/02/ultimate-3d-floodfill-scanline.html?m=1

    还有代码。

    如果递归能够保持有序,那么它将是最快的。

    如果通过数据网格(图像)进行递归,也可以以网格格式存储递归的处理,因为处理的步骤代表来自网格的像素,而不是将结果分解为树格式。