把地图划分成格子
设置两个表:
openList:放的是当前可到达的格子。最初的起点先放入这个表
closeList:放的是格子数据被锁死不需要再修改的格子
格子的数据包括:
G:从起点到这个格子移动的代价,或者说距离。
H:这个格子到终点的估算代价/距离。用曼哈顿距离表示(即该格子与终点在x轴和y轴距离上的和:d(i,j)=|X1-X2|+|Y1-Y2|.)
F:G+H
父节点parent:与G值的计算有关。G表示了从起点到这个格子的路径消耗,parent指明了路径的方向,也就是parent为G所表示的路径上该格子的前一个格子
重复下面的步骤:
1.从openList中选中F值最小的格子,作为当前格
2.把当前格换到closeList。因为当前格不会有新的更短的到起始格子的路径消耗。
3.以当前格寻找新的可到达的格子,即周围的相邻格子。
对这些格子进行判断:
*如果格子不可通过或者在closeList忽略
*如果格子不在openList,已当前格作为父节点,计算格子的FGH值,放入openList
*如果格子在openList,格子有旧的路径消耗(旧的父节点和G值),用当前格作为新的路径来计算G值,与旧的G值比较。如果新的G值小,则把格子父节点设为当前格,G值刷新。也就是以当前格子作为新路径和格子记录的旧路径比较看哪个是这个格子到起点的最短路径消耗。并记录下来
重复上面三个步骤,直到:
1.终点格子被加入了closeList,即在openList里找到的F值最小的格子是终点格子。以终点格子的父节点倒推回起点格子便是最短路径消耗
2.openList已经空了,表示路径不存在,起点无法到达终点
总结:以起点开始往外扩散能到达的格子。并确定离终点最近的格子到起点的最短路径消耗。直到扩散的格子涵盖了终点,或无法在扩散。
下面的例子,是我在项目中用到的a*
function ElementData:getShortestPath( toData )
--toData是终点
local closeTable = {}
--把起始点放入open表,sureRoute是G值,到起点的距离消耗
local openTable = {[self._posId] = {["id"] = self._posId, ["x"] = self._x, ["y"] = self._y, ["sureRoute"] = 0}}
--判断是否重复上面的三个步骤
local isContinue, node = self:_AStarAlgorithm( openTable, closeTable, toData)
while (isContinue) do
isContinue, node = self:_AStarAlgorithm( openTable, closeTable, toData)
end
--找到终点的话,用终点的parent逆推路径
if node then
local temp = {}
table.insert(temp, node.id)
while (node.parent) do
node = node.parent
table.insert(temp, node.id)
end
local step = {}
local num = #temp
for i = 1, num do
table.insert(step, temp[num - i + 1])
end
return step
end
return nil
end
function ElementData:_AStarAlgorithm( openTable, closeTable, toData)
--如果open表空,没找到路径
if next(openTable) == nil then
return false
else
--找到open表里F值最小的格子作当前格
local node = self:_getFirstPriorityNode(openTable)
--如果当前格是终点,返回
if node.id == toData:getId() then
return false, node
else
--当前格放入close表
closeTable[node.id] = node
--x,y的判断是项目里的业务逻辑,不用管,这个步骤是将当前格node的上下左右四个点加入open表作判断
if node.x > 1 then
local nextData = self:getGridDataByPos(node.x - 1, node.y)
self:_updateOpenTable(openTable, closeTable, nextData, node, toData)
end
if node.x < 5 then
local nextData = self:getGridDataByPos(node.x + 1, node.y)
self:_updateOpenTable(openTable, closeTable, nextData, node, toData)
end
if node.y > 1 then
local nextData = self:getGridDataByPos(node.x, node.y - 1)
self:_updateOpenTable(openTable, closeTable, nextData, node, toData)
end
if node.y < self._rows - 2 then
local nextData = self:getGridDataByPos(node.x, node.y + 1)
self:_updateOpenTable(openTable, closeTable, nextData, node, toData)
end
openTable[node.id] = nil
end
return true
end
end
function ElementData:_updateOpenTable(openTable, closeTable, nextData, node, toData)
--如果格子在close表则忽略
if closeTable[nextData:getId()] then
return
end
--格子可以到达的判断
if not nextData:cannotMove() and nextData:getY() ~= 1 then
--sureRoute为当前格的G值,即当前格到起点的距离消耗
local sureRoute = node.sureRoute
local nextNode = openTable[nextData:getId()]
--判断新的可到达格子是否已经在open表(即有旧的可到达格子路径消耗)
if nextNode then
--sureRoute+1,以当前格作路径算新格子到起点的距离消耗(G值),判断是否小于旧的路径消耗
if sureRoute + 1 < nextNode["sureRoute"] then
--小于的话更新新的G值(sureRoute)和F值(route),并设置父节点为当前格(新的最短距离消耗的路径方向)
nextNode["sureRoute"] = sureRoute + 1
nextNode["route"] = sureRoute + 1 + math.abs(toData:getX() - nextData:getX()) + math.abs(toData:getY() - nextData:getY())
nextNode["parent"] = node
end
else
--没有旧路径消耗直接设置新格子数据
openTable[nextData:getId()] = {["id"] = nextData:getId(), ["x"] = nextData:getX(), ["y"] = nextData:getY(), ["parent"] = node, ["sureRoute"] = sureRoute + 1, ["route"] = sureRoute + 1 + math.abs(toData:getX() - nextData:getX()) + math.abs(toData:getY() - nextData:getY())}
end
end
end
function ElementData:_getFirstPriorityNode(openTable)
local priorityNode
--route是F值
for k, v in pairs(openTable) do
if not priorityNode then
priorityNode = v
elseif v.route < priorityNode.route then
priorityNode = v
end
end
return priorityNode
end
注:如果不考虑H值(即不在openlist/候选集寻找离终点最近的格子),算法就是Dijkstra算法。是贪心思想的实现。
贪心思想:将问题建立数学模型,再分成若干子问题,对子问题求解。寻找每个子问题的局部最优解。通过子问题局部最优解合成原问题的解。
贪心无法确保得到的解一定是最优解。通常用来解最大或最小问题。
例如:Dijkstra就是将分解起点终点之间每个可到达的目标点,寻找起点到这些目标点的最小距离。再根据每个目标点的最小距离合成起点到终点的最小距离。a*则是在选择计算目标点的算法里通过曼哈顿距离(启发函数,通常是曼哈顿算法)选择离终点最近的目标点