基本概念
图(graph)是用线连接在一起的顶点或节点的集合,即两个要素:边和顶点。每一条边连接个两个顶点,用(i,j)表示顶点为 i 和 j 的边。
如果用图示来表示一个图,一般用圆圈表示顶点,线段表示边。有方向的边称为有向边,对应的图成为有向图,没有方向的边称为无向边,对应的图叫无向图。对于无向图,边(i, j)和(j,i)是一样的,称顶点 i 和 j 是邻接的,边(i,j)关联于顶点 i 和 j ;对于有向图,边(i,j)表示由顶点 i 指向顶点 j 的边,即称顶点 i 邻接至顶点 j ,顶点 i 邻接于顶点 j ,边(i,j)关联至顶点 j 而关联于顶点 i 。
对于很多的实际问题,不同顶点之间的边的权值(长度、重量、成本、价值等实际意义)是不一样的,所以这样的图被称为加权图,反之边没有权值的图称为无权图。所以,图分为四种:加权有向图,加权无向图,无权有向图,无权无向图。
在一个无向图中,与一个顶点相关联的边数成为该顶点的度。而对于有向图,则用入度来表示关联至该顶点的边数,出度来表示关联于该顶点的边数。
一个具有n个顶点和n(n-1)/2条边的无向图称为一个完全图,即每个顶点的度等于总顶点数减1。
图的描述
抽象数据类型
定义抽象数据类型graph,有向图、无向图、加权图和无权图都可以根据此ADT实现。
numberOfVerticices():返回图的顶点数
numberOfEdges:返回图的边数
exitsEdge(i, j):如果边(i,j)存在,则返回true,否则返回false
insertEdge(theEdge):插入边theEdge
eraseEdge(i, j):删除边(i,j)
degree(i):返回顶点 i 的度(无向图)
inDegree(i):返回顶点 i 的入度
outDegree(i):返回顶点 i 的出度
directed():当且仅当有向图,返回true
weighted():当且仅当加权图,返回true
无权图的描述
对无向图的描述方法都是基于邻接的方式:邻接矩阵、邻接链表和邻接数组。
邻接矩阵
用图e的邻接矩阵表示图f的无权图,A(i,j)等于1表示边(i,j)存在,等于0则表示不存在。有向图无向图都可以用矩阵这样表示,但是对于无向图,矩阵具有对称性,即A(i,j)和A(j,i)一样,所以为了节省空间,可以用下三角(上三角)矩阵表示。
邻接链表
图g是图f对应的邻接链表,每一个和顶点 i 关联的顶点 j ,依次构成一条链表,节点的元素是顶点,节点的next指针指向下一个 j 构成的顶点,最后一个节点指向nullptr,7条链表用一个表List来维护。
邻接数组
图g是图f对应的邻接数组,可以看成一个列数不等的二位数组,也可以看成由一个元素为一维数组的表List。
加权图的描述
对于加权图,一般使用邻接矩阵或邻接链表描述。
邻接矩阵
和无权图的邻接矩阵描述相同道理,只不过A(I,j)的值不再是0或1,而是对应的加权图中的权值,而对于不存在的边,可以用一个固定的值表示,如-1。
邻接链表
和无权图的邻接链表描述也类似,每一个和顶点 i 关联的顶点 j 依次构成一条链表,不过需要改变的是节点元素包含两个域{顶点j,权值},节点next指向下一个节点,也用一个List维护这些链表。
类实现(C++)
使用邻接矩阵描述实现加权有向图,权值类型为T,无向图和无权图都可以由它派生出。
//加权图的边
template <typename T>
class weightedEdge {
public:
weightedEdge() {}
weightedEdge(int theV1, int theV2, T theW) : v1(theV1), v2(theV2), w(theW) {}
~weightedEdge() {}
int vertex1() const { return v1; }
int vertex2() const { return v2; }
T weight() const { return w; }
operator T() const { return w; }
private:
int v1,
v2;
T w;
};
//邻接矩阵加权有向图
template <typename T>
class adjacencyWDigraph {
public:
//构造函数和析构函数
adjacencyWDigraph(int numberOfVertices = 0, T theNoEdge = 0);
~adjacencyWDigraph();
int numberOfVertices() const { return n; }
int numberOfEdges() const { return e; }
bool directed() const { return true; }
bool weighted() const { return true; }
//判断边(i,j)是否存在
bool existEdge(int i, int j) const;
//插入、删除边
void insertEdge(weightedEdge<T> *theEdge);
void eraseEdge(int i, int j);
//顶点的入度和出度
int inDegree(int theVertex) const;
int outDegree(int theVertex) const;
//广度优先遍历和深度优先遍历
void bfs(int v, int reach[], int lable);
void dfs(int v, int reach[], int lable);
//迭代器
class iterator {
public:
iterator(T *theRow, T theNoEdge, int numberOfVertices) {
row = theRow;
noEdge = theNoEdge;
n = numberOfVertices;
currentVertex = 1;
}
~iterator() {}
int next() {
for (int j = currentVertex; j <= n; ++j)
if (row[j] != noEdge) {
currentVertex = j + 1;
return j;
}
currentVertex = n + 1;
return 0;
}
int next(T &theWeight) {
for (int j = currentVertex; j <= n; ++j)
if (row[j] != noEdge) {
currentVertex = j + 1;
theWeight = row[j];
return j;
}
currentVertex = n + 1;
return 0;
}
private:
T *row; //邻接矩阵的行
T noEdge;
int n;
int currentVertex;
};
//生成迭代器
iterator* makeIterator(int theVertex) {
checkVertex(theVertex);
return new iterator(a[theVertex], noEdge, n);
}
private:
int n; //顶点数
int e; //边数
int **a; //邻接矩阵(二维数组)
T noEdge; //表示不存在的边
//检查顶点是否存在
void checkVertex(int theVertex) const;
};
template <typename T>
adjacencyWDigraph<T>::adjacencyWDigraph(int numberOfVertices = 0, T theNoEdge = 0) {
if (numberOfVertices < 0) {
cout << "number of vertices must be >= 0";
exit(1);
}
n = numberOfVertices;
e = 0;
noEdge = theNoEdge;
a = new T*[n + 1];
for (int i = 0; i <= n; ++i)
a[i] = new int[n + 1];
//初始化邻接矩阵
for (int i = 1; i <= n; ++i)
fill(a[i], a[i] + n + 1, noEdge);
}
template <typename T>
adjacencyWDigraph<T>::~adjacencyWDigraph<T>() {
for (int i = 0; i <= n; ++i)
delete[] a[i];
delete[] a;
a = nullptr;
}
template <typename T>
bool adjacencyWDigraph<T>::existEdge(int i, int j) const {
if (i < 1 || i > n || j < 1 || j > n || a[i][j] == noEdge)
return false;
else
return true;
}
template <typename T>
void adjacencyWDigraph<T>::insertEdge(weightedEdge<T> *theEdge) {
int v1 = theEdge->vertex1();
int v2 = theEdge->vertex2();
if (v1 < 1 || v1 > n || v2 < 1 || v2 > n || v1 == v2) {
cout << "(" << v1 << "," << v2 << ") is not a permissble edge";
exit(1);
}
if (a[v1][v2] == noEdge)
++e;
a[v1][v2] = theEdge->weight();
}
template <typename T>
void adjacencyWDigraph<T>::eraseEdge(int i, int j) {
if (i >= 1 && i <= n && j >= 1 && j <= n && a[i][j] != noEdge) {
a[i][j] = noEdge;
--e;
}
}
template <typename T>
void adjacencyWDigraph<T>::checkVertex(int theVertex) const {
if (theVertex < 1 || theVertex > n) {
cout << "no vertex " << theVertex;
exit(1);
}
}
template <typename T>
int adjacencyWDigraph<T>::inDegree(int theVertex) const {
checkVertex(theVertex);
int sum = 0;
for (int i = 1; i <= n; ++i)
if (a[i][theVertex] != noEdge)
++sum;
return sum;
}
template <typename T>
int adjacencyWDigraph<T>::outDegree(int theVertex) const {
checkVertex(theVertex);
int sum = 0;
for (int j = 1; j <= n; ++j)
if (a[theVertex][j] != noEdge)
++sum;
return sum;
}
图的遍历
很多算法需要从一个已知的顶点开始,搜索所有可以到达的顶点。所谓顶点u是从顶点v可到达的,是指有一条顶点v到顶点u的路径。这种搜索有两种常见的方法:广度优先搜索(breadth first search,BFS)和深度优先搜索(depth first search,DFS)。一般来说,深度优先搜索方法效率更高,使用的也更多。
广度优先搜索
template <typename T>
void adjacencyWDigraph<T>::bfs(int v, int reach[], int lable) {
queue<int> q;
reach[v] = lable;
q.push(v);
while (!q.empty()) {
//从队列中删除一个标记过的顶点
int w = q.front();
q.pop();
iterator *iw = makeIterator(w);
int u;
while((u = iw->next()) != 0)
//u是w的相邻顶点
if (reach[u] == 0) {
q.push(u);
reach[u] = lable;
}
delete iw;
}
}
深度优先搜索
template <typename T>
void adjacencyWDigraph<T>::dfs(int v, int reach[], int lable) {
reach[v] = lable;
iterator *iv = makeIterator(v);
int u;
while ((u = iv->next()) != 0)
//u是v的相邻顶点
if (reach[u] == 0)
dfs(u, reach, lable);
delete iv;
}