为了方便后期的热更新需求,项目基本上全部逻辑都使用了纯lua实现,最近遇到一个大地图寻路的算法问题,有两百多个城市的拓扑结构图,寻找最短路径,算法本身没有什么好讲的,A*的广度优先算法,但在lua层照搬A*的实现逻辑,会有极大的性能压力,要求算法本身做些有利于脚本的优化。
优化思路如下:
1.A*需要开放节点列表,关闭节点列表,涉及大量的链表操作,在lua里从表结构里删除和向表结构里添加节点都是非常耗费性能的操作,基本思路是用一个表结构来同时实现开放节点列表和关闭节点列表。
local searchNodes = {}
searchNodes[Node2] = 权重 --开放节点
searchNodes[Node3] = false --关闭节点
2.A*查找最优子节点时,需要遍历开放节点列表,选取当前最优扩展节点,通常采取的做法是对开放节点列表做一次排序(也有算法在插入时维护开放节点的有序性),选取最优节点。但对lua而言,排序是很耗性能的操作,并切要求必须维持开放节点列表是一个数组,这样不管删除和插入都会导致数组的后续元素更新,所以采用筛取法来获取最优节点。
local function onSearchGood()
local cNode = nil
local cStep = 10000000000
for cityId,cityStep in pairs(searchNodes) do
if cityStep and cStep > cityStep then
cNode = cityId
cStep = cityStep
end
end
if cNode then
searchNodes[cNode] = false
end
return cNode
end
3.路径缓存,根据以上两步基本能处理节点扩展的问题,剩下的就是当前路径的缓存问题,c++的常规实现是通过一个链表实现,让当前节点指向其父节点,就可以反向根据某个节点推导出其从开始节点到当前节点的路径。最近在参照别的算法实现时,发现一个在脚本中非常巧妙的实现方式,就是用一个字符串巧妙地实现路径缓存的问题。
local searchPathes = {}
searchPathes[child] = searchPathes[parent]..">"..child
这样通过查找这个searchPathes就可以方便地知道当前节点来源路径 n1>n2>n3... ,通过字符串的分割,就可以获得整个完整的路径。
– 寻找最短路径
function WorldUtils.findPath2(startId,endId)
if not startId or not endId then
return
end
local path = {}
local pathInfo = {}
local openPoints = {}
local curNation = xxx --当前阵营
local cityListInfo = xxx -- 城市附属数据
path[startId] = 0
pathInfo[startId] = startId
local function onSearchNode2(start)
if not start then
return
end
local cityInfo = _cc_config:getMapCityById(start) --城市联通数据
local childs = cityInfo.connect_city -- childs
for _,child in ipairs(childs) do
local childId = child[1]
if childId == endId then
openPoints = {}
pathInfo[childId] = pathInfo[start].." "..childId
return
end
local currCityInfo = cityListInfo:getCityBaseInfo(childId)
if currCityInfo then
if curNation == currCityInfo.nation then
local distance = 1
if currCityInfo.state == 2 then
distance = 1000
end
local oldStep = path[childId]
local newStep = path[start] + distance
-- 之前设置的距离大于新计算出来的距离
if not oldStep or oldStep > newStep then
oldStep = newStep
path[childId] = newStep
pathInfo[childId] = pathInfo[start].." "..childId
end
if false ~= openPoints[childId] then
openPoints[childId] = oldStep
end
end
end
end
end
local function onSearchGood() --最优节点
local cNode = nil
local cStep = 10000000000
for cityId,cityStep in pairs(openPoints) do
if cityStep and cStep > cityStep then
cNode = cityId
cStep = cityStep
end
end
if cNode then
openPoints[cNode] = false
end
return cNode
end
local function onSearchNereast(start) --扩展节点
openPoints[start] = 0
while true do
local city = onSearchGood()
if not city then
break
else
onSearchNode2(city)
end
end
end
--local currTime = socket.gettime()
onSearchNereast(startId)
--dump(socket.gettime() - currTime)
if pathInfo[endId] then
return string.split(pathInfo[endId]," ")
end
return nil
end