本文首发于我的个人博客网站:https://www.xerrors.fun/A-star-demo/
也可在此页面交互演示
1. A* 算法的 JavaScript 实现
Codepen 在线编辑上面的演示程序
A* 算法是一种启发式搜索算法,这篇文章介绍的算是挺容易理解的了,本篇文章侧重于实战部分,理论部分参考上面那篇文章。
由于是需要做出交互界面,所以需要使用 JavaScript 来实现 A* 算法,实现起来难度不是很大,不过很少使用 JavaScript 的数据处理,所以很多时候还是要一边查一边写。最终功夫不负有心人,熬出来了这个页面。
这里就体现出来使用 vuepress 作为博客网站框架的优点了,我可以在文章里面写 Vue,文档和交互式页面放在一起,可以更加直观的理解算法。
对广度优先搜索和深度优先搜索比较熟悉的同学,理解 A* 算法还有一个角度;深度优先和广度优点的区别在于分别是使用栈和队列来存储节点以及下一个节点的信息,从头部存取节点就是深度优点搜索,分别从头部和尾部存取节点就是广度优先搜索。
那么对于A*算法而言,不是使用栈或者队列,而是使用优先队列,也就是每一次取的节点是所有节点中的最小的。(这里的小的概念根据不同的用途有差异)。
这里说明一下,很多教程都是使用的 open 和 close 表,这里 duck 不必,使用优先队列就可以很好的解决问题。
首先思考两个问题:
- 如何计算每个节点的大小
- 如何实现优先队列
解决了上面两个问题之后,A* 算法的实现思路就跟 DFS 和 BFS 没什么区别了;
如何计算每个节点的大小?没错,就是使用A*算法的核心,启发式搜索,F = G + H,简单的再说一下,F 是该节点的大小,它取决于两个部分,G 表示已经付出的成本,也就是已经走了多少步;H 表示预计还要付出多少成本,也就是估计还要走多少步。
如何实现优先队列?实现优先队列的理想办法就是使用堆排序啦!但是呢,JavaScript 里面我没有找到现成的库(如果有的话可以在下方留言告诉我),而且我也懒得去自己实现,所以本篇文章的脚本并没有使用性能更好的堆排序,而是选择直接使用 JS 自带的排序。
2. 解决八数码问题
八数码问题,只要是看了上面的演示应该知道是什么。在实现的时候是使用一个节点类来表示的,关于 JavaScript 类不是很了解的同学可以了解一下JavaSript 类 - MDN。而且跟网上很多教程不一样的一个地方在于我把可移动的那个点作为 9,纯粹是为了看着舒服 😃 ,而且为了可视化方便,每个节点的历史足迹也都保存在了 chain 属性里面。
class Node {
// 构造器
constructor(path, pre_chain, to) {
this.path = path; // 当前节点的路径
this.index = path.indexOf(9); // 当前节点可移动的点的位置
this.chain = Array.from(pre_chain)
this.chain.push(this.calcId(path))
this.to = to
}
……
}
下面就是解决**如何计算每个节点的大小?**我所使用的是 距离 = 已走的步数 + 各个点距离终点的曼哈顿距离之和;关于计算曼哈顿距离需要稍微理解一下,因为这里计算曼哈顿距离是在二维上面计算曼哈顿距离,但是我的路径是一个一维的数组,所以需要把以为坐标转换到二维坐标来操作。核心在于 += 那一行。
启发式搜索所得到