欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

Java编程实现A*算法完整代码

程序员文章站 2023-12-13 09:48:46
前言 a*搜寻算法俗称a星算法。这是一种在图形平面上,有多个节点的路径,求出最低通过成本的算法。常用于游戏中 通过二维数组构建的一个迷宫,“%”表示墙壁,a为起点,b为...

前言

a*搜寻算法俗称a星算法。这是一种在图形平面上,有多个节点的路径,求出最低通过成本的算法。常用于游戏中

通过二维数组构建的一个迷宫,“%”表示墙壁,a为起点,b为终点,“#”代表障碍物,“*”代表算法计算后的路径

本文实例代码结构:

Java编程实现A*算法完整代码

% % % % % % %  
% o o o o o %  
% o o # o o %  
% a o # o b %  
% o o # o o %  
% o o o o o %  
% % % % % % %  
============================= 
经过a*算法计算后 
============================= 
% % % % % % %  
% o o * o o %  
% o * # * o %  
% a o # o b %  
% o o # o o %  
% o o o o o %  
% % % % % % % <

算法理论

算法的核心公式为:f=g+h

把地图上的节点看成一个网格。

g=从起点a,沿着产生的路径,移动到网格上指定节点的移动消耗,在这个例子里,我们令水平或者垂直移动的耗费为10,对角线方向耗费为14。我们取这些值是因为沿对角线

的距离是沿水平或垂直移动耗费的的根号2,或者约1.414倍。为了简化,我们用10和14近似。

既然我们在计算沿特定路径通往某个方格的g值,求值的方法就是取它父节点的g值,然后依照它相对父节点是对角线方向或者直角方向(非对角线),分别增加14和10。例子中这

个方法的需求会变得更多,因为我们从起点方格以外获取了不止一个方格。

h=从当前格移动到终点b的预估移动消耗。为什么叫”预估“呢,因为我们没有办法事先知道路径的长度,这里我们使用曼哈顿方法,它计算从当前格到目的格之间水平和垂直

的方格的数量总和,忽略对角线方向。然后把结果乘以10。

f的值是g和h的和,这是我们用来判断优先路径的标准,f值最小的格,我们认为是优先的路径节点。

实现步骤

算法使用java写的,先看一看节点类的内容

package a_star_search; 
/** 
 * 节点类 
 * @author zx 
 * 
 */ 
public class node { 
  private int x; //x坐标 
  private int y; //y坐标 
  private string value;  //表示节点的值 
  private double fvalue = 0; //f值 
  private double gvalue = 0; //g值 
  private double hvalue = 0; //h值 
  private boolean reachable; //是否可到达(是否为障碍物) 
  private node pnode;   //父节点 
   
  public node(int x, int y, string value, boolean reachable) { 
    super(); 
    this.x = x; 
    this.y = y; 
    this.value = value; 
    reachable = reachable; 
  } 
   
  public node() { 
    super(); 
  } 
 
  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 string getvalue() { 
    return value; 
  } 
  public void setvalue(string value) { 
    this.value = value; 
  } 
  public double getfvalue() { 
    return fvalue; 
  } 
  public void setfvalue(double fvalue) { 
    fvalue = fvalue; 
  } 
  public double getgvalue() { 
    return gvalue; 
  } 
  public void setgvalue(double gvalue) { 
    gvalue = gvalue; 
  } 
  public double gethvalue() { 
    return hvalue; 
  } 
  public void sethvalue(double hvalue) { 
    hvalue = hvalue; 
  } 
  public boolean isreachable() { 
    return reachable; 
  } 
  public void setreachable(boolean reachable) { 
    reachable = reachable; 
  } 
  public node getpnode() { 
    return pnode; 
  } 
  public void setpnode(node pnode) { 
    pnode = pnode; 
  }   
} 

还需要一个地图类,在map的构造方法中,我通过创建节点的二维数组来实现一个迷宫地图,其中包括起点和终点

