有目录,不迷路
- 缘起
- 代码地址
- 炸弹人
- 问题
缘起
各位小伙伴们好呀!本人最近看了下《啊哈算法》,写的确实不错。
但稍显遗憾的是,书籍示例代码是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) 小人根本走不进去:
所以,这个答案是错误的,我们使用枚举的思想根本无法判断哪片空地小人是否可以到达。
那,怎么办呢?不要着急,我们在后期的博客中讲《万能的搜索》时,再来解决这个问题!