四连通图的检测
题目
给定一个方阵,定义连通性:上下左右相邻,并且值相同。可以想象一张地图,不同的区域被染成了不同的颜色。现在我们需要判断图中任意两点是否在同一个连通区间中。
输入:
整数N(N<50),代表矩阵的行列数
输入N行,每行N个字符,代表矩阵中的元素
输入一个整数M(M<1000)表示询问次数
输入M行每行代表一个询问,格式为4个整数x1 y1 x2 y2,代表需要检测的点
输出:
true或false
样例输入:
10
0 0 1 0 0 0 0 0 0 0
0 0 1 1 1 0 0 0 0 0
0 0 0 0 1 1 1 1 1 0
0 0 0 1 1 0 0 0 1 0
1 1 1 1 0 1 0 0 1 0
0 0 0 0 0 1 0 0 1 0
0 0 0 0 0 1 0 0 1 1
0 1 1 1 1 1 1 0 0 0
0 0 0 0 0 1 0 0 0 0
0 0 0 0 0 0 0 0 0 0
3
0 0 9 9
0 2 6 8
4 4 4 6
题意理解
根据上图的输入,我们将矩阵进行划分,划分的每个区域中就是一个连通块。
那题目所说的判断任意两点是否在同一连通区间中又是什么意思呢?
在上面这个图中两个红色方框就处于相同连通区间中,而蓝色方框就处于两个不同的连通区间中。我想此时大家应该理解了题目中的意思,那我们就来探究一下解题思路。
解题思路
这个题目可以使用深度优先搜索来解决,具体思路就是,每次判断相邻的的点是否是同一连通区,如果是就判断这个点是否是我们的目标点,如果不是就继续搜索下去,如果当前连通区全部搜索完后仍没有到达目标点就证明这两个点不再通过一个连通区中。
为了简洁,我们一下图为基础,来探究深度搜索的过程,我们以(0,0)处作为起点:
第一步:我们向右搜索,在这个过程中先判断相邻点是否可达(只有在同一连通区才可达),很明显(0,1)这个点是可达的,现在我们貌似发现了一个问题,我们只需要判断(0,1)和(0,3)是否可达,因为如果(0,1)和(0,3)可达,那么(0,0)和(0,3)也是可达的。
注意:访问过的元素一定要做上标记,具体原因后面再讲
第二步:此时我们到达了(0,1),继续向右搜索,如果发现(0,2)是不可达的,所以我们就不能指望向右的路线了,此时我们向下搜索,发现是可达的:
第三步:我们现在到达了(1,1)这个点,发现向右和向下的点都不可达,那我们此时就只剩下向左和向上两个方向了,但是上面的元素已经标记访问过了,也就是代表不可达,如果不标记,我们向上搜索又回到了第二步的地方,那就形成了死循环,这也是为什么在图的搜索中必须标记图中的每个点是否被访问过的原因。此时我们就只能向左搜索了:
第四步:到达(1,0)这个点后,我们发现四个方向都走不通了,此时返回false。
你可以看出来上面的每一次搜索其实步骤都是一样的,采用右、下、左、上的搜索步骤,所以我们采用递归的方式解决问题。这也应证了一句话:深搜递归,宽搜用队列。
代码实现
public class Hello {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
int N = scanner.nextInt();
int[][] map = new int[N][N];
for (int i = 0; i < N; i++) {
for (int j = 0; j < N; j++) {
map[i][j] = (char) scanner.nextInt();
}
scanner.nextLine();
}
int M = scanner.nextInt();
int[][] points = new int[M][4];
for (int i = 0; i < M; i++) {
for (int j = 0; j < 4; j++) {
points[i][j] = scanner.nextInt();
}
scanner.nextLine();
}
for (int i = 0; i < points.length; i++) {
System.out.println(checkPass(map, new int[N][N], points[i]));
}
}
/**
* @param map 方阵的二维数组
* @param visited 表示方阵对应的点是否被访问
* @param points 需要查询的点
* @return
*/
private static boolean checkPass(int[][] map, int[][] visited, int[] points) {
int y1 = points[0];
int x1 = points[1];
int y2 = points[2];
int x2 = points[3];
boolean top = false;
boolean down = false;
boolean left = false;
boolean right = false;
if (y1 == y2 && x1 == x2) {
return true;
}
//以x1,y1为基础进行深度搜索,看是否能够搜索到x2,y2
//第一个判断的是边界
//第二个判断的是节点是否已经被访问过了
//第三个判断的是矩阵点的值是否相同,如果不相同就证明不是一个区块,就不能向这个方向走
if (y1 - 1 >= 0 && visited[y1 - 1][x1] == 0 && map[y1][x1] == map[y1 - 1][x1]) {
points[0] = y1 - 1;
visited[y1 - 1][x1] = 1;
top = checkPass(map, visited, points);
//回溯,回溯的作用就是为后面的递归创造环境
points[0] = y1;
}
if (y1 + 1 <= map.length - 1 && visited[y1 + 1][x1] == 0 && map[y1][x1] == map[y1 + 1][x1]) {
points[0] = y1 + 1;
visited[y1 + 1][x1] = 1;
down = checkPass(map, visited, points);
points[0] = y1;
}
if (x1 - 1 >= 0 && visited[y1][x1 - 1] == 0 && map[y1][x1] == map[y1][x1 - 1]) {
points[1] = x1 - 1;
visited[y1][x1 - 1] = 1;
left = checkPass(map, visited, points);
points[1] = x1;
}
if (x1 + 1 <= map[0].length - 1 && visited[y1][x1 + 1] == 0 && map[y1][x1] == map[y1][x1 + 1]) {
points[1] = x1 + 1;
visited[y1][x1 + 1] = 1;
left = checkPass(map, visited, points);
points[1] = x1;
}
return top || down || left || right;
}
}