package a_star_search;
public class map {
	private node[][] map;
	//节点数组 
	private node startnode;
	//起点 
	private node endnode;
	//终点 
	public map() {
		map = new node[7][7];
		for (int i = 0;i<7;i++){
			for (int j = 0;j<7;j++){
				map[i][j] = new node(i,j,"o",true);
			}
		}
		for (int d = 0;d<7;d++){
			map[0][d].setvalue("%");
			map[0][d].setreachable(false);
			map[d][0].setvalue("%");
			map[d][0].setreachable(false);
			map[6][d].setvalue("%");
			map[6][d].setreachable(false);
			map[d][6].setvalue("%");
			map[d][6].setreachable(false);
		}
		map[3][1].setvalue("a");
		startnode = map[3][1];
		map[3][5].setvalue("b");
		endnode = map[3][5];
		for (int k = 1;k<=3;k++){
			map[k+1][3].setvalue("#");
			map[k+1][3].setreachable(false);
		}
	}
	<span style="white-space:pre">  </span>//展示地图 
	public void showmap(){
		for (int i = 0;i<7;i++){
			for (int j = 0;j<7;j++){
				system.out.print(map[i][j].getvalue()+" ");
			}
			system.out.println("");
		}
	}
	public node[][] getmap() {
		return map;
	}
	public void setmap(node[][] map) {
		this.map = map;
	}
	public node getstartnode() {
		return startnode;
	}
	public void setstartnode(node startnode) {
		this.startnode = startnode;
	}
	public node getendnode() {
		return endnode;
	}
	public void setendnode(node endnode) {
		this.endnode = endnode;
	}
}

下面是最重要的astar类

操作过程

1从起点a开始,并且把它作为待处理点存入一个“开启列表”,这是一个待检查方格的列表。

2寻找起点周围所有可到达或者可通过的方格,跳过无法通过的方格。也把他们加入开启列表。为所有这些方格保存点a作为“父方格”。当我们想描述路径的时候,父方格的资

料是十分重要的。后面会解释它的具体用途。

3从开启列表中删除起点a,把它加入到一个“关闭列表”,列表中保存所有不需要再次检查的方格。

经过以上步骤,“开启列表”中包含了起点a周围除了障碍物的所有节点。他们的父节点都是a,通过前面讲的f=g+h的公式,计算每个节点的g,h,f值,并按照f的值大小,从小

到大进行排序。并对f值最小的那个节点做以下操作

4,把它从开启列表中删除,然后添加到关闭列表中。

5,检查所有相邻格子。跳过那些不可通过的(1.在”关闭列表“中,2.障碍物),把他们添加进开启列表,如果他们还不在里面的话。把选中的方格作为新的方格的父节点。

6,如果某个相邻格已经在开启列表里了,检查现在的这条路径是否更好。换句话说,检查如果我们用新的路径到达它的话,g值是否会更低一些。如果不是,那就什么都不

做。(这里,我的代码中并没有判断)

7,我们重复这个过程,直到目标格(终点“b”)被添加进“开启列表”,说明终点b已经在上一个添加进“关闭列表”的节点的周围,只需走一步,即可到达终点b。

8,我们将终点b添加到“关闭列表”

9,最后一步,我们要将从起点a到终点b的路径表示出来。父节点的作用就显示出来了,通过“关闭列表”中的终点节点的父节点,改变其value值,顺藤摸瓜即可以显示出路径。

看看代码

package a_star_search;
import java.util.arraylist;
public class astar {
	/** 
   * 使用arraylist数组作为“开启列表”和“关闭列表” 
   */
	arraylist<node> open = new arraylist<node>();
	arraylist<node> close = new arraylist<node>();
	/** 
   * 获取h值 
   * @param currentnode:当前节点 
   * @param endnode:终点 
   * @return 
   */
	public double gethvalue(node currentnode,node endnode){
		return (math.abs(currentnode.getx() - endnode.getx()) + math.abs(currentnode.gety() - endnode.gety()))*10;
	}
	/** 
   * 获取g值 
   * @param currentnode:当前节点 
   * @return 
   */
	public double getgvalue(node currentnode){
		if(currentnode.getpnode()!=null){
			if(currentnode.getx()==currentnode.getpnode().getx()||currentnode.gety()==currentnode.getpnode().gety()){
				//判断当前节点与其父节点之间的位置关系(水平?对角线) 
				return currentnode.getgvalue()+10;
			}
			return currentnode.getgvalue()+14;
		}
		return currentnode.getgvalue();
	}
	/** 
   * 获取f值 : g + h 
   * @param currentnode 
   * @return 
   */
	public double getfvalue(node currentnode){
		return currentnode.getgvalue()+currentnode.gethvalue();
	}
	/** 
   * 将选中节点周围的节点添加进“开启列表” 
   * @param node 
   * @param map 
   */
	public void inopen(node node,map map){
		int x = node.getx();
		int y = node.gety();
		for (int i = 0;i<3;i++){
			for (int j = 0;j<3;j++){
				//判断条件为:节点为可到达的(即不是障碍物,不在关闭列表中),开启列表中不包含,不是选中节点 
				if(map.getmap()[x-1+i][y-1+j].isreachable()&&!open.contains(map.getmap()[x-1+i][y-1+j])&&!(x==(x-1+i)&&y==(y-1+j))){
					map.getmap()[x-1+i][y-1+j].setpnode(map.getmap()[x][y]);
					//将选中节点作为父节点 
					map.getmap()[x-1+i][y-1+j].setgvalue(getgvalue(map.getmap()[x-1+i][y-1+j]));
					map.getmap()[x-1+i][y-1+j].sethvalue(gethvalue(map.getmap()[x-1+i][y-1+j],map.getendnode()));
					map.getmap()[x-1+i][y-1+j].setfvalue(getfvalue(map.getmap()[x-1+i][y-1+j]));
					open.add(map.getmap()[x-1+i][y-1+j]);
				}
			}
		}
	}
	/** 
   * 使用冒泡排序将开启列表中的节点按f值从小到大排序 
   * @param arr 
   */
	public void sort(arraylist<node> arr){
		for (int i = 0;i<arr.size()-1;i++){
			for (int j = i+1;j<arr.size();j++){
				if(arr.get(i).getfvalue() > arr.get(j).getfvalue()){
					node tmp = new node();
					tmp = arr.get(i);
					arr.set(i, arr.get(j));
					arr.set(j, tmp);
				}
			}
		}
	}
	/** 
   * 将节点添加进”关闭列表“ 
   * @param node 
   * @param open 
   */
	public void inclose(node node,arraylist<node> open){
		if(open.contains(node)){
			node.setreachable(false);
			//设置为不可达 
			open.remove(node);
			close.add(node);
		}
	}
	public void search(map map){
		//对起点即起点周围的节点进行操作 
		inopen(map.getmap()[map.getstartnode().getx()][map.getstartnode().gety()],map);
		close.add(map.getmap()[map.getstartnode().getx()][map.getstartnode().gety()]);
		map.getmap()[map.getstartnode().getx()][map.getstartnode().gety()].setreachable(false);
		map.getmap()[map.getstartnode().getx()][map.getstartnode().gety()].setpnode(map.getmap()[map.getstartnode().getx()][map.getstartnode().gety()]);
		sort(open);
		//重复步骤 
		do{
			inopen(open.get(0), map);
			inclose(open.get(0), open);
			sort(open);
		}
		while(!open.contains(map.getmap()[map.getendnode().getx()][map.getendnode().gety()]));
		//知道开启列表中包含终点时,循环退出 
		inclose(map.getmap()[map.getendnode().getx()][map.getendnode().gety()], open);
		showpath(close,map);
	}
	/** 
   * 将路径标记出来 
   * @param arr 
   * @param map 
   */
	public void showpath(arraylist<node> arr,map map) {
		if(arr.size()>0){
			node node = new node();
			//<span style="white-space:pre">    </span>node = map.getmap()[map.getendnode().getx()][map.getendnode().gety()]; 
			//<span style="white-space:pre">    </span>while(!(node.getx() ==map.getstartnode().getx()&&node.gety() ==map.getstartnode().gety())){ 
			//<span style="white-space:pre">    </span>node.getpnode().setvalue("*"); 
			//<span style="white-space:pre">    </span>node = node.getpnode(); 
			//<span style="white-space:pre">  </span>}
		}
		//<span style="white-space:pre">  </span>map.getmap()[map.getstartnode().getx()][map.getstartnode().gety()].setvalue("a");
	}
}

最后写一个main方法

package a_star_search; 
 
public class maintest { 
   
  public static void main(string[] args) { 
    map map = new map(); 
    astar astar = new astar(); 
    map.showmap(); 
    astar.search(map); 
    system.out.println("============================="); 
    system.out.println("经过a*算法计算后"); 
    system.out.println("============================="); 
    map.showmap();  
  } 
} 

修改地图再测试一下,看看效果

% % % % % % % 
% o o o o o % 
% o o # o o % 
% a o # o b % 
% o o # o o % 
% o o o o o % 
% % % % % % % 
=============================
经过a*算法计算后
=============================
% % % % % % % 
% o o o o o % 
% o o # o o % 
% a o # o b % 
% o o # o o % 
% o o o o o % 
% % % % % % % 

总结

保证找到最短路径(最优解的)条件,关键在于估价函数h(n)的选取:估价值h(n)<=n到目标节点的距离实际值,这种情况下,搜索的点数多,搜索范围大,效率低。但能得到

最优解。如果估价值>实际值,搜索的点数少,搜索范围小,效率高,但不能保证得到最优解。

最大的感触就是:做事最忌三天打渔,两天晒网。量可以不大,但必须有连续性,贵在坚持。

希望每一个程序员,都能开心的敲着代码,做自己喜欢做的事。

以上就是本文关于java编程实现a*算法完整代码的全部内容,希望对大家有所帮助。感兴趣的朋友可以继续参阅本站其他相关专题,如有不足之处,欢迎留言指出。

上一篇:

下一篇: