A*算法是一种静态路网中求解最短路径最有效的直接搜索方法,注意——是最有效的直接搜索算法,之后涌现了很多预处理算法(如ALT,CH,HL等等),在线查询效率是A*算法的数千甚至上万倍。
核心思想
公式: f(n) = g(n) + h(n)
其中, f(n) 是从初始状态经由状态n到目标状态的代价估计,g(n) 是在状态空间中从初始状态到状态n的实际代价,h(n) 是从状态n到目标状态的最佳路径的估计代价,保证找到最短路径(最优解的)条件,关键在于估价函数f(n)的选取(或者说h(n)的选取)。
距离估计与实际值越接近,估价函数取得就越好
例如对于几何路网来说,可以取两节点间曼哈顿距离做为距离估计,即f=g(n) + (abs(dx - nx) + abs(dy - ny));这样估价函数f(n)在g(n)一定的情况下,会或多或少的受距离估计值h(n)的制约,节点距目标点近,h值小,f值相对就小,能保证最短路的搜索向终点的方向进行。
算法实现(路径搜索)
接下来,我们用上面的方法写一个简单的应用例子,实现下图的搜索功能。
先建立网格的css
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<style>
* {
margin: 0;
padding: 0;
}
li {
list-style: none;
}
#ul {
height: auto;
overflow: hidden;
margin: 20px auto;
border: 1px #000 solid;
border-bottom: none;
border-right: none;
}
#ul li {
border: 1px #000 solid;
border-top: none;
border-left: none;
float: left;
}
#ul li.sty1 {
background-color: red;
}
#ul li.sty2 {
background-color: green;
}
#ul li.sty3 {
background-color: blue;
}
#input {
width: 100px;
position: absolute;
left: 50%;
margin-left: -50px;
}
</style>
<body>
<ul id="ul"></ul>
<input type="button" value="开始寻路" id="input">
</body>
</html>
在script中写入map,然后绘制网格
let map = [
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,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,0,0,0,0,3,0,0,0,0,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,0,0,0,0,3,0,0,0,0,0,0,0,0,
0,0,0,1,0,0,0,0,3,3,3,3,3,3,3,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,0,0,0,0,3,0,3,3,3,3,3,0,0,0,0,0,
0,0,0,0,0,0,0,0,3,0,3,0,0,0,0,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,0,0,0,0,3,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,3,0,0,0,0,0,0,2,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,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
]
let oUl = document.getElementById('ul')
let aLi = oUl.getElementsByTagName('li')
let oInput = document.getElementById('input')
let beginLi = oUl.getElementsByClassName('sty1') // 开始点
let endLi = oUl.getElementsByClassName('sty2') // 结束点
let cols = Math.sqrt(map.length) // 行数
let sizeGird = 20 // 表格宽度
function createMap() {
oUl.style.width = cols * (sizeGird + 1) + 1 + 'px'
for (let i = 0; i < map.length; i++) {
let oLi = document.createElement('li')
oLi.style.width = sizeGird + 'px'
oLi.style.height = sizeGird + 'px'
oUl.appendChild(oLi)
if (map[i] === 1) {
oLi.className = 'sty1'
} else if (map[i] ===2) {
oLi.className = 'sty2'
} else if (map[i] ===3) {
oLi.className = 'sty3'
}
}
}
createMap()
编写估价算法
function f(nodeLi) {
return g(nodeLi) + h(nodeLi)
}
function g(nodeLi) {
let a = beginLi[0].offsetLeft - nodeLi.offsetLeft
let b = beginLi[0].offsetTop - nodeLi.offsetTop
return Math.sqrt(a*a + b*b)
}
function h(nodeLi) {
let a = endLi[0].offsetLeft - nodeLi.offsetLeft
let b = endLi[0].offsetTop - nodeLi.offsetTop
return Math.sqrt(a*a + b*b)
}
做两个数组,openArr储存已选择的路径(下一步将不会走这些路径)、closeArr 储存不可选的路径
let openArr = []
let closeArr = []
初始的openArr就是图上的红方块,初始的closeArr是蓝方块。可选择的路径result是当前路径元素周边的8个黄色的方块,查找过程用findLi函数实现。
function findLi(nowLi) {
let result = []
for (let i = 0; i < aLi.length; i++) {
if (filter(aLi[i])) {
result.push(aLi[i])
}
}
function filter(li) {
for (let i = 0; i < closeArr.length; i++) {
if (closeArr[i] == li) {
return false
}
}
for (let i = 0; i < openArr.length; i++) {
if (openArr[i] == li) {
return false
}
}
return true
}
for (let i = 0; i < result.length; i++) {
if ((Math.abs(nowLi.offsetLeft - result[i].offsetLeft) <= (sizeGird + 1)) && (Math.abs(nowLi.offsetTop - result[i].offsetTop) <= sizeGird + 1)) {
openArr.push(result[i])
}
}
}
通过上面的方法,我们已经可以找到当前路径元素的下一步可选路径,共有8种选择,接下来就是如何从这8种选择选下步?这里我们选择估价最小为下一路径元素。
function openFn() {
let nowLi = openArr.shift()
if (nowLi == endLi[0]) {
return ;
}
closeFn(nowLi)
findLi(nowLi)
openArr.sort(function(li1, li2) {
return li1.num - li2.num
})
openFn()
}
这里我们用排序的方式找到估价最小的路径元素,需要把计算的估价挂载到该路径元素上,对findLi方法进行改造,
if ((Math.abs(nowLi.offsetLeft - result[i].offsetLeft) <= (sizeGird + 1)) && (Math.abs(nowLi.offsetTop - result[i].offsetTop) <= sizeGird + 1)) {
result[i].num = f(result[i])
openArr.push(result[i])
}
上面的方法,已经实现了路径的搜索,接下来我们再做一些操作,把搜索过程展示出来。
该搜索过程的全部信息就存储在closeArr数组,就是上图中标出的不可选路径的第二部分。但由于障碍物蓝色方块的存在,closeArr的路径可能会很长,想要清晰的展示出搜索过程,展示closeArr不是好方法。
我们可以利用回溯,closeArr的最后一位是目标点,由目标点回溯到上一步路径元素,然后由该路径元素回溯到它的上一步路径元素,直到回溯到起始点,该回溯过程就可以表示搜索过程。
我们再次对findLi方法进行改造,挂载回溯用的的上一步路径元素。
if ((Math.abs(nowLi.offsetLeft - result[i].offsetLeft) <= (sizeGird + 1)) && (Math.abs(nowLi.offsetTop - result[i].offsetTop) <= sizeGird + 1)) {
result[i].num = f(result[i])
result[i].parent = nowLi
openArr.push(result[i])
}
最后写一个showLine方法,用于展示该回溯过程。
function showLine() {
let result = []
let lastLi = closeArr.pop()
let isNow = 0
findParent(lastLi)
function findParent(li) {
result.unshift(li)
if (li.parent == beginLi[0]) {
return ;
}
findParent(li.parent)
}
let timer = setInterval(function() {
result[isNow].style.background = 'red'
isNow++
if(isNow == result.length) {
clearInterval(timer)
}
}, 500)
}
好的,这样我们就完成整个搜索过程。
完整代码如下:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<style>
* {
margin: 0;
padding: 0;
}
li {
list-style: none;
}
#ul {
height: auto;
overflow: hidden;
margin: 20px auto;
border: 1px #000 solid;
border-bottom: none;
border-right: none;
}
#ul li {
border: 1px #000 solid;
border-top: none;
border-left: none;
float: left;
}
#ul li.sty1 {
background-color: red;
}
#ul li.sty2 {
background-color: green;
}
#ul li.sty3 {
background-color: blue;
}
#input {
width: 100px;
position: absolute;
left: 50%;
margin-left: -50px;
}
</style>
<body>
<ul id="ul"></ul>
<input type="button" value="开始寻路" id="input">
</body>
<script >
let oUl = document.getElementById('ul')
let aLi = oUl.getElementsByTagName('li')
let oInput = document.getElementById('input')
let beginLi = oUl.getElementsByClassName('sty1')
let endLi = oUl.getElementsByClassName('sty2')
let map = [
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,3,3,3,3,3,3,3,3,0,0,0,0,0,
0,0,0,0,0,0,0,3,3,3,3,3,3,3,3,0,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,0,0,0,0,3,0,0,0,0,0,0,0,0,
0,0,0,1,0,0,0,0,3,3,3,3,3,3,3,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,0,0,0,0,3,0,3,3,3,3,3,0,0,0,0,0,
0,0,0,0,0,0,0,0,3,0,3,0,0,0,0,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,0,0,0,0,3,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,3,0,0,0,0,0,0,2,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,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
]
let cols = Math.sqrt(map.length)
let sizeGird = 20
let openArr = []
let closeArr = []
init ()
function init() {
createMap()
oInput.onclick = function() {
openFn()
}
}
function createMap() {
oUl.style.width = cols * ( sizeGird + 1 ) + 1 + 'px'
for (let i = 0; i < map.length; i++) {
let oLi = document.createElement('li')
oLi.style.width = sizeGird + 'px'
oLi.style.height = sizeGird + 'px'
oUl.appendChild(oLi)
if (map[i] === 1) {
oLi.className = 'sty1'
openArr.push(oLi)
} else if (map[i] ===2) {
oLi.className = 'sty2'
} else if (map[i] ===3) {
oLi.className = 'sty3'
closeArr.push(oLi)
}
}
}
function f(nodeLi) {
return g(nodeLi) + h(nodeLi)
}
function g(nodeLi) {
let a = beginLi[0].offsetLeft - nodeLi.offsetLeft
let b = beginLi[0].offsetTop - nodeLi.offsetTop
return Math.sqrt(a*a + b*b)
}
function h(nodeLi) {
let a = endLi[0].offsetLeft - nodeLi.offsetLeft
let b = endLi[0].offsetTop - nodeLi.offsetTop
return Math.sqrt(a*a + b*b)
}
function openFn() {
let nowLi = openArr.shift()
if (nowLi == endLi[0]) {
showLine()
return ;
}
closeFn(nowLi)
findLi(nowLi)
openArr.sort(function(li1, li2) {
return li1.num - li2.num
})
openFn()
}
function closeFn(nowLi) {
closeArr.push(nowLi)
}
function findLi(nowLi) {
let result = []
for (let i = 0; i < aLi.length; i++) {
if (filter(aLi[i])) {
result.push(aLi[i])
}
}
function filter(li) {
for (let i = 0; i < closeArr.length; i++) {
if (closeArr[i] == li) {
return false
}
}
for (let i = 0; i < openArr.length; i++) {
if (openArr[i] == li) {
return false
}
}
return true
}
for (let i = 0; i < result.length; i++) {
if ((Math.abs(nowLi.offsetLeft - result[i].offsetLeft) <= (sizeGird + 1)) && (Math.abs(nowLi.offsetTop - result[i].offsetTop) <= sizeGird + 1)) {
result[i].num = f(result[i])
result[i].parent = nowLi
openArr.push(result[i])
}
}
}
function showLine() {
let result = []
let lastLi = closeArr.pop()
console.log(lastLi)
let isNow = 0
findParent(lastLi)
function findParent(li) {
result.unshift(li)
if (li.parent == beginLi[0]) {
return ;
}
findParent(li.parent)
}
let timer = setInterval(function() {
result[isNow].style.background = 'red'
isNow++
if(isNow == result.length) {
clearInterval(timer)
}
}, 500)
}
</script>
</html>
网友评论