目錄:
快速開始使用graph-tool
- 創建和操縱圖
-- 遍歷頂點和邊
----- 遍歷所有頂點或邊
----- 遍歷一個頂點的neighbourhood
名詞解釋:
instante:實例
directed:有向;undirected:無向
on-the-fly:動態
property maps:屬性映射
vertices and edges:頂點和邊
descriptor:描述符
degree:度
index:索引
attribute:屬性;property
invalidated:無效
iteration:迭代;iterators:迭代器
neighbours:鄰接點
obtain:獲取
graph_tool模塊提供了一個圖形類和一些操作它的算法。(graph_tool是一個模塊,提供了類及其算法)
為了提高性能,這個類的內部以及大多數算法都是用c++編寫的,使用了Boost Graph庫。(Boost Graph Library,C++)
必須先導入模塊,才能使用。包被細分成幾個子模塊。使用一個命令就可以將它們全部導入:(導入,子模塊)
>>> from graph_tool.all import *
在下面,總是默認這一行是被運行的。
創建和操縱圖
通過實例化一個Graph類來創建一個空圖:(類的實例化)
>>> g = Graph()
默認情況下,新創建的圖總是有向圖。要想創建一個無向圖,必須加入一個參數:(默認有向圖)
>>> ug = Graph(directed=False)
可以使用set_directed()方法動態地將一個圖在無向圖和有向圖之間切換(反之亦然)。
圖的這種屬性可以通過is_directed()查詢:
>>> ug = Graph()
>>> ug.set_directed(False)
>>> assert(ug.is_directed() == False) #assert是什么意思?
也可以通過另一個圖來創建一個新圖,這時,整個圖(它的內部屬性映射,參見屬性映射章節)將被復制:
>>> g1 = Graph()
>>> # ... construct g1 ...
>>> g2 = Graph(g1) # g1 and g2 are copies
以上,g2是g1的“深度”拷貝,即g2的任何修改不會影響到g1。
一旦創建了一個圖,就可以向它填充頂點和邊。
可以使用add_vertex()方法添加頂點,這將返回一個頂點類的實例,也被稱為頂點描述符。(vertex類的實例,descriptor)
例如,下面的代碼創建了兩個頂點,並返回了存儲在變量v1和v2中的頂點描述符。
>>> v1 = g.add_vertex()
>>> v2 = g.add_vertex()
可以通過類似的方式添加圖的邊,使用add_edge()方法,這將返回一個邊的描述符(edge類的實例)。
>>> e = g.add_edge(v1, v2)
上面的代碼創建了一個從v1到v2的有向邊。
我們可以使用graph_draw()函數顯示到目前為止我們創建的圖。
>>> graph_draw(g, vertex_text=g.vertex_index, vertex_font_size=18,
... output_size=(200, 200), output="two-nodes.png")
<...>
圖1:通過上面的命令創建的一個簡單的有兩個頂點和一條邊的有向圖
使用頂點和邊的描述符,可以以任意的方式檢查和操作圖。
例如,為了獲得一個頂點的出度,我們可以簡單地調用out_degree()方法:
>>> print(v1.out_degree())
1
類似地,我們可以使用in_degree()方法查詢入度。
注意:
對於無向圖,“出度”是度的同義詞,在這種情況下,一個頂點的入度總是零。
邊的描述符有兩個有用的方法:source()和 target(),它分別返回一條邊的來源頂點和目標頂點。(source 和 target)
>>> print(e.source(), e.target())
0 1
add_vertex()方法還接受一個可選參數來指定要創建頂點的數量。(參數:數量)
如果該值大於1,則返回一個在添加頂點描述符上的迭代器:(返回,迭代器)
>>> vlist = g.add_vertex(10)
>>> print(len(list(vlist)))
10
圖中的每個頂點有一個唯一的索引,它總是在0和N-1之間,N則是頂點的數量。(索引)
可以通過使用圖的vertex_index屬性來獲取這個索引(這是一個屬性映射,見屬性映射一章),或將頂點描述符轉換為int類型。(圖的屬性映射獲得索引)
>>> v = g.add_vertex()
>>> print(g.vertex_index[v]) #注意是方括號
12
>>> print(int(v))
12
也可以隨時使用remove_vertex()和remove_edge()方法將邊和頂點刪除:
>>> g.remove_edge(e) # e no longer exists
>>> g.remove_vertex(v2) # the second vertex is also gone
注意:
刪除一個頂點通常是一個數學處理錯誤操作。
頂點都存儲在一個STL向量內部,所以刪除列表中間的一個元素需要改變列表的其余部分。
因此,快速刪除(數學處理錯誤)僅是可能是:保證只刪除列表的最后頂點(最后添加的頂點),或者相對頂點順序是無效的。
最后一個行為可以通過remove_vertex() 的 fast == True 選項實現,導致被刪除的頂點與最后一個頂點“交換”(即最大索引),這將反過來繼承被刪除了的頂點的索引。
警告:
由於以上原因,刪除索引小於(n-1)的頂點將會使最后的(fast = True)或所有的(fast = True)指向較高索引頂點的描述符失效。
因此,如果在指定時間要刪除超過一個頂點,他們應該被以索引降序的方式刪除:
# 'del_list' is a list of vertex descriptors
for v in reversed(sorted(del_list)):
g.remove_vertex(v)
或者(最好),列表(或任何迭代器)直接作為頂點參數傳遞給remove_vertex()函數,上述過程在C++內部執行。
注意,屬性映射值(參見屬性映射)不受頂點刪除后索引變化的影響。
注意:
刪除一條邊是一個(O(k{s} + k{t}))的操作,(k{s})是源頂點的出度,和(k{t})是目標頂點的入度。
可以通過設置set_fast_edge_removal()為真,更快地實現這個,在這種情況下,它變成(O(1)),以犧牲(O(E))空間大小。
邊的描述符不會在邊被刪除之后失效。
既然頂點是索引的唯一標識,那就沒有必要保持頂點標識符 lying around to access them at a later point.
如果我們知道了它的索引,我們就可以獲取頂點的標識符,通過一個給定的索引,使用vertex()方法。
>>> v = g.vertex(8)
這需要一個索引,並返回一個頂點描述符。
邊不能直接得到其索引,但如果一條邊的源和目標頂點指定了,那就可以通過使用edge()方法獲得它。
>>> g.add_edge(g.vertex(2), g.vertex(3))
<...>
>>> e = g.edge(2, 3)
獲得邊或頂點描述符的另一種方法是通過迭代,見迭代部分。
這實際上是獲取頂點和邊的描述符最有效的方式。
和頂點一樣,邊也有唯一的索引,這是由edge_index屬性指定的:
>>> e = g.add_edge(g.vertex(0), g.vertex(1))
>>> print(g.edge_index[e])
1
不同於頂點,邊的索引不一定要符合任何特定的范圍。
如果沒有邊被刪除,邊的索引范圍將在([0,E-1]),(E)是邊的數量,早添加的邊將有更低的索引值。
然而,如果一個邊被刪除,其索引將“空缺”,其余的索引停留在未修改的狀態,因此不會處於([0,E-1])的范圍內。
如果一個新的邊被添加,它將重用舊的索引,以升序的方式。
遍歷頂點和邊
算法必須經常遍歷頂點、邊、頂點的出度等。
圖和頂點類提供了不同類型的迭代器來實現這些。
迭代器總是指向邊或頂點描述符。
遍歷所有頂點和邊
我們應該使用vertices()和edges()方法來遍歷圖所有的頂點或邊:
for v in g.vertices():
print(v)
for e in g.edges():
print(e)
上面的代碼將依序打印圖的頂點和邊。
遍歷領接點
頂點的出、入邊,出、如鄰接點可以分別通過out_edges()、in_edges()、out_neighbours()和** in_neighbours()**方法來迭代。
from itertools import izip
for v in g.vertices():
for e in v.out_edges():
print(e)
for w in v.out_neighbours():
print(w)
# the edge and neighbours order always match
for e,w in izip(v.out_edges(), v.out_neighbours()):
assert(e.target() == w)
上面的代碼將打印圖中所有頂點的出邊和出鄰接點。
注意:
迭代器訪問頂點和邊的順序總是與其被添加到圖中的順序一致(除了edges()返回的迭代器)。
通常,算法不關心這個次序,但是如果需要,可以使用這個固有的順序。
警告:
任何情況下,在迭代時,你都不應該刪除頂點或邊的描述符,因為這會使迭代器失效(同C++)。
如果你打算在迭代期間刪除頂點或邊,您必須首先將它們存儲某個地方(如列表),然后在不使用迭代器的時候刪除它們。
在迭代期間刪除會導致異常。