------------------siwuxie095
有權圖
這里介紹有權圖(Weighted Graph),所謂有權圖,就是圖中的
每一條邊上都會有相應的一個或一組值。通常情況下,這個值只是
一個數字
如:在交通運輸網中,邊上的權值可能表示的是路程,也可能表示
的是運輸費用(顯然二者都是數字)。不過,邊上的權值也有可能
是其它東西,比如說是一個字符串,甚至是一個更加復雜的數據包,
里面集合了更多的數據
關於有權圖的實現,看如下實例:

(1)鄰接矩陣
|
|
0 |
1 |
2 |
3 |
| 0 |
0 |
0.12 |
0 |
0 |
| 1 |
0.12 |
0 |
0.34 |
0.52 |
| 2 |
0 |
0.34 |
0 |
0.28 |
| 3 |
0 |
0.52 |
0.28 |
0 |
對於鄰接矩陣 A 來說,只需要在位置 A[i][j] 處寫上相應的權值
即可,對於沒有邊的地方,權值為 0
(2)鄰接表
| 0 |
{to:1,w:0.12} |
|
|
| 1 |
{to:1,w:0.12} |
{to:2,w:0.34} |
{to:3,w:0.52} |
| 2 |
{to:1,w:0.34} |
{to:3,w:0.28} |
|
| 3 |
{to:1,w:0.52} |
{to:2,w:0.28} |
|
對於鄰接表來說,只需要存儲兩個信息:
1)相鄰的頂點的索引
2)相應邊上的權值
顯然,這兩個信息不能用一個簡單的基本數據類型來表達,因此,
需要把它們封裝成一個類 Edge,即 每個頂點下都是和當前頂點
相鄰的所有邊
不管是鄰接矩陣,還是鄰接表,為了保證二者具有統一的接口,
需要在鄰接矩陣的 A[i][j] 處也存儲邊 Edge,而不僅僅是權值,
而沒有邊的地方,存儲為 NULL 即可
因為 NULL 的存在,鄰接矩陣和鄰接表就不能簡單存儲邊 Edge,
而要存儲邊指針 *Edge
程序:
Edge.h:
| #ifndef EDGE_H #define EDGE_H
#include <iostream> #include <cassert> using namespace std;
//邊信息:兩個頂點和權值 template<typename Weight> class Edge {
private:
int a, b; //邊的兩個頂點a和b(如果是有向圖,就默認從頂點a指向頂點b) Weight weight; //邊上的權值
public:
Edge(int a, int b, Weight weight) { this->a = a; this->b = b; this->weight = weight; }
//默認構造函數 Edge(){}
~Edge(){}
int v(){ return a; }
int w(){ return b; }
Weight wt() { return weight; }
//知道邊的一個頂點x,返回另一個頂點 int other(int x) { assert(x == a || x == b); return x == a ? b : a; }
//友元函數重載 friend ostream &operator<<(ostream &os, const Edge &e) { os << e.a << "-" << e.b << ": " << e.weight; return os; }
bool operator<(Edge<Weight> &e) { return weight < e.wt(); }
bool operator<=(Edge<Weight> &e) { return weight <= e.wt(); }
bool operator>(Edge<Weight> &e) { return weight > e.wt(); }
bool operator>=(Edge<Weight> &e) { return weight >= e.wt(); }
bool operator==(Edge<Weight> &e) { return weight == e.wt(); } };
#endif |
SparseGraph.h:
| #ifndef SPARSEGRAPH_H #define SPARSEGRAPH_H
#include "Edge.h" #include <iostream> #include <vector> #include <cassert> using namespace std;
// 稀疏圖 - 鄰接表 template<typename Weight> class SparseGraph {
private:
int n, m; //n 和 m 分別表示頂點數和邊數 bool directed; //directed表示是有向圖還是無向圖 vector<vector<Edge<Weight> *>> g; //g[i]里存儲的就是和頂點i相鄰的所有邊指針
public:
SparseGraph(int n, bool directed) { this->n = n; this->m = 0; this->directed = directed; //g[i]初始化為空的vector for (int i = 0; i < n; i++) { g.push_back(vector<Edge<Weight> *>()); } }
~SparseGraph() {
for (int i = 0; i < n; i++) { for (int j = 0; j < g[i].size(); j++) { delete g[i][j]; } } }
int V(){ return n; } int E(){ return m; }
void addEdge(int v, int w, Weight weight) { assert(v >= 0 && v < n); assert(w >= 0 && w < n);
g[v].push_back(new Edge<Weight>(v, w, weight)); //(1)頂點v不等於頂點w,即不是自環邊 //(2)且不是有向圖,即是無向圖 if (v != w && !directed) { g[w].push_back(new Edge<Weight>(w, v, weight)); }
m++; }
//hasEdge()判斷頂點v和頂點w之間是否有邊 //hasEdge()的時間復雜度:O(n) bool hasEdge(int v, int w) { assert(v >= 0 && v < n); assert(w >= 0 && w < n);
for (int i = 0; i < g[v].size(); i++) { if (g[v][i]->other(v) == w) { return true; } }
return false; }
void show() {
for (int i = 0; i < n; i++) { cout << "vertex " << i << ":\t"; for (int j = 0; j < g[i].size(); j++) { cout << "{to:" << g[i][j]->w() << ",wt:" << g[i][j]->wt() << "}\t"; } cout << endl; } }
//鄰邊迭代器(相鄰,即 adjacent) // //使用迭代器可以隱藏迭代的過程,按照一定的 //順序訪問一個容器中的所有元素 class adjIterator { private:
SparseGraph &G; //圖的引用,即要迭代的圖 int v; //頂點v int index; //相鄰頂點的索引
public:
adjIterator(SparseGraph &graph, int v) : G(graph) { this->v = v; this->index = 0; }
//要迭代的第一個元素 Edge<Weight> *begin() { //因為有可能多次調用begin(), //所以顯式的將index設置為0 index = 0; //如果g[v]的size()不為0 if (G.g[v].size()) { return G.g[v][index]; }
return NULL; }
//要迭代的下一個元素 Edge<Weight> *next() { index++; if (index < G.g[v].size()) { return G.g[v][index]; }
return NULL; }
//判斷迭代是否終止 bool end() { return index >= G.g[v].size(); } }; };
#endif |
DenseGraph.h:
| #ifndef DENSEGRAPH_H #define DENSEGRAPH_H
#include "Edge.h" #include <iostream> #include <vector> #include <cassert> using namespace std;
// 稠密圖 - 鄰接矩陣 template<typename Weight> class DenseGraph {
private:
int n, m; //n 和 m 分別表示頂點數和邊數 bool directed; //directed表示是有向圖還是無向圖 vector<vector<Edge<Weight> *>> g; //二維矩陣,存儲邊指針
public:
DenseGraph(int n, bool directed) { this->n = n; this->m = 0; this->directed = directed; //二維矩陣:n行n列,全部初始化為NULL for (int i = 0; i < n; i++) { g.push_back(vector<Edge<Weight> *>(n, NULL)); } }
~DenseGraph() { for (int i = 0; i < n; i++) { for (int j = 0; j < n; j++) { if (g[i][j] != NULL) { delete g[i][j]; } } } }
int V(){ return n; } int E(){ return m; }
//在頂點v和頂點w之間建立一條邊 void addEdge(int v, int w, Weight weight) { assert(v >= 0 && v < n); assert(w >= 0 && w < n);
//如果頂點v和頂點w之間已經存在一條邊,就刪掉, //之后按照傳入權值重建一條邊,即直接覆蓋 if (hasEdge(v, w)) { delete g[v][w];
//如果是無向圖,還要刪除和主對角線對稱的值 if (!directed) { delete g[w][v]; }
m--; }
g[v][w] = new Edge<Weight>(v, w, weight);
//如果是無向圖,還要在和主對角線對稱處添加值 if (!directed) { g[w][v] = new Edge<Weight>(w, v, weight); }
m++; }
//hasEdge()判斷頂點v和頂點w之間是否有邊 //hasEdge()的時間復雜度:O(1) bool hasEdge(int v, int w) { assert(v >= 0 && v < n); assert(w >= 0 && w < n); return g[v][w] != NULL; }
void show() {
for (int i = 0; i < n; i++) { for (int j = 0; j < n; j++) { if (g[i][j]) { cout << g[i][j]->wt() << "\t"; } else { cout << "NULL\t"; } } cout << endl; } }
//鄰邊迭代器(相鄰,即 adjacent) class adjIterator { private:
DenseGraph &G; //圖引用,即要迭代的圖 int v; //頂點v int index; //相鄰頂點的索引
public:
adjIterator(DenseGraph &graph, int v) : G(graph) { this->v = v; this->index = -1; }
//要迭代的第一個元素 Edge<Weight> *begin() { //找第一個權值不為NULL的元素,即為要迭代的第一個元素 index = -1; return next(); }
//要迭代的下一個元素 Edge<Weight> *next() { for (index += 1; index < G.V(); index++) { if (G.g[v][index]) { return index; } }
return NULL; }
//判斷迭代是否終止 bool end() { return index >= G.V(); } }; };
#endif |
ReadGraph.h:
| #ifndef READGRAPH_H #define READGRAPH_H
#include <iostream> #include <string> #include <fstream> #include <sstream> #include <cassert> using namespace std;
//從文件中讀取圖的測試用例 template <typename Graph, typename Weight> class ReadGraph {
public: ReadGraph(Graph &graph, const string &filename) {
ifstream file(filename); string line; //一行一行的讀取 int V, E;
assert(file.is_open());
//讀取file中的第一行到line中 assert(getline(file, line)); //將字符串line放在stringstream中 stringstream ss(line); //通過stringstream解析出整型變量:頂點數和邊數 ss >> V >> E;
//確保文件里的頂點數和圖的構造函數中傳入的頂點數一致 assert(V == graph.V());
//讀取file中的其它行 for (int i = 0; i < E; i++) {
assert(getline(file, line)); stringstream ss(line);
int a, b; Weight w; ss >> a >> b >> w; assert(a >= 0 && a < V); assert(b >= 0 && b < V); graph.addEdge(a, b, w); } } };
#endif |
main.cpp:
| #include "SparseGraph.h" #include "DenseGraph.h" #include "ReadGraph.h" #include <iostream> #include <iomanip> using namespace std;
int main() {
string filename = "testG1.txt"; int V = 8;
//設置精確度,這里保留兩位小數 cout << fixed << setprecision(2);
// Test Weighted Dense Graph DenseGraph<double> g1 = DenseGraph<double>(V, false); ReadGraph<DenseGraph<double>, double> readGraph1(g1, filename); g1.show(); cout << endl;
// Test Weighted Sparse Graph SparseGraph<double> g2 = SparseGraph<double>(V, false); ReadGraph<SparseGraph<double>, double> readGraph2(g2, filename); g2.show(); cout << endl;
system("pause"); return 0; } |
運行一覽:

其中,testG1.txt 的內容如下:

該文件可以分成兩個部分:
(1)第一行:兩個數字分別代表頂點數和邊數
(2)其它行:每一行的前兩個數字表示一條邊,第三個數字表示權值
【made by siwuxie095】
