@

前些日子我有兄弟给我打电话,问我会不会人工智能,来实现一个机器人在仓库自动寻路的功能。因为没有接触过这样的场景,但是自己又比较对此兴趣,所以就看了一些自动寻路的算法,比如:基于二叉树的深度优先遍历、D Star、A Star算法,其中我感觉A Star算法最好。下面我给大家介绍一下,首次实现的语言是Java,但是Java不太直观,又不想使用Java的图形界面,所以就使用JS+HTML来实现的,首先展示一下效果图。

效果图如下:

1、什么是A Start算法

A*搜索算法是求出在一个二维平面中从起点到终点最低运算代价的算法,它可以算出A点B点的最短距离,也就是最优路径。常见的应有主要是在游戏中,人物的自动寻路;机器人探路;交通路线导航等。

2、A Star算法的原理和流程

2.1 前提

在讲述A Star算法之前,需要声明下列这些属性:

(1)从起点开始扩散的节点;

(2)最短距离计算公式:F = G + H;

(3)欧几里得距离计算公式:p = $\sqrt (x_2 - x_1)^2+(y_2 - y_1)^2$(其实就是勾股定理);

(4)OPENLIST 和 CLOSELIST;

上面的属性和公式不懂没关系,下面我会对他们一一进行详细介绍。非常简单!

2.1.1 从起点开始扩散的节点

我们在HTML页面上使用横和 竖画出来的格子。所谓扩散就是以 起点 为基点向上、下、左、右四个放向进行扩散,这些扩展的节点就是可以走的“路”。如下图所示黄色的方格就是扩散的点:

PS:A Star有四个方向和八个方向的扩散。扩展四个方向的节点就是目前我们所说的;八个方向是还包含了,上左、上右、下左、下右四个方向的节点。我们通篇使用的是四个方向的扩展。

2.1.2 最短距离计算公式:F = G + H

如何在扩散的节点中找到最优也就是最短的一条路呢?就需要用到这个公式:F=G+H。那么这个公式里面的属性都代表什么意识呢?下面我们就说明一下:

(1)G:

表示从起点到扩散的四个节点的距离,换句话说就是从起点到扩散的四个节点需要移动的格子。G的值可以使用欧几里得距离计算公式进行计算。

如下图:

(2)H:

表示从起点开始,到终点需要移动的格子(注意:忽略障碍物,可以从障碍物中穿过去),这个距离需要通过 欧几里得距离计算公式 公式算出来。当然你没必要一定要使用欧几里得距离计算公式,你还可以单纯的将起点终点的x报坐标差值和y坐标差值进行相加即可。

如下图:



(3)F:

F = G + H,在扩散节点中F的值最小的就是我们需要走的节点。也就是最短的路径。

2.1.3 欧几里得距离计算公式

这个公式是用来计算H和G的。公式:p = $\sqrt (x_2 - x_1)^2+(y_2 - y_1)^2$ 。其实就是终点的x坐标减去起点的x坐标的平方 + 终点的y坐标减去起点的y坐标的平方 开根号,这不就是勾股定理嘛。

2.1.4 OPENLIST 和 CLOSELIST

OPENLIST和CLOSELIST代表两个“容器”(“容器”在代码中就是两个集合,使用List集合或者数组声明都可以)。这个两个“容器”存放的内容和作用如下:

(1)OPENLIST

用于存储扩散的节点。刚开始由起点开始向四个方向扩散的节点就需要放到OPENLIST集合中(如果扩散的节点是障碍物或者是在CLOSELIST中已经存在则不放入)。OPENLIST是主要遍历的集合,计算F值的节点都是来自这个集合。

(2)CLOSELIST

用于存储起点障碍物节点走过的点。在扩散节点的时候,需要到CLOSELIST集合中去检查,如果扩散的节点D已经在CLOSELIST集合中了(根据坐标进行判断),或者是D节点是障碍物那么就跳过此节点。走过的点也需要放到CLOSELIST中去。

走过的点如下图所示:

2.2 流程

2.2.1 第一步:扩散

从起点开始向上下左右扩散四个节点。假如起点的坐标为(x:3,y:2),那么四个节点为:上(x:2,y:2)、下(x:4,y:2)、左(x:3,y:1)、右(x:3,y:3)。

2.2.2 第二步:检查节点

遍历CLOSELIST集合,判断扩散的这四个节点是否存在于CLOSELIST或者OPENLIST中,如果不存在放到OPENLIST中,反之跳过该节点。如果该节点是障碍物也需要跳过。

