摘要
种子填充算法是一种用于填充封闭区域的计算机图形学技术,广泛应用于画图软件、游戏开发和图像处理等领域。其核心思想是从一个种子点开始,向四周扩散填充颜色,直到遇到边界为止。算法分为递归法和非递归法:递归法通过不断调用自身实现填充,但可能因递归层数过多导致栈溢出;非递归法则使用队列或栈来管理待填充的像素点,避免了栈溢出的风险。此外,算法还支持八连通填充和边界填充等变种,以适应不同场景需求。虽然种子填充算法简单直观,但在处理大面积区域时可能效率较低,因此常与其他优化技术结合使用。
1. 问题背景——“油漆桶工具的魔法”
想象你在画画,
有一块不规则的区域(比如一片叶子),
你想用“油漆桶”工具一下子把它全部涂上颜色。
你只需要在区域内部点一下,
油漆就会像水一样,
从你点的地方蔓延到整个封闭区域,
但不会流出边界。
这就是种子填充算法的工作原理!
2. 形象比喻——“油漆桶倒水” or “小蚂蚁探险队”
- 油漆桶倒水:你在区域里倒一桶水,水会沿着通路流到每个角落,遇到墙壁(边界)就停下。
- 小蚂蚁探险队:一只蚂蚁从你点的地方出发,每到一个新格子就叫来更多蚂蚁,大家一起把能走的地方都走遍,遇到墙壁就不走了。
3. 种子填充算法的两种方式
1)递归法(Flood Fill)
- 蚂蚁到一个格子,先涂色,然后往上下左右四个方向继续爬。
- 每到一个新格子,重复这个过程。
- 直到所有能走的地方都被涂色。
2)非递归法(队列/栈法)
- 蚂蚁带着一个“待办清单”(队列或栈)。
- 每次从清单里取出一个格子,涂色,然后把它的邻居(上下左右)加入清单。
- 不断重复,直到清单空了。
4. 递归法——“蚂蚁分身术”
流程:
- 蚂蚁到达一个格子(x, y)。
- 如果这个格子还没涂色,先涂上。
- 然后分别往上、下、左、右四个方向递归地继续。
- 每个新格子都重复这个过程。
伪代码:
def flood_fill(x, y, old_color, new_color):
if 画布[x][y] != old_color:
return
画布[x][y] = new_color
flood_fill(x+1, y, old_color, new_color)
flood_fill(x-1, y, old_color, new_color)
flood_fill(x, y+1, old_color, new_color)
flood_fill(x, y-1, old_color, new_color)
形象解释:
每只蚂蚁遇到新格子就分出四只新蚂蚁,大家一起把所有能走的地方都走遍。
优点: 代码简单,思路清晰。
缺点: 区域很大时,递归层数太多,容易“爆栈”!
5. 非递归法——“蚂蚁带队列”
流程:
- 蚂蚁带着一个“待办清单”(队列或栈),最开始只放入起点。
- 每次从清单里取出一个格子,涂色。
- 把它的上下左右邻居(如果还没涂色)加入清单。
- 重复,直到清单空了。
伪代码:
def flood_fill_non_recursive(x, y, old_color, new_color):
queue = [(x, y)]
while queue:
cx, cy = queue.pop(0) # 队列用pop(0),栈用pop()
if 画布[cx][cy] == old_color:
画布[cx][cy] = new_color
queue.append((cx+1, cy))
queue.append((cx-1, cy))
queue.append((cx, cy+1))
queue.append((cx, cy-1))
形象解释:
蚂蚁们排队,每次一个蚂蚁出队,叫来新伙伴加入队伍,直到所有能走的地方都走完。
优点: 不会爆栈,适合大区域。
缺点: 需要额外的队列空间。
6. 动画演示思路
- 你在区域内部点一下,
- 油漆像水波一样向四周扩散,
- 每个格子被依次染色,
- 油漆遇到边界就停下,
- 最终整个区域都被涂满。
7. 特殊情况与优化
- 八连通填充:除了上下左右,还可以斜着走(八个方向),让填充更完整。
- 防止重复:每个格子只涂一次,避免死循环。
- 边界填充:有时不是填充某种颜色的区域,而是遇到特定“边界色”就停下。
8. 实际应用
- 画图软件:油漆桶工具,点击区域自动填色。
- 游戏开发:迷宫探索、区域染色、地形扩展。
- 图像处理:区域分割、连通域标记。
9. 总结
种子填充算法,
就像一群聪明的小蚂蚁,
从你点的地方出发,
沿着通路把整个区域都涂满,
遇到墙壁就停下。
递归法像分身术,简单但容易累坏(爆栈);
非递归法像排队,稳妥又高效。
它是油漆桶工具背后的“魔法小队长”!
10. 八连通填充——“蚂蚁会拐弯”
前面说的都是四连通(上下左右),
但有时候区域里有斜角的小缝隙,
四连通的蚂蚁过不去,
会留下没涂到的小点。
八连通的蚂蚁更聪明,
它们不仅会上下左右,还会斜着走(↖↗↙↘),
这样就能把所有角落都涂满!
八连通伪代码:
directions = [(-1,0), (1,0), (0,-1), (0,1), (-1,-1), (-1,1), (1,-1), (1,1)]
def flood_fill_8(x, y, old_color, new_color):
queue = [(x, y)]
while queue:
cx, cy = queue.pop()
if 画布[cx][cy] == old_color:
画布[cx][cy] = new_color
for dx, dy in directions:
queue.append((cx+dx, cy+dy))
11. 边界填充——“遇到墙就停”
有时候你不是想填某种颜色的区域,
而是想让油漆遇到“墙”就停下,
比如填充一个黑色边框圈起来的区域。
边界填充算法的思路是:
- 只要不是“墙色”,就继续涂色和扩展。
伪代码:
def boundary_fill(x, y, boundary_color, fill_color):
if 画布[x][y] != boundary_color and 画布[x][y] != fill_color:
画布[x][y] = fill_color
boundary_fill(x+1, y, boundary_color, fill_color)
boundary_fill(x-1, y, boundary_color, fill_color)
boundary_fill(x, y+1, boundary_color, fill_color)
boundary_fill(x, y-1, boundary_color, fill_color)
12. 动画演示——“油漆波浪”
你可以想象这样一个动画:
- 你在区域内部点一下,
- 油漆像波浪一样一圈圈扩散,
- 每一圈都比上一圈多涂一层,
- 最终整个区域都被涂满,
- 油漆遇到边界就停下。
如果用队列实现,动画会像水波一样一圈圈扩展;
如果用递归实现,动画会像蚂蚁钻洞,四处乱窜。
13. 代码实现(Python示例,非递归八连通)
def flood_fill_non_recursive_8(x, y, old_color, new_color):
directions = [(-1,0), (1,0), (0,-1), (0,1), (-1,-1), (-1,1), (1,-1), (1,1)]
stack = [(x, y)]
while stack:
cx, cy = stack.pop()
if 画布[cx][cy] == old_color:
画布[cx][cy] = new_color
for dx, dy in directions:
nx, ny = cx+dx, cy+dy
if 0 <= nx < 宽度 and 0 <= ny < 高度:
stack.append((nx, ny))
14. 实际应用举例
- 画图软件:油漆桶工具,点击区域自动填色。
- 游戏开发:迷宫探索、地形扩展、区域染色。
- 图像处理:连通域标记、区域分割、物体识别。
- 医学图像:自动分割肿瘤、器官等区域。
15. 种子填充与扫描线填充的对比
特点 | 种子填充算法(Seed Fill) | 扫描线填充算法(Scanline Fill) |
---|---|---|
适用场景 | 任意形状、带洞、复杂区域 | 规则多边形、无洞或少洞 |
填充方式 | 从一点向四周扩散 | 一行一行横向填充 |
实现方式 | 递归/队列/栈 | 边表+排序+区间填充 |
优缺点 | 简单灵活,易爆栈 | 高效,适合大多边形 |
动画效果 | 像水波扩散 | 一排一排刷地板 |
16. 优化与变种
- 线段种子填充:每次不是只涂一个点,而是涂一整段,减少队列长度,提高效率。
- 防止重复入队:用标记数组或直接染色,避免同一个点多次入队。
- 多线程/并行填充:多个“蚂蚁”同时扩散,速度更快。
17. 总结升级版
种子填充算法,
就像一群聪明的小蚂蚁,
从你点的地方出发,
沿着通路把整个区域都涂满,
遇到墙壁就停下。
递归法像分身术,简单但容易累坏(爆栈);
非递归法像排队,稳妥又高效。
八连通蚂蚁会拐弯,边界填充遇墙停。
它是油漆桶工具背后的“魔法小队长”,
也是图像处理、游戏开发、医学分割等领域的好帮手!