前言
这个我也不知道算不算是A*搜索算法,可能只是A搜索算法。
首先看相关的定义:
启发式搜索在搜索过程中根据启发信息评估各个节点的重要性,优先搜索重要的节点。
估价函数的任务就是估计待搜索节点“有希望”的程度。
估价函数f(n)定义为从初始节点经过节点n到达目的节点的路径的最小代价估计值,其中一般形式是
f(n)=g(n)+h(n)
g(n)是从初始节点到节点n的实际代价,而h(n)是从节点n到目的节点的最佳路径的估计价值。
在这篇博客中就把事件进行简化了,g(n)表示当前走过的步数,h(n)估计代价用曼哈顿距离来表示。
参考链接:
人工智能大作业——A*算法迷宫寻路问题_a*算法实现迷宫寻路功能-CSDN博客https://blog.csdn.net/weixin_46037153/article/details/107136560A*搜索算法(A-Star Search)简介及保姆级代码解读 (qq.com)https://mp.weixin.qq.com/s?__biz=MzU1NjEwMTY0Mw==&mid=2247554483&idx=1&sn=c3e6a3259f1b469eded07a5498790186&chksm=fbc86cd7ccbfe5c12f4dddf139f73cb5c2879044867bced71edb0243b155f3d68eda50d0db6a&scene=27机器人路径规划算法(十一)A-star算法 - Mronne's Bloghttps://mronne.github.io/2020/04/03/%E6%9C%BA%E5%99%A8%E4%BA%BA%E8%B7%AF%E5%BE%84%E8%A7%84%E5%88%92%E7%AE%97%E6%B3%95-%E5%8D%81%E4%B8%80-A-star-%E7%AE%97%E6%B3%95.html
思路讲解
前期准备
用MAP二维数组来表示待搜索的地图,其中1表示道路受阻碍,0表示通路。
定义Grid来表示地图中的节点,其中x,y表示坐标,fn,hn,gn是之后需要用到的估计值。
定义两个集合来储存新加入的节点以及走过的节点,我这里用到的是链表,因为添加和删除元素比较多。
//定义地图,直接利用二维数组来实现(0表示可以通过,1表示不能通过
public static int[][] MAP={
{0,1,0,0,0,0,0},
{0,0,1,0,0,0,0},
{1,0,0,1,0,0,0},
{0,1,0,1,1,1,1},
{0,0,0,0,0,0,0},
{0,0,0,0,0,0,0},
{0,0,0,1,1,1,0},
};
//把方格抽象成一个类
public static class Grid{
private int x;//横坐标
private int y;//纵坐标
//fn=hn+gn
private int fn;//估计函数
private int hn;//估计代价
private int gn;//实际代价
private Grid parent;//父节点
//构造方法
public Grid(int x,int y){
this.x=x;
this.y=y;
}
//实例化一个方格节点
public void initGrid(Grid parent,Grid end){
this.parent=parent;
//计算gn
if(parent!=null){
//实际的代价加一相当于前进了一步
this.gn=parent.gn+1;
}else {
this.gn=1;
}
//计算hn的大小(这里用的估计代价是曼哈顿距离
this.hn=Math.abs(this.x-end.x)+Math.abs(this.y-this.y);
//计算fn的大小
this.fn=this.gn+this.hn;
}
}
//准备两个链表来储存需要选择的节点以及已经走过的节点
public static LinkedList<Grid> selectList=new LinkedList<Grid>();
public static LinkedList<Grid> walkList=new LinkedList<Grid>();
查找最小估价值的节点
方法的作用是返回selectList集合中的fn最小的节点。(fn表示最小代价估计值)
//返回最小的那个节点
private static Grid findMinGrid(LinkedList<Grid>selectList){
Grid tmpgrid=selectList.get(0);
for(Grid grid:selectList){
//更新最小节点
if(grid.fn<tmpgrid.fn){
tmpgrid=grid;
}
}
return tmpgrid;
}
判断当前节点是否已经添加
判断(x,y)坐标是否在grids集合中,如果相等,表示已经添加到集合中了。
//判断当前的节点是否已经添加
private static boolean contains(LinkedList<Grid>grids,int x,int y){
for (Grid grid:grids){
//坐标在grids集合中有就是已经被添加了
if((grid.x==x)&&(grid.y==y)){
return true;
}
}
return false;
}
判断当前的节点是否合法
判断当前节点的坐标是否越界,以及节点是否已经被添加到集合中了,如果已经被添加了就不用管了。
//判断当前节点是否合法
private static boolean legitimacy(int x,int y,LinkedList<Grid>selectList,LinkedList<Grid>walkList) {
//判断坐标是否越界
if (x < 0 || x >= MAP.length || y < 0 || y >= MAP[0].length) {
return false;
}
//判断当前节点是否是障碍
if (MAP[x][y] == 1) {
return false;
}
//判断当前节点是否被添加
if (contains(selectList, x, y)) {
return false;
}
//判断当前节点是否已经走过了
if (contains(walkList, x, y)) {
return false;
}
//所以条件都满足,他就是合法的
return true;
}
添加相邻的节点
他的思路就是先找出目前fn最小的那个节点,然后对那个节点相邻的节点进行判断,如果相邻的节点是合法的,就把节点添加到集合中。
//把所有符合要求的相邻节点都放置到list集合里面里面
private static LinkedList<Grid>findNeighbors(Grid grid, LinkedList<Grid>selectList,LinkedList<Grid>walkList){
LinkedList<Grid>list=new LinkedList<Grid>();
//判断相邻节点的合法性
if(legitimacy(grid.x,grid.y-1,selectList,walkList)){//下(用数组来看的话他就是往上)
list.add(new Grid(grid.x,grid.y-1));
}
if(legitimacy(grid.x,grid.y+1,selectList,walkList)){//上
list.add(new Grid(grid.x,grid.y+1));
}
if(legitimacy(grid.x-1,grid.y,selectList,walkList)){//左
list.add(new Grid(grid.x-1,grid.y));
}
if(legitimacy(grid.x+1,grid.y,selectList,walkList)){//右
list.add(new Grid(grid.x+1,grid.y));
}
return list;
}
算法实现
先把start节点最开始添加到集合中,然后不断的扩展他,知道集合中没有节点,或者找到终点节点表示结束。
//A*算法的实现(需要开始位置和结束位置
private static void algorithmImplementation(Grid start,Grid end){
//将起点加入链表之后开始寻路
selectList.add(start);
//链表不为空(没有最后没有到达终点也会停止)
while(selectList.size()>0){
//找到在需要选择的节点中最小的那个节点,之后需要用它进行扩展
Grid nowGrid=findMinGrid(selectList);
//从中删除最小的那个节点
selectList.remove(nowGrid);
//将这个节点添加到已经走过的路径中
walkList.add(nowGrid);
//寻找他的相邻节点,把合法的节点都添加进来
LinkedList<Grid>neighbors=findNeighbors(nowGrid,selectList,walkList);
for(Grid grid:neighbors){
//判断集合中是否添加了grid节点
if(!selectList.contains(grid)){
//进行初始化
grid.initGrid(nowGrid,end);
selectList.add(grid);
}
}
//判断是否可以结束
for(Grid grid:selectList){
if((grid.x==end.x)&&(grid.y==end.y)){
walkList.add(end);
return;
}
}
}
}
主方法
对储存的节点进行打印,走过的道路用2来表示。
//主方法
public static void main(String[] args) {
System.out.println("原始地图是:");
for (int i = 0; i < MAP.length; i++) {
for (int j = 0; j < MAP[0].length; j++) {
System.out.print(MAP[i][j]+"\t");
}
System.out.println();
}
Grid grid1=new Grid(0,0);
Grid grid2=new Grid(MAP.length-1,MAP[0].length-1);
algorithmImplementation(grid1,grid2);
//走过的路径用2表示
for(Grid grid:walkList){
MAP[grid.x][grid.y]=2;
}
System.out.println("搜索后的地图是:");
for (int i = 0; i < MAP.length; i++) {
for (int j = 0; j < MAP[0].length; j++) {
System.out.print(MAP[i][j]+"\t");
}
System.out.println();
}
}
Java
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
public class AStarSearch {
//定义地图,直接利用二维数组来实现(0表示可以通过,1表示不能通过
public static int[][] MAP={
{0,1,0,0,0,0,0},
{0,0,1,0,0,0,0},
{1,0,0,1,0,0,0},
{0,1,0,1,1,1,1},
{0,0,0,1,0,0,0},
{0,1,0,0,0,0,0},
{0,0,0,1,1,1,0},
};
//把方格抽象成一个类
public static class Grid{
private int x;//横坐标
private int y;//纵坐标
//fn=hn+gn
private int fn;//估计函数
private int hn;//估计代价
private int gn;//实际代价
private Grid parent;//父节点
//构造方法
public Grid(int x,int y){
this.x=x;
this.y=y;
}
//实例化一个方格节点
public void initGrid(Grid parent,Grid end){
this.parent=parent;
//计算gn
if(parent!=null){
//实际的代价加一相当于前进了一步
this.gn=parent.gn+1;
}else {
this.gn=1;
}
//计算hn的大小(这里用的估计代价是曼哈顿距离
this.hn=Math.abs(this.x-end.x)+Math.abs(this.y-this.y);
//计算fn的大小
this.fn=this.gn+this.hn;
}
}
//准备两个链表来储存需要选择的节点以及已经走过的节点
public static LinkedList<Grid> selectList=new LinkedList<Grid>();
public static LinkedList<Grid> walkList=new LinkedList<Grid>();
//A*算法的实现(需要开始位置和结束位置
private static void algorithmImplementation(Grid start,Grid end){
//将起点加入链表之后开始寻路
selectList.add(start);
//链表不为空(没有最后没有到达终点也会停止)
while(selectList.size()>0){
//找到在需要选择的节点中最小的那个节点,之后需要用它进行扩展
Grid nowGrid=findMinGrid(selectList);
//从中删除最小的那个节点
selectList.remove(nowGrid);
//将这个节点添加到已经走过的路径中
walkList.add(nowGrid);
//寻找他的相邻节点,把合法的节点都添加进来
LinkedList<Grid>neighbors=findNeighbors(nowGrid,selectList,walkList);
for(Grid grid:neighbors){
//判断集合中是否添加了grid节点
if(!selectList.contains(grid)){
//进行初始化
grid.initGrid(nowGrid,end);
selectList.add(grid);
}
}
//判断是否可以结束
for(Grid grid:selectList){
if((grid.x==end.x)&&(grid.y==end.y)){
walkList.add(end);
return;
}
}
}
}
//把所有符合要求的相邻节点都放置到list集合里面里面
private static LinkedList<Grid>findNeighbors(Grid grid, LinkedList<Grid>selectList,LinkedList<Grid>walkList){
LinkedList<Grid>list=new LinkedList<Grid>();
//判断相邻节点的合法性
if(legitimacy(grid.x,grid.y-1,selectList,walkList)){//下(用数组来看的话他就是往上)
list.add(new Grid(grid.x,grid.y-1));
}
if(legitimacy(grid.x,grid.y+1,selectList,walkList)){//上
list.add(new Grid(grid.x,grid.y+1));
}
if(legitimacy(grid.x-1,grid.y,selectList,walkList)){//左
list.add(new Grid(grid.x-1,grid.y));
}
if(legitimacy(grid.x+1,grid.y,selectList,walkList)){//右
list.add(new Grid(grid.x+1,grid.y));
}
return list;
}
//判断当前节点是否合法
private static boolean legitimacy(int x,int y,LinkedList<Grid>selectList,LinkedList<Grid>walkList) {
//判断坐标是否越界
if (x < 0 || x >= MAP.length || y < 0 || y >= MAP[0].length) {
return false;
}
//判断当前节点是否是障碍
if (MAP[x][y] == 1) {
return false;
}
//判断当前节点是否被添加
if (contains(selectList, x, y)) {
return false;
}
//判断当前节点是否已经走过了
if (contains(walkList, x, y)) {
return false;
}
//所以条件都满足,他就是合法的
return true;
}
//判断当前的节点是否已经添加
private static boolean contains(LinkedList<Grid>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<Grid>selectList){
Grid tmpgrid=selectList.get(0);
for(Grid grid:selectList){
//更新最小节点
if(grid.fn<tmpgrid.fn){
tmpgrid=grid;
}
}
return tmpgrid;
}
//主方法
public static void main(String[] args) {
System.out.println("原始地图是:");
for (int i = 0; i < MAP.length; i++) {
for (int j = 0; j < MAP[0].length; j++) {
System.out.print(MAP[i][j]+"\t");
}
System.out.println();
}
Grid grid1=new Grid(0,0);
Grid grid2=new Grid(MAP.length-1,MAP[0].length-1);
algorithmImplementation(grid1,grid2);
//走过的路径用2表示
for(Grid grid:walkList){
MAP[grid.x][grid.y]=2;
}
System.out.println("搜索后的地图是:");
for (int i = 0; i < MAP.length; i++) {
for (int j = 0; j < MAP[0].length; j++) {
System.out.print(MAP[i][j]+"\t");
}
System.out.println();
}
}
}
总结
现在只写了Java的代码,之后会有时间会用其他语言写,之前问了老师,他说用Python可以实现图形化的那种,但是面前还不太会,以后有时间就会更新一下这篇博客。