2.2.3 第三步:计算F的值

遍历OPENLIST集合,并计算集合中节点的F的值,找出F值为最小的节点(距离最近)minNode,这个节点就是要走的节点。然后把除了mindNode之外的其它扩散的节点放入到CLOSELIST中。

2.2.4 第四步:改变起点的位置

通过第三步我们找到了F值最小的一个节点minNode,那么就把起点等于minNode。然后继续进行扩散重复上面的四个步骤,直至在扩散的节点中包含终点我们走过的节点就是最短路径。

3.A Star算法代码实现

Java代码我就不放出来了,如果想要的可以评论区留言。下面是用JS写的,本人在其他文章里面也说过我的JS就是二半吊子(但是注释写的多)。写的不好的地方大家指出,我会即时更正!

HTML:

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>寻路02</title>
<style>
*{
margin: 0;
padding: 0;
}
#con{
width: 100%;
height: 100%;
}
body{
font-size: 0px;
}
#map{
width: 800px;
height: 800px;
/*border: 1px gray solid;*/
margin-top: 20px;
border-radius: 5px;
/*background-image: url("store/grass.png");*/
}
.square {
width: 40px;
height: 40px;
border: 1px gray solid;
/*background-image: url("store/tree01.png");*/
display: inline-block;
box-sizing:border-box;
color: red;
}
.roadblock{
width: 1px;
height: 1px;
border: 1px black solid;
background-color: black;
}
.p{
color: red;
margin: 0px;
padding: 0px;
display: inline-block;
}
</style>
</head>
<body>
<div id="con">
<button onclick="drawOther();">随机生成障碍物</button>
<button onclick="startFindWay()">开始寻路</button>
<button onclick="stop()">停止</button>
<div id="map">
</div>
</div>
</body>
</html>

JS:

 <script>
