@Java实现A_Star算法
代码结构
因为这里注重算法的内容,所以结构就没有细分,先把贴图放一下:
这里把一些结构体或者叫JavaBean写成了单独的类,main里主要负责功能的实现,包括了读入文本、Astar算法的实现、和输出图片。迷宫文本存放在src同级的text文件夹里
代码介绍
- State(状态)
public class State {
Value[][] matrix; //可变长二维数组
int x_pos;
int y_pos;
}
class Value{
int Manhattan_Distance;
int signal;
}
State是一个状态,在这里可以简单地理解成二维数组,由于一个格子既要存放曼哈顿距离的值还要存储地图信息,这里就单独用一个Value类来存放,并将二维数组的类型设置成Value。x,y是当前所处的位置
- Node(节点)
一个节点应该包括以下四个成员
1.当前状态(current state):保存当前信息
2.父级节点(parent node):追踪有效路径
3.动作 [action(how parent get to the current state)]
4.消耗(path cost)
public class Node {
State cur_state;
Node pre_node;
int acton;
int path_cost;
Node(){}
}
- Action
这里就是上下左右四个方向移动,我们用Node result(Node current, int action_type)函数实现,这里以向上为例
Node next = new Node();
//用state代替cur_node.cur_state
State state = new State();
state = cur_node.cur_state;
if(acton == Action.UP){
//如果正上方的是空格或者是终点,因为打印的时候要区分终点,所以这里就把终点和空格都写进来
if(state.matrix[state.x_pos-1][state.y_pos].signal == SPACE
|| state.matrix[state.x_pos-1][state.y_pos].signal == END){
//System.out.println("goup");
//保存值设为PASSED
next.cur_state.matrix[state.x_pos-1][state.y_pos].signal = PASSED;
//因为向上走了,所以行数减一,这里x不是横轴坐标,不要当成数轴了
next.cur_state.x_pos -=1;
//原来节点的索引保留下来,类似链表
next.pre_node = cur_node;//原来的
//记录动作
next.acton = Action.UP;
//记录开销
next.path_cost ++;
//如果上面能走通就直接返回了,代表一个动作的完成
return next;
}
}
- 算法主体
start:创建frontier容器,读取并保存状态,读入初始状态
repeat:
1.如果frontier为空,则返回无解(no solution)
2.从frontier移除一个node ps:为了避免死循环,我们还需要建立一个保存已探索节点的容器(explored node)
3.如果移除的这个node包含目标状态(goal state),则返回结果(result:从起始状态到终止状态的node序列)
4.否则调用扩展函数扩展node,将扩展的node加入frontier
移除node的算法
1.DFS(深度优先算法):frontier使用栈(Stack)结构存储节点
2.BFS(广度优先算法):frontier使用队列(Queue)结构存储节点
3.GBFS(Greedy Best_first Search):使用激发函数(Heuritic function,e.g:曼哈顿距离)衡量node移除优先级
4.A* Search:同时考虑g(n)+h(n)
h(n):Heuritic function g(n):cost
上代码:
while(true){
if(frontier.isEmpty()){
System.out.println("此迷宫无解(no solution)");
return;
}else{
//判断激发函数值
int heuritic = Integer.MAX_VALUE;
Node temp = new Node();
temp = frontier.get(0);
int flag = 0;
//遍历找出最小的激活值
for(int i =0; i < frontier.size(); i++){
int x = frontier.get(i).cur_state.x_pos;
int y = frontier.get(i).cur_state.y_pos;
int gn = frontier.get(i).cur_state.matrix[x][y].Manhattan_Distance;
int hn = frontier.get(i).path_cost;
if(heuritic > (hn + gn) || heuritic == (hn + gn)){
//更新更小的激活值
heuritic = (hn + gn);
flag = i;
}
}
//计算得出下一步要走的节点,并把它从frontier中移除
temp = frontier.get(flag);
frontier.remove(flag);
//System.out.println("弹出的是"+temp);
//完成了一步
count ++;
//把temp加入以探索节点
exploered.add(temp);
//如果这个状态是终点,返回result
if(temp.cur_state.x_pos == final_state.x_pos &&
temp.cur_state.y_pos == final_state.y_pos){
temp.cur_state.matrix[final_state.x_pos][final_state.y_pos].signal = END;
output(temp.cur_state.matrix, BARRIER);
System.out.println("\n");
System.out.println("AI通过" + count + "次尝试走出了迷宫");
System.out.println("有效路径如下:");
traceBack(temp);
return;
}else{//否则调用扩展,扩展可到达节点
Node n = new Node();
for(int i = Action.UP; i <= Action.RIGHT; i++){
n = result(temp, i);
//能走通,而且未探索过
if(n != null && !exploered.contains(n)){
frontier.add(n);
//System.out.println("加入了"+n);
}
}
}
}
}
- 曼哈顿距离
定义:
曼哈顿距离(Manhattan Distance)是由十九世纪的赫尔曼·闵可夫斯基所创词汇 ,是种使用在几何度量空间的几何学用语,用以标明两个点在标准坐标系上的绝对轴距总和。我们可以定义曼哈顿距离的正式意义为L1-距离或城市区块距离,也就是在欧几里德空间的固定直角坐标系上两点所形成的线段对轴产生的投影的距离总和。
计算:
例如在平面上,
坐标(x1,y1)的i点与坐标(x2,y2)的j点的曼哈顿距离为:
d(i,j)=|X1-X2|+|Y1-Y2|.
//计算整个状态图每个点的曼哈顿距离
public static void manhattanSet(State state, int x_goal, int y_goal){
int lenx = state.matrix.length;
//遍历数组,计算每一个格子的曼哈顿距离
for(int i = 0; i < lenx; i++){
for(int j =0; j < state.matrix[i].length; j++){
if(state.matrix[i][j] != null && state.matrix[i][j].signal !=BARRIER){
state.matrix[i][j].Manhattan_Distance = Math.abs(x_goal-i) + Math.abs(y_goal-j);
}
}
}
}
总结
这就是Astar实现的大概过程,讲的不够详细,可能有很多不清楚的地方,这里我把源码跟大家分享出来,有疑问的朋友可以随时找我讨论,有大佬能指出我的问题我也非常欢迎!
GitHub源码: https://github.com/DrTinker/GitHub 2020/4/22更新