摘要:原文鏈接最近在知乎上看到一個(gè)問題,隨機(jī)生成指定面積單連通區(qū)域,感覺還挺有意思的,于是整理一下寫一篇新文章。問題闡述如下圖所示,在的區(qū)域中,隨機(jī)生成面積為的單連通區(qū)域,該隨機(jī)包括位置隨機(jī)以及形狀隨機(jī)。
原文鏈接:https://xcoder.in/2018/04/01/random-connected-area/
最近在知乎上看到一個(gè)問題,「隨機(jī)生成指定面積單連通區(qū)域?」,感覺還挺有意思的,于是整理一下寫一篇新文章。
問題闡述如下圖所示,在 10x10 的區(qū)域中,隨機(jī)生成面積為 6 的單連通區(qū)域,該「隨機(jī)」包括「位置隨機(jī)」以及「形狀隨機(jī)」。
注意:
單連通區(qū)域定義是該區(qū)域每一個(gè)區(qū)塊上下左右至少連著另一個(gè)區(qū)塊;
采用周期性結(jié)構(gòu),超出右邊移到最左邊,以此類推。
其中點(diǎn) 2 可以分采用和不采用周期性結(jié)構(gòu)分別討論。隨便說說
這個(gè)問題,我不知道原題提問者想要做什么事。但是就這題本身而言,我們可以拿它去生成一個(gè)隨機(jī)地圖,例如:
建造、等待的沙盒類手游,游戲中有一個(gè)空島,玩家能在上面建造自己的建筑然后等待各種事件完成。空島形狀隨機(jī)生成,并且都聯(lián)通且面積一定,這樣每個(gè)玩家進(jìn)去的時(shí)候就能得到不同地形。解決一下
在得知了問題原題之后,我們就可以照著題目的意思開始解決了。
DFS其實(shí)這么一個(gè)問題一出現(xiàn),腦子里面就瞬間涌出幾個(gè)詞匯:DFS、Flood fill、并查集等等。
那么其實(shí)這最粗暴的辦法相當(dāng)于你假想有一個(gè)連通區(qū)域,然后你去 Flood fill 它——至于墻在哪,在遞歸的每一個(gè)節(jié)點(diǎn)的時(shí)候隨機(jī)一下搜索方向的順序就可以了。
實(shí)現(xiàn)外殼我們先實(shí)現(xiàn)一個(gè)類的框架吧(我是 Node.js 開發(fā)者,自然用 JavaScript 進(jìn)行 Demo 的輸出)。
const INIT = Symbol("init"); class Filler { /** * Filler 構(gòu)造函數(shù) * @constructor * @param {Number} length 地圖總寬高 * @param {Number} needArea 需要填充的區(qū)域面積 */ constructor(length, needArea) { this.length = length; this.needArea = needArea; } /** * 初始化地圖 */ [INIT]() { /** * 為了方便,地圖就用一個(gè)二維字符數(shù)組表示 * * + . 代表空地 * + x 代表填充 */ this.map = []; this.count = 0; for(let i = 0; i < this.length; i++) { let row = []; for (let j = 0; j < this.length; j++) row.push("."); this.map.push(row); } } /** * 填充遞歸函數(shù) * @param {Number} x 坐標(biāo) X 軸的值 * @param {Number} y 坐標(biāo) Y 軸的值 * @return 填充好的地圖二維數(shù)組 */ fill(x, y) { // 待實(shí)現(xiàn) } }非周期性實(shí)現(xiàn)
有了架子之后,我們就可以實(shí)現(xiàn)遞歸函數(shù) fill 了,整理一下流程如下:
隨機(jī)一個(gè)起點(diǎn)位置,并以此開始遞歸搜索;
fill(x, y) 進(jìn)入遞歸搜索:
如果需要初始化地圖就調(diào)用 this[INIT]();
this.count++,表示填充區(qū)域面積加了 1,并在數(shù)組中將該位置填充為 x;
this.count 是否等于所需要的面積:
若等于,則返回當(dāng)前的地圖狀態(tài);
若不等于,則繼續(xù) 2.4;
隨機(jī)四個(gè)方向的順序;
對(duì)四個(gè)方向進(jìn)行循環(huán):
x、y 軸的值按當(dāng)前方向走一個(gè)算出新的坐標(biāo)值 newX 和 newY;
判斷坐標(biāo)是否合法(越界算非法):
若非法則回 2.5 繼續(xù)下一個(gè)方向;
若合法則繼續(xù) 2.5.3;
遞歸 fill(newX, newY) 得到結(jié)果,若有結(jié)果則返回;
若循環(huán)完四個(gè)方向都還沒返回結(jié)果則會(huì)跳到這一步來,這個(gè)時(shí)候進(jìn)行狀態(tài)還原,遞歸跳回上一層進(jìn)行下一個(gè)狀態(tài)的搜索。
在這里「狀態(tài)還原」表示把 this.count-- 還原回當(dāng)前坐標(biāo)填充前的狀態(tài),并且把當(dāng)前填充的 "x" 給還原回 "."。
照著上面的流程很快就能得出代碼結(jié)論:
const _ = require("lodash"); class Filler { ... fill(x, y) { // 初始化地圖 const needInit = !arguments[2]; if(needInit) this[INIT](); // 如果當(dāng)前坐標(biāo)已被填充,則返回空 if(this.map[x][y] === "x") return; // 填充當(dāng)前坐標(biāo) this.count++; this.map[x][y] = "x"; // 填充滿了則返回當(dāng)前地圖 if(this.count === this.needArea) return Object.assign([], this.map); // 隨機(jī)四個(gè)方向的順序 const dirs = _.shuffle([ [ 0, 1 ], [ 0, -1 ], [ 1, 0 ], [ -1, 0 ] ]); // 循環(huán)四個(gè)方向 for(let i = 0; i < 4; i++) { const dir = dirs[i]; let newX = x + dir[0]; let newY = y + dir[1]; // 判斷邊界 { if(newX < 0 || newX >= this.length || newY < 0 || newY >= this.length) continue; } // 進(jìn)入下一層遞歸并得到結(jié)果 const ret = this.fill(newX, newY, true); // 若結(jié)果非空則返回結(jié)果 if(ret) return ret; } // 狀態(tài)還原 this.count--; this.map[x][y] = "."; } }
這么一來,類就寫好了。接下去我們只要實(shí)現(xiàn)一些交互的代碼,就可以看效果了。
點(diǎn)我進(jìn)入 JSFiddle 看效果。
如果懶得進(jìn)入 JSFiddle 看,也可以看看下面的幾個(gè)截圖:
10x10 填 50 效果圖
10x10 填 6 效果圖
50x50 填 50 效果圖
周期性實(shí)現(xiàn)其實(shí)原題說了一個(gè)條件,那就是采用周期性結(jié)構(gòu),超出右邊移到最左邊,以此類推。
而我們前文的代碼其實(shí)是照著非周期性結(jié)構(gòu)來實(shí)現(xiàn)的。不過如果我們要將其改成周期性實(shí)現(xiàn)也很簡單,只需要把前文代碼中邊界判斷的那一句代碼改為周期性計(jì)算的代碼即可,也就是說要把這段代碼:
// 判斷邊界 { if(newX < 0 || newX >= this.length || newY < 0 || newY >= this.length) continue; }
改為:
// 周期性計(jì)算 { if(newX < 0) newX = this.length - 1; if(newX >= this.length) newX = 0; if(newY < 0) newY = this.length - 1; if(newY >= this.length) newY = 0; }
這個(gè)時(shí)候出來的效果就是這樣的了:
10x10 填 50 周期性效果圖
拋棄狀態(tài)還原至此為止 DFS 的代碼基本上完成了。不過目前來說,當(dāng)然這個(gè)算法的一個(gè)缺陷就是,當(dāng)需要面積與總面積比例比較大的時(shí)候,有可能陷入搜索的死循環(huán)(或者說效率特別低),因?yàn)橐粩鄰?fù)盤。
所以我們可以做點(diǎn)改造——由于我們不是真的為了搜索到某個(gè)狀態(tài),而只是為了填充我們的小點(diǎn)點(diǎn),那么 DFS 中比較經(jīng)典的「狀態(tài)還原」就不需要了,也就是說:
this.count--; this.mat[x][y] = ".";
這兩行代碼可以刪掉了,用刪掉上面兩行代碼的代碼跑一下,我用 50x50 填充 800 格子的效果:
繼續(xù)之前的 50x50 填充 50:
上面 DFS 的方法,由于每次都要走完一條路,雖然會(huì)轉(zhuǎn)彎導(dǎo)致黏連,但在填充區(qū)域很小的情況下,很容易生成“瘦瘦的區(qū)域”。
這里再給出另一個(gè)方法,一個(gè) for 搞定的,思路如下:
先隨機(jī)一個(gè)起始點(diǎn),并將該點(diǎn)加入邊界池;
循環(huán) N - 1 次(N 為所需要填充的面積):
從邊界池中隨機(jī)取出一個(gè)邊界;
算出與其接壤的四個(gè)點(diǎn),取出還未被填充的點(diǎn);
在取出的點(diǎn)中隨機(jī)一個(gè)將其填充;
填充后計(jì)算改點(diǎn)接壤的四個(gè)點(diǎn)是否有全都是已經(jīng)填充了的,若不是,則將該坐標(biāo)加入邊界池;
拿著剛才計(jì)算的接壤的四個(gè)點(diǎn),分別判斷其是否周邊四個(gè)點(diǎn)都已被填充,若是且該點(diǎn)在邊界池中,則從邊界池拿走;
回到第二大步繼續(xù)循環(huán);
返回填充好的結(jié)果。
給出代碼 Demo:
function random(max) { return Math.round(Math.random() * max); } class Filler2 { constructor(length, needArea) { this.length = length; this.needArea = needArea; } _getContiguous(frontier) { return Filler2.DIRS.map(dir => ({ x: frontier.x + dir[0], y: frontier.y + dir[1] })); } fill() { const mat = []; for (let i = 0; i < this.length; i++) { let row = []; for (let j = 0; j < this.length; j++) row.push("."); mat.push(row); } const start = { x: random(this.length - 1), y: random(this.length - 1) }; mat[start.x][start.y] = "x"; let frontierCount = 1; const frontiers = { [`${start.x}:${start.y}`]: true }; for (let i = 1; i < this.needArea; i++) { // 取出一個(gè)邊界 const randIdx = random(frontierCount - 1); const frontier = Object.keys(frontiers)[randIdx].split(":").map(n => parseInt(n)); // _getContiguous 算出接壤坐標(biāo),filter 去除無用坐標(biāo) const newCoors = this._getContiguous({ x: frontier[0], y: frontier[1] }).filter(coor => { if (coor.x < 0 || coor.y < 0 || coor.x >= this.length || coor.y >= this.length) return false; if (mat[coor.x][coor.y] === "x") return false; return true; }); // 隨機(jī)取一個(gè)坐標(biāo) const newCoor = newCoors[random(0, newCoors.length - 1)]; // 填充進(jìn)去 mat[newCoor.x][newCoor.y] = "x"; // 獲取接壤坐標(biāo) const contiguousOfNewCoor = this._getContiguous(newCoor).filter(coor => { if (coor.x < 0 || coor.y < 0 || coor.x >= this.length || coor.y >= this.length) return false; return true; }); // 若有一個(gè)接壤點(diǎn)為空,就認(rèn)為當(dāng)前坐標(biāo)是邊界,若是邊界則把當(dāng)前坐標(biāo)加入對(duì)象 if (contiguousOfNewCoor.reduce((ret, coor) => { if (mat[coor.x][coor.y] === "x") return ret; return true; }, false)) { frontiers[`${newCoor.x}:${newCoor.y}`] = true; frontierCount++; } // 再檢查接壤的坐標(biāo)是否繼續(xù)為邊界 for (let i = 0; i < contiguousOfNewCoor.length; i++) { const cur = contiguousOfNewCoor[i]; const isFrontier = this._getContiguous(cur).filter(coor => { if (coor.x < 0 || coor.y < 0 || coor.x >= this.length || coor.y >= this.length) return false; return true; }).reduce((ret, coor) => { if (mat[coor.x][coor.y] === "x") return ret; return true; }, false); // 若不是邊界的話,只管刪除 if (!isFrontier && frontiers[`${cur.x}:${cur.y}`]) { delete frontiers[`${cur.x}:${cur.y}`]; frontierCount--; } } } // 一圈下來,就出結(jié)果了 return mat; } } Filler2.DIRS = [ [ 0, 1 ], [ 0, -1 ], [ 1, 0 ], [ -1, 0 ] ];
注意:上面的代碼是我一溜煙寫出來的,所以并沒有后續(xù)優(yōu)化代碼簡潔度,其實(shí)很多地方的代碼可以抽象并復(fù)用的,懶得改了,能看就好了。用的時(shí)候就跟之前 DFS 代碼一樣 new 一個(gè) Filler2 出來并 fill 就好了。效果依然可以去 JSFiddle 看。
或者也可以直接看效果圖:
50x50 填充 800 胖胖的區(qū)域
50x50 填充 50 胖胖的區(qū)域
顯而易見,跟之前 DFS 生成出來的奇形怪狀相比,這種算法生成的連通區(qū)域更像是一塊 Mainland,而前者則更像是一個(gè)洼地沼澤或者叢林。
結(jié)合一下?前面兩種算法,一個(gè)是生成瘦瘦的稀奇古怪的面積,一個(gè)是生成胖胖的區(qū)域。有沒有辦法說在生成胖胖的區(qū)域的情況下允許一定的稀奇古怪的形狀呢?
其實(shí)將兩種算法結(jié)合一下就好了。結(jié)合的做法有很多,這里舉一個(gè)例子,大家可以自己再去想一些出來。
首先將需要的區(qū)域?qū)Π敕郑磁浔?1 : 1),例如如果需要 800,就分為 400 跟 400。(為了長得好看,其實(shí)這個(gè)比例可以自行調(diào)配)
將前一半的區(qū)域用 for 生成胖胖的區(qū)域;
將剩下的區(qū)域隨機(jī)幾次,每次隨機(jī)一個(gè)剩下所需要的面積以內(nèi)的數(shù),將這個(gè)數(shù)字作為 DFS 所需要生成的面積量,并從邊界數(shù)組中隨機(jī)取一個(gè)邊界坐標(biāo)并計(jì)算其合法接壤坐標(biāo)開始進(jìn)行 DFS 得到結(jié)果;
循環(huán)第 3 步知道所需區(qū)域面積符合要求為止。
注意:為了保證每次 DFS 一開始的時(shí)候都能取到最新的邊界坐標(biāo),在 DFS 流程中的時(shí)候每標(biāo)一個(gè)區(qū)域填充也必須走一遍邊界坐標(biāo)更新的邏輯。
具體代碼就不放文章里面解析了,大家也可以到 JSFiddle 中去觀看。
或者也可以直接看效果圖:
50x50 填充 800 混合區(qū)域(配比 3 : 1)
50x50 填充 50 胖胖的區(qū)域(配比 4 : 1)
還能更喪心病狂嗎?結(jié)合了兩種算法,我們得到了一個(gè)我認(rèn)為可能會(huì)更好看一點(diǎn)的區(qū)域。
此外,我們還能繼續(xù)「喪心病狂」一點(diǎn),例如兩種方式交替出現(xiàn),流程如下:
指定特定方法和面積,奇數(shù)次用 for,偶數(shù)次用 DFS;
如果是 for 則隨機(jī)一個(gè) Math.min(剩余面積, 總面積 / 4) 的數(shù)字;
如果是 DFS 則隨機(jī)一個(gè) Math.min(剩余面積, 總面積 / 10) 的數(shù)字;
從邊界數(shù)組中取一個(gè)坐標(biāo),并從合法接壤坐標(biāo)中取一個(gè)坐標(biāo)出來;
以第 2 步取出的坐標(biāo)為起點(diǎn),使用第 1 步指定的方法生成第 1 步指定的面積的單連通區(qū)域;
如果生成面積仍小于指定面積,則回到第 1 步繼續(xù)循環(huán),否則返回當(dāng)前結(jié)果。
依舊是給出 JSFiddle 的預(yù)覽。
或者也可以直接看效果圖:
50x50 填充 800 喪病區(qū)域
50x50 填充 800 喪病區(qū)域
注意:這里只給出思路,具體配比和詳細(xì)流程大家可以繼續(xù)優(yōu)化。幾張效果對(duì)比圖
最后,這里給出幾張 10x10 填 50 的效果圖放一起對(duì)比一下。
DFS | 周期性 DFS | 非還原 DFS | 非還原周期性 DFS | 胖胖的 | 結(jié)合 | 更喪病 |
以及,幾張 50x50 填充 800 面積的效果圖對(duì)比。
DFS | 周期性 DFS | 非還原 DFS | 非還原周期性 DFS | 胖胖的 | 結(jié)合 | 更喪病 |
之所以多出一節(jié)來,是因?yàn)槲以趯懟卮鹨约斑@篇文章的時(shí)候腦抽了一下,迷迷糊糊想成了連通區(qū)域,感謝評(píng)論區(qū)童鞋的提醒。實(shí)際上單連通區(qū)域要稍微復(fù)雜一些。
在拓?fù)鋵W(xué)中,單連通是拓?fù)淇臻g的一種性質(zhì)。直觀地說,單連通空間中所有閉曲線都能連續(xù)地搜索至一點(diǎn)。此性質(zhì)可以由空間的基本群刻畫。這個(gè)空間不是單連通的,它有三個(gè)洞 ——單連通@Wikipedia
對(duì)于非周期性的區(qū)域來說,生成一個(gè)單連通區(qū)域只要在上面的方法里面加點(diǎn)料就可以了。即在一個(gè)位置填充的時(shí)候,判斷一下將它填充進(jìn)去之后是否會(huì)出現(xiàn)所謂的「洞」。而這一點(diǎn)在非周期性區(qū)域中,由于在填充當(dāng)前坐標(biāo)前,已存在的區(qū)域已經(jīng)是一個(gè)單連通區(qū)域,所以枚舉一下幾種情況即可排除非單連通區(qū)域的情況:
新加的坐標(biāo)其上下都有填充,但其左右為空;或者左右都有填充,但其上下為空;
新加的坐標(biāo)只有一面相鄰有填充,但該面對(duì)面的邊所對(duì)應(yīng)的兩個(gè)角對(duì)過去至少有一個(gè)角與其它坐標(biāo)共享頂點(diǎn);
新加的坐標(biāo)同一個(gè)頂點(diǎn)的兩條邊有接壤,且其對(duì)角頂點(diǎn)對(duì)過去的坐標(biāo)與其共享頂點(diǎn)。
而對(duì)于周期性的區(qū)域來說,暫時(shí)我還沒想到很好的辦法。
對(duì)于情況一而言,如果處于對(duì)面的兩接壤坐標(biāo)都有填充,且再多一個(gè)接壤面的話,原小區(qū)域內(nèi)只有可能是「匚」型,那么填充進(jìn)去只會(huì)形成一個(gè) 2x3 的實(shí)心區(qū)域,而如果只有處于對(duì)面的兩個(gè)接壤坐標(biāo)有填充的話,說明原小區(qū)域有兩個(gè)面對(duì)面隔空的區(qū)域,它們形成單連通區(qū)域的大前提就是從其它地方繞出去將它們連起來,若這個(gè)時(shí)候?qū)⑺鼈冮]合的話,勢(shì)必會(huì)形成一個(gè)空洞,如下圖所示:
對(duì)于情況二而言,如果只有一面有填充,但是對(duì)面的頂點(diǎn)有共享的話,可以類比為情況一,舉例如下:
對(duì)于情況三而言,其實(shí)就是情況二加一條邊有填充,如果在情況二的情況下,在上圖“原”的區(qū)域下方的空若已有填充,那么在“新”的位置填充進(jìn)去,就形不成空洞了。畢竟如果“空”的位置已有填充的話,若先前狀態(tài)生成沒有洞的連通區(qū)域,則“空”下方也必定不是一個(gè)空洞的區(qū)域。
在解析完三種情況后,算法就明朗起來——在上面的 DFS 算法每次執(zhí)行填充操作的時(shí)候,都判斷一下當(dāng)前填充是否符合剛才列舉的三種情況,若符合,則不填充該點(diǎn)。
所以只需對(duì) DFS 的那個(gè)代碼做一下修改就好了,首先把狀態(tài)還原兩行代碼刪掉,然后在之前
if (newX < 0 || newX >= this.length || newY < 0 || newY >= this.length) continue;
這句代碼之下加一個(gè)條件判斷就好了:
if(this.willBreak(newX, newY)) { continue; }
剩下的就是去實(shí)現(xiàn) this.willBreak() 函數(shù):
class Filler { ... willBreak(x, y) { // 九宮格除自己以外的其它格狀態(tài) let u = false, d = false, l = false, r = false; let lu = false, ld = false, ru = false, rd = false; if(x - 1 >= 0 && this.map[x - 1][y] === "x") u = true; if(x + 1 < this.length && this.map[x + 1][y] === "x") d = true; if(y - 1 >= 0 && this.map[x][y - 1] === "x") l = true; if(y + 1 < this.length && this.map[x][y + 1] === "x") r = true; if(x - 1 >= 0 && y - 1 >= 0 && this.map[x - 1][y - 1] === "x") lu = true; if(x - 1 >= 0 && y + 1 < this.length && this.map[x - 1][y + 1] === "x") ru = true; if(x + 1 < this.length && y - 1 >= 0 && this.map[x + 1][y - 1] === "x") ld = true; if(x + 1 < this.length && y + 1 < this.length && this.map[x + 1][y + 1] === "x") rd = true; // 情況 1 if((l & r) ^ (u & d)) return true; // 情況 2 if(l + r + u + d === 1) { if(l && (ru || rd)) return true; if(r && (lu || ld)) return true; if(u && (ld || rd)) return true; if(d && (lu || ru)) return true; } // 情況 3 if(l + r + u + d === 2) { // 情況 1 已經(jīng)被 return 了,所以相加為 2 的肯定是共享頂點(diǎn) if(l && u && rd) return true; if(l && d && ru) return true; if(r && u && ld) return true; if(r && d && lu) return true; } return false; } }
進(jìn) JSFiddle 看完整代碼。
然后是 50x50 填充 800 的效果:
以及 10x10 填充 50:
注意:左下角的洞看起來是洞,實(shí)際上是處于邊界了,而填充區(qū)域無法與邊界合成閉合區(qū)域,實(shí)際上將地圖往外想想空一格就可以知道它并不是一個(gè)洞了。當(dāng)然如果讀者執(zhí)意不允許這種情況發(fā)生,那么只需要在 willBreak() 函數(shù)判斷的時(shí)候做點(diǎn)手腳就可以了,至于怎么做手腳大家自己想吧。
這種情況生成的地圖比較像迷宮,哪怕是針對(duì)「胖胖的區(qū)域」做這個(gè)改進(jìn),JSFiddle 出來的也是下面的效果:
所以呢,繼續(xù)優(yōu)化——我們知道有三種情況是會(huì)生成非單連通區(qū)域的,所以當(dāng)我們探測(cè)到這種情況的時(shí)候,去 BFS 它內(nèi)外區(qū)域,看看究竟是哪個(gè)區(qū)域被封閉出一個(gè)空洞來,探測(cè)出來之后再看看我們目前還需要填充的區(qū)域面積跟這個(gè)空洞的面積是否夠用,若夠用則將空洞補(bǔ)起來,不夠用則當(dāng)前一步重新來過——即再隨機(jī)一個(gè)坐標(biāo)看看行不行。
思想說出來了,具體的實(shí)現(xiàn)還是看看我寫在 JSFiddle 里面的代碼吧。
50x50 填充 800 的效果如下:
這么一來,我們很容易能跟 DFS 的算法結(jié)合起來,即之前說過的更喪病的算法。結(jié)合方法很簡單,分別把改進(jìn)過的 DFS 和胖胖區(qū)域的算法一起融合進(jìn)之前喪病算法的代碼中就好了。老樣子我還是把代碼更新到了 JSFiddle 里面。大家看看 50x50 填充 800 的效果吧:
最后,由于一開始文章寫的概念性錯(cuò)誤給大家?guī)淼牟蛔儽硎痉浅1?,好在最后我還是補(bǔ)全了一下文章。
小結(jié)本文主要還是講了,如何隨機(jī)生成一個(gè)指定面積的單連通區(qū)域。從一開始拍腦袋就能想到 DFS 開始,延伸到胖胖的區(qū)域,然后從個(gè)人認(rèn)為「圖不好看」開始,想辦法如何結(jié)合一下兩種算法使其變得更自然。
針對(duì)同一件事的算法們并非一成不變或者不可結(jié)合的。不是說該 DFS 就只能 DFS,該 for 就只能 for,稍微結(jié)合一下也許食用效果更佳哦。
哦對(duì)了,在這之前還有一個(gè)例子就是我在三年多前寫的主題色提取的文章《圖片主題色提取算法小結(jié)》,其中就講到我最后的方法就是結(jié)合了八叉樹算法和最小差值法,使其在提取比較貼近的顏色同時(shí)又能夠規(guī)范化提取出來的顏色。
總之就是多想想,與諸君共勉。
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/93953.html
摘要:數(shù)據(jù)結(jié)構(gòu)程序數(shù)據(jù)結(jié)構(gòu)算法數(shù)據(jù)結(jié)構(gòu)基本概念數(shù)據(jù)的邏輯結(jié)構(gòu)反映數(shù)據(jù)元素之間的關(guān)系的數(shù)據(jù)元素集合的表示。這兩部分信息組成數(shù)據(jù)元素的存儲(chǔ)映象,稱為結(jié)點(diǎn)。 本文涉及更多的是概念,代碼部分請(qǐng)參考之前寫過的 2 篇博客 基于 Javascript 的排序算法 基于 javascript 的基本數(shù)據(jù)結(jié)構(gòu)和查找算法 本文主要是基礎(chǔ)的數(shù)據(jù)結(jié)構(gòu)和算法概念,可能部分地方會(huì)涉及更高級(jí)的算法和算法,具體內(nèi)容以...
摘要:找到給定的二維數(shù)組中最大的島嶼面積。思路給定一個(gè)由和組成的二維數(shù)組,其中代表島嶼土地,要求找出二維數(shù)組中最大的島嶼面積,沒有則返回。樣例如樣例所示,二維數(shù)組的最大島嶼面積為,下面來講解深度優(yōu)先搜索的做法。 ...
摘要:該研究成果由韓國團(tuán)隊(duì)發(fā)表于論文地址訓(xùn)練數(shù)據(jù)恰當(dāng)?shù)挠?xùn)練數(shù)據(jù)有助于提高網(wǎng)絡(luò)訓(xùn)練性能。在將損失函數(shù)應(yīng)用于輸入圖像之前,用輸入圖像替換了掩模外部的圖像的剩余部分??傮w損失函數(shù)如下其中,發(fā)生器用進(jìn)行訓(xùn)練,鑒別器用進(jìn)行訓(xùn)練。 為一個(gè)設(shè)計(jì)師,是否整天因?yàn)榉爆嵖菰锏男迗D工作不勝其煩?現(xiàn)在,一款基于GAN的AI修圖大師可以將你從這類工作中解放出來。修輪廓、改表情、生發(fā)、加耳環(huán)、去眼鏡、補(bǔ)殘圖,你能想到的它都能...
摘要:阿里云服務(wù)器平臺(tái)在云端提供統(tǒng)一硬件平臺(tái)與中間件,可大大降低加速器的開發(fā)與部署成本。我們相信,通過即開即用的硬件資源統(tǒng)一的軟硬件邏輯開發(fā)接口和市場(chǎng),阿里云能夠真正兌現(xiàn)計(jì)算資源平民化的承諾。 阿里云ECS的異構(gòu)計(jì)算團(tuán)隊(duì)和高性能計(jì)算團(tuán)隊(duì)一直致力于將計(jì)算資源平民化;高性能計(jì)算團(tuán)隊(duì)在做的E-HPC就是要讓所有云上用戶都能夠瞬間擁有一個(gè)小型的超算集群,使得超算不再僅僅是一些超算中心和高校的特權(quán);而...
摘要:折交叉驗(yàn)證集,每折包含約張訓(xùn)練圖像和張測(cè)試圖像,正樣本邊界負(fù)樣本其他負(fù)樣本,訓(xùn)練集中共圖像塊。浸潤性導(dǎo)管癌是乳腺癌中最長出現(xiàn)的亞種。 Deep learning for digital pathology image analysis: A comprehensive tutorial with selected use cases Deep learning for digital ...
閱讀 2789·2021-11-02 14:42
閱讀 3172·2021-10-08 10:04
閱讀 1193·2019-08-30 15:55
閱讀 1036·2019-08-30 15:54
閱讀 2327·2019-08-30 15:43
閱讀 1688·2019-08-29 15:18
閱讀 871·2019-08-29 11:11
閱讀 2370·2019-08-26 13:52