算法思想
Floyd算法是一种动态规划算法,查找i到j之间的最短距离,我们可以找一个中间点k,然后变成子问题,i到k的最短路径和k到j的最短路径。也就是说,我们可以枚举中间点k,找到最小的d[i][k]+d[k][j],作为d[i][j]的最小值。
Floyd构造的结构非常巧妙:找i和j之间通过编号不超过k(k从1到n)的节点的最短路径(一定要注意,这里是当前最短路径,当k=n时达到最终最短路径)。为了便于说明,我们可以弄一个三维数组f[k][i][j]表示i和j之间可以通过编号不超过k的节点的“最短路径”。对于k-1到k,只有两种可能,经过编号为k的点,要么不能找到一条从i到j的更短路,此时有f[k][i][j] = f[k-1][i][j] ;要么能找到,那这个最短路径一定是d[i][k]+d[k][j],那么就用这个较小的距离去更新d[i][j]。综合以上两种情况,f[k][i][j] = min(f[k-1][i][j] , f[k-1][i][k]+f[k-1][k][j])
动态转移推导(重要)
- 动态转移思想可以认为是在建立一种当前状态向之前状态的转移的方法。
- d[k][i][j]表示使用1号到K号的中间点来计算i到j直接按的最短距离。
- i和j之间的最短距离有两种情况,情况一:最短距离经过K点;情况二:最短距离不经过k点。
- 如果i和j之间的最短距离经过k点,则D[k,i,j] = D[K,i,j],否则D[k,i,j] = D[k-1,i,j];那么最终的结果为D[k,i,j] = min(D[k,i,j],D[k-1,i,k]+D[k-1,k,j])。
- 因此可知,如果i到j之间的最短距离如果不经过k点,则可转移到k-1、k-2... 一直到1,总是可以将k的状态向前转移到上一个状态。
- 最后通过k从1到n的遍历,可最终找到i到j之间的最短距离。
- 两点之间的最短距离如果需要经过第三点的话 这个第三点一定不是起点或终点(这样的话就是两点之间的距离是最短距离了)。
- 如果点i和点j之间经过k点,则i点和k点之间一定不会经过j 即路径之间不会出现相互引用到的情况。
通俗理解
- Floyd算法通过依次修改中间节点,计算任意两点的经过中间节点后的距离,计算出任意两个节点之间的最短距离;每次计算完成的结果都是当前的最优解,当所有的中间节点被遍历结束后获得的结果就是整个算法的最优解。
算法步骤分析
(图片资源来源于网络)
算法代码实现
- 算法中点的定义
--[[
-- Floyd算法中的点的数据结构
]]
local max_distance = 9999999999999999999999999999
local LinkObj = ns.class("LinkObj")
function LinkObj:ctor(pointIdx,distance)
ns.utils.registerVariableAndSetGetFunc(self,"pointIdx",pointIdx or 0)
ns.utils.registerVariableAndSetGetFunc(self,"distance",distance or 0)
end
local FloydPointObj = ns.class("FloydPointObj")
function FloydPointObj:ctor(pointIdx)
ns.utils.registerVariableAndSetGetFunc(self,"pointIdx", pointIdx or 0) -- 当前点的ID
ns.utils.registerVariableAndSetGetFunc(self,"links", {}) -- 当前点的所有的相邻点
end
--[[
-- 添加相邻点
]]
function FloydPointObj:addLink(pointIdx,distance)
local link = LinkObj.new(pointIdx,distance)
table.insert(self._links,link)
return self
end
--[[
-- 获取两个点是否是相邻
]]
function FloydPointObj:isLink(pointIdx)
for i = 1,#self._links do
local link = self._links[i]
if link and link:getPointIdx() == pointIdx then
return true
end
end
return false
end
--[[
-- 获取两个点之间的距离,如果两个点不相邻,返回无穷大
]]
function FloydPointObj:getLinkDistance(pointIdx)
for i = 1,#self._links do
local link = self._links[i]
if link and link:getPointIdx() == pointIdx then
return link:getDistance()
end
end
return max_distance
end
return FloydPointObj
- 算法的实现
local FloydCommand = ns.class("FloydCommand")
function FloydCommand:ctor()
self._D = {} -- 存放最短距离的二维数组
self._P = {} -- 存放最短路径经过的点的二维数据
end
--[[
-- 计算出所有的点之间的最短距离的数组
]]
function FloydCommand:execute(floydPointObjs)
local points = floydPointObjs or {}
local pointNums = #points
local D = self._D
local P = self._P
-- 初始化距离数组和最短路径经过的点的数组
for i =1,pointNums do
local pointObj = points[i]
local subDisTab = {}
local subPointTab = {}
for j = 1,pointNums do
local subPointObj = points[j]
local distance = pointObj:getLinkDistance(subPointObj:getPointIdx())
table.insert(subDisTab,distance)
table.insert(subPointTab,{pointObj:getPointIdx()})
end
table.insert(D,subDisTab)
table.insert(P,subPointTab)
end
-- 开始执行算法核心循环
for k = 1,pointNums do -- 该层循环用于计算所有的点之间经过该点之后的距离是否是最短距离
for i = 1,pointNums do
for j = 1,pointNums do
local distance = D[i][k] + D[k][j]
if D[i][j] > distance then
D[i][j] = distance
local points = ns.clone(P[i][k])
table.insertto(points,ns.clone(P[k][j]))
P[i][j] = points
end
end
end
end
return self
end
function FloydCommand:find(beginPoint,endPoint)
local distance = self._D[beginPoint][endPoint] or 0
local points = ns.clone(self._P[beginPoint][endPoint] or {})
-- 注意P中的路径不包括终点,需要手动添加
table.insert(points,endPoint)
return points,distance
end
return FloydCommand