Flood Fill Algorithm |
Sample Problem: Connected Fields
Farmer John's fields are broken into fields, with paths between some of them. Unfortunately, some fields are not reachable from other fields via the paths.
Define a superfield is a collection of fields that are all reachable from each other. Calculate the number of superfields.
The Abstraction
Given: a undirected graph
The component of a graph is a maximal-sized (though not necessarily maximum) subgraph which is connected.
Calculate the component of the graph.
This graph has three components: {1,4,8}, {2,5,6,7,9}, and {3}.
The Algorithm: Flood Fill
Flood fill can be performed three basic ways: depth-first, breadth-first, and breadth-first scanning. The basic idea is to find some node which has not been assigned to a component and to calculate the component which contains. The question is how to calculate the component.
In the depth-first formulation, the algorithm looks at each step through all of the neighbors of the current node, and, for those that have not been assigned to a component yet, assigns them to this component and recurses on them.
In the breadth-first formulation, instead of recursing on the newly assigned nodes, they are added to a queue.
In the breadth-first scanning formulation, every node has two values: component and visited. When calculating the component, the algorithm goes through all of the nodes that have been assigned to that component but not visited yet, and assigns their neighbors to the current component.
The depth-first formulation is the easiest to code and debug, but can require a stack as big as the original graph. For explicit graphs, this is not so bad, but for implicit graphs, such as the problem presented has, the numbers of nodes can be very large.
The breadth-formulation does a little better, as the queue is much more efficient than the run-time stack is, but can still run into the same problem. Both the depth-first and breadth-first formulations run in N + M time, where N is the number of vertices and M is the number of edges.
The breadth-first scanning formulation, however, requires very little extra space. In fact, being a little tricky, it requires no extra space. However, it is slower, requiring up to N*N + M time, where N is the number of vertices in the graph.
Pseudocode for Breadth-First Scanning
This code uses a trick to not use extra space, marking nodes to be visited as in component -2 and actually assigning them to the current component when they are actually visited.
#
#
10
11
12
13
14
15
16
17
18
19
20
Running time of this algorithm is O(N 2), where N is the numbers of nodes. Every edge is traversed twice (once for each end-point), and each node is only marked once.
Execution Example
Consider the graph from above.
The algorithm starts with all nodes assigned to no component.
Going through the nodes in order first node not assigned to any component yet is vertex 1. Start a new component (component 1) for that node, and set the component of node 1 to -2 (any nodes not shown are unassigned).
Node | Component |
1 | -2 |
Now, in the flood_fill code, the first time through the do loop, it finds the node 1 is assigned to component -2. Thus, it reassigns it to component 1, signifying that it has been visited, and then assigns its neighbors (node 4) to component -2.
Node | Component |
1 | 1 |
4 | -2 |
As the loop through all the nodes continues, it finds that node 4 is also assigned to component -2, and processes it appropriately as well.
Node | Component |
1 | 1 |
4 | 1 |
8 | -2 |
Node 8 is the next to be processed.
Node | Component |
1 | 1 |
4 | 1 |
8 | 1 |
Now, the for loop continues, and finds no more nodes that have not been assigned yet. Since the until clause is not satisfied ( num_visited = 3), it tries again. This time, no nodes are found, so the function exits and component 1 is complete.
The search for unassigned nodes continues, finding node 2. A new component (component 2) is allocated, node 2 is marked as in component -2, and flood_fill is called.
Node | Component |
1 | 1 |
2 | -2 |
4 | 1 |
8 | 1 |
Node 2 is found as marked in component -2, and is processed.
Node | Component |
1 | 1 |
2 | 2 |
4 | 1 |
7 | -2 |
8 | 1 |
9 | -2 |
Next, node 7 is processed.
Node | Component |
1 | 1 |
2 | 2 |
4 | 1 |
5 | -2 |
7 | 2 |
8 | 1 |
9 | -2 |
Then node 9 is processed.
Node | Component |
1 | 1 |
2 | 2 |
4 | 1 |
5 | -2 |
7 | 2 |
8 | 1 |
9 | 2 |
The terminating condition does not hold ( num_visited = 3), so the search through for nodes assigned to component -2 starts again. Node 5 is the first one found.
Node | Component |
1 | 1 |
2 | 2 |
4 | 1 |
5 | 2 |
6 | -2 |
7 | 2 |
8 | 1 |
9 | 2 |
Node 6 is the next node found to be in component -2.
Node | Component |
1 | 1 |
2 | 2 |
4 | 1 |
5 | 2 |
6 | 2 |
7 | 2 |
8 | 1 |
9 | 2 |
No more nodes are found assigned to component -2, but the terminating condition does not hold, so one more pass through the nodes is performed, finding no nodes assigned to component -2. Thus, the search for unassigned nodes continue from node 2, finding node 3 unassigned.
Node | Component |
1 | 1 |
2 | 2 |
3 | -2 |
4 | 1 |
5 | 2 |
6 | 2 |
7 | 2 |
8 | 1 |
9 | 2 |
Node 3 is processed.
Node | Component |
1 | 1 |
2 | 2 |
3 | 3 |
4 | 1 |
5 | 2 |
6 | 2 |
7 | 2 |
8 | 1 |
9 | 2 |
From here, the algorithm eventually terminates, as there are no more nodes assigned to component -2 and no unassigned nodes. The three components of the graph have been determined, along with the component to which each node belongs.
**********************************************************************************
+ From wikipedia
Flood fill, also called seed fill, is an algorithm that determines the area connected to a given node in a multi-dimensional array. It is used in the "bucket" fill tool of paint programs to determine which parts of a bitmap to fill with color.
+ the algorithms
The flood fill algorithm takes three parameters: a start node, a target color, and a replacement color. The algorithm looks for all nodes in the array which are connected to the start node by a path of the target color, and changes them to the replacement color. There are many ways in which the flood-fill algorithm can be structured, but they all make use of a queue or stack data structure, explicitly or implicitly.
从上面的算法介绍可知,凡是会搜索的同学就能很轻易地掌握floodfill。因为floodfill算法从大体而言可以细分为两种算法思想,一种是DFS,一种是BFS。以下讲介绍两大种常用的算法,并简单分析其中用到的dfs和bfs。
1. per-pixel fill (点点填充)
2. scanline fill (扫描线填充)
//stack friendly and fast floodfill algorithm(递归深搜的写法)
void floodFillScanline(int x, int y, int newColor, int oldColor)
{
if(oldColor == newColor) return;
if(screenBuffer[x][y] != oldColor) return;
int y1;
//draw current scanline from start position to the top
y1 = y;
while(y1 < h && screenBuffer[x][y1] == oldColor)
{
screenBuffer[x][y1] = newColor;
y1++;
}
//draw current scanline from start position to the bottom
y1 = y - 1;
while(y1 >= 0 && screenBuffer[x][y1] == oldColor)
{
screenBuffer[x][y1] = newColor;
y1--;
}
//test for new scanlines to the left
y1 = y;
while(y1 < h && screenBuffer[x][y1] == newColor)
{
if(x > 0 && screenBuffer[x - 1][y1] == oldColor)
{
floodFillScanline(x - 1, y1, newColor, oldColor);
}
y1++;
}
y1 = y - 1;
while(y1 >= 0 && screenBuffer[x][y1] == newColor)
{
if(x > 0 && screenBuffer[x - 1][y1] == oldColor)
{
floodFillScanline(x - 1, y1, newColor, oldColor);
}
y1--;
}
//test for new scanlines to the right
y1 = y;
while(y1 < h && screenBuffer[x][y1] == newColor)
{
if(x < w - 1 && screenBuffer[x + 1][y1] == oldColor)
{
floodFillScanline(x + 1, y1, newColor, oldColor);
}
y1++;
}
y1 = y - 1;
while(y1 >= 0 && screenBuffer[x][y1] == newColor)
{
if(x < w - 1 && screenBuffer[x + 1][y1] == oldColor)
{
floodFillScanline(x + 1, y1, newColor, oldColor);
}
y1--;
}
}
//The scanline floodfill algorithm using our own stack routines, faster(广搜队列的写法)
void floodFillScanlineStack(int x, int y, int newColor, int oldColor)
{
if(oldColor == newColor) return;
emptyStack();
int y1;
bool spanLeft, spanRight;
if(!push(x, y)) return;
while(pop(x, y))
{
y1 = y;
while(y1 >= 0 && screenBuffer[x][y1] == oldColor) y1--;
y1++;
spanLeft = spanRight = 0;
while(y1 < h && screenBuffer[x][y1] == oldColor )
{
screenBuffer[x][y1] = newColor;
if(!spanLeft && x > 0 && screenBuffer[x - 1][y1] == oldColor)
{
if(!push(x - 1, y1)) return;
spanLeft = 1;
}
else if(spanLeft && x > 0 && screenBuffer[x - 1][y1] != oldColor)
{
spanLeft = 0;
}//写这一部分是防止因为同一列上有断开的段而造成的可能的“没填充”
if(!spanRight && x < w - 1 && screenBuffer[x + 1][y1] == oldColor)
{
if(!push(x + 1, y1)) return;
spanRight = 1;
}
else if(spanRight && x < w - 1 && screenBuffer[x + 1][y1] != oldColor)
{
spanRight = 0;
} //写这一部分是防止因为同一列上有断开的段而造成的可能的“没填充”
y1++;
}
}
}
以上两个小算法填充的方向都是纵向填充,你也可以修改成横向填充。