最佳优先搜索算法是一种启发式搜索算法(Heuristic Algorithm),其基于广度优先搜索算法,不同点是其依赖于估价函数对将要遍历的节点进行估价,选择代价小的节点进行遍历,直到找到目标点为止。BFS算法不能保证找到的路径是一条最短路径,但是其计算过程相对于Dijkstra
算法会快很多。
a. 如果Y不在Open队列和Closed队列中时(未被遍历过),通过估价函数计算出Y节点的估价,并将Y节点添加到Open队列中,并根据估价对Open队列中的节点进行排序。
b. 如果Y节点在Open队列中时,通过估价函数重新计算出Y节点的估价,并和Open队列中的Y节点的估价进行比较,如果估计高于Open队列中的Y节点的估价,替换Open队列中的Y节点的估价,并对Open队列中的节点重新排序。
c. 如果Y节点在Closed队列中时,通过估价函数计算出Y节点的估值,并和Closed队列中的Y节点的估价进行比较,如果高于Closed队列中的估价时,将Y节点从Closed队列中移除,并添加到Open队列中,并对Open队列进行排序。
最佳优先搜索是一种启发式搜索算法。广度优先搜索和深度优先搜索都属于穷举类型的搜索,需要依次遍历所有的节点,当空间非常大的时候,这种方式的效率就会非常差。而启发式的搜索是对状态控件中的每个点进行评估,然后选出最好的位置。
启发估价函数公式为:
BFS算法只关注当前点到目标点之间的代价:
f(n) = h(n)。
n表示当前的点,g(n)为从起始点到点n的实际代价,h(n)为从点n到目标点的估价。
A*算法将BFS算法和Dijkstra算法结合在一起,结合两算法的优点,既可以查找最短路径的,有拥有和BFS差不多的效率。
A星算法会结合从出发点到当前点的实际代价和当前点到目标点之间的总代价:
f(n) = g(n) + h(n)。
曼哈顿距离
欧式距离(欧几里得距离)
--[[
-- A*算法的点的结构
]]
local AStarPointObj = ns.class("AStarPointObj")
function AStarPointObj:ctor()
ns.utils.registerVariableAndSetGetFunc(self, "pointId" , 0) -- 点的ID
ns.utils.registerVariableAndSetGetFunc(self, "line" , 0) -- 列
ns.utils.registerVariableAndSetGetFunc(self, "row" , 0) -- 行
ns.utils.registerVariableAndSetGetFunc(self, "pos" , ns.p(0,0)) -- 点的位置
ns.utils.registerVariableAndSetGetFunc(self, "canReach" , true) -- 该点是否可以通过
ns.utils.registerVariableAndSetGetFunc(self, "pre" , nil) -- 前一个节点(在移动的过程中记录上一个点、便于路径追溯)
ns.utils.registerVariableAndSetGetFunc(self, "evaluate" , 0) -- 当前的估价(从起始点开始经过当前点到达目标点的总的代价的预估)
ns.utils.registerVariableAndSetGetFunc(self, "cost" , 0) -- 从起始点开始到当前点已经消耗的代价
end
--[[
-- 初始化顶点数据
]]
function AStarPointObj:initWithParams(pointId,line,row,pos,canReach)
self._pointId = pointId or 0
self._line = line or 0
self._row = row or 0
self._pos = pos or ns.p(0,0)
self._canReach = (canReach == nil) and false or canReach
return self
end
return AStarPointObj
--[[
-- A*算法
]]
local MAX_DISTANCE = 999999999999999
local AStarCommand = ns.class("AStarCommand")
--[[
-- @searchType 0:曼哈顿距离; 1:欧几里德距离
]]
function AStarCommand:ctor(pointMap,searchType)
self._pointMap = pointMap or {}
self._searchType = searchType or 0
end
function AStarCommand:find(beginPointId,endPointId)
local timeCommand = app.command.timeRecordCommand()
self:resetPoint()
local openedList = {} -- 优先队列,记录将要处理的点,每次从队列中查找估值最小的点
local openedKeyMap = {} -- openList中的点的索引的map,避免相同的点被多次添加到openedList中
local closedMap = {} -- map,记录已经处理过的点、避免同一个点被反复查询
-- 对OpenedList列表排序
local sortOpenList = function()
table.sort(openedList,function(a,b)
local evaluateA = a:getEvaluate()
local evaluateB = b:getEvaluate()
if evaluateA ~= evaluateB then
return evaluateA < evaluateB
end
return a:getPointId() < b:getPointId()
end)
end
local beginPoint = self:getPointObj(beginPointId)
beginPoint:setEvaluate(self:getEvaluate(beginPointId,endPointId))
table.insert(openedList,beginPoint)
openedKeyMap[beginPointId] = true
timeCommand:start("AStarCommand:find")
while true do
-- 对opened列表排序,选择估价最小的点
sortOpenList()
-- opened中没有点了,查找失败
if #openedList <= 0 then
ns.control.app.createView("Toast", "路径查找失败 - 1"):open()
return {}
end
-- 查找到了目标点
local firstObj = openedList[1]
if firstObj:getPointId() == endPointId then
-- ns.control.app.createView("Toast", "寻路成功!!! "):open()
timeCommand:stop("AStarCommand:find")
local pointIds = {}
local curPoint = firstObj
while true do
if curPoint and curPoint:getPointId() ~= beginPointId then
table.insert(pointIds,1,curPoint:getPointId())
curPoint = curPoint:getPre()
else
break
end
end
table.insert(pointIds,1,beginPointId)
return pointIds
end
-- 将第一个点从Opened中移除并添加到closed中,将所有的和firstPoint相关的点添加到Opened列表中
closedMap[firstObj:getPointId()] = firstObj
table.remove(openedList,1)
openedKeyMap[firstObj:getPointId()] = false
-- 当前的行和列
local curLine = firstObj:getLine()
local curRow = firstObj:getRow()
-- 查找相邻的节点,并处理
for i = -1,1 do
for j = -1,1 do
if (i ~= j or i ~= 0) and (self._searchType == 1 or (self._searchType == 0 and (i == 0 or j == 0))) then -- 处理欧几里德距离和曼哈顿距离
local newLine = curLine + i
local newRow = curRow + j
local newObj = self:getPointObjByRowAndLine(newLine,newRow)
if newObj and newObj:getCanReach() then
local pointId = newObj:getPointId()
-- 新节点基于当前节点的总的代价
local cost = firstObj:getCost() + self:getEvaluate(firstObj:getPointId(),pointId)
local evaluate = cost + self:getEvaluate(pointId,endPointId)
-- local evaluate = self:getEvaluate(pointId,endPointId)
if not closedMap[pointId] or newObj:getEvaluate() > evaluate then
-- 将该节点从closedMap中移除
closedMap[pointId] = nil
if not openedKeyMap[pointId] then
newObj:setEvaluate(evaluate)
newObj:setCost(cost)
newObj:setPre(firstObj)
table.insert(openedList,newObj)
openedKeyMap[pointId] = true
-- ns.logW("添加点到OpenList列表中:"..pointId.. "估价 = "..evaluate)
elseif newObj:getEvaluate() > evaluate then -- 如果该节点已经添加到OpenedList中了,比较并更新估值、父节点等信息
newObj:setEvaluate(evaluate)
newObj:setCost(cost)
newObj:setPre(firstObj)
-- ns.logW("更新节点的估价:"..pointId.. "估价 = "..evaluate)
end
end
end
end
end
end
end
end
--[[
-- 重置点位的数据
]]
function AStarCommand:resetPoint()
for i = 1,#self._pointMap do
local rows = self._pointMap[i]
for j =1,#rows do
local obj = rows[j]
if obj then
obj:setPre(nil)
obj:setEvaluate(MAX_DISTANCE)
end
end
end
end
--[[
-- 估价函数(当前点到目标点的代价)
]]
function AStarCommand:getEvaluate(beginPointId,endPointId)
local evaluate = self:getDistance(beginPointId,endPointId)
return evaluate
end
--[[
-- 获取两个点之间的距离两点之间的代价
]]
function AStarCommand:getDistance(beginPointId,endPointId)
local beginPoint = self:getPointObj(beginPointId)
local endPoint = self:getPointObj(endPointId)
if not beginPoint or not endPoint then
return MAX_DISTANCE
end
local beginPos = beginPoint:getPos()
local endPos = endPoint:getPos()
if self._searchType == 0 then -- 曼哈顿距离
local distance = math.abs(endPos.x - beginPos.x) + math.abs(endPos.y - beginPos.y)
return distance
elseif self._searchType == 1 then -- 欧几里德距离(三角距离)
return app.commonUtils.getDistance(beginPos,endPos)
end
return MAX_DISTANCE
end
--[[
-- 获取点的数据
]]
function AStarCommand:getPointObj(pointId)
for i = 1,#self._pointMap do
local rows = self._pointMap[i]
for j =1,#rows do
local obj = rows[j]
if obj and obj:getPointId() == pointId then
return obj
end
end
end
return nil
end
--[[
-- 通过行和列获取到点的数据
]]
function AStarCommand:getPointObjByRowAndLine(line,row)
for i = 1,#self._pointMap do
local rows = self._pointMap[i]
for j =1,#rows do
local obj = rows[j]
if obj and obj:getLine() == line and obj:getRow() == row then
return obj
end
end
end
return nil
end
return AStarCommand
A*算法详解
模拟寻路的地址