最短路径 - Floyd算法

算法思想

  • 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算法通过依次修改中间节点,计算任意两点的经过中间节点后的距离,计算出任意两个节点之间的最短距离;每次计算完成的结果都是当前的最优解,当所有的中间节点被遍历结束后获得的结果就是整个算法的最优解。

算法步骤分析

(图片资源来源于网络)


image

算法代码实现

  • 算法中点的定义
--[[
--      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

参考文章

探求Floyd算法的动态规划本质

floyd算法:我们真的明白floyd吗?

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,254评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,875评论 3 387
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,682评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,896评论 1 285
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,015评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,152评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,208评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,962评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,388评论 1 304
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,700评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,867评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,551评论 4 335
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,186评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,901评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,142评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,689评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,757评论 2 351