本来是想写一块的,但是为了这个国庆的专属勋章就分开写了,这个侧重还是对作业题目要求的实现。
因为地图不是自己定义的,是在文本文件里面的,所以需要我们自己进行读取,把数据拿出来放在二维的字符数组里面,同时还是需要标记他的起点和终点。因为都是字符,所以读取一行字符串,把字符放在字符数组里面就行了。
A*搜索算法-CSDN博客https://blog.csdn.net/weixin_64066303/article/details/133325621?spm=1001.2014.3001.5501
这里主要就是写一个前面没有提到的。
最短路径我在网上没有看到Java的实现,写的都是伪代码,思路都很简单,就是从终点往前面找,一直到起点就行了,不能从起点往终点直接实现。
举一个例子,假设起点在左上方,终点在右下方,前面一直在右上方找,突然发现代价大于走下方的代价,或者是他往右上方找完之后没路了,那他直接就跳到走下方去寻找,最后呈现出来的路径进行从一边直接跳到另一边这种,这显示和实际不符合,所以需要从终点往起点来寻找。
我这里是用HashMap来实现的,用当前的节点作为值,相邻满足要求的节点作为键,之后就只需要从终点一直把值的结果作为键,就可以得到前一步的结果了,这里我之前是用TreeMap但是失败了,非常诡异,他键和值都有,但是用那个键去查找他的值结果是null,很离谱。
import java.io.*;
import java.util.HashMap;
import java.util.LinkedList;
public class bigMazeSearch {
public static void main(String[] args) throws IOException {
//定义文本文件的路径
File file = new File("C:\\Users\\25496\\Desktop\\artificial intelligence\\bigMaze.txt");
FileReader fileReader = new FileReader(file);
BufferedReader bufferedReader = new BufferedReader(fileReader);
//定义字符二维数组的行数和列数
int rows = 50;
int cols = 50;
//创建字符二维数组
char[][] mazeArray = new char[rows][cols];
String line;
//记录起点
Grid startGrid = new Grid(0, 0);
//记录终点
Grid endGrid = new Grid(0, 0);
//记录最短路径和扩展路径的步数
int shortestPath=0;
int expandingCrackingPath=0;
int row = 0;
while ((line = bufferedReader.readLine()) != null && row < rows) {//逐行读取内容
//System.out.println(line);
for (int col = 0; col < cols && col < line.length(); col++) {
//把读取的字符储存到字符数组中
mazeArray[row][col] = line.charAt(col);
//更新起点
if (mazeArray[row][col] == 'S') {
startGrid.setX(row);
startGrid.setY(col);
} else if (mazeArray[row][col] == 'E') {
//更新终点
endGrid.setX(row);
endGrid.setY(col);
}
}
row++;
}
bufferedReader.close();
fileReader.close();
//输出二维字符数组的内容
System.out.println("原始地图是:");
for (int i = 0; i < row; i++) {
for (int j = 0; j < cols; j++) {
System.out.print(mazeArray[i][j] + " ");
}
System.out.println();
}
//开始时间(获取当前时间戳的毫秒值)
long startTime=System.currentTimeMillis();
//调用A*搜索算法
AStarSearch(startGrid, endGrid, mazeArray);
//结束时间
long endTime= System.currentTimeMillis();
//System.out.println("算法调用");
//标记路径
while (true) {
Grid grid = resultMap.get(endGrid);
endGrid = grid;
if (endGrid == null) {
break;
}
//到达起点
if (endGrid.x == startGrid.x && endGrid.y == startGrid.y) {
// mazeArray[endGrid.x][endGrid.y] = '#';
break;
}
mazeArray[endGrid.x][endGrid.y] = '@';
//最短路径
shortestPath++;
}
System.out.println("\n\n路径是:");
//路径地图是
for (int i = 0; i < row; i++) {
for (int j = 0; j < cols; j++) {
System.out.print(mazeArray[i][j] + " ");
}
System.out.println();
}
expandingCrackingPath=closedList.size();
long timeElapsed=endTime-startTime;
System.out.println("最多路径是:"+shortestPath+"步");
System.out.println("扩展节点数是:"+expandingCrackingPath);
System.out.println("算法的运行时间是(毫秒)"+timeElapsed);
}
//把方格抽象成一个类
public static class Grid {
private int x;//横坐标
private int y;//纵坐标
//fn=hn+gn
private int fn;//估计函数
private int hn;//估计代价
private int gn;//实际代价
private Grid present;//当前节点
//构造方法
public Grid(int x, int y) {
this.x = x;
this.y = y;
}
public int getX() {
return x;
}
public void setX(int x) {
this.x = x;
}
public int getY() {
return y;
}
public void setY(int y) {
this.y = y;
}
//实例化一个方格节点
public void initGrid(Grid present, Grid end) {
this.present = present;
//计算gn
if (present != null) {
//实际的代价加一相当于前进了一步
this.gn = present.gn + 1;
} else {
this.gn = 1;
}
//计算hn的大小(这里用的估计代价是曼哈顿距离
this.hn = Math.abs(this.x - end.x) + Math.abs(this.y - end.y);
//计算fn的大小
this.fn = this.gn + this.hn;
}
public String toString() {
return "(" + this.x + "," + this.y + ")" + "fn:" + this.fn + " gn:" + this.gn + " hn:" + this.hn;
}
@Override
public int hashCode() {
int tmp = (this.y + (this.x + 1) / 2);
return x + (tmp * tmp);
}
//重写equals方法,不然比较的是地址值
@Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (obj == null || getClass() != obj.getClass()) return false;
Grid other = (Grid) obj;
if (this.x == other.x && this.y == other.y) {
return true;
}
return false;
}
}
//准备两个链表来储存需要选择的节点以及已经走过的节点
public static LinkedList openList = new LinkedList();
public static LinkedList closedList = new LinkedList();
public static HashMap resultMap = new HashMap();
//A*算法的实现(需要开始位置和结束位置
private static void AStarSearch(Grid start, Grid end, char[][] mazeArray) {
//将起点加入链表之后开始寻路
openList.add(start);
//链表不为空(没有最后没有到达终点也会停止)
while (openList.size() > 0) {
//找到在需要选择的节点中最小的那个节点,之后需要用它进行扩展
Grid nowGrid = findMinGrid(openList);
//从中删除最小的那个节点
openList.remove(nowGrid);
//将这个节点添加到已经走过的路径中
closedList.add(nowGrid);
//寻找他的相邻节点,把合法的节点都添加进来
LinkedList neighbors = findNeighbors(nowGrid, openList, closedList, mazeArray);
for (Grid grid : neighbors) {
//判断集合中是否添加了grid节点
if (!openList.contains(grid)) {
//进行初始化
grid.initGrid(nowGrid, end);
//添加到待搜索集合中
openList.add(grid);
if (grid != null && nowGrid != null) {
//子节点作为键,父节点作为值
resultMap.put(grid, nowGrid);
}
//System.out.println(grid + " " + resultMap.get(grid));
}
}
//判断是否可以结束
for (Grid grid : openList) {
if ((grid.x == end.x) && (grid.y == end.y)) {
closedList.add(end);
return;
}
}
}
}
//把所有符合要求的相邻节点都放置到list集合里面里面
private static LinkedList findNeighbors(Grid grid, LinkedList openList, LinkedList closeedList, char[][] mazeArray) {
LinkedList list = new LinkedList();
//判断相邻节点的合法性
if (legitimacy(grid.x, grid.y - 1, openList, closeedList, mazeArray)) {//下(用数组来看的话他就是往上)
list.add(new Grid(grid.x, grid.y - 1));
}
if (legitimacy(grid.x, grid.y + 1, openList, closeedList, mazeArray)) {//上
list.add(new Grid(grid.x, grid.y + 1));
}
if (legitimacy(grid.x - 1, grid.y, openList, closeedList, mazeArray)) {//左
list.add(new Grid(grid.x - 1, grid.y));
}
if (legitimacy(grid.x + 1, grid.y, openList, closeedList, mazeArray)) {//右
list.add(new Grid(grid.x + 1, grid.y));
}
return list;
}
//判断当前节点是否合法
private static boolean legitimacy(int x, int y, LinkedList openList, LinkedList closedList, char[][] mazeArray) {
//判断坐标是否越界
if (x < 0 || x >= mazeArray.length || y < 0 || y >= mazeArray[0].length) {
return false;
}
//判断当前节点是否是障碍
if (mazeArray[x][y] == '%') {
return false;
}
//判断当前节点是否被添加
if (contains(openList, x, y)) {
return false;
}
//判断当前节点是否已经走过了
if (contains(closedList, x, y)) {
return false;
}
//所以条件都满足,他就是合法的
return true;
}
//判断当前的节点是否已经添加
private static boolean contains(LinkedList grids, int x, int y) {
for (Grid grid : grids) {
//坐标在grids集合中有就是已经被添加了
if ((grid.x == x) && (grid.y == y)) {
return true;
}
}
return false;
}
//返回最小的那个节点
private static Grid findMinGrid(LinkedList selectList) {
Grid tmpgrid = selectList.get(0);
for (Grid grid : selectList) {
//更新最小节点
if (grid.fn < tmpgrid.fn) {
tmpgrid = grid;
}
}
return tmpgrid;
}
}
直接输出太大了,不能截图,这是复制到记事本里面的结果。
用idea还可以通过ANSI转义码来实现的输出不同的颜色,eclipse的话还需要自己安装插件才行,用颜色的好处就很对比更加明显了。
代码的启发式函数采用的是曼哈顿,欧几里得的话还需要自己去实现哦。(这个最短路径是一样的,但是扩展节点的数量不一样)
本来还想写一起的,但还是分开的,这个思路网上的都是一样的,就是实现不相同。