zoukankan      html  css  js  c++  java
  • 对A-Star寻路算法的粗略研究

    首先来看看完成后的效果:

    其中灰色代表路障,绿色是起点和移动路径,红色代表终点

     

    为了继续学习,需要明白几个概念。

    曼哈顿距离

     曼哈顿距离的定义是,两个物体南北方向的距离与东西方向的距离之和。看起来就好像是直角三角形的两条边之和。

    用代码表示出来就是:

    /**
     * 计算两点间的曼哈顿距离
     * @param goalNode  {Object} 终点坐标
     * @param startNode {Object} 起点坐标
     * @returns {number} 两点间的曼哈顿距离
     */
    function Manhattan(goalNode,startNode) {
        return Math.abs(goalNode.x - startNode.x) + Math.abs(goalNode.y - startNode.y);
    }
    View Code

    公式F=G+H

    G:从起点开始,沿着计算出的可能路径,移动到该路径上的移动消耗。

    H:计算出的可能路径到终点的移动预估消耗。

    F:G与H之和。

    以下图来说明:

    起点S周围有四个可选路径a、b、c、d(为简单起见,不考虑对角线也可行走的情况),先来看路径a。

    从起点S到达a的移动耗费是1格,故G=1。而从a到达终点G的移动耗费估算是5格,故H=5。F是G与H的值相加,为6。

    经过观察,a、b、c三个路径的F值是一样的。而d路径的F值为4。可见F值越小,到达终点的花费越少,因此应该选择d路径作为下一步。

    到达d路径后,重复前面的过程,搜索周围的路径,找到F值最小的作为下一步,同时将这个路径作为新的起始点。因为接下来每个路径的F值

    都是参照这个新起始点来计算的。

    由上图可知,S通往G的最佳路径是d、e、f。

    但是还有一种情况,比如下图(灰色表示路障,无法通行):

    e和f的F值是一样的,这时候选择哪个呢?其实都可以,一般选择最后一个被计算出来的路径即可。

    具体实现

    上述方法虽然可行,但是如果不加以限制,会造成一些不良后果。当从起点S到达新路径d时,d仍需要对周围的路径进行探索,起点S也将包含其中,很显然这是不必要而且浪费的。对此,我们需要维护两个列表,一个称为路径开启列表,一个称为路径关闭列表。

    var open = [];  //开启列表
    var close = [];  //关闭列表

    open列表的职责是探索周围路径时,将可通行的路径(无路障的路径)加入列表中;

    close列表则负责将各个新起点加入其中(相应的这些新起点也要从open列表中移除),下一次执行路径探索时,如果该路径存在这个列表中,则忽略它。

    当终点G被包含在close列表中时,搜索结束。

    需要注意的是,为每个新起点标记它的父结点,即它是从哪里过来的(比如d路径的父结点就是S,e的父结点是d),这样在到达终点G时,就能够根据它的父结点

    一级一级地返回,从而找到这条“通路”的所有坐标,有点像链表这种数据结构。

    完整代码:

    html部分

    <!doctype html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>a-star</title>
        <style>
            body{
                margin: 0;
                font-size:12px;
            }
            table{border-collapse: collapse;
                width: 100%; table-layout: fixed}
            table th,table td{border:1px solid #000}
            #t{
                width: 831px;
            }
            #t td{
                width: 30px;
                height: 30px;
                text-align: center;
            }
            .start{background-color: #00fc5f}
            .block{background-color:#cacaca}
            .goal{background-color: #ff2211}
            .visited{background-color: #009921;}
        </style>
        <script src="../jquery-2.1.3.js"></script>
    </head>
    <body>
    <input id="start" type="button" value="开始寻路"/>
    <script src="a-star.js"></script>
    <script>
        $('#start').bind('click',function() {
            move(start,end);
        });
    </script>
    </body>
    </html>
    View Code

    js部分

      1 var open = [],  //开启列表
      2     close = [], //关闭列表
      3     start = {}, //起点坐标
      4     end = {};   //终点坐标
      5 var d = document;
      6 
      7 /**
      8  * 检查待测坐标是否在坐标集合内
      9  * @param toBeCheckNode {Object} 待检查坐标 {x,y}
     10  * @param sourceNode {Array} 坐标集合
     11  * @returns {boolean} 待测坐标是否在坐标集合内
     12  */
     13 function isNodeExists(toBeCheckNode,sourceNode) {
     14     for(var i in sourceNode) {
     15         if (sourceNode.hasOwnProperty(i)) {
     16             if (parseInt(toBeCheckNode.x) === sourceNode[i].x && parseInt(toBeCheckNode.y) === sourceNode[i].y) return true;
     17         }
     18     }
     19     return false;
     20 }
     21 
     22 /**
     23  * 返回数组中的某个元素
     24  * @param el 待返回元素
     25  * @param arr 数组
     26  * @returns {Object} 返回该元素
     27  */
     28 function getElementInArray(el,arr) {
     29     for(var i in arr) {
     30         if(arr.hasOwnProperty(i)) {
     31             if(parseInt(el.x) === arr[i].x && parseInt(el.y) === arr[i].y) {
     32                 return arr[i];
     33             }
     34         }
     35     }
     36     return null;
     37 }
     38 
     39 /**
     40  * 计算两点间的曼哈顿距离
     41  * @param goalNode  {Object} 终点坐标
     42  * @param startNode {Object} 起点坐标
     43  * @returns {number} 两点间的曼哈顿距离
     44  */
     45 function Manhattan(goalNode,startNode) {
     46     return Math.abs(goalNode.x - startNode.x) + Math.abs(goalNode.y - startNode.y);
     47 }
     48 
     49 /**
     50  * 选择最佳路径作为新起始点
     51  * @param openArray {Array} 开启列表
     52  * @returns {Object} 返回新起始点
     53  */
     54 function selectNewStart(openArray) {
     55     var minNode = openArray[0],i;
     56     for(i = 0,len = openArray.length - 1; i < len; i++) {
     57         if(minNode.F >= openArray[i+1].F) {
     58             minNode = openArray[i+1];
     59         }
     60     }
     61     start = minNode;
     62     //将新开始点加入关闭列表
     63     close.push(start);
     64 
     65     //将新开始点从开启列表中移除
     66     for(i = 0; i < openArray.length; i++) {
     67         if(minNode.x === openArray[i].x && minNode.y === openArray[i].y) {
     68             openArray.splice(i,1);
     69             break;
     70         }
     71     }
     72     return start;
     73 }
     74 
     75 /**
     76  * 遍历周围节点并加入开启列表
     77  * @param node {Object} 一个起始点
     78  */
     79 function searchAround(node) {
     80     for(var i = -1; i <= 1;i++) {
     81         for(var j = -1; j <= 1; j++) {
     82             var x = node.x + i,
     83                 y = node.y + j;
     84             //判断是否为有效的路径点
     85             var nodeExsits = findCurrentPositionInfo(x,y) != null;
     86             if(!nodeExsits) continue;
     87             var t = parseInt(findCurrentPositionInfo(x,y).getAttribute('type'));
     88 
     89             if(!(x !== node.x && y !== node.y)) {
     90                 if(x!== node.x || y !== node.y) {
     91                     var curNode = {x:x,y:y,type:t};
     92 
     93                     //如果该坐标无法通行,则加入关闭列表中
     94                     if(curNode.type === 4 ||
     95                         curNode.type === 0 ||
     96                         curNode.type === 44) {
     97                         if(isNodeExists(curNode,close)) continue;
     98                         close.push(curNode);
     99                     }
    100 
    101                     //如果该坐标已在关闭列表中,略过
    102                     if(isNodeExists(curNode,close)) continue;
    103 
    104                     //如果该坐标已在开启列表中,则重新计算它的G值
    105                     if(isNodeExists(curNode,open)) {
    106                         var new_GValue = Manhattan(curNode,start),
    107                             //在开启列表中取出这个元素
    108                             inOpenNode = getElementInArray(curNode,open),
    109                             //取出旧的G值
    110                             old_GValue = inOpenNode.G;
    111 
    112                         //如果G值更小,则意味着当前到达它的路径比上一次的好,更新它的父结点
    113                         //以及G值,并重新计算它的F值
    114                         if(new_GValue < old_GValue) {
    115                             inOpenNode.parent = start;
    116                             inOpenNode.G = new_GValue;
    117                             inOpenNode.F = inOpenNode.G + inOpenNode.H;
    118                         }
    119                         continue;
    120                     }
    121 
    122 
    123                     //设置父节点
    124                     curNode.parent = {x:node.x,y:node.y};
    125                     curNode.G = Manhattan(curNode,node);
    126                     curNode.H = Manhattan(end,curNode);
    127                     //估算值
    128                     curNode.F = curNode.G + curNode.H;
    129                     //将坐标加入开启列表中
    130                     open.push(curNode);
    131                 }
    132             }
    133         }
    134     }
    135 }
    136 
    137 
    138 function findCurrentPositionInfo(x, y) {
    139     var tds = $('td'),
    140         s = x + "," + y;
    141     for(var i = 0; i < tds.length; i++) {
    142         if(tds[i].innerHTML === s) return tds[i];
    143     }
    144     return null;
    145 }
    146 
    147 
    148 function generateMap() {
    149     var t = d.createElement('table');
    150     t.id = 't';
    151     d.body.appendChild(t);
    152 
    153     var html = '';
    154     for(var i = -3; i < 10; i++) {
    155         html += '<tr>';
    156         for(var j = -3; j < 15; j++) {
    157             if(i === 0 && j === 0) {
    158                 html += '<td class="start" type="1">'+j+','+i+'</td>';
    159             } else {
    160                 html += '<td type="1">'+j+','+i+'</td>';
    161             }
    162         }
    163         html += '</tr>';
    164     }
    165     t.innerHTML = html;
    166 }
    167 
    168 function addStone() {
    169     for(var i = 0; i < 50; i++) {
    170         var r = Math.ceil(Math.random() * 233);
    171         if(r === 57) continue;
    172         var res = tdCollections.eq(r).addClass('block');
    173         res.attr('type',0);
    174     }
    175 }
    176 
    177 function setGoal() {
    178     var r = Math.ceil(Math.random() * 233);
    179     if(r === 57 || tdCollections.eq(r).hasClass('block')) return setGoal();
    180     var res = tdCollections.eq(r).addClass('goal');
    181 
    182     //var res = tdCollections.eq(24).addClass('goal');
    183 
    184     return {
    185         x:res.html().split(',')[0],
    186         y:res.html().split(',')[1]
    187     }
    188 }
    189 
    190 function setColor(start) {
    191     var x = start.x,
    192         y = start.y,
    193         el = findCurrentPositionInfo(x,y);
    194 
    195     $(el).addClass('visited');
    196 }
    197 
    198 function move(s,e) {
    199     searchAround(s);
    200     s = selectNewStart(open);
    201     setColor(s);
    202     if(!isNodeExists(e,close)) {
    203         setTimeout(function() {
    204             log();
    205             return move(s,e);
    206         },100);
    207     }
    208 }
    209 
    210 function init() {
    211     open = [];
    212     close = [];
    213     start = {};
    214     end = {};
    215 }
    216 
    217 function log() {
    218     console.log('当前起点:',start);
    219     console.log('开启列表:',open);
    220     console.log('关闭列表:',close);
    221 }
    222 
    223 generateMap();
    224 var tdCollections = $('td');
    225 addStone();
    226 end = setGoal();
    227 start = {x:0,y:0,type:1};
    228 close.push(start);
    229 
    230 //move(start,end);
    View Code
  • 相关阅读:
    angularjs事件通信$on,$emit,$broadcast详解
    es6入门7--Set Map数据结构
    ES6函数参数默认值作用域的模拟原理实现与个人的一些推测
    JS判断数组是否包含某元素
    es6入门6--数组拓展运算符,Array.from()基本用法
    js new一个对象的过程,实现一个简单的new方法
    js中的NaN,isNaN与Number.isNaN的区别,如何判断一个值严格等于NaN
    详解 JDK8 新增的日期时间类
    详解 枚举
    详解 动态代理
  • 原文地址:https://www.cnblogs.com/undefined000/p/5139959.html
Copyright © 2011-2022 走看看