缘起
各位小伙伴们好呀!本人最近看了下《啊哈算法》,写的确实不错。
但稍显遗憾的是,书籍示例代码是c语言,而不是本人常用的Java。
那就弥补遗憾,说干就干,把这本书的示例语言用java写一遍, 顺带附上一些自己的理解!
今天我们来用枚举的思想玩一个炸弹人的游戏!
来不及买纸质书但又想尽快感受算法魅力的童鞋也甭担心,电子版的下载链接已经放到下方了,可尽情下载。
链接:https://pan.baidu.com/s/1imxiElcCorw2F-HJEnB-PA?pwd=jmgs
提取码:jmgs
代码地址
本文代码已开源:
git clone https://gitee.com/guqueyue/my-blog-demo.git
请切换到gitee分支,
然后查看aHaAlgorithm模块下的src/main/java/com/guqueyue/aHaAlgorithm/chapter_3_Enum
即可!
炸弹人
说起炸弹人,大家可能想到的是英雄联盟游戏里面的爆破鬼才 — 吉格斯。
不过这里说的是小霸王游戏机里面的“炸弹人”游戏,一听就有点年代感了。
现在有一个特殊的关卡如下。你只有一枚炸弹,但是这枚炸弹威力超强(杀伤距离超长,可以消灭杀伤范围内所有的敌人)。
请问在哪里放置炸弹才可以消灭最多的敌人呢?
那么,我们要怎么用代码来计算这个问题呢?
首先,我们要将图形化的地图进行模型化处理,我们用 #
来表示墙壁、 G
表示敌人,.
表示空地,
其中炸弹只能放在空地上,爆炸范围为上、下、左、右四个范围,并且不能穿墙。
char[][] arr = {
{'#', '#', '#', '#', '#', '#', '#', '#', '#', '#', '#', '#', '#'},
{'#', 'G', 'G', '.', 'G', 'G', 'G', '#', 'G', 'G', 'G', '.', '#'},
{'#', '#', '#', '.', '#', 'G', '#', 'G', '#', 'G', '#', 'G', '#'},
{'#', '.', '.', '.', '.', '.', '.', '.', '#', '.', '.', 'G', '#'},
{'#', 'G', '#', '.', '#', '#', '#', '.', '#', 'G', '#', 'G', '#'},
{'#', 'G', 'G', '.', 'G', 'G', 'G', '.', '#', '.', 'G', 'G', '#'},
{'#', 'G', '#', '.', '#', 'G', '#', '.', '#', '.', '#', '#', '#'},
{'#', '#', 'G', '.', '.', '.', 'G', '.', '.', '.', '.', '.', '#'},
{'#', 'G', '#', '.', '#', 'G', '#', '#', '#', '.', '#', 'G', '#'},
{'#', '.', '.', '.', 'G', '#', 'G', 'G', 'G', '.', 'G', 'G', '#'},
{'#', 'G', '#', '.', '#', 'G', '#', 'G', '#', '.', '#', 'G', '#'},
{'#', 'G', 'G', '.', 'G', 'G', 'G', '#', 'G', '.', 'G', 'G', '#'},
{'#', '#', '#', '#', '#', '#', '#', '#', '#', '#', '#', '#', '#'}
};
那么,初始化地图之后,我们要怎么利用枚举的思想来解决这个问题呢?
很简单,暴力就好了。我们直接遍历每个点,计算每个点可以消灭敌人的个数就好了。
如果遇到可以消灭敌人更多数的点,就把它的坐标和可消灭敌人数记录下来。
至于如何计算每个点可以消灭敌人的个数,写四个循环分别往四个方向计算就好了。例如,求上方的可消灭敌人数:
int sum = 0, x = i, y = j; // sum: 当前坐标可消灭敌人数,x: 当前横坐标,y: 当前纵坐标
// 向上找
while (arr[x][y] != '#' && x >= 0 && x < w) { // 四周一定有墙,所以 坐标是否越界其实不用判断
if (arr[x][y] == 'G') {
sum++;
}
x--;
}
因为我们这里是向上,所以用x--
,其他方向如下图所示:
完整代码如下:
package com.guqueyue.aHaAlgorithm.chapter_3_Enum;
/**
* @Author: guqueyue
* @Description: 炸弹人
* @Date: 2024/1/17
**/
public class BomberMan {
public static void main(String[] args) {
char[][] arr = {
{'#', '#', '#', '#', '#', '#', '#', '#', '#', '#', '#', '#', '#'},
{'#', 'G', 'G', '.', 'G', 'G', 'G', '#', 'G', 'G', 'G', '.', '#'},
{'#', '#', '#', '.', '#', 'G', '#', 'G', '#', 'G', '#', 'G', '#'},
{'#', '.', '.', '.', '.', '.', '.', '.', '#', '.', '.', 'G', '#'},
{'#', 'G', '#', '.', '#', '#', '#', '.', '#', 'G', '#', 'G', '#'},
{'#', 'G', 'G', '.', 'G', 'G', 'G', '.', '#', '.', 'G', 'G', '#'},
{'#', 'G', '#', '.', '#', 'G', '#', '.', '#', '.', '#', '#', '#'},
{'#', '#', 'G', '.', '.', '.', 'G', '.', '.', '.', '.', '.', '#'},
{'#', 'G', '#', '.', '#', 'G', '#', '#', '#', '.', '#', 'G', '#'},
{'#', '.', '.', '.', 'G', '#', 'G', 'G', 'G', '.', 'G', 'G', '#'},
{'#', 'G', '#', '.', '#', 'G', '#', 'G', '#', '.', '#', 'G', '#'},
{'#', 'G', 'G', '.', 'G', 'G', 'G', '#', 'G', '.', 'G', 'G', '#'},
{'#', '#', '#', '#', '#', '#', '#', '#', '#', '#', '#', '#', '#'}
};
// maxSum: 最大可消灭敌人数,maxX: 最大可消灭敌人数的横坐标,maxY: 最大可消灭敌人数的纵坐标, w: 矩阵宽度,h: 矩阵高度
int maxSum = 0, maxX = -1, maxY = -1, w = arr.length, h = arr[0].length;
for (int i = 0; i < w; i++) {
for (int j = 0; j < h; j++) {
// 空地才可以防止炸弹
if(arr[i][j] != '.') {
continue;
}
int sum = 0, x = i, y = j; // sum: 当前坐标可消灭敌人数,x: 当前横坐标,y: 当前纵坐标
// 向上找
while (arr[x][y] != '#' && x >= 0 && x < w) { // 四周一定有墙,所以 坐标是否越界其实不用判断
if (arr[x][y] == 'G') {
sum++;
}
x--;
}
// 向下找
x = i;
y = j;
while (arr[x][y] != '#' && x >= 0 && x < w) {
if (arr[x][y] == 'G') {
sum++;
}
x++;
}
// 向左找
x = i;
y = j;
while (arr[x][y] != '#' && y >= 0 && y < h) {
if (arr[x][y] == 'G') {
sum++;
}
y--;
}
// 向右找
x = i;
y = j;
while (arr[x][y] != '#' && y >= 0 && y < h) {
if (arr[x][y] == 'G') {
sum++;
}
y++;
}
if (sum > maxSum) {
maxX = i;
maxY = j;
maxSum = sum;
}
}
}
System.out.printf("在(%d, %d)处放置炸弹,杀伤的敌人最多,为%d人\n", maxX, maxY, maxSum);
}
}
运行,得:
问题
上文的代码虽然偏长,但是思路是不是很简单顺畅?
但是正如作者所说,枚举的思想虽然简单粗暴,但是不够智能:
如,我们像作者一样更改地图:
package com.guqueyue.aHaAlgorithm.chapter_3_Enum;
/**
* @Author: guqueyue
* @Description: 炸弹人
* @Date: 2024/1/17
**/
public class BomberMan {
public static void main(String[] args) {
char[][] arr = {
{'#', '#', '#', '#', '#', '#', '#', '#', '#', '#', '#', '#', '#'},
{'#', 'G', 'G', '.', 'G', 'G', 'G', '#', 'G', 'G', 'G', '.', '#'},
{'#', '#', '#', '.', '#', 'G', '#', 'G', '#', 'G', '#', 'G', '#'},
{'#', '.', '.', '.', '.', '.', '.', '.', '#', '.', '.', 'G', '#'},
{'#', 'G', '#', '.', '#', '#', '#', '.', '#', 'G', '#', 'G', '#'},
{'#', 'G', 'G', '.', 'G', 'G', 'G', '.', '#', '.', 'G', 'G', '#'},
{'#', 'G', '#', '.', '#', 'G', '#', '.', '#', '.', '#', '#', '#'},
{'#', '#', 'G', '.', '.', '.', 'G', '.', '.', '.', '.', '.', '#'},
{'#', 'G', '#', '.', '#', 'G', '#', '#', '#', '.', '#', 'G', '#'},
{'#', '.', '.', '.', 'G', '#', 'G', 'G', 'G', '.', 'G', 'G', '#'},
{'#', 'G', '#', '.', '#', 'G', '#', 'G', '#', '.', '#', 'G', '#'},
{'#', 'G', 'G', '.', 'G', 'G', 'G', '#', 'G', '.', 'G', 'G', '#'},
{'#', '#', '#', '#', '#', '#', '#', '#', '#', '#', '#', '#', '#'}
};
arr = new char[][]{
{'#', '#', '#', '#', '#', '#', '#', '#', '#', '#', '#', '#', '#'},
{'#', 'G', 'G', '.', 'G', 'G', 'G', '#', 'G', 'G', 'G', '.', '#'},
{'#', '#', '#', '.', '#', 'G', '#', 'G', '#', 'G', '#', 'G', '#'},
{'#', '.', '.', '.', '.', '.', '.', '.', '#', '.', '.', 'G', '#'},
{'#', 'G', '#', '.', '#', '#', '#', '.', '#', 'G', '#', 'G', '#'},
{'#', 'G', 'G', '.', 'G', 'G', 'G', '.', '#', '.', 'G', 'G', '#'},
{'#', 'G', '#', '.', '#', 'G', '#', '.', '#', '.', '#', '.', '#'},
{'#', '#', 'G', '.', '.', '.', 'G', '.', '.', '.', '.', '.', '#'},
{'#', 'G', '#', '.', '#', 'G', '#', '#', '#', '.', '#', 'G', '#'},
{'#', '.', '.', '.', 'G', '#', 'G', 'G', 'G', '.', 'G', 'G', '#'},
{'#', 'G', '#', '.', '#', 'G', '#', 'G', '#', '.', '#', 'G', '#'},
{'#', 'G', 'G', '.', 'G', 'G', 'G', '#', 'G', '.', 'G', 'G', '#'},
{'#', '#', '#', '#', '#', '#', '#', '#', '#', '#', '#', '#', '#'}
};
// maxSum: 最大可消灭敌人数,maxX: 最大可消灭敌人数的横坐标,maxY: 最大可消灭敌人数的纵坐标, w: 矩阵宽度,h: 矩阵高度
int maxSum = 0, maxX = -1, maxY = -1, w = arr.length, h = arr[0].length;
for (int i = 0; i < w; i++) {
for (int j = 0; j < h; j++) {
// 空地才可以防止炸弹
if(arr[i][j] != '.') {
continue;
}
int sum = 0, x = i, y = j; // sum: 当前坐标可消灭敌人数,x: 当前横坐标,y: 当前纵坐标
// 向上找
while (arr[x][y] != '#' && x >= 0 && x < w) { // 四周一定有墙,所以 坐标是否越界其实不用判断
if (arr[x][y] == 'G') {
sum++;
}
x--;
}
// 向下找
x = i;
y = j;
while (arr[x][y] != '#' && x >= 0 && x < w) {
if (arr[x][y] == 'G') {
sum++;
}
x++;
}
// 向左找
x = i;
y = j;
while (arr[x][y] != '#' && y >= 0 && y < h) {
if (arr[x][y] == 'G') {
sum++;
}
y--;
}
// 向右找
x = i;
y = j;
while (arr[x][y] != '#' && y >= 0 && y < h) {
if (arr[x][y] == 'G') {
sum++;
}
y++;
}
if (sum > maxSum) {
maxX = i;
maxY = j;
maxSum = sum;
}
}
}
System.out.printf("在(%d, %d)处放置炸弹,杀伤的敌人最多,为%d人\n", maxX, maxY, maxSum);
}
}
运行,得:
但是,很明显 (1, 11) 小人根本走不进去:
所以,这个答案是错误的,我们使用枚举的思想根本无法判断哪片空地小人是否可以到达。
那,怎么办呢?不要着急,我们在后期的博客中讲《万能的搜索》时,再来解决这个问题!