直接跳到 3. 看代码,注释较为详细
理论知识参考
代码参考(写的太好了!)
1. 什么是图
- 表示 “多对多” 的关系
- 包含:1. 一组顶点:通常用V(vertex) 表示顶点集合;2. 一组边:通常用E(edge) 表示边的集合。
- 边是定点对:(v, w)∈E,其中v, w∈V
- 有向边<v, w> 表示从 v 指向 w 的边(单行边)
表示方法
-
邻接矩阵表示法
好处:
- 直观、简单、好理解
- 方便检查任意一对的顶点间是否存在边
- 方便找任一顶点的所有 “邻接点” (有边直接相连的顶点)
- 方便计算任一顶点的 “度” (从该点发出的边数为 “出度” ,指向该点的边数为 “入度”)
- 无向图:对应行(或列)非零元素的个数
- 有向图:对应行非零元素的个数是 “出度” ;对应列非零元素的个数是 “入度”
缺点:
- 浪费空间:存稀疏图(点很多而边很少)有大量无效元素。(对稠密图特别是完全图还是很合算的)
- 浪费时间:统计稀疏图中一共有多少条边
-
邻接表
G[N] 为指针数组,对应矩阵每行一个链表,只存非0元素
特点:
- 方便找任一顶点的所有 “邻接点”
- 节约稀疏图的空间
- 需要N个头指针 + 2E个结点(每个结点至少两个域)
- 方便计算任一顶点的 “度” ?
- 对无向图:是的
- 对有向图:只能计算 “出度” ;需要构造 “逆邻接表”(存指向自己的边)来方便计算 “入度”
- 不方便检查任意一对顶点间是否存在边
2. 图的遍历
-
深度优先搜索(Depth First Search,DFS):一条道走到黑,然后回退!(基于栈)
// 伪码描述--类似于树的先序遍历 void DFS(vertex V) { visited[V] = true; for ( V 的每个邻接点 W ) if (!visited[W]) DFS(W); }
若有N个顶点、E条边,时间复杂度是:
- 用邻接表存储图,有O(N+E)
- 用邻接矩阵存储图,有O(N²)
-
广度优先搜索(Breadth First Search, BFS):广泛撒网,共同前行!(基于队列)
// 伪码描述 void BFS(vertex V) { visited[V] = true; enqueue(V, Q); while (!isEmpty(Q)) { V = dequeue(Q); for ( V 的每个邻接点 W) if (!visited[W]) { visited[W] = true; enqueue(W, Q); } } }
若有N个顶点、E条边,时间复杂度是:
- 用邻接表存储图,有O(N+E)
- 用邻接矩阵存储图,有O(N²)
图不连通怎么办?
每调用一次DFS(V),就把 V 所在的连通分量遍历一遍。BFS也是一样。
void ListComponents(Graph G) {
for (each V in G)
if (!visited[v]) {
DFS(V); // or BFS(V);
}
}
3. 代码实现
3.1 广度优先遍历
邻接矩阵实现:
#include <iostream>
#include <queue>
using namespace std;
typedef int ElemType;
#define MAXNUM 100
bool visited[MAXNUM]; // 定义visited数组,记录顶点的访问情况
queue<ElemType> q; // 队列用来记录相邻结点
// !!!邻接矩阵表示图的广度优先遍历 !!!
// 邻接矩阵存储表示
struct AMGraph {
ElemType vertex[MAXNUM]; // 顶点表
int matrix[MAXNUM][MAXNUM]; // 邻接矩阵
int vernum, edgenum; // 当前顶点数和边数
};
// 找到顶点v对应下标
int locateVertex(AMGraph& G, ElemType v) {
for (int i = 0; i < G.vernum; ++i) {
if (G.vertex[i] == v) {
return i;
}
}
return -1;
}
// 采用邻接矩阵表示法,构建无向图G
bool createUDG(AMGraph& G) {
int i, j, k;
ElemType v1, v2;
cin >> G.vernum >> G.edgenum; // 输入总顶点数和边数
for (i = 0; i < G.vernum; ++i) {
cin >> G.vertex[i]; // 输入顶点表
}
// 初始化邻接矩阵边,0 表示顶点i和j之间无边
for (i = 0; i < G.vernum; ++i) {
for (j = 0; j < G.edgenum; ++j) {
G.matrix[i][j] = 0;
}
}
// 1 表示两顶点之间有边
for (k = 0; k < G.edgenum; ++k) {
cin >> v1 >> v2;
i = locateVertex(G, v1); // 找到顶点i的下标
j = locateVertex(G, v2); // 找到顶点j的下标
G.matrix[i][j] = G.matrix[j][i] = 1; // 即i和j之间有边
}
return true;
}
// 用邻接矩阵表示图的广度优先遍历
void BFS_AM(AMGraph& G, ElemType v0) {
int i, j, v, w;
i = locateVertex(G, v0); // 找到v0对应的下标
cout << v0 << " ";
visited[i] = 1; // 标记v0已被访问
q.push(v0); // v0入队
while (!q.empty()) {
v = q.front(); // 输出队头元素,将要访问邻接点
i = locateVertex(G, v); // 得到顶点v的下标
q.pop(); // 将队头元素出队
for (j = 0; j < G.vernum; ++j) {
w = G.vertex[j]; // 在j位置上的顶点,先保存下来
// 如果顶点v和w之间有边且顶点w未被访问
if (G.matrix[i][j] && !visited[j]) {
cout << w << " "; // !!!遍历 !!!
q.push(w); // 顶点w入队
visited[j] = 1; // 顶点w已被访问
}
}
}
}
邻接表实现:
#include <iostream>
#include <queue>
using namespace std;
typedef int ElemType;
#define MAXNUM 100
bool visited[MAXNUM]; // 定义visited数组,记录顶点的访问情况
queue<ElemType> q; // 队列用来记录相邻结点
// !!! 用邻接表表示图的广度优先搜索 !!!
// 邻接表存储表示
// 表结点
struct EdgeNode {
int locateVer; // 该边所指向的顶点的位置
EdgeNode* nextEdge; // 指向下一条边的指针
int info; // 和边有关的信息,如权值
};
// 顶点结点
struct VerNode {
ElemType data;
EdgeNode* firstEdge; // 指向第一条依附在该顶点的边的指针
};
struct ALGraph {
VerNode vertex[MAXNUM]; // 表头结点表
int vernum, edgenum; // 总顶点数和边数
};
// 找到顶点对应的下标
int locateVertex(ALGraph& G, ElemType v) {
for (int i = 0; i < G.vernum; ++i) {
if (G.vertex[i].data == v) {
return i;
}
}
return -1;
}
bool createALG(ALGraph& G) {
int i, j, k;
ElemType v1, v2;
cin >> G.vernum >> G.edgenum; // 输入总顶点数和总边数
for (i = 0; i < G.vernum; ++i) {
cin >> G.vertex[i].data; // 输入顶点值
G.vertex[i].firstEdge = nullptr; // 初始化每个表头结点的指针域
}
// 输入各边,构造邻接表
for (k = 0; k < G.edgenum; ++k) {
cin >> v1 >> v2; // 输入一条边的两侧顶点
i = locateVertex(G, v1); // 找到顶点i的下标
j = locateVertex(G, v2); // 找到顶点j的下标
// 操作表结点
EdgeNode* p1 = new EdgeNode; // 创建一个边结点
p1->locateVer = j; // 邻接点域为j
p1->nextEdge = G.vertex[i].firstEdge;
G.vertex[i].firstEdge = p1;
// 因为一条边对应两个顶点,直接生成另一对称的新的表结点
EdgeNode* p2 = new EdgeNode;
p2->locateVer = i;
p2->nextEdge = G.vertex[j].firstEdge;
G.vertex[j].firstEdge = p1;
}
return true;
}
// 采用邻接表表示图的广度优先遍历----操作与邻接矩阵表示类似
void BFS_AL(ALGraph& G, ElemType v0) {
int i, j, v;
EdgeNode* p = nullptr;
cout << v0 << " ";
i = locateVertex(G, v0);
visited[i] = 1;
q.push(v0);
while (!q.empty()) {
v = q.front(); // 输出队列中的第一个顶点
i = locateVertex(G, v); // 计算其下标
q.pop(); // 顶点v出队
// 遍历顶点v的邻接点
for (p = G.vertex[i].firstEdge; p; p = p->nextEdge) {
j = p->locateVer; // 记录该顶点下标
// 如果顶点p未被访问
if (!visited[j]) {
cout << G.vertex[j].data << " "; // 访问顶点p
visited[j] = 1; // 标记p已被访问
q.push(G.vertex[j].data); // 将顶点p入队
}
}
}
}
3.2 深度优先遍历
邻接矩阵实现
#include <iostream>
#include <queue>
using namespace std;
typedef int ElemType;
#define MAXNUM 100
bool visited[MAXNUM]; // 定义visited数组,记录顶点的访问情况
queue<ElemType> q; // 队列用来记录相邻结点
// !!!邻接矩阵表示图的深度优先遍历 !!!
// 邻接矩阵存储表示
struct AMGraph {
ElemType vertex[MAXNUM]; // 顶点表
int matrix[MAXNUM][MAXNUM]; // 邻接矩阵
int vernum, edgenum; // 当前顶点数和边数
};
// 找到顶点v对应下标
int locateVertex(AMGraph& G, ElemType v) {
for (int i = 0; i < G.vernum; ++i) {
if (G.vertex[i] == v) {
return i;
}
}
return -1;
}
// 采用邻接矩阵表示法,构建无向图G
bool createUDG(AMGraph& G) {
int i, j, k;
ElemType v1, v2;
cin >> G.vernum >> G.edgenum; // 输入总顶点数和边数
for (i = 0; i < G.vernum; ++i) {
cin >> G.vertex[i]; // 输入顶点表
}
// 初始化邻接矩阵边,0 表示顶点i和j之间无边
for (i = 0; i < G.vernum; ++i) {
for (j = 0; j < G.edgenum; ++j) {
G.matrix[i][j] = 0;
}
}
// 1 表示两顶点之间有边
for (k = 0; k < G.edgenum; ++k) {
cin >> v1 >> v2;
i = locateVertex(G, v1); // 找到顶点i的下标
j = locateVertex(G, v2); // 找到顶点j的下标
G.matrix[i][j] = G.matrix[j][i] = 1; // 即i和j之间有边
}
return true;
}
void DFS_AM(AMGraph& G, int i) {
int j;
cout << G.vertex[i] << " "; // 输出顶点
visited[i] = 1; // 标记为已访问
for (j = 0; j < G.vernum; ++j) {
// 如果两顶点之间有边且该顶点还未被访问,则递归调用DFS
if (G.matrix[i][j] && !visited[j]) {
DFS_AM(G, j);
}
}
}
邻接表实现:
#include <iostream>
#include <queue>
using namespace std;
typedef int ElemType;
#define MAXNUM 100
bool visited[MAXNUM]; // 定义visited数组,记录顶点的访问情况
queue<ElemType> q; // 队列用来记录相邻结点
// !!! 用邻接表表示图的深度优先搜索 !!!
// 邻接表存储表示
// 表结点
struct EdgeNode {
int locateVer; // 该边所指向的顶点的位置
EdgeNode* nextEdge; // 指向下一条边的指针
int info; // 和边有关的信息,如权值
};
// 顶点结点
struct VerNode {
ElemType data;
EdgeNode* firstEdge; // 指向第一条依附在该顶点的边的指针
};
struct ALGraph {
VerNode vertex[MAXNUM]; // 表头结点表
int vernum, edgenum; // 总顶点数和边数
};
// 找到顶点对应的下标
int locateVertex(ALGraph& G, ElemType v) {
for (int i = 0; i < G.vernum; ++i) {
if (G.vertex[i].data == v) {
return i;
}
}
return -1;
}
bool createALG(ALGraph& G) {
int i, j, k;
ElemType v1, v2;
cin >> G.vernum >> G.edgenum; // 输入总顶点数和总边数
for (i = 0; i < G.vernum; ++i) {
cin >> G.vertex[i].data; // 输入顶点值
G.vertex[i].firstEdge = nullptr; // 初始化每个表头结点的指针域
}
// 输入各边,构造邻接表
for (k = 0; k < G.edgenum; ++k) {
cin >> v1 >> v2; // 输入一条边的两侧顶点
i = locateVertex(G, v1); // 找到顶点i的下标
j = locateVertex(G, v2); // 找到顶点j的下标
// 操作表结点
EdgeNode* p1 = new EdgeNode; // 创建一个边结点
p1->locateVer = j; // 邻接点域为j
p1->nextEdge = G.vertex[i].firstEdge;
G.vertex[i].firstEdge = p1;
// 因为一条边对应两个顶点,直接生成另一对称的新的表结点
EdgeNode* p2 = new EdgeNode;
p2->locateVer = i;
p2->nextEdge = G.vertex[j].firstEdge;
G.vertex[j].firstEdge = p1;
}
return true;
}
void DFS_AL(ALGraph& G, int i) {
int j;
cout << G.vertex[i].data << " "; // 访问该顶点
visited[i] = 1; // 标记为以访问
// 是否可以不用申请一个空间?
// EdgeNode* p = new EdgeNode;
// p = G.vertex[i].firstEdge;
EdgeNode* p = G.vertex[i].firstEdge;
while (p) {
j = p->locateVer; // 该顶点的下标
if (!visited[j]) {
DFS_AL(G, j);
}
p = p->nextEdge;
}
}