用C编写的工作非递归填充算法?

时间:2009-08-10 20:46:54

标签: c algorithm flood-fill

我一直在努力寻找一种有效的填充算法。在许多算法中,我只尝试过“递归线填充”,其中一个行为完全符合它应该的主要警告,它偶尔会打击堆栈。 :(

我已经尝试了很多我发现的非递归实现,并且它们都非常温和:要么在奇怪的地方留下空白,要么泛滥整个区域(当它们被封闭时)。

任何人都有一个非递归填充工作源代码用C语言编写(或者c ++不是太大的OOP而且我可以很容易地解开)?

12 个答案:

答案 0 :(得分:25)

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

以下是一些非递归实现floodfill的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));
        }
    }
}

答案 1 :(得分:11)

这是一些可以满足您需求的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 :(得分:9)

快速谷歌搜索会显示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 */
            ...
        }
    }
}

答案 3 :(得分:7)

通过使用本地数据来模拟堆栈,是否存在所有递归函数都可以作为迭代函数实现的证明?您可以使用std :: vector来创建算法的类似堆栈的行为而不会破坏堆栈,因为它将使用堆。

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

答案 4 :(得分:3)

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

您所需要的只是选择一个漂亮,紧凑的工作表示。最坏的情况:创建一个struct,保存您通常会传递给递归版本的参数...

答案 5 :(得分:2)

我们注意到我们在3d卷上的Floodfill实现消耗了很多内存;所以我们通过以下方式修改了代码(有了很大的改进):

  1. 在起点周围创建一个半径= 10 voxs的球体,并将该半径内的所有体素标记为“要被访问”

  2. 如果当前体素>阈值,插入1.

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

  4. radius = radius + 10(体素)

  5. 所以,一次,我们的洪水填充是在同心球上进行填充,这是整个卷的一部分,正如我所说,这大大减少了内存消耗,但我仍在寻找实施/想法会更好。

答案 6 :(得分:2)

我不知道我的回答是否与您提出的问题完全相关,但此后我提出了我的C版Flood-Fill算法,它不使用递归调用。

1-11-2017:NEW-VERSION;成功测试了两个BITMAPS。

它仅使用新点的偏移队列,它在窗口上工作:双缓冲区的WinnOffs-(WinDimX,WinDimY):* VBuffer(屏幕或图像的副本),以及可选的,它写一个泛洪填充结果的掩码(* ExtraVBuff)。 在调用之前必须用0填充ExtraVBuff(如果你不需要掩码,你可以设置ExtraVBuff = NULL);在通话后使用它你可以做渐变式填充或其他绘画效果。 NewFloodFill的每像素32位工作,它是一个C函数。我在1991年重新编写了这个算法(我用他的Pascal写的),但现在它在C中工作,每像素32位;也没有使用任何函数调用,只是在队列中每次“弹出”之后只进行一次除法,并且永远不会溢出队列,如果它以正确的方式调整大小(大约是图像的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未压缩.BMP图像(ffill1.bmp):

enter image description here

通过所示的测试程序填写如下(ffill2.bmp):

enter image description here

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

enter image description here

答案 7 :(得分:1)

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

  

我不敢通过简单的递归尝试深度优先搜索;递归的深度仅受删除限制,我的实验表明,问题删除可能需要超过一百万的堆栈深度。所以我把堆栈放在一个辅助数据结构中。使用显式堆栈实际上也可以轻松尝试广度优先搜索,事实证明广度优先搜索可以比深度优先搜索使用少40倍的空间。

对于我的数据结构,我使用了Dave Hanson C Interfaces and ImplementationsSeq_T;从深度优先到广度优先改变只需要改变一个函数调用。

答案 8 :(得分:1)

您可以将递归泛洪填充快速转换为超高性能的伪递归...不要编辑这些行,只需添加新行即可: 将递归函数放在XY循环中以增加结构。 将找到的邻居记录到“找到的邻居数组”中 而不是内存,因此您将开始将递归的4-16-64样式树打包到XY数组中。内存使用量从1吉字节到2兆字节。 还可以使用称为“填充的邻居数组”的2D数组...对于标记为“填充的邻居数组”中已填充的任何像素,中止该函数,此操作对每个重复项使用2条指令,对每个泛洪操作使用20条指令,然后迭代填充像多米诺骨牌一样向左和向上快速疯狂地移动。

1024x1024使用大约1百万个* 20条指令,对于单个内核来说这是0.1秒。

这样,我在i7上达到了每秒900万个填充像素,我有一个视频作为证明,并且在博客页面上提供了代码和理论解释:

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

这是我尝试解释其工作原理的页面。 http://unity3dmc.blogspot.com/2017/02/ultimate-3d-floodfill-scanline.html?m=1

和代码。

如果能够保持井井有条,则递归将是最快的。

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

答案 9 :(得分:0)

以下是基于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)

这里是非递归例程的指南,该例程每秒完成1000万像素:it's called marching-floodfills,如果您将以前的递归例程前进到X-Y循环中会发生什么情况。

写自己的内存,一个2D数组记录已验证的空间,另一个数组记录完整的填充图像,并使用此循环系统对其进行读写...平均每个像素20条指令。我使用上述视频逻辑以每秒500万个体素处理了20亿个体素图。

答案 11 :(得分:0)

我发现Paul Heckbert所做的填充是最简单的非递归C实现:

use std::sync::Arc;
use parking_lot::{Mutex, MutexGuard, MappedMutexGuard};
use std::collections::HashMap;

pub struct MyStruct {
    data: Arc<Mutex<HashMap<i32, Vec<i32>>>>,
}

impl MyStruct {
    pub fn get_data_for(&self, i: &i32) -> MappedMutexGuard<Vec<i32>> {
        MutexGuard::map(self.data.lock(), |d| d.get_mut(i).unwrap())
    }
}

来源:https://github.com/erich666/GraphicsGems/blob/master/gems/SeedFill.c