读完本文,你可以去力扣拿下如下题目:

733.图像渲染

-----------

啥是 FloodFill 算法呢,最直接的一个应用就是「颜色填充」,就是 Windows 绘画本中那个小油漆桶的标志,可以把一块被圈起来的区域全部染色。

这种算法思想还在许多其他地方有应用。比如说扫雷游戏,有时候你点一个方格,会一下子展开一片区域,这个展开过程,就是 FloodFill 算法实现的。

类似的,像消消乐这类游戏,相同方块积累到一定数量,就全部消除,也是 FloodFill 算法的功劳。

通过以上的几个例子,你应该对 FloodFill 算法有个概念了,现在我们要抽象问题,提取共同点。

一、构建框架

以上几个例子,都可以抽象成一个二维矩阵(图片其实就是像素点矩阵),然后从某个点开始向四周扩展,直到无法再扩展为止。

矩阵,可以抽象为一幅「图」,这就是一个图的遍历问题,也就类似一个 N 叉树遍历的问题。几行代码就能解决,直接上框架吧:

// (x, y) 为坐标位置void fill(int x, int y) {    fill(x - 1, y); // 上    fill(x + 1, y); // 下    fill(x, y - 1); // 左    fill(x, y + 1); // 右}

这个框架可以解决所有在二维矩阵中遍历的问题,说得高端一点,这就叫深度优先搜索(Depth First Search,简称 DFS),说得简单一点,这就叫四叉树遍历框架。坐标 (x, y) 就是 root,四个方向就是 root 的四个子节点。

下面看一道 LeetCode 题目,其实就是让我们来实现一个「颜色填充」功能。

根据上篇文章,我们讲了「树」算法设计的一个总路线,今天就可以用到:

int[][] floodFill(int[][] image,        int sr, int sc, int newColor) {    int origColor = image[sr][sc];    fill(image, sr, sc, origColor, newColor);    return image;}void fill(int[][] image, int x, int y,        int origColor, int newColor) {    // 出界:超出边界索引    if (!inArea(image, x, y)) return;    // 碰壁:遇到其他颜色,超出 origColor 区域    if (image[x][y] != origColor) return;    image[x][y] = newColor;        fill(image, x, y + 1, origColor, newColor);    fill(image, x, y - 1, origColor, newColor);    fill(image, x - 1, y, origColor, newColor);    fill(image, x + 1, y, origColor, newColor);}boolean inArea(int[][] image, int x, int y) {    return x >= 0 && x < image.length        && y >= 0 && y < image[0].length;}

只要你能够理解这段代码,一定要给你鼓掌,给你 99 分,因为你对「框架思维」的掌控已经炉火纯青,此算法已经 cover 了 99% 的情况,仅有一个细节问题没有解决,就是当 origColor 和 newColor 相同时,会陷入无限递归。

二、研究细节

为什么会陷入无限递归呢,很好理解,因为每个坐标都要搜索上下左右,那么对于一个坐标,一定会被上下左右的坐标搜索。被重复搜索时,必须保证递归函数能够能正确地退出,否则就会陷入死循环。

为什么 newColor 和 origColor 不同时可以正常退出呢?把算法流程画个图理解一下:

可以看到,fill(1, 1) 被重复搜索了,我们用 fill(1, 1)* 表示这次重复搜索。fill(1, 1)* 执行时,(1, 1) 已经被换成了 newColor,所以 fill(1, 1)* 会在这个 if 语句被怼回去,正确退出了。

// 碰壁:遇到其他颜色,超出 origColor 区域if (image[x][y] != origColor) return;

但是,如果说 origColor 和 newColor 一样,这个 if 语句就无法让 fill(1, 1)* 正确退出,而是开启了下面的重复递归,形成了死循环。

三、处理细节

如何避免上述问题的发生,最容易想到的就是用一个和 image 一样大小的二维 bool 数组记录走过的地方,一旦发现重复立即 return。

 // 出界:超出边界索引if (!inArea(image, x, y)) return;// 碰壁:遇到其他颜色,超出 origColor 区域if (image[x][y] != origColor) return;// 不走回头路if (visited[x][y]) return;visited[x][y] = true;image[x][y] = newColor;

完全 OK,这也是处理「图」的一种常用手段。不过对于此题,不用开数组,我们有一种更好的方法,那就是回溯算法。

前文 回溯算法框架套路讲过,这里不再赘述,直接套回溯算法框架:

void fill(int[][] image, int x, int y,        int origColor, int newColor) {    // 出界:超出数组边界    if (!inArea(image, x, y)) return;    // 碰壁:遇到其他颜色,超出 origColor 区域    if (image[x][y] != origColor) return;    // 已探索过的 origColor 区域    if (image[x][y] == -1) return;        // choose:打标记,以免重复    image[x][y] = -1;    fill(image, x, y + 1, origColor, newColor);    fill(image, x, y - 1, origColor, newColor);    fill(image, x - 1, y, origColor, newColor);    fill(image, x + 1, y, origColor, newColor);    // unchoose:将标记替换为 newColor    image[x][y] = newColor;}

这种解决方法是最常用的,相当于使用一个特殊值 -1 代替 visited 数组的作用,达到不走回头路的效果。为什么是 -1,因为题目中说了颜色取值在 0 - 65535 之间,所以 -1 足够特殊,能和颜色区分开。

四、拓展延伸:自动魔棒工具和扫雷

大部分图片编辑软件一定有「自动魔棒工具」这个功能:点击一个地方,帮你自动选中相近颜色的部分。如下图,我想选中老鹰,可以先用自动魔棒选中蓝天背景,然后反向选择,就选中了老鹰。我们来分析一下自动魔棒工具的原理。

显然,这个算法肯定是基于 FloodFill 算法的,但有两点不同:首先,背景色是蓝色,但不能保证都是相同的蓝色,毕竟是像素点,可能存在肉眼无法分辨的深浅差异,而我们希望能够忽略这种细微差异。第二,FloodFill 算法是「区域填充」,这里更像「边界填充」。

对于第一个问题,很好解决,可以设置一个阈值 threshold,在阈值范围内波动的颜色都视为 origColor:

if (Math.abs(image[x][y] - origColor) > threshold)    return;

对于第二个问题,我们首先明确问题:不要把区域内所有 origColor 的都染色,而是只给区域最外圈染色。然后,我们分析,如何才能仅给外围染色,即如何才能找到最外围坐标,最外围坐标有什么特点?

可以发现,区域边界上的坐标,至少有一个方向不是 origColor,而区域内部的坐标,四面都是 origColor,这就是解决问题的关键。保持框架不变,使用 visited 数组记录已搜索坐标,主要代码如下:

int fill(int[][] image, int x, int y,    int origColor, int newColor) {    // 出界:超出数组边界    if (!inArea(image, x, y)) return 0;    // 已探索过的 origColor 区域    if (visited[x][y]) return 1;    // 碰壁:遇到其他颜色,超出 origColor 区域    if (image[x][y] != origColor) return 0;​    visited[x][y] = true;        int surround =           fill(image, x - 1, y, origColor, newColor)        + fill(image, x + 1, y, origColor, newColor)        + fill(image, x, y - 1, origColor, newColor)        + fill(image, x, y + 1, origColor, newColor);        if (surround < 4)        image[x][y] = newColor;        return 1;}

这样,区域内部的坐标探索四周后得到的 surround 是 4,而边界的坐标会遇到其他颜色,或超出边界索引,surround 会小于 4。如果你对这句话不理解,我们把逻辑框架抽象出来看:

int fill(int[][] image, int x, int y,    int origColor, int newColor) {    // 出界:超出数组边界    if (!inArea(image, x, y)) return 0;    // 已探索过的 origColor 区域    if (visited[x][y]) return 1;    // 碰壁:遇到其他颜色,超出 origColor 区域    if (image[x][y] != origColor) return 0;    // 未探索且属于 origColor 区域    if (image[x][y] == origColor) {        // ...        return 1;    }}

这 4 个 if 判断涵盖了 (x, y) 的所有可能情况,surround 的值由四个递归函数相加得到,而每个递归函数的返回值就这四种情况的一种。借助这个逻辑框架,你一定能理解上面那句话了。

这样就实现了仅对 origColor 区域边界坐标染色的目的,等同于完成了魔棒工具选定区域边界的功能。

这个算法有两个细节问题,一是必须借助 visited 来记录已探索的坐标,而无法使用回溯算法;二是开头几个 if 顺序不可打乱。读者可以思考一下原因。

同理,思考扫雷游戏,应用 FloodFill 算法展开空白区域的同时,也需要计算并显示边界上雷的个数,如何实现的?其实也是相同的思路,遇到雷就返回 true,这样 surround 变量存储的就是雷的个数。当然,扫雷的 FloodFill 算法不能只检查上下左右,还得加上四个斜向。

以上详细讲解了 FloodFill 算法的框架设计,二维矩阵中的搜索问题,都逃不出这个算法框架

根据坐标如何标记图片_如何玩转FloodFill算法?相关推荐

  1. 根据坐标如何标记图片_推荐收藏 | 一文搞定SCI论文图片

    SCI论文图片的编辑是一门简单,却不容易的学问.在编辑图片的过程中,涉及到很多软件的配合使用,同时我们的目的不仅是满足投稿杂志的参数要求,还希望尽量做得美观好看. 现基于各大网站.公众号关于SCI作图 ...

  2. 快速排序 挖坑_由浅入深玩转快速排序算法

    由浅入深玩转快速排序算法 快速排序可以说是最快的通用排序算法,它甚至被誉为20世纪科学和工程领域的十大算法之一.在众多排序算法中其无论是时间复杂度还是空间复杂度都颇具优势.作为开发工程师,我们很有必要 ...

  3. mpAndroidchart 坐标和图表距离_【玩转图表系列】六步,美化你的图表,让老板刮目相看!...

    近期隆重推出图表分析系列,包括销售分析.盈亏分析.费用分析.趋势分析.进度分析等,通过双坐标图.甘特图.瀑布图.双层饼图等系列图表精美展现,专业高效,让你从初级学到高级.从小白跨越专业. 今天开启我们 ...

  4. 已知两点坐标拾取怎么操作_已知的操作员学习-第4部分

    已知两点坐标拾取怎么操作 有关深层学习的FAU讲义 (FAU LECTURE NOTES ON DEEP LEARNING) These are the lecture notes for FAU's ...

  5. 通过opencv标记图片以及写入Excel小方法

    通过opencv标记图片以及写入Excel小方法 通过opencv根据坐标绘制图片框,然后保存图片 将结果读入并且写入Excel中进行保存 Python strip() 方法用于移除字符串头尾指定的字 ...

  6. al00华为手机_华为al00手机报价图片_华为al00手机

    *为提供尽可能准确的产品信息.规格参数.产品特性,华为或荣耀可能实时调整和修订以上页面中的文字表述.图片效果等内容,以求与实际产品性能.规格.指数.零部件等信 [华为畅玩5(CUN-AL00)金色手机 ...

  7. python节日贺卡图片大全_新年贺卡图片_新年贺卡手工制作图片

    新年贺卡图片_新年贺卡手工制作图片 导读:小编根据大家的需要整理了一份关于<新年贺卡图片_新年贺卡手工制作图片>的内容,具体内容:新年到来,制作一张手工图片,送上新年满满的祝福,本文是小编 ...

  8. python docx 合并文档 图片_使用python抓取大量简历文档内数据(word:docx;pdf;图片等)输出表格文件...

    1. 文章背景描述: 近期公司有员工离职了,技术岗位的. 让HR招人,招聘进度也太慢了,实在等不及,就撸起袖子自己上.(之前从来没招聘过) 自己在某招聘网站注册后,花了若干人民币,短时间收到大量求职者 ...

  9. vue吸管拾色器、利用canvas获取坐标点颜色、canvas获取坐标点颜色图片跨域、图片转base64、colorPicker

    vue吸管拾色器.利用canvas获取坐标点颜色.canvas获取坐标点颜色图片跨域.图片转base64.colorPicker 1.需求:表格中主颜色和次颜色是需要从缩略图上吸取颜色,然后渲染色块, ...

最新文章

  1. HTML5学习笔记二 HTML基础
  2. linux下文件的压缩和解压缩
  3. 数据科学 IPython 笔记本 8.9 自定义图例
  4. Vsftp在Ubuntu的安装与配置
  5. 《第一行代码》学习笔记12-UI(1)
  6. Kotlin习惯用语和编码风格[转自Kotlin中文文档]
  7. 基于HMM的拼音输入法
  8. linux在123目录下找文件,linux在当前目录下根据文件名查找文件
  9. IDEA 在debug 模式下启动tomcat报错:Application Server was not ..reason:Unable to ping server at localhos:1199
  10. PHP正则表达式笔记与实例详解
  11. 百度,谷歌,360,搜狗,神马等蜘蛛IP段大全
  12. PEER地震波数据转化成lsdyna求解器格式曲线关键字方法
  13. 云服务器ecs是vps还是虚拟主机,云服务器ecs是vps还是虚拟主机
  14. UML 之 顺序图(Sequence Diagram)
  15. java核心之类和对象
  16. rk3568 修改开机动画
  17. 转贴:[铿锵发金石 幽眇感鬼神] 高瞻“家园”诗4首浅议[蛮桂]
  18. 研究人员通过监听你的电脑处理器发出的细小声音破解了世界上最困难的加密算法之壹:4096 位 RSA
  19. SQL Server 2005通过端口1433连接到主机127.0.0.1的TCP/IP连接失败解决方案
  20. php的getopt函数,PHP 中 getopt 函数的用法

热门文章

  1. 1 用python进行OpenCV实战之用OpenCV3实现图片载入、显示和储存
  2. ionic4中使用Swiper触屏滑动--技术
  3. Linux系统管理必备知识之利用ssh传输文件
  4. ModuleNotFoundError: No module named 'tools.nnwrap' pytorch 安装
  5. VS Code搭建C/C++开发环境超详细教程
  6. 华为4D成像雷达、智能驾驶平台MDC 810
  7. 2021年大数据Spark(十一):应用开发基于IDEA集成环境
  8. Python bytes 的使用
  9. ImageView / Text 使用 android:state_pressed 没有效果的处理方法
  10. ERROR: Failed to resolve: com.android.support:appcompat-v7:29.0.0