实验4 搜索
学号:6130116217
专业班级:计算机科学与技术165班
课程名称:算法分析与设计实验
一、全球变暖
你有一张某海域NxN像素的照片,".“表示海洋、”#"表示陆地,如下所示:
请你计算:依照科学家的预测,照片中有多少岛屿会被完全淹没。
1、分析
- 此题为自选的典型搜索问题,可以采用dfs或者bfs,这里用dfs。
- 思路:n表示像素宽度,char[][] map存储淹没前岛屿,char[][] map1存储淹没后岛屿。boolean[][] vis记录某个坐标是否已访问。
- 从每个点开始dfs,记录开始岛屿数目start。处理淹没,再次重复上述dfs搜索过程记录淹没后岛屿数量end。两者差值即为结果。
- 注意:岛屿淹没后可能变多,结果可以是负数。
2、代码
package JavaA.s9;
import java.io.*;
import java.util.*;
public class 全球变暖 {
static Scanner in = new Scanner(System.in);
static int n;
static char[][] map = new char[1010][1010];
static char[][] map1 = new char[1010][1010];
static String temp;
static int start, end;
static boolean[][] vis = new boolean[1010][1010];
public static void main(String[] args) throws ParseException, FileNotFoundException {
Scanner in = new Scanner(new File("src/JavaA/s9/8.txt"));
n = in.nextInt();
System.out.println("N= " + n);
for (int i = 1; i <= n; ++i) {// 读入岛屿
temp = in.next();
for (int j = 1; j <= n; ++j) {
map[i][j] = map1[i][j] = temp.charAt(j - 1);
}
}
// 计算初始岛屿数目start
for (int i = 1; i <= n; ++i) {
for (int j = 1; j <= n; ++j) {
if (!vis[i][j] && map[i][j] == '#') {
dfs(i, j);
start++;
}
}
}
// 处理淹没,只要四个相邻位置有'.'就会被淹没
for (int i = 1; i <= n; ++i) {
for (int j = 1; j <= n; ++j) {
if (map1[i][j] == '#' && (map1[i - 1][j] == '.' || map1[i + 1][j] == '.' || map1[i][j - 1] == '.'
|| map1[i][j + 1] == '.'))
map[i][j] = '.';
}
}
// 计算淹没后岛屿数量end
for (int i = 1; i <= n; ++i)
Arrays.fill(vis[i], false);
for (int i = 1; i <= n; ++i) {
for (int j = 1; j <= n; ++j) {
if (!vis[i][j] && map[i][j] == '#') {
dfs(i, j);
end++;
}
}
}
// 输出结果
System.out.println("淹没前数量:" + start + "\t\t淹没后数量:" + end);
for (int i = 1; i <= n; ++i) {
for (int j = 1; j <= n; ++j) {
System.out.print(map1[i][j]);
}
System.out.print("\t\t\t");
for (int j = 1; j <= n; ++j) {
System.out.print(map[i][j]);
}
System.out.println();
}
// 淹没
System.out.println("淹没的数量: " + (start - end));
}
static void dfs(int x, int y) {
if (x < 1 || x > n || y < 1 || y > n)// 越界
return;
if (vis[x][y] || map[x][y] != '#')// 已访问过或者是陆地,退出
return;
vis[x][y] = true;
dfs(x - 1, y);// 向四个方向延伸
dfs(x + 1, y);
dfs(x, y - 1);
dfs(x, y + 1);// 每个点不能两次访问,无需回溯
}
}
3、测试
- 下面为3组数据运行结果截图
测试1
测试2(结果为负数)
测试3(结果为负数)
二、高斯八皇后问题
1、分析
- 此题为课本中9.1.1节的高斯八皇后问题。
- 课本中使用的是枚举法,我用带回溯的dfs(深度优先搜索)重写了一遍。
- 思路是:用一个数组int b[]记录每行皇后所在列数,boolean vis[]记录某一列是否已存在皇后。选定某一个皇后前都检查是否在同一列、同一斜线是否已存在皇后。这样,就不会走错误的路,保证每一个选定的皇后都是合法的。
- 经过测试,dfs方法比枚举法速度快得多。
2、修改
- 下面列出课本中的c语言代码改写成java,和dfs的代码。
(1).枚举
package 算法设计;
import java.io.*;
import java.util.*;
public class 高斯八皇后 {
static Scanner in = new Scanner(System.in);
static int a, i, j, k, s, t, x, y;
static int[] f = new int[10];
static int[] g = new int[10];
public static void main(String[] args) throws FileNotFoundException {
long time = System.currentTimeMillis();
Scanner in = new Scanner(new File("src/算法设计/5.txt"));
System.out.println("高斯八皇后问题的解为: ");
// 步长为9枚举8位数
for (a = 12345678; a <= 87654321; a = a + 9) {
y = a;
for (i = 1; i <= 8; i++)
f[i] = 0;
for (k = 1; k <= 8; k++) {
x = y % 10;
f[x]++;
g[k] = x;
y = y / 10; // 分离a各个数字并用f,g数组统计
}
for (t = 0, i = 1; i <= 8; i++)
if (f[i] != 1) {
t = 1; // 数字1--8出现不为1次,返回
break;
}
if (t == 1)
continue;
for (k = 1; k <= 7; k++)
for (j = k + 1; j <= 8; j++)
// 同处在45度斜线上,返回
if (Math.abs(g[j] - g[k]) == j - k) {
t = 1;
k = 7;
break;
}
if (t == 1)
continue;
s++; // 输出8皇后问题的解
System.out.print(a + " ");
if (s % 6 == 0)
System.out.println();
}
System.out.println();
System.out.println("高斯八皇后问题解的个数: " + s);
System.out.println((System.currentTimeMillis() - time) + "ms");
}
}
(2).dfs
package 算法设计;
import java.io.*;
import java.util.*;
public class 高斯八皇后 {
static Scanner in = new Scanner(System.in);
static boolean[] vis = new boolean[10];// 访问标记
static int[] b = new int[10];// 记录某一列是否已选
static int count;// 总数
public static void main(String[] args) throws FileNotFoundException {
long time = System.currentTimeMillis();
Scanner in = new Scanner(new File("src/算法设计/5.txt"));
System.out.println("高斯八皇后问题的解为: ");
dfs(0);
System.out.println();
System.out.println("高斯八皇后问题解的个数: "+count);
System.out.println((System.currentTimeMillis() - time) + "ms");
}
static void dfs(int n) {
if (n == 8) {
count++;
for (int i = 1; i <= 8; ++i)// 打印一个解
System.out.print(b[i]);
System.out.print(count%6==0?"\n":" ");
return;
}
for (int i = 1; i <= 8; ++i) {
if (!vis[i]) {
boolean flag = false;// 检测是否有统一斜线的皇后
for (int j = 1; j <= n; ++j) {
if (n + 1 - j == Math.abs(i - b[j]))
flag = true;
}
if (!flag) {// 可以选该位置,进行dfs并回溯
vis[i] = true;
b[n + 1] = i;
dfs(n + 1);
vis[i] = false;
}
}
}
}
}
3、测试
- 下面为两种方法运行结果截图
(1)、枚举
(2)、dfs
4、比较
- 下面是两种方法分别运行5次所用时间记录。
测试次数 | 枚举(ms) | dfs(ms) |
---|---|---|
1 | 295 | 23 |
2 | 327 | 15 |
3 | 338 | 14 |
4 | 300 | 28 |
5 | 297 | 15 |
- 不难看出:课本上的枚举法平均用时约为300ms,而我写的dfs法平均用时约20ms。因此修改后的带回溯dfs方法效率约为课本上枚举法的15倍,这是一个非常大的改进。