接續上一篇文章:使用Boost Graph library(一)
讓我們從一個新的圖的開始,定義一些屬性,然後加入一些帶屬性的頂點和邊。我們將給出所有的代碼,這樣你不需要將我們前面給出的代碼片段拼接起來。
// Property types
typedef property<edge_weight_t, int> EdgeWeightProperty;
typedef property<vertex_name_t, std::string,
property<vertex_index2_t, int> > VertexProperties;
// Graph type
typedef adjacency_list<vecS, vecS, undirectedS,
VertexProperties, EdgeWeightProperty> Graph;
// Graph instance
Graph g;
// Property accessors
property_map<Graph, vertex_name_t>::type
city_name = get(vertex_name, g);
property_map<Graph, vertex_index2_t>::type
city_index2 = get(vertex_index2, g);
property_map<Graph, edge_weight_t>::type
edge_distance = get(edge_weight, g);
// Create the vertices
typedef graph_traits<Graph>::vertex_descriptor Vertex;
Vertex u1;
u1 = add_vertex(g);
city_name[u1] = "Los Angeles";
city_index2[u1] = 3;
Vertex u2;
u2 = add_vertex(g);
city_name[u2] = "Bakersfield";
city_index2[u2] = 2;
Vertex u3;
u3 = add_vertex(g);
city_name[u3] = "New York";
city_index2[u3] = 1;
// Create the edges
typedef graph_traits<Graph>::edge_descriptor Edge;
Edge e1;
e1 = (add_edge(u1, u2, g)).first;
edge_distance[e1] = 100;
Edge e2;
e2 = add_edge(u1, u3, g).first;
edge_distance[e2] = 2500;
第一段代碼中,我們定義了屬性類型,接着定義了圖類型,然後我們創建了圖的一個實例g。下一步我們要獲得對象的屬性來存取對象,如同我們前面所說的。
我們開始增加頂點和邊。爲了簡化代碼,從vertex_descriptor創建了一個自己的類型Vertex,接着我們傳遞g給協助函數add_vertex,從而爲g增加一個頂點。
注意
請記住,圖中的頂點是沒有位置的,所以我們不需要告訴圖我們想要增加頂點的位置,我們只需增加頂點即可。
圖增加一個頂點後返回該頂點的頂點描述,這樣我們可以利用屬性存取對象設置該頂點相應的屬性:city_name 和city_index2。設置屬性是容易的,我們只要將頂點象個索引似的放在[]中。
創建邊也是類似的,除了add_edge函數返回的是pair類型,而不是邊類型。所以我們給該函數的返回值加上.first得到需要的邊類型。我們將結果(邊類型)保存在一個變量中,接着我們使用該變量來設置屬性。由於在數學上邊無非就是兩個頂點組成的集合,所以我們需要將它們(兩個頂點)和圖本身傳遞給函數add_edge。
遍歷頂點和邊
爲了遍歷邊和頂點,你需要調用edges或vertices來獲得相應的迭代子。這些函數的返回值類型是pair,你可以在你的遍歷中使用它們。下面的兩小塊代碼展示瞭如何遍歷邊和頂點。
// Iterate through the vertices and print them out
typedef graph_traits<Graph>::vertex_iterator vertex_iter;
std::pair<vertex_iter, vertex_iter> vp;
for (vp = vertices(g); vp.first != vp.second; ++vp.first)
std::cout << city_name[*vp.first] << " " << city_index2[*vp.first] << std::endl;
std::cout << std::endl;
// Iterate through the edges and print them out
Vertex v1, v2;
typedef graph_traits<Graph>::edge_iterator edge_iter;
std::pair<edge_iter, edge_iter> ep;
edge_iter ei, ei_end;
for (tie(ei, ei_end) = edges(g); ei != ei_end; ++ei)
std::cout << edge_distance[*ei] << endl;
上面的這兩小塊代碼使用了兩種不同的方法來演示edges和vertices函數的返回值類型pair的使用。當遍歷頂點時,返回的是一個pair(我們爲這個pair創建了一個typedef);爲了訪問頂點,我們使用vp.first,這是一個迭代子;象絕大多數C++中的迭代子一樣,可以通過解引用(dereference)的方式來獲得它指向的對象。這樣我們可以寫出城市名稱和序號:
city_name[*vp.first]
city_index2[*vp.first]
如果你不想使用pair,標準庫中有一個方便的協助函數tie,它可以將pair中的各個部分賦給tie函數中的實參。比如,edges函數返回一個pair,調用:
tie(ei, ei_end) = edges(g)
將保存pair的第一項給ei,第二項給ei_end。這樣,在循環中,你可以簡單地使用*ei來存取邊,就像:
edge_distance[*ei]
使用圖和boos解決問題
雖然遊戲Six Degrees of Kevin Bacon很好玩,但事實上游戲中的問題就是最短路徑問題,該問題有一些具體的應用,而超出了電影明星所考慮的問題。比方說,你叫一個貨運公司幫你從Bakersfield , California到Lake Mary, Florida運送貨物,運送方需要找到一條從出發地到目的地的最短路徑。通常,這個包裹需要經過幾個城市,每個城市就可以看作圖的一個頂點,並且城市之間是用邊連接起來的。這個圖是帶權重的,這是因爲城市之間的距離是在變化的。在這一點上,這個問題有別於Six Degrees問題的。
數學家和計算機學家研究出了許許多多算法來求解圖論問題,這其中就包括了最短路徑問題,許多書全文討論如何使用圖論方法來解決問題。當然,許多大問題都涉及大量的頂點和邊,這樣的問題最適合讓計算機來計算了。
Boost庫包含許多著名的圖算法,這樣你就不需要自己編寫代碼實現這些算法了。比如它就包含了幾種不同的最短路徑算法。事實上,Kevin Bacon問題並不是圖論真正關心的問題,因爲它所有邊的權重都是1,這意味着當兩個演員出現在一本電影中,他們就連接起來了,而這連接並沒有所謂的物理距離。
當所有的邊的權重都是1時,算法就等價於廣度優先算法(BFS),BFS是用於從根節點(頂點)出發遍歷所有的頂點。其思想是從根頂點出發,訪問與根頂點相連的頂點,並標記這些頂點爲已經訪問過的。接着你再用同樣的方法繼續訪問還沒訪問過的頂點,直到所有的頂點都被訪問,或者在你找到你想要的頂點上停止遍歷,這也意味着你找到了你所要頂點的最短路徑。
提示
請考慮:當你找到你所要尋找的頂點,你如何可以認爲你找到了最短路徑呢?這是因爲,假如有比你現在找到的更短的路徑,它應當在此之前就已經被找到了。這個理由聽起來很平凡,但從數學上,你已經能過看到算法是如何找到最短路徑的。
Boost庫的文檔包含了Kevin Bacon遊戲的很好的示例,在這裏就不重複這些類似的代碼了,我們建議去查看這個文檔。
對大多數程序員來說,比Kevin Bacon遊戲更感興趣的是文件依賴問題。象make和ant程序都需要這個算法,比方說,你在寫一個電子表格軟件,表中有許多單元格,它們包含了對其他單元格引用的公式;需要你給出這些公式的計算順序。如果單元格A1的公式使用了單元格A2,並且A2也包含了一個公式,你就必須在計算A1之前先計算A2中的公式;這就是依賴問題。同樣的道理也出現在編譯程序(如make和ant)中,如果file1依賴於file2,就需要在編譯file1之前編譯file2。Boost Graph庫的文檔中有一個求解文件依賴問題的示例。