两种连通区域
四连通区域:从区域内一点出发,可通过上、下、左、右四个方向的移动组合,在不越出区域的前提下,能到达区域内的任意像素
八连通区域:从区域内每一像素出发,可通过八个方向,即上、下、左、右、左上、右上、左下、右下移动的组合,在不越出区域的前提下,能到达区域内的任意像素。
基本原理
从多边形区域内部的某一像素点(称为种子)开始,由此出发找到区域内的其它所有像素。
算法的执行过程:
从(x,y)开始,先检测该点的颜色,若它与边界色和填充色均不相同,则用填充色填充该点。然后检测相邻位置,以确定它们是否是边界色和填充色,若不是,则填充该相邻点。直到检测完区域边界范围内的所有像素为止。
void ZhongZiTC4 (int seedx, int seedy, int fcolor, int bcolor)
{
int current = getpixel (seedx, seedy);
if ((current != bcolor) && (current != fcolor))
{ putpixel (seedx, seedy, fcolor);
ZhongZiTC4 (seedx+1, seedy, fcolor, bcolor); //右
ZhongZiTC4 (seedx–1, seedy, fcolor, bcolor); //左
ZhongZiTC4 (seedx, seedy+1, fcolor, bcolor); //上
ZhongZiTC4 (seedx, seedy–1, fcolor, bcolor); //下
}
}
给定一个由 ‘1’(陆地)和 ‘0’(水)组成的的二维网格,计算岛屿的数量。一个岛被水包围,并且它是通过水平方向或垂直方向上相邻的陆地连接而成的。你可以假设网格的四个边均被水包围。
示例 1:
输入:
11110
11010
11000
00000
输出: 1
示例 2:
输入:
11000
11000
00100
00011
输出: 3
题解:
1、DFS
注意开始先判断直接返回的情况特殊输入情况;
遍历所有点,遇到一个‘1’ 就count++,然后开始深搜,标记与该点连接的所有点为‘0’。
class Solution {
int count;
char[][] grid;
int m;//行
int n;//列
public int numIslands(char[][] gr) {
if(gr==null) return 0;
m=gr.length;
if(m==0)return 0;
n=gr[0].length;
if(n==0) return 0;
count=0;
grid=gr;
for(int i=0;ifor(int j=0;jif(grid[i][j]=='0') continue;
count++;
myDFS(i,j);
}
}
return count;
}
private void myDFS(int i, int j) {
if(i<0||i>=m||j<0||j>=n){//递归退出
return;
}
if(grid[i][j]=='1'){
grid[i][j]='0';
myDFS(i-1,j);
myDFS(i+1,j);
myDFS(i,j-1);
myDFS(i,j+1);
}
}
}
2、BFS
耗时比DFS长
如果按照以往,先放入队列,出队时再访问,会超时,改为入队前先访问,再入队,通过。可能是因为,出队时再访问,会调用封装对象的 coor.x和coor.y 属性,增加耗时。
class Solution {
int count;
char[][] grid;
int m;// 行
int n;// 列
int[][] fx = new int[][] { { 1, 0 }, { -1, 0 }, { 0, 1 }, { 0, -1 } }; // 表示移动的位置,下,上、右、左
public int numIslands(char[][] gr) {
if (gr == null || gr.length == 0 || gr[0].length == 0)
return 0;
m = gr.length;
n = gr[0].length;
count = 0;
grid = gr;
for (int i = 0; i < m; i++) {
for (int j = 0; j < n; j++) {
if (grid[i][j] == '0')
continue;
count++;
markByBFS(i, j);// 用宽度优先搜索标记与其相连的点
}
}
return count;
}
private void markByBFS(int x, int y) {
Queue queue = new LinkedList<>();// 宽度优先遍历,需要用到队列,将每个点封装成一个Coordinate类,便于保存横轴坐标值
grid[x][y] = '0'; //先访问在入队
queue.add(new Coordinate(x, y));//先入队
while (!queue.isEmpty()) {
Coordinate coor = queue.poll();
for (int i = 0; i < 4; i++) {
int xx = coor.x + fx[i][0];
int yy = coor.y + fx[i][1];
if (xx < m && xx >= 0 && yy < n && yy >= 0) {//没有越界
if(grid[xx][yy]=='1'){//没被访问过
grid[xx][yy] = '0'; //访问之
queue.add(new Coordinate(xx, yy));
}
}
}
}
}
}
class Coordinate {
int x, y;
public Coordinate(int x, int y) {
this.x = x;
this.y = y;
}
}
给定一个二维的矩阵,包含 ‘X’ 和 ‘O’(字母 O)。
找到所有被 ‘X’ 围绕的区域,并将这些区域里所有的 ‘O’ 用 ‘X’ 填充。
示例:
X X X X
X O O X
X X O X
X O X X
运行你的函数后,矩阵变为:
X X X X
X X X X
X X X X
X O X X
解释:
被围绕的区间不会存在于边界上,换句话说,任何边界上的 ‘O’ 都不会被填充为 ‘X’。 任何不在边界上,或不与边界上的 ‘O’ 相连的 ‘O’ 最终都会被填充为 ‘X’。如果两个元素在水平或垂直方向相邻,则称它们是“相连”的。
题解:
本来应该从非边界上的 O
开始遍历,但需要考虑与边界字符的连接问题,较为复杂。
转换思维,遍历四周边界,并开始深搜,将与边界上O
相连的O
,都标记为另一种标记,如 *
。
最后遍历所有位置,将*
变为O
,其他都变为 X
。
class Solution {
char[][] grid;
int m;// 行
int n;// 列
public void solve(char[][] board) {
if (board == null || board.length == 0 || board[0].length == 0)
return ;
m = board.length;
n = board[0].length;
grid = board;
for (int i = 0; i < n; i++) {
if (grid[0][i] == 'O'){//遍历第一行
markByDFS(0, i);
}
if(grid[m-1][i]=='O'){//遍历最后一行
markByDFS(m-1,i);
}
}
for(int i=1;i1;i++){
if(grid[i][0] == 'O'){//遍历第一列
markByDFS(i, 0);
}
if(grid[i][n-1] == 'O'){//遍历最后一列
markByDFS(i, n-1);
}
}
for(int i=0;ifor(int j=0;jif(grid[i][j]=='*'){
grid[i][j]='O';
}else{
grid[i][j]='X';
}
}
}
}
private void markByDFS(int x, int y) {
if(x>=m||x<0||y>=n||y<0){
return;
}
if(grid[x][y]=='O'){
grid[x][y]='*';
markByDFS(x-1,y);
markByDFS(x+1,y);
markByDFS(x,y-1);
markByDFS(x,y+1);
}
}
}