算法專題:Graph Theory

圖論Graph Theory是CS里面相當重要的一個領域,也是非常博大精深的一塊拘泞。這里主要實現(xiàn)一些比較基礎的算法纷纫。
圖可以分為有向圖和無向圖,有權圖和無權圖陪腌。圖的基本表示方法有鄰接矩陣辱魁,鄰接鏈表。兩者可以互相轉換诗鸭,這里都用鄰接鏈表作為圖的表示染簇。

BFS/DFS
BFS和DFS是圖的遍歷的基礎算法,就是從某一個節(jié)點開始遍歷整個圖强岸。對圖的有向性和有權性并沒有要求锻弓,對無向圖可視為每條邊都是雙向的。
簡而言之蝌箍,BFS就是維持一個queue青灼,每次把節(jié)點的未遍歷neighbors放進這個隊列,重復此過程直至隊列為空妓盲。這種方式是層層遞進的聚至。DFS則是一條道先走到黑,然后再換一條路本橙,用遞歸實現(xiàn)會簡潔很多扳躬。兩個方法都需要一個輔助空間visited來記錄已經(jīng)遍歷過的節(jié)點,避免走回頭路。假如需要記錄路徑贷币,可以用path代替visited击胜。
graph = {'A': ['B', 'C'], 'B': ['D', 'E'], 'C': ['D', 'E'], 'D': ['E'], 'E': ['A']}

# BFS DFS
def recursive_dfs(graph, start, path=[]):
    path = path + [start]
    for node in graph[start]:
        if node not in path:
            path = recursive_dfs(graph, node, path)
    return path

def iterative_dfs(graph, start):
    path = []
    # dfs uses a stack, so that it visits the last node
    # pushed to stack (explores as deep as possible first)
    stack = [start]
    while stack:
        v = stack.pop()  # pops out the last in (go deeper)
        if v not in path:
            path += [v]  # add v to path
            stack += graph[v]  # push v's neighbors to stack
    return path

def iterative_bfs(graph, start):
    path = []
    queue = [start]
    # bfs uses a queue, so that it visits the nodes pushed
    # to the queue first. So it first visits all the neighbors
    # and then the neighbors' neighbors (explores the soroundings
    # as much as possible without going deep)
    while queue:
        v = queue.pop(0)  # pops out the first in (go wider)
        if v not in path:
            path += [v]
            queue += graph[v]  # push v's neighbors to queue
     return path 

兩種方法各有千秋。T:O(V+E) S:O(V)

** Dijkstra**
問題:在無向圖G=(V,E)中役纹,假設每條邊E[i]的長度為W[i]偶摔,找到頂點V0到其余各點的最短路徑。這個算法是典型的單源最短路徑算法促脉,要求不能出現(xiàn)負權值辰斋,即W[i]>=0.
算法的思想很簡單。在算法進行中的某個時刻瘸味,整個圖的所有頂點可以分成兩塊宫仗,一塊是已經(jīng)完成的,一塊是待完成的旁仿。那么待完成的肯定有一部分是和已經(jīng)完成的部分相連的藕夫,因此它們距離源點V0的路徑長度也是可以獲得的,從里面挑一個最小的加入已經(jīng)完成的部分枯冈,并更新這個點的下一層neighbors的可能路徑值毅贮。重復步驟直至所有點都完成。
事實上尘奏,因為剛開始只有W[i]的值滩褥,可以給每個點增加一個屬性,即到V0的最短路徑炫加。剛開始铸题,只有V0到自己的值是0,別的點都是正無窮琢感。然后考慮V0的neighbors,其可能(因為可能有更短的路徑)的路徑值就是V0的路徑值加上V0到其的W[i]探熔,選擇一個最小的驹针,確定其路徑值。然后再考慮V0和剛剛加進來的點的neighbors的可能路徑值诀艰,再找一個最小的柬甥。反復直至完成。

# Dijkstra T:O(V^2) S:O(V)
def popmin(pqueue):
    # A (ascending or min) priority queue keeps element with
    # lowest priority on top. So pop function pops out the element with
    # lowest value. It can be implemented as sorted or unsorted array
    # (dictionary in this case) or as a tree (lowest priority element is
    # root of tree)
    lowest = 1000
    keylowest = None
    for key in pqueue:
        if pqueue[key] < lowest:
            lowest = pqueue[key]
            keylowest = key
    del pqueue[keylowest]
    return keylowest

def dijkstra(graph, start):
    # Using priority queue to keep track of minium distance from start
    # to a vertex.
    pqueue = {}  # vertex: distance to start
    dist = {}  # vertex: distance to start
    pred = {}  # vertex: previous (predecesor) vertex in shortest path
    # initializing dictionaries
    for v in graph:
        dist[v] = 1000
        pred[v] = -1
    dist[start] = 0
    for v in graph:
        pqueue[v] = dist[v]  # equivalent to push into queue

    while pqueue:
        u = popmin(pqueue)  # for priority queues, pop will get the element with smallest value
        for v in graph[u].keys():  # for each neighbor of u
            w = graph[u][v]  # distance u to v
            newdist = dist[u] + w
            if (newdist < dist[v]):  # is new distance shorter than one in dist?
                # found new shorter distance. save it
                pqueue[v] = newdist
                dist[v] = newdist
                pred[v] = u

     return dist, pred 

可以看到其垄,這里對popmin的實現(xiàn)是用的比較原始的O(n)方法苛蒲,假如用堆的話,可以將效率提高為O(ElogV)绿满。

** Bellman-Ford**
上面提到臂外,Dijkstra不能在含有負權邊的圖上使用,而Bellman-Ford算法可以。但是這個算法效率更低漏健,為O(VE)嚎货。假如有負權回路,會報錯而不會繼續(xù)進行計算(負權回路的存在讓最短路徑變得沒有意義)蔫浆。
Bellman-Ford算法可以大致分為三個部分:
第一殖属,初始化所有點。每一個點保存一個值瓦盛,表示從原點到達這個點的距離洗显,將原點的值設為0,其它的點的值設為無窮大(表示不可達)原环。
第二挠唆,進行循環(huán),循環(huán)下標為從1到n-1(n等于圖中點的個數(shù))扮念。在循環(huán)內(nèi)部损搬,遍歷所有的邊,進行松弛計算巧勤。
第三弄匕,遍歷途中所有的邊(edge(u,v))剩瓶,判斷是否存在這樣情況:
d(v) > d (u) + w(u,v)
有則返回false城丧,表示途中存在從源點可達的權為負的回路亡哄。
為什么要循環(huán)n-1次?因為最短路徑最多n-1條邊(不會包含環(huán))愿卸。

# Bellman-Ford T:O(VE) S:O(V)
# Step 1: For each node prepare the destination and predecessor
def initialize(graph, source):
    d = {}  # Stands for destination
    p = {}  # Stands for predecessor
    for node in graph:
        d[node] = float('Inf')  # We start admiting that the rest of nodes are very very far
        p[node] = None
    d[source] = 0  # For the source we know how to reach
    return d, p


def relax(node, neighbour, graph, d, p):
    # Step 2: If the distance between the node and the neighbour is lower than the one I have now
    if d[neighbour] > d[node] + graph[node][neighbour]:
        # Record this lower distance
        d[neighbour] = d[node] + graph[node][neighbour]
        p[neighbour] = node


def bellman_ford(graph, source):
    d, p = initialize(graph, source)
    for i in range(len(graph) - 1):  # Run this until is converges
        for u in graph:
            for v in graph[u]:  # For each neighbour of u
                relax(u, v, graph, d, p)  # Lets relax it

    # Step 3: check for negative-weight cycles
    for u in graph:
        for v in graph[u]:
            assert d[v] <= d[u] + graph[u][v]

     return d, p 

Flord-Wayshall
該算法用于求圖中任意兩點的最短路徑,復雜度O(V^3)宦焦。這個算法是基于DP的一種算法。思想也非常簡單笼平,考慮節(jié)點u到節(jié)點v的距離為d[u][v],假設有某個節(jié)點k使得u到k然后再到v的距離比原來的小寓调,那就替換之。

# Floyd-Warshall T:O(V^3) S:O(V)
def floydwarshall(graph):
    # Initialize dist and pred:
    # copy graph into dist, but add infinite where there is
    # no edge, and 0 in the diagonal
    dist = {}
    pred = {}
    for u in graph:
        dist[u] = {}
        pred[u] = {}
        for v in graph:
            dist[u][v] = 1000
            pred[u][v] = -1
        dist[u][u] = 0
        for neighbor in graph[u]:
            dist[u][neighbor] = graph[u][neighbor]
            pred[u][neighbor] = u

    for t in graph:
        # given dist u to v, check if path u - t - v is shorter
        for u in graph:
            for v in graph:
                newdist = dist[u][t] + dist[t][v]
                if newdist < dist[u][v]:
                    dist[u][v] = newdist
                    pred[u][v] = pred[t][v]  # route new path through t
 return dist, pred
最后編輯于
?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市余黎,隨后出現(xiàn)的幾起案子载萌,更是在濱河造成了極大的恐慌扭仁,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,348評論 6 491
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件搀突,死亡現(xiàn)場離奇詭異仰迁,居然都是意外死亡顽分,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,122評論 2 385
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來悬秉,“玉大人和泌,你說我怎么就攤上這事祠肥。” “怎么了县恕?”我有些...
    開封第一講書人閱讀 156,936評論 0 347
  • 文/不壞的土叔 我叫張陵忠烛,是天一觀的道長美尸。 經(jīng)常有香客問我师坎,道長,這世上最難降的妖魔是什么蕊温? 我笑而不...
    開封第一講書人閱讀 56,427評論 1 283
  • 正文 為了忘掉前任义矛,我火速辦了婚禮症革,結果婚禮上鸯旁,老公的妹妹穿的比我還像新娘。我一直安慰自己艇挨,他們只是感情好,可當我...
    茶點故事閱讀 65,467評論 6 385
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著脉漏,像睡著了一般侧巨。 火紅的嫁衣襯著肌膚如雪鞭达。 梳的紋絲不亂的頭發(fā)上皇忿,一...
    開封第一講書人閱讀 49,785評論 1 290
  • 那天,我揣著相機與錄音繁扎,去河邊找鬼锻离。 笑死,一個胖子當著我的面吹牛卫键,可吹牛的內(nèi)容都是我干的莉炉。 我是一名探鬼主播碴犬,決...
    沈念sama閱讀 38,931評論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼絮宁,長吁一口氣:“原來是場噩夢啊……” “哼服协!你這毒婦竟也來了绍昂?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 37,696評論 0 266
  • 序言:老撾萬榮一對情侶失蹤窘游,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后艾蓝,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,141評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,483評論 2 327
  • 正文 我和宋清朗相戀三年于置,在試婚紗的時候發(fā)現(xiàn)自己被綠了俱两。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,625評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡讲婚,死狀恐怖筹麸,靈堂內(nèi)的尸體忽然破棺而出活合,到底是詐尸還是另有隱情,我是刑警寧澤物赶,帶...
    沈念sama閱讀 34,291評論 4 329
  • 正文 年R本政府宣布白指,位于F島的核電站,受9級特大地震影響酵紫,放射性物質發(fā)生泄漏告嘲。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,892評論 3 312
  • 文/蒙蒙 一奖地、第九天 我趴在偏房一處隱蔽的房頂上張望橄唬。 院中可真熱鬧,春花似錦参歹、人聲如沸仰楚。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,741評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽僧界。三九已至,卻和暖如春械筛,著一層夾襖步出監(jiān)牢的瞬間捎泻,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,977評論 1 265
  • 我被黑心中介騙來泰國打工埋哟, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留笆豁,地道東北人。 一個月前我還...
    沈念sama閱讀 46,324評論 2 360
  • 正文 我出身青樓赤赊,卻偏偏與公主長得像闯狱,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子抛计,可洞房花燭夜當晚...
    茶點故事閱讀 43,492評論 2 348

推薦閱讀更多精彩內(nèi)容

  • 弗洛伊德算法適用于為圖中每一個頂點求最短路徑哄孤,思路如下 檢查圖中任何一個 到 任何另一個點能否通過第一個點降低最短...
    RichardW閱讀 949評論 0 1
  • 1 概述 最短路徑是圖中的常見問題,最典型的應用是:當我們用百度地圖或高德地圖引導我們?nèi)ツ硞€地方時吹截,它通常會給出一...
    CodingTech閱讀 1,495評論 4 9
  • https://zh.visualgo.net/graphds 淺談圖形結構https://zh.visualgo...
    狼之獨步閱讀 4,137評論 0 0
  • 1736年瘦陈,瑞士數(shù)學家Euler(歐拉)在他的一篇論文中討論了格尼斯七橋問題凝危,由此誕生了一個全新的數(shù)學分支——圖論...
    不困于情閱讀 4,393評論 0 9
  • 今天下班約了個朋友L見面,L是我的小學同學晨逝,小時候玩得很好蛾默,慢慢的,越長大聯(lián)系得就越少了捉貌,最近聯(lián)系得比較頻繁支鸡,約好...
    貳佰沒有伍_b84f閱讀 208評論 1 1