[算法学习]A星算法

一、适用场景

在一张地图中,绘制从起点移动到终点的最优路径,地图中会有障碍物,必须绕开障碍物。

二、算法思路

1. 回溯法得到路径

(如果有路径)采用“结点与结点的父节点”的关系从最终结点回溯到起点,得到路径。

2. 路径代价的估算:F = G+H

A星算法的代价计算使用了被称作是启发式的代价函数。

先说明一下各符号意义:G表示的是 ** 从起点到当前结点的实际路径代价 ** (为啥叫实际?就是已经走过了,边走边将代价计算好了);H表示 ** 当前结点到达最终结点的估计代价 ** (为啥叫估计?就是还没走过,不知道前面有没障碍、路通不通,所以只能用估计);F表示 ** 当前结点所在路径从起点到最终点预估的总路径代价 ** 。

G的计算方式:计算方式有挺多种的,这里我们就用这种吧,假设每个结点代表一个正方形,横竖移动距离:斜移动距离=1:1.4(根号2),我们取个整数10和14吧,也就是说当前结点G值=父节点的G+(10或14)。

H的计算方式:估价计算也有很多种方式,我们这里使用“曼哈顿”法,H=|当前结点x值-最终结点x值|+|当前结点y值-最终结点y值|(”||”表示绝对值)。

如下图(图不是自己做的,从网上借来的,自己画的话~…惨不忍睹!)

3. 辅助表:Open、Close列表

在A星算法中,需要使用两个辅助表来记录结点。

一个用于 ** 记录可被访问的结点 ** ,成为Open表;一个是 ** 记录已访问过的结点 ** ,称为Close表。

** 这两个表决定了算法的结束:条件是最终结点在Close表中(找到路径)或Open表为空(找不到了路径)。 **

4. 移动结点、相邻结点的处理

上面的理解的话,现在就来移动当前的节点,寻找路径。

每次从Open表中取出F值最小的结点出来(** 这里我们使用优先队列来处理比较好 ),作为当前结点;然后将当前结点的所有邻结点按照 邻结点规则 ** 加入到Open表中;最后将当前结点放入Close表中,这里就是每次循环的执行内容。

** 邻结点规则

(1) 当邻结点不在地图中,不加入Open表;

(2) 当邻结点是障碍物,不加入Open表;

(3) 当邻结点在Close表中,不加入Open表;

(4) 当邻结点不在Open中,加入Open表, 设该邻结点的父节点为当前结点

(5) 当邻结点在Open表中,我们需要做个比较:如果邻结点的G值>当前结点的G值+当前结点到这个邻结点的代价,那么修改该邻结点的父节点为当前的结点(因为在Open表中的结点除了起点,都会有父节点),修改G值=当前结点的G值+当前结点到这个邻结点的代价 **

蓝色框框表示在Close表中,绿色的框框表示在Open表中

最后回溯得到路径

三、代码实现(Java)

1. 输入

(1) 代表地图二值二维数组(0表示可通路,1表示路障)

  1. int[][] maps = {
  2. { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
  3. { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
  4. { 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0 },
  5. { 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0 },
  6. { 0, 0, 0, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0 },
  7. { 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0 },
  8. { 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0 }
  9. };

(2) 按照二维数组的特点,坐标原点在左上角,所以y是高,x是宽,y向下递增,x向右递增,我们将x和y封装成一个类,好传参,重写equals方法比较坐标(x,y)是不是同一个。

  1. public class Coord
  2. {
  3. public int x;
  4. public int y;
  5. public Coord(int x, int y)
  6. {
  7. this.x = x;
  8. this.y = y;
  9. }
  10. @Override
  11. public boolean equals(Object obj)
  12. {
  13. if (obj == null) return false;
  14. if (obj instanceof Coord)
  15. {
  16. Coord c = (Coord) obj;
  17. return x == c.x && y == c.y;
  18. }
  19. return false;
  20. }
  21. }

(3) 封装路径结点类,字段包括:坐标、G值、F值、父结点,实现Comparable接口,方便优先队列排序。

  1. public class Node implements Comparable<Node>
  2. {
  3. public Coord coord; // 坐标
  4. public Node parent; // 父结点
  5. public int G; // G:是个准确的值,是起点到当前结点的代价
  6. public int H; // H:是个估值,当前结点到目的结点的估计代价
  7. public Node(int x, int y)
  8. {
  9. this.coord = new Coord(x, y);
  10. }
  11. public Node(Coord coord, Node parent, int g, int h)
  12. {
  13. this.coord = coord;
  14. this.parent = parent;
  15. G = g;
  16. H = h;
  17. }
  18. @Override
  19. public int compareTo(Node o)
  20. {
  21. if (o == null) return -1;
  22. if (G + H > o.G + o.H)
  23. return 1;
  24. else if (G + H < o.G + o.H) return -1;
  25. return 0;
  26. }
  27. }

(4) 最后一个数据结构是A星算法输入的所有数据,封装在一起,传参方便。:grin:

  1. public class MapInfo
  2. {
  3. public int[][] maps; // 二维数组的地图
  4. public int width; // 地图的宽
  5. public int hight; // 地图的高
  6. public Node start; // 起始结点
  7. public Node end; // 最终结点
  8. public MapInfo(int[][] maps, int width, int hight, Node start, Node end)
  9. {
  10. this.maps = maps;
  11. this.width = width;
  12. this.hight = hight;
  13. this.start = start;
  14. this.end = end;
  15. }
  16. }

2. 处理

(1) 在算法里需要定义几个常量来确定:二维数组中哪个值表示障碍物、二维数组中绘制路径的代表值、计算G值需要的横纵移动代价和斜移动代价。

  1. public final static int BAR = 1; // 障碍值
  2. public final static int PATH = 2; // 路径
  3. public final static int DIRECT_VALUE = 10; // 横竖移动代价
  4. public final static int OBLIQUE_VALUE = 14; // 斜移动代价

(2) 定义两个辅助表:Open表和Close表。Open表的使用是需要取最小值,在这里我们使用Java工具包中的优先队列PriorityQueue,Close只是用来保存结点,没其他特殊用途,就用ArrayList。

  1. Queue<Node> openList = new PriorityQueue<Node>(); // 优先队列(升序)
  2. List<Node> closeList = new ArrayList<Node>();

(3) 定义几个布尔判断方法:最终结点的判断、结点能否加入open表的判断、结点是否在Close表中的判断。

  1. /**
  2. * 判断结点是否是最终结点
  3. */
  4. private boolean isEndNode(Coord end,Coord coord)
  5. {
  6. return coord != null && end.equals(coord);
  7. }
  8. /**
  9. * 判断结点能否放入Open列表
  10. */
  11. private boolean canAddNodeToOpen(MapInfo mapInfo,int x, int y)
  12. {
  13. // 是否在地图中
  14. if (x < 0 || x >= mapInfo.width || y < 0 || y >= mapInfo.hight) return false;
  15. // 判断是否是不可通过的结点
  16. if (mapInfo.maps[y][x] == BAR) return false;
  17. // 判断结点是否存在close表
  18. if (isCoordInClose(x, y)) return false;
  19. return true;
  20. }
  21. /**
  22. * 判断坐标是否在close表中
  23. */
  24. private boolean isCoordInClose(Coord coord)
  25. {
  26. return coord!=null&&isCoordInClose(coord.x, coord.y);
  27. }
  28. /**
  29. * 判断坐标是否在close表中
  30. */
  31. private boolean isCoordInClose(int x, int y)
  32. {
  33. if (closeList.isEmpty()) return false;
  34. for (Node node : closeList)
  35. {
  36. if (node.coord.x == x && node.coord.y == y)
  37. {
  38. return true;
  39. }
  40. }
  41. return false;
  42. }

(4) 计算H值,“曼哈顿” 法,坐标分别取差值相加

  1. private int calcH(Coord end,Coord coord)
  2. {
  3. return Math.abs(end.x - coord.x) + Math.abs(end.y - coord.y);
  4. }

(5) 从Open列表中查找结点

  1. private Node findNodeInOpen(Coord coord)
  2. {
  3. if (coord == null || openList.isEmpty()) return null;
  4. for (Node node : openList)
  5. {
  6. if (node.coord.equals(coord))
  7. {
  8. return node;
  9. }
  10. }
  11. return null;
  12. }

(6) 添加邻结点到Open表

  1. /**
  2. * 添加所有邻结点到open表
  3. */
  4. private void addNeighborNodeInOpen(MapInfo mapInfo,Node current)
  5. {
  6. int x = current.coord.x;
  7. int y = current.coord.y;
  8. // 左
  9. addNeighborNodeInOpen(mapInfo,current, x - 1, y, DIRECT_VALUE);
  10. // 上
  11. addNeighborNodeInOpen(mapInfo,current, x, y - 1, DIRECT_VALUE);
  12. // 右
  13. addNeighborNodeInOpen(mapInfo,current, x + 1, y, DIRECT_VALUE);
  14. // 下
  15. addNeighborNodeInOpen(mapInfo,current, x, y + 1, DIRECT_VALUE);
  16. // 左上
  17. addNeighborNodeInOpen(mapInfo,current, x - 1, y - 1, OBLIQUE_VALUE);
  18. // 右上
  19. addNeighborNodeInOpen(mapInfo,current, x + 1, y - 1, OBLIQUE_VALUE);
  20. // 右下
  21. addNeighborNodeInOpen(mapInfo,current, x + 1, y + 1, OBLIQUE_VALUE);
  22. // 左下
  23. addNeighborNodeInOpen(mapInfo,current, x - 1, y + 1, OBLIQUE_VALUE);
  24. }
  25. /**
  26. * 添加一个邻结点到open表
  27. */
  28. private void addNeighborNodeInOpen(MapInfo mapInfo,Node current, int x, int y, int value)
  29. {
  30. if (canAddNodeToOpen(mapInfo,x, y))
  31. {
  32. Node end=mapInfo.end;
  33. Coord coord = new Coord(x, y);
  34. int G = current.G + value; // 计算邻结点的G值
  35. Node child = findNodeInOpen(coord);
  36. if (child == null)
  37. {
  38. int H=calcH(end.coord,coord); // 计算H值
  39. if(isEndNode(end.coord,coord))
  40. {
  41. child=end;
  42. child.parent=current;
  43. child.G=G;
  44. child.H=H;
  45. }
  46. else
  47. {
  48. child = new Node(coord, current, G, H);
  49. }
  50. openList.add(child);
  51. }
  52. else if (child.G > G)
  53. {
  54. child.G = G;
  55. child.parent = current;
  56. // 重新调整堆
  57. openList.add(child);
  58. }
  59. }
  60. }

(7) 回溯法绘制路径

  1. private void drawPath(int[][] maps, Node end)
  2. {
  3. if(end==null||maps==null) return;
  4. System.out.println("总代价:" + end.G);
  5. while (end != null)
  6. {
  7. Coord c = end.coord;
  8. maps[c.y][c.x] = PATH;
  9. end = end.parent;
  10. }
  11. }

(8) 开始算法,循环移动结点寻找路径,设定循环结束条件,Open表为空或者最终结点在Close表

  1. public void start(MapInfo mapInfo)
  2. {
  3. if(mapInfo==null) return;
  4. // clean
  5. openList.clear();
  6. closeList.clear();
  7. // 开始搜索
  8. openList.add(mapInfo.start);
  9. moveNodes(mapInfo);
  10. }
  11. /**
  12. * 移动当前结点
  13. */
  14. private void moveNodes(MapInfo mapInfo)
  15. {
  16. while (!openList.isEmpty())
  17. {
  18. if (isCoordInClose(mapInfo.end.coord))
  19. {
  20. drawPath(mapInfo.maps, mapInfo.end);
  21. break;
  22. }
  23. Node current = openList.poll();
  24. closeList.add(current);
  25. addNeighborNodeInOpen(mapInfo,current);
  26. }
  27. }

附:源码地址:点击这里

来自为知笔记(Wiz)

时间: 2024-08-19 10:11:22

[算法学习]A星算法的相关文章

算法学习笔记 KMP算法之 next 数组详解

最近回顾了下字符串匹配 KMP 算法,相对于朴素匹配算法,KMP算法核心改进就在于:待匹配串指针 i 不发生回溯,模式串指针 j 跳转到 next[j],即变为了 j = next[j]. 由此时间复杂度由朴素匹配的 O(m*n) 降到了 O(m+n), 其中模式串长度 m, 待匹配文本串长 n. 其中,比较难理解的地方就是 next 数组的求法.next 数组的含义:代表当前字符之前的字符串中,有多大长度的相同前缀后缀,也可看作有限状态自动机的状态,而且从自动机的角度反而更容易推导一些. "前

A*搜寻算法(A星算法)

A*搜寻算法[编辑] 维基百科,自由的百科全书 本条目需要补充更多来源.(2015年6月30日) 请协助添加多方面可靠来源以改善这篇条目,无法查证的内容可能会被提出异议而移除. A*搜索算法,俗称A星算法.这是一种在图形平面上,有多个节点的路径,求出最低通过成本的算法.常用于游戏中的NPC的移动计算,或在线游戏的BOT的移动计算上. 该算法综合了BFS(Breadth First Search)和Dijkstra算法的优点:在进行启发式搜索提高算法效率的同时,可以保证找到一条最优路径(基于评估函

经典算法学习之贪心算法

贪心算法也是用来求解最优化问题的,相比较动态规划很多问题使用贪心算法更为简单和高效,但是并不是所有的最优化问题都可以使用贪心算法来解决. 贪心算法就是在每个决策点都做出在当时看来最佳的选择. 贪心算法的设计步骤: 1.将最优化问题转换为:对其做出一次选择之后,只剩下一个问题需要求解的形式(动态规划会留下多个问题需要求解) 2.证明做出贪心选择之后,原问题总是存在最优解,即贪心算法总是安全的 3.证明做出贪心选择后,剩余的子问题满足性质:其最优解与贪心选择组合即可得到原问题的最优解,这样就得到了最

[算法学习笔记]排序算法——堆排序

堆排序 堆排序(heapsort)也是一种相对高效的排序方法,堆排序的时间复杂度为O(n lgn),同时堆排序使用了一种名为堆的数据结构进行管理. 二叉堆 二叉堆是一种特殊的堆,二叉堆是完全二叉树或者是近似完全二叉树.二叉堆满足堆特性:父节点的键值总是保持固定的序关系于任何一个子节点的键值,且每个节点的左子树和右子树都是一个二叉堆. 如上图显示,(a)是一个二叉堆(最大堆), (b)是这个二叉堆在数组中的存储形式. 通过给个一个节点的下标i, 很容易计算出其父节点,左右子节点的的下标,为了方便,

算法学习之排序算法(三)(选择排序法)

1.引言 选择排序工作原理是每一次从待排序的数据元素中选出最小(或最大)的一个元素,存放在序列的起始位置,直到全部待排序的数据元素排完. 选择排序是不稳定的排序方法.选择排序是和冒泡排序差不多的一种排序.和冒泡排序交换相连数据不一样的是,选择排序只有在确定了最小的数据之后,才会发生交换.怎么交换呢?我们可以以下面一组数据作为测试: 2, 1, 5, 4, 9 第一次排序:1, 2, 5, 4, 9 第二次排序: 1, 2, 5, 4, 9 第三次排序: 1, 2, 4, 5, 9 第四次排序:

算法学习之排序算法:插入排序(直接插入排序、折半插入排序、2-路插入排序)

引言: 插入排序作为最简单易于理解的排序算法,基本实现比较简单.本文详细介绍直接插入排序,并给出实现,简单的介绍折半插入排序,并给出2-路插入排序和表插入排序两种插入排序,但并未给出具体实现. 一.直接插入排序 直接插入排序的基本操作是将一个记录插入到已排好序的有序表中,从而得到一个新的.记录数增1的有序表. 算法描述: 步骤1.将待排序的一组记录中的第1个记录拿出来作为一组有序的记录(当然此时该组记录仅有1个记录). 步骤2.依次将待排序的一组记录中的记录拿出来插入到前面已排好序的记录中. 步

寻路算法之A星算法

核心算法 1 package cn.liushaofeng.algorithm; 2 3 import java.util.ArrayList; 4 import java.util.List; 5 6 /** 7 * A Star Algorithm 8 * @author liushaofeng 9 * @date 2015-8-24 下午11:05:48 10 * @version 1.0.0 11 */ 12 public class AstarAlgorithm 13 { 14 pri

算法学习之查找算法:静态查找表(1)顺序表查找

引言: 对查找表一般的操作有:1.查询某个"特定的"数据元素是否在查找表中:2.检索某个"特定的"数据元素的各种属性:3.在查找表中插入一个数据元素:4.从查找表中删去某个数据元素. 静态查找表的操作只包括两种:1.查找某个"特定的"数据元素是否在查找表中:2.检索某个"特定的"数据元素的各种属性: 静态查找表又有四种表现形式:顺序表的查找.有序表的查找.静态树的查找.索引顺序表的查找. 静态查找涉及的关键字类型和数据元素类型

算法学习之排序算法:堆排序

要了解堆排序,首先要了解堆的概念,因为本文主要研究堆排序的算法,此处对数据结构堆只是给出概念:n个元素的序列{k1,k2,...kn},当且仅当满足如下关系时,称之为堆. k[i] <= k[2i]且k[i] <= k[2i+1] (或 k[i] >= k[2i]且k[i] >= k[2i+1]) 比如:序列96.83.27.38.11.09(或12.36.24.85.47.30.53.91)都是堆. 如果将堆对应的一维数组看成是一个二叉树,则堆的含义表明:完全二叉树中所有非终端结