window.onload=function () {
init();
drawMAP();
}
//地图div
var bg = document.querySelector('#map');
//开放集合
var openList=[];
//闭合集合
var closeList=[];
//起点
var startNode={};
//终点
var endNode = {};
//在由当前节点扩散的节点中 F值最小的一个节点
// count是一个计数器,用来判断是否进入了死胡同
var minF = {topNode:'', F:'' ,G:'',H:'',x:'',y:''};
//当期节点
var currentNode = {};
//绘制地图
function drawMAP() {
for(var i = 0 ; i < 20; i++){
for(var j = 0; j < 20;j ++ ){
var div = document.createElement('div');
div.className = 'square'
var p = document.createElement('p');
p.className = 'p';
p.innerHTML='('+i+','+j+')';
div.append(p)
div.id = i+'-'+j;
bg.append(div);
}
}
}
//初始化
function init() {
//添加起点和终点
startNode.x=1;
startNode.y=2;
startNode.des='start';
endNode.x =15;
endNode.y = 8;
endNode.des='end';
//添加障碍物
openList.push(startNode);
//将当前节点设置为startNode
currentNode = startNode;
}
//绘制障碍物、起点、终点
function drawOther() {
//绘制起点
var idStart = startNode.x+'-'+startNode.y;
document.getElementById(idStart).style.backgroundColor='red';
//绘制终点
var idEnd = endNode.x +'-'+endNode.y;
document.getElementById(idEnd).style.backgroundColor='blue';
randCreatBlock();
}
//随机生成障碍物
function randCreatBlock() {
for (let i = 0; i < 100; i++) {
var x = Math.floor(Math.random()*(20));
var y = Math.floor(Math.random()*(20));
if ( x == startNode.x && y == startNode.y) {return ;}
if(x == endNode.x && y == endNode.y){return ;}
var point = x+'-'+y;
document.getElementById(point).style.backgroundColor = 'black';
var node = {x:x,y:y};
closeList.push(node);
}
}
//寻路
function findWay() {
//扩散上下左右四个节点
var up ={topNode:'', F:'',G:'',H:'',x:currentNode.x-1,y:currentNode.y};
var down ={topNode:'', F:'',G:'',H:'',x:currentNode.x+1,y:currentNode.y};
var left ={topNode:'', F:'',G:'',H:'',x:currentNode.x,y:currentNode.y-1};
var right ={topNode:'', F:'',G:'',H:'',x:currentNode.x,y:currentNode.y+1};
//检查这些扩散的节点是否合法,如果合法放到openlist中
checkNode(up);
checkNode(down);
checkNode(left);
checkNode(right);
//移除已扩散完毕的节点移除,并放到closeList中去
removeNode();
//计算F
computersF(); }
function checkNode(node) {
//校验扩散的点是否超过了地图边界
if(node.x<0||node.y<0){
return ;
}
//如果node存在closelist中则忽略
for (let i = 0; i < closeList.length; i++) {
if (closeList[i].x == node.x && closeList[i].y == node.y) {
return;
}
}
for (let i = 0; i <openList.length; i++) {
if(openList[i].x==node.x&&openList[i].y==node.y){
return;
}
}
if(node.topNode == '' ||node.topNode == null){
node.topNode = currentNode;
}
//如果扩散的这些节点 一个也没有存到openList中,那么说明进入了死胡同
openList.push(node);
changeColor(node.x,node.y,'k');
}
//改变颜色
function changeColor(x,y,desc) {
var id = x+'-'+y;
if(desc == 'k'){
document.getElementById(id).style.backgroundColor = 'yellow';
}
if(desc == 'r'){
document.getElementById(id).style.backgroundColor = 'pink';
}
}
//计算FGH
function computersF() {
var x = endNode.x;
var y = endNode.y;
for (let i = 0; i < openList.length; i++) {
//计算H
var hx = parseInt(x) - parseInt(openList[i].x);
if(hx<0){
hx = -(parseInt(x) - parseInt(openList[i].x));
}
var hy = parseInt(y) - parseInt(openList[i].y);
if(hy<0){
hy = -(parseInt(y) - parseInt(openList[i].y));
}
var H = hx+hy;
openList[i].H= H;
//计算G
var G = Math.sqrt(Math.floor(Math.pow(parseInt(currentNode.x) - parseInt(openList[i].x),2))+
Math.floor(Math.pow(parseInt(currentNode.y) - parseInt(openList[i].y),2)));
openList[i].G= G;
//计算F
var F = G + H;
openList[i].F = F;
if(minF.F==''){
minF = openList[i];
}else {
if(minF.F>F){
minF = openList[i];
}
}
}
//201和204行代码把openList赋值给了minF,openList并没有定义count,count为undefined
// 所以需要判断
if(minF.count==undefined){
minF.count = 0;
}
minF.count++;
console.log(this.minF.count);
//将当前节点设置为F最小的节点
currentNode = minF; if(minF.count!=undefined&&minF.count>1){
//说明进入了死胡同
//1.将此节点放到closeList中
this.removeNode();
//2.在openList中去寻找 仅次于 此节点(进入死胡同)的其它节点
var minFSecond = openList[0];
for (let i = 0; i < openList.length; i++) {
console.log(openList[i])
if(minFSecond.F>=openList[i].F){
minFSecond = openList[i];
}
}
if(minFSecond.count==undefined){
minFSecond.count = 0;
}
minF = minFSecond;
currentNode = minFSecond;
console.log(currentNode);
}
//并将当前节点的颜色变为红色
var id= currentNode.x +'-'+currentNode.y;
document.getElementById(id).style.backgroundColor='red';
}
//移除节点
function removeNode() {
var index = openList.indexOf(currentNode);
openList.splice(index,1);
closeList.push(currentNode);
//并将当前节点的颜色改变
changeColor(currentNode.x,currentNode.y,'r');
}
var myStart;
// 启动
function startFindWay(){
myStart = setInterval(function startFindWay() {
findWay();
if(minF.x===endNode.x&&minF.y===endNode.y){
clearInterval(myStart);
return;
}
},100);
}
//停止
function stop(){
clearInterval(myStart);
}
</script>

4. 结语

A Star扩散四个方向的算法就这些。如果有时间可以把扩散八个方向的总结一下。写这篇文章我想向大家提供的是A Star算法的过程与实现的思路,但是如果想要真正运用还需要考虑很多东西,要贴合自己的场景。上面的代码还有问题。希望大家指出来,我会即时更正。还有什么不懂的可以在评论区讨论。

路漫漫其修远兮,吾将上下而求索

使用A Star 算法实现自动寻路详解的更多相关文章

  1. JVM垃圾回收算法及回收器详解

    引言 本文主要讲述JVM中几种常见的垃圾回收算法和相关的垃圾回收器,以及常见的和GC相关的性能调优参数. GC Roots 我们先来了解一下在Java中是如何判断一个对象的生死的,有些语言比如Pyth ...

  2. 【机器学习】【条件随机场CRF-2】CRF的预测算法之维特比算法(viterbi alg) 详解 + 示例讲解 + Python实现

    1.CRF的预测算法条件随机场的预测算法是给定条件随机场P(Y|X)和输入序列(观测序列)x,求条件概率最大的输出序列(标记序列)y*,即对观测序列进行标注.条件随机场的预测算法是著名的维特比算法(V ...

  3. c++ LeetCode(初级数组篇)十一道算法例题代码详解(一)

    原文作者:aircraft 原文链接:https://www.cnblogs.com/DOMLX/p/10940636.html 唉!最近忙着面试找实习,然后都是面试的很多是leetcode的算法题, ...

  4. 最短路径Floyd算法【图文详解】

    Floyd算法 1.定义概览 Floyd-Warshall算法(Floyd-Warshall algorithm)是解决任意两点间的最短路径的一种算法,可以正确处理有向图或负权的最短路径问题,同时也被 ...

  5. KMP算法 Next数组详解

    题面 题目描述 如题,给出两个字符串s1和s2,其中s2为s1的子串,求出s2在s1中所有出现的位置. 为了减少骗分的情况,接下来还要输出子串的前缀数组next.如果你不知道这是什么意思也不要问,去百 ...

  6. Dijkstra算法之 Java详解

    转载:http://www.cnblogs.com/skywang12345/ 迪杰斯特拉算法介绍 迪杰斯特拉(Dijkstra)算法是典型最短路径算法,用于计算一个节点到其他节点的最短路径. 它的主 ...

  7. $PollardRho$ 算法及其优化详解

    \(PollardRho\) 算法总结: Pollard Rho是一个非常玄学的算法,用于在\(O(n^{1/4})\)的期望时间复杂度内计算合数n的某个非平凡因子(除了1和它本身以外能整除它的数). ...

  8. Kruskal算法 - C语言详解

    最小生成树 在含有n个顶点的连通图中选择n-1条边,构成一棵极小连通子图,并使该连通子图中n-1条边上权值之和达到最小,则称其为连通网的最小生成树.  例如,对于如上图G4所示的连通网可以有多棵权值总 ...

  9. SHA1算法实现及详解

    1 SHA1算法简介 安全哈希算法(Secure Hash Algorithm)主要适用于数字签名标准(Digital Signature Standard DSS)里面定义的数字签名算法(Digit ...

随机推荐

  1. [刷题] 198 House Robber

    要求 你是一个小偷,每个房子中有价值不同的宝物,但若偷连续的两栋房子,就会触发报警系统,求最多可偷价值多少的宝物 示例 [3,4,1,2],返回6[3,(4),1,(2)] [4,3,1,2],返回6 ...

  2. 查看 swappiness 值

    Swap的使用频率  发表于 2017-06-02 |  分类于 Linux |  评论数: 通过调整swappiness的值, 可以调整系统使用 swap 的频率 该值越小, 表示越大限度的使用物理 ...

  3. 强哥PHP面向对象学习笔记

    面向对象编程OOP目标:重用性.灵活性.扩展性特点:封装.继承.多态 类的书写方法:class PersionName{} 特征:属性.其实就是变量行为:方法.其实就是函数 1.实例化对象2.对象中成 ...

  4. Jenkins远程代码执行漏洞

    于一个月前,进行服务器巡检时,发现服务器存在不明进程,并且以Jenkins用户身份来运行.当时进行了处理并修复了漏洞.在此补上修复过程 第一反应是Jenkins存在漏洞,于是Google Jenkin ...

  5. win10家庭版升级 到win10企业版

    成功升级3小时  20200124 拿到电脑 win10家庭版 不会用 找admin都找不到只能用企业版 升级win10家庭版 到win10企业版 在msdn下载win10企业版iso iso 文件管 ...

  6. 嵌入式Boa服务器上CGI开发-(转自Bryce.Xiao)

    嵌入式WEB服务器常见的有lighttpd shttpd thttpdboa mathopd minihttpdappwebgoahead=============================== ...

  7. 【转】Spring_IOC学习

    原文地址:http://github.thinkingbar.com/spring/ 一.XML文件语法的知识点 对于XML没有提示的话,在Eclipse中搜索XML catalog设置.对于XML文 ...

  8. Centos6.5 修改主机名(hostname)

    centos6需要修改两处:一处是/etc/sysconfig/network,另一处是/etc/hosts,只修改任一处会导致系统启动异常.首先切换到root用户. /etc/sysconfig/n ...

  9. SystemVerilog数组(一)

  10. fragment不适用binding的加载视图方法

    abstract class BaseFragment :Fragment(){ override fun onCreateView( inflater: LayoutInflater, contai ...