A*算法 和 最佳优先搜索算法(Best-First-Searc

2019-04-26  本文已影响0人  jepsonCheng

BFS算法

算法原理

最佳优先搜索算法是一种启发式搜索算法(Heuristic Algorithm),其基于广度优先搜索算法,不同点是其依赖于估价函数对将要遍历的节点进行估价,选择代价小的节点进行遍历,直到找到目标点为止。BFS算法不能保证找到的路径是一条最短路径,但是其计算过程相对于Dijkstra
算法会快很多

算法流程

估价函数(或叫启发函数)的实现原理

最佳优先搜索是一种启发式搜索算法。广度优先搜索和深度优先搜索都属于穷举类型的搜索,需要依次遍历所有的节点,当空间非常大的时候,这种方式的效率就会非常差。而启发式的搜索是对状态控件中的每个点进行评估,然后选出最好的位置。

启发估价函数公式为:

n表示当前的点,g(n)为从起始点到点n的实际代价,h(n)为从点n到目标点的估价。

BFS算法的探测过程

BFS算法的探测过程 BFS遇到障碍物时的寻路方式

(图片来源于网络)

A*算法

算法介绍

A*算法将BFS算法和Dijkstra算法结合在一起,结合两算法的优点,既可以查找最短路径的,有拥有和BFS差不多的效率。

A*寻路的探测过程

A* 没有障碍物时的寻路 A* 在寻路过程中遇到障碍物时的寻路过程

距离计算方式

距离距离

(图片来源于网络)

曼哈顿距离

欧式距离(欧几里得距离)

A*算法的lua实现

--[[
--      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*算法详解

模拟寻路的地址

上一篇 下一篇

猜你喜欢

热点阅读