根据坐标如何标记图片_如何玩转FloodFill算法?
讀完本文,你可以去力扣拿下如下題目:
733.圖像渲染
-----------
啥是 FloodFill 算法呢,最直接的一個(gè)應(yīng)用就是「顏色填充」,就是 Windows 繪畫本中那個(gè)小油漆桶的標(biāo)志,可以把一塊被圈起來的區(qū)域全部染色。
這種算法思想還在許多其他地方有應(yīng)用。比如說掃雷游戲,有時(shí)候你點(diǎn)一個(gè)方格,會(huì)一下子展開一片區(qū)域,這個(gè)展開過程,就是 FloodFill 算法實(shí)現(xiàn)的。
類似的,像消消樂這類游戲,相同方塊積累到一定數(shù)量,就全部消除,也是 FloodFill 算法的功勞。
通過以上的幾個(gè)例子,你應(yīng)該對(duì) FloodFill 算法有個(gè)概念了,現(xiàn)在我們要抽象問題,提取共同點(diǎn)。
一、構(gòu)建框架
以上幾個(gè)例子,都可以抽象成一個(gè)二維矩陣(圖片其實(shí)就是像素點(diǎn)矩陣),然后從某個(gè)點(diǎn)開始向四周擴(kuò)展,直到無法再擴(kuò)展為止。
矩陣,可以抽象為一幅「圖」,這就是一個(gè)圖的遍歷問題,也就類似一個(gè) N 叉樹遍歷的問題。幾行代碼就能解決,直接上框架吧:
// (x, y) 為坐標(biāo)位置void fill(int x, int y) { ? ?fill(x - 1, y); // 上 ? ?fill(x + 1, y); // 下 ? ?fill(x, y - 1); // 左 ? ?fill(x, y + 1); // 右}這個(gè)框架可以解決所有在二維矩陣中遍歷的問題,說得高端一點(diǎn),這就叫深度優(yōu)先搜索(Depth First Search,簡(jiǎn)稱 DFS),說得簡(jiǎn)單一點(diǎn),這就叫四叉樹遍歷框架。坐標(biāo) (x, y) 就是 root,四個(gè)方向就是 root 的四個(gè)子節(jié)點(diǎn)。
下面看一道 LeetCode 題目,其實(shí)就是讓我們來實(shí)現(xiàn)一個(gè)「顏色填充」功能。
根據(jù)上篇文章,我們講了「樹」算法設(shè)計(jì)的一個(gè)總路線,今天就可以用到:
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 區(qū)域 ? ?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 分,因?yàn)槟銓?duì)「框架思維」的掌控已經(jīng)爐火純青,此算法已經(jīng) cover 了 99% 的情況,僅有一個(gè)細(xì)節(jié)問題沒有解決,就是當(dāng) origColor 和 newColor 相同時(shí),會(huì)陷入無限遞歸。
二、研究細(xì)節(jié)
為什么會(huì)陷入無限遞歸呢,很好理解,因?yàn)槊總€(gè)坐標(biāo)都要搜索上下左右,那么對(duì)于一個(gè)坐標(biāo),一定會(huì)被上下左右的坐標(biāo)搜索。被重復(fù)搜索時(shí),必須保證遞歸函數(shù)能夠能正確地退出,否則就會(huì)陷入死循環(huán)。
為什么 newColor 和 origColor 不同時(shí)可以正常退出呢?把算法流程畫個(gè)圖理解一下:
可以看到,fill(1, 1) 被重復(fù)搜索了,我們用 fill(1, 1)* 表示這次重復(fù)搜索。fill(1, 1)* 執(zhí)行時(shí),(1, 1) 已經(jīng)被換成了 newColor,所以 fill(1, 1)* 會(huì)在這個(gè) if 語句被懟回去,正確退出了。
// 碰壁:遇到其他顏色,超出 origColor 區(qū)域if (image[x][y] != origColor) return;但是,如果說 origColor 和 newColor 一樣,這個(gè) if 語句就無法讓 fill(1, 1)* 正確退出,而是開啟了下面的重復(fù)遞歸,形成了死循環(huán)。
三、處理細(xì)節(jié)
如何避免上述問題的發(fā)生,最容易想到的就是用一個(gè)和 image 一樣大小的二維 bool 數(shù)組記錄走過的地方,一旦發(fā)現(xiàn)重復(fù)立即 return。
// 出界:超出邊界索引if (!inArea(image, x, y)) return;// 碰壁:遇到其他顏色,超出 origColor 區(qū)域if (image[x][y] != origColor) return;// 不走回頭路if (visited[x][y]) return;visited[x][y] = true;image[x][y] = newColor;完全 OK,這也是處理「圖」的一種常用手段。不過對(duì)于此題,不用開數(shù)組,我們有一種更好的方法,那就是回溯算法。
前文 回溯算法框架套路講過,這里不再贅述,直接套回溯算法框架:
void fill(int[][] image, int x, int y, ? ? ? ?int origColor, int newColor) { ? ?// 出界:超出數(shù)組邊界 ? ?if (!inArea(image, x, y)) return; ? ?// 碰壁:遇到其他顏色,超出 origColor 區(qū)域 ? ?if (image[x][y] != origColor) return; ? ?// 已探索過的 origColor 區(qū)域 ? ?if (image[x][y] == -1) return; ? ? ? ?// choose:打標(biāo)記,以免重復(fù) ? ?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:將標(biāo)記替換為 newColor ? ?image[x][y] = newColor;}這種解決方法是最常用的,相當(dāng)于使用一個(gè)特殊值 -1 代替 visited 數(shù)組的作用,達(dá)到不走回頭路的效果。為什么是 -1,因?yàn)轭}目中說了顏色取值在 0 - 65535 之間,所以 -1 足夠特殊,能和顏色區(qū)分開。
四、拓展延伸:自動(dòng)魔棒工具和掃雷
大部分圖片編輯軟件一定有「自動(dòng)魔棒工具」這個(gè)功能:點(diǎn)擊一個(gè)地方,幫你自動(dòng)選中相近顏色的部分。如下圖,我想選中老鷹,可以先用自動(dòng)魔棒選中藍(lán)天背景,然后反向選擇,就選中了老鷹。我們來分析一下自動(dòng)魔棒工具的原理。
顯然,這個(gè)算法肯定是基于 FloodFill 算法的,但有兩點(diǎn)不同:首先,背景色是藍(lán)色,但不能保證都是相同的藍(lán)色,畢竟是像素點(diǎn),可能存在肉眼無法分辨的深淺差異,而我們希望能夠忽略這種細(xì)微差異。第二,FloodFill 算法是「區(qū)域填充」,這里更像「邊界填充」。
對(duì)于第一個(gè)問題,很好解決,可以設(shè)置一個(gè)閾值 threshold,在閾值范圍內(nèi)波動(dòng)的顏色都視為 origColor:
if (Math.abs(image[x][y] - origColor) > threshold) ? ?return;對(duì)于第二個(gè)問題,我們首先明確問題:不要把區(qū)域內(nèi)所有 origColor 的都染色,而是只給區(qū)域最外圈染色。然后,我們分析,如何才能僅給外圍染色,即如何才能找到最外圍坐標(biāo),最外圍坐標(biāo)有什么特點(diǎn)?
可以發(fā)現(xiàn),區(qū)域邊界上的坐標(biāo),至少有一個(gè)方向不是 origColor,而區(qū)域內(nèi)部的坐標(biāo),四面都是 origColor,這就是解決問題的關(guān)鍵。保持框架不變,使用 visited 數(shù)組記錄已搜索坐標(biāo),主要代碼如下:
int fill(int[][] image, int x, int y, ? ?int origColor, int newColor) { ? ?// 出界:超出數(shù)組邊界 ? ?if (!inArea(image, x, y)) return 0; ? ?// 已探索過的 origColor 區(qū)域 ? ?if (visited[x][y]) return 1; ? ?// 碰壁:遇到其他顏色,超出 origColor 區(qū)域 ? ?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;}這樣,區(qū)域內(nèi)部的坐標(biāo)探索四周后得到的 surround 是 4,而邊界的坐標(biāo)會(huì)遇到其他顏色,或超出邊界索引,surround 會(huì)小于 4。如果你對(duì)這句話不理解,我們把邏輯框架抽象出來看:
int fill(int[][] image, int x, int y, ? ?int origColor, int newColor) { ? ?// 出界:超出數(shù)組邊界 ? ?if (!inArea(image, x, y)) return 0; ? ?// 已探索過的 origColor 區(qū)域 ? ?if (visited[x][y]) return 1; ? ?// 碰壁:遇到其他顏色,超出 origColor 區(qū)域 ? ?if (image[x][y] != origColor) return 0; ? ?// 未探索且屬于 origColor 區(qū)域 ? ?if (image[x][y] == origColor) { ? ? ? ?// ... ? ? ? ?return 1; ? }}這 4 個(gè) if 判斷涵蓋了 (x, y) 的所有可能情況,surround 的值由四個(gè)遞歸函數(shù)相加得到,而每個(gè)遞歸函數(shù)的返回值就這四種情況的一種。借助這個(gè)邏輯框架,你一定能理解上面那句話了。
這樣就實(shí)現(xiàn)了僅對(duì) origColor 區(qū)域邊界坐標(biāo)染色的目的,等同于完成了魔棒工具選定區(qū)域邊界的功能。
這個(gè)算法有兩個(gè)細(xì)節(jié)問題,一是必須借助 visited 來記錄已探索的坐標(biāo),而無法使用回溯算法;二是開頭幾個(gè) if 順序不可打亂。讀者可以思考一下原因。
同理,思考掃雷游戲,應(yīng)用 FloodFill 算法展開空白區(qū)域的同時(shí),也需要計(jì)算并顯示邊界上雷的個(gè)數(shù),如何實(shí)現(xiàn)的?其實(shí)也是相同的思路,遇到雷就返回 true,這樣 surround 變量存儲(chǔ)的就是雷的個(gè)數(shù)。當(dāng)然,掃雷的 FloodFill 算法不能只檢查上下左右,還得加上四個(gè)斜向。
以上詳細(xì)講解了 FloodFill 算法的框架設(shè)計(jì),二維矩陣中的搜索問題,都逃不出這個(gè)算法框架。
總結(jié)
以上是生活随笔為你收集整理的根据坐标如何标记图片_如何玩转FloodFill算法?的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 劳斯莱斯的雨伞价值多少钱?
- 下一篇: fanuc机器人四边形编程_FANUC机