------------------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】