Python 實現圖的深度優先和廣度優先搜索

 

 

    在介紹 python 實現圖的深度優先和廣度優先搜索前,我們先來了解下什麼是“圖”。

1 一些定義

頂點

    頂點(也稱爲“節點”)是圖的基本部分。它可以有一個名稱,我們將稱爲“鍵”。

    邊(也稱爲“弧”)是圖的另一個基本部分。邊連接兩個頂點,以表明它們之間存在關係。

權重

    邊可以被加權以示出從一個頂點到另一個頂點的成本。例如,在將一個城市連接到另一個城市的道路的圖表中,邊上的權重可以表示兩個城市之間的距離。

     利用這些定義,我們可以正式定義圖。圖可以由 G 表示,其中 G =(V,E)。對於圖 G,V 是一組頂點,E 是一組邊。每個邊是一個元組 (v,w),其中 w,v ∈ V。我們可以添加第三個組件到邊元組來表示權重。子圖 s 是邊 e 和頂點 v 的集合,使得 e⊂E 和 v⊂V 。

    下圖展示了簡單加權有向圖的另一示例。正式地,我們可以將該圖表示爲:

六個頂點的集合:

V={V0,V1,V2,V3,V4,V5};

9 條邊的集合:

E={(v0,v1,5),(v1,v2,4),(v2,v3,9),(v3,v4,7),(v4,v0,1),(v0,v5,2),(v5,v4,8),(v3,v5,3),(v5,v2,1)}

2 常見的圖結構

    有兩個衆所周知的圖形、實現,鄰接矩陣和鄰接表。

2.1 鄰接矩陣

    實現圖的最簡單的方法之一是使用二維矩陣。在該矩陣實現中,每個行和列表示圖中的頂點。存儲在行 v 和列 w 的交叉點處的單元中的值表示是否存在從頂點 v 到頂點 w 的邊。 當兩個頂點通過邊連接時,我們說它們是相鄰的。 下圖展示了圖的鄰接矩陣。單元格中的值表示從頂點 v 到頂點 w 的邊的權重。

    鄰接矩陣的優點是簡單,對於小圖,很容易看到哪些節點連接到其他節點。 然而,注意矩陣中的大多數單元格是空的。 因爲大多數單元格是空的,我們說這個矩陣是“稀疏的”。矩陣不是一種非常有效的方式來存儲稀疏數據。

2.2 鄰接表

    實現稀疏連接圖的更空間高效的方法是使用鄰接表。在鄰接表實現中,我們保存 Graph 對象中的所有頂點的主列表,然後圖中的每個頂點對象維護連接到的其他頂點的列表。 在我們的頂點類的實現中,我們將使用字典而不是列表,其中字典鍵是頂點,值是權重。

    鄰接表實現的優點是它允許我們緊湊地表示稀疏圖。 鄰接表還允許我們容易找到直接連接到特定頂點的所有鏈接。

3 python 實現圖

    在我們的 Graph 抽象數據類型的實現中,我們將創建兩個類,Graph(保存頂點的主列表)和 Vertex(將表示圖中的每個頂點)。每個頂點使用字典來跟蹤它連接的頂點和每個邊的權重。這個字典稱 connectedTo 。

    下面展示了 Vertex 類的代碼。構造函數只是初始化 id ,通常是一個字符串和 connectedTo 字典。 addNeighbor 方法用於從這個頂點添加一個連接到另一個。getConnections 方法返回鄰接表中的所有頂點,如 connectedTo 實例變量所示。 getWeight 方法返回從這個頂點到作爲參數傳遞的頂點的邊的權重。

class Vertex:
    def __init__(self,key):
        self.id = key
        self.connectedTo = {}

    def addNeighbor(self,nbr,weight=0):
        self.connectedTo[nbr] = weight

    def __str__(self):
        return str(self.id) + ' connectedTo: ' + str([x.id for x in self.connectedTo])

    def getConnections(self):
        return self.connectedTo.keys()

    def getId(self):
        return self.id

    def getWeight(self,nbr):
        return self.connectedTo[nbr]

    下面展示了 Graph 類的代碼,包含將頂點名稱映射到頂點對象的字典。Graph 還提供了將頂點添加到圖並將一個頂點連接到另一個頂點的方法。getVertices方法返回圖中所有頂點的名稱。此外,我們實現了__iter__ 方法,以便輕鬆地遍歷特定圖中的所有頂點對象。 這兩種方法允許通過名稱或對象本身在圖形中的頂點上進行迭代。

class Graph:
    def __init__(self):
        self.vertList = {}
        self.numVertices = 0

    def addVertex(self,key):
        self.numVertices = self.numVertices + 1
        newVertex = Vertex(key)
        self.vertList[key] = newVertex
        return newVertex

    def getVertex(self,n):
        if n in self.vertList:
            return self.vertList[n]
        else:
            return None

    def __contains__(self,n):
        return n in self.vertList

    def addEdge(self,f,t,cost=0):
        if f not in self.vertList:
            nv = self.addVertex(f)
        if t not in self.vertList:
            nv = self.addVertex(t)
        self.vertList[f].addNeighbor(self.vertList[t], cost)

    def getVertices(self):
        return self.vertList.keys()

    def __iter__(self):
        return iter(self.vertList.values())

 

    我們用上面定義的兩個類,建立如下的圖結構。首先我們創建 6 個編號爲 0 到 5 的頂點。然後我們展示頂點字典。注意,對於每個鍵 0 到 5,我們創建了一個頂點的實例。接下來,我們添加將頂點連接在一起的邊。 最後,嵌套循環驗證圖中的每個邊緣是否正確存儲。
 

g = Graph()
for i in range(6):
    g.addVertex(i)
g.addEdge(0,1,5)
g.addEdge(0,5,2)
g.addEdge(1,2,4)
g.addEdge(2,3,9)
g.addEdge(3,4,7)
g.addEdge(3,5,3)
g.addEdge(4,0,1)
g.addEdge(5,4,8)
g.addEdge(5,2,1)
g.vertList

for v in g:
    for w in v.getConnections():
        print("( %s , %s )" % (v.getId(), w.getId()))

4 圖的搜索

    回溯法(探索與回溯法)是一種選優搜索法,按選優條件向前搜索,以達到目標。但當探索到某一步時,發現原先選擇並不優或達不到目標,就退回一步重新選擇,這種走不通就退回再走的技術爲回溯法,而滿足回溯條件的某個狀態的點稱爲“回溯點”。

 

4.1 廣度優先搜索 

    實現步驟:

 

(1)頂點v入隊列。

(2)當隊列非空時則繼續執行,否則算法結束。

(3)出隊列取得隊頭頂點v;訪問頂點v並標記頂點v已被訪問。

(4)查找頂點v的第一個鄰接頂點col。

(5)若v的鄰接頂點col未被訪問過的,則col入隊列。

(6)繼續查找頂點v的另一個新的鄰接頂點col,轉到步驟(5)。直到頂點v的所有未被訪問過的鄰接點處理完。轉到步驟(2)。

    實現代碼:

from pythonds.graphs import Graph, Vertex
from pythonds.basic import Queue

def bfs(g,start):
  start.setDistance(0)
  start.setPred(None)
  vertQueue = Queue()
  vertQueue.enqueue(start)
  while (vertQueue.size() > 0):
    currentVert = vertQueue.dequeue()
    for nbr in currentVert.getConnections():
      if (nbr.getColor() == 'white'):
        nbr.setColor('gray')
        nbr.setDistance(currentVert.getDistance() + 1)
        nbr.setPred(currentVert)
        vertQueue.enqueue(nbr)
    currentVert.setColor('black')

    搜索過程如下圖所示,初始點是白色,探索點是灰色,終結點是黑色

 

 

4.2 深度優先搜索

    實現步驟:

(1)訪問初始頂點v並標記頂點v已訪問。

(2)查找頂點v的第一個鄰接頂點w。

(3)若頂點v的鄰接頂點w存在,則繼續執行;否則回溯到v,再找v的另外一個未訪問過的鄰接點。

(4)若頂點w尚未被訪問,則訪問頂點w並標記頂點w爲已訪問。

(5)繼續查找頂點w的下一個鄰接頂點wi,如果v取值wi轉到步驟(3)。直到連通圖中所有頂點全部訪問過爲止。

    實現代碼:

    由於 dfs 和它的輔助函數dfsvisit 這兩個函數使用一個變量來跟蹤調用 dfsvisit 的時間,所以我們選擇將代碼實現爲繼承自 Graph 類。此實現通過添加時間實例變量和兩個方法 dfs 和 dfsvisit來擴展 Graph 類。看看第 11 行,你會注意到,dfs 方法在調用 dfsvisit 的圖中所有的頂點迭代,這些節點是白色的。我們迭代所有節點而不是簡單地從所選擇的起始節點進行搜索的原因是爲了確保圖中的所有節點都被考慮到,沒有頂點從深度優先森林中被遺漏。for aVertex in self 語句可能看起來不尋常,但請記住,在這種情況下,self是 DFSGraph 類的一個實例,遍歷實例中的所有頂點是一件自然的事情。代碼如下:

from pythonds.graphs import Graph
class DFSGraph(Graph):
    def __init__(self):
        super().__init__()
        self.time = 0

    def dfs(self):
        for aVertex in self:
            aVertex.setColor('white')
            aVertex.setPred(-1)
        for aVertex in self:
            if aVertex.getColor() == 'white':
                self.dfsvisit(aVertex)

    def dfsvisit(self,startVertex):
        startVertex.setColor('gray')
        self.time += 1
        startVertex.setDiscovery(self.time)
        for nextVertex in startVertex.getConnections():
            if nextVertex.getColor() == 'white':
                nextVertex.setPred(startVertex)
                self.dfsvisit(nextVertex)
        startVertex.setColor('black')
        self.time += 1
        startVertex.setFinish(self.time)

    搜索過程如下圖所示,初始點是白色,探索點是灰色,終結點是黑色

    下圖展示了由深度優先搜索算法構造的樹:

 

參考資料:《problem-solving-with-algorithms-and-data-structure-using-python》 
http://www.pythonworks.org/pythonds

 




 

 

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章