
最开始在 2023-07-10 23:53:04 编辑,但一直TJ
一、图的定义
图由顶点集 和边集 组成,分为无向图和有向图。我们以无向图为例讲解(有向图只需修改边的存储逻辑)。
二、图结构的存储
图的存储常用「邻接矩阵法」和「邻接表法」,我们分别实现:
2.1 邻接矩阵法
邻接矩阵是一个二维数组,matrix[i][j]表示顶点i和j之间是否有边(或边的权值)。
cpp
#include <iostream>
using namespace std;
// 最大顶点数(可根据需求调整)
const int MAX_VERTEX = 100;
// 邻接矩阵存储的图
struct GraphMatrix {
char vertices[MAX_VERTEX]; // 顶点数组(存顶点数据,比如字符)
int matrix[MAX_VERTEX][MAX_VERTEX]; // 邻接矩阵
int vertexNum; // 实际顶点数
int edgeNum; // 实际边数
};
// 初始化图
void initGraphMatrix(GraphMatrix &G) {
G.vertexNum = 0;
G.edgeNum = 0;
// 初始化邻接矩阵为0(无边)
for (int i = 0; i < MAX_VERTEX; i++) {
for (int j = 0; j < MAX_VERTEX; j++) {
G.matrix[i][j] = 0;
}
}
}
// 添加顶点
void addVertex(GraphMatrix &G, char v) {
if (G.vertexNum >= MAX_VERTEX) {
cout << "顶点数超过上限!" << endl;
return;
}
G.vertices[G.vertexNum++] = v;
}
// 添加边(无向图,matrix[i][j]和matrix[j][i]都要设为1)
void addEdge(GraphMatrix &G, int i, int j) {
if (i < 0 || i >= G.vertexNum || j < 0 || j >= G.vertexNum) {
cout << "顶点序号非法!" << endl;
return;
}
G.matrix[i][j] = 1;
G.matrix[j][i] = 1; // 无向图的关键:双向赋值
G.edgeNum++;
}
// 打印邻接矩阵
void printGraphMatrix(GraphMatrix &G) {
cout << "邻接矩阵:" << endl;
// 打印顶点表头
cout << " ";
for (int i = 0; i < G.vertexNum; i++) {
cout << G.vertices[i] << " ";
}
cout << endl;
// 打印每行数据
for (int i = 0; i < G.vertexNum; i++) {
cout << G.vertices[i] << " ";
for (int j = 0; j < G.vertexNum; j++) {
cout << G.matrix[i][j] << " ";
}
cout << endl;
}
}
// 测试邻接矩阵
int main() {
GraphMatrix G;
initGraphMatrix(G);
// 添加顶点
addVertex(G, 'A');
addVertex(G, 'B');
addVertex(G, 'C');
addVertex(G, 'D');
// 添加边:A-B、A-C、B-D
addEdge(G, 0, 1);
addEdge(G, 0, 2);
addEdge(G, 1, 3);
// 打印
printGraphMatrix(G);
return 0;
}
运行结果:
邻接矩阵:
A B C D
A 0 1 1 0
B 1 0 0 1
C 1 0 0 0
D 0 1 0 0
2.2 邻接表法
邻接表是「数组 + 链表」的结构:数组存顶点,每个顶点对应一个链表,存与它相连的顶点。
cpp
#include <iostream>
using namespace std;
const int MAX_VERTEX = 100;
// 邻接表的边节点
struct EdgeNode {
int adjVertex; // 邻接顶点的序号
EdgeNode *next; // 下一个边节点
};
// 邻接表的顶点节点
struct VertexNode {
char data; // 顶点数据
EdgeNode *firstEdge; // 指向第一个边节点
};
// 邻接表存储的图
struct GraphList {
VertexNode vertices[MAX_VERTEX];
int vertexNum;
int edgeNum;
};
// 初始化图
void initGraphList(GraphList &G) {
G.vertexNum = 0;
G.edgeNum = 0;
for (int i = 0; i < MAX_VERTEX; i++) {
G.vertices[i].firstEdge = NULL;
}
}
// 添加顶点
void addVertexList(GraphList &G, char v) {
if (G.vertexNum >= MAX_VERTEX) {
cout << "顶点数超过上限!" << endl;
return;
}
G.vertices[G.vertexNum].data = v;
G.vertices[G.vertexNum].firstEdge = NULL;
G.vertexNum++;
}
// 添加边(无向图,双向添加)
void addEdgeList(GraphList &G, int i, int j) {
if (i < 0 || i >= G.vertexNum || j < 0 || j >= G.vertexNum) {
cout << "顶点序号非法!" << endl;
return;
}
// 1. 给i添加指向j的边
EdgeNode *newEdge = new EdgeNode;
newEdge->adjVertex = j;
newEdge->next = G.vertices[i].firstEdge;
G.vertices[i].firstEdge = newEdge;
// 2. 给j添加指向i的边(无向图)
EdgeNode *newEdge2 = new EdgeNode;
newEdge2->adjVertex = i;
newEdge2->next = G.vertices[j].firstEdge;
G.vertices[j].firstEdge = newEdge2;
G.edgeNum++;
}
// 打印邻接表
void printGraphList(GraphList &G) {
cout << "邻接表:" << endl;
for (int i = 0; i < G.vertexNum; i++) {
cout << G.vertices[i].data << " -> ";
EdgeNode *p = G.vertices[i].firstEdge;
while (p != NULL) {
cout << G.vertices[p->adjVertex].data << " ";
p = p->next;
}
cout << endl;
}
}
// 测试邻接表
int main() {
GraphList G;
initGraphList(G);
// 添加顶点
addVertexList(G, 'A');
addVertexList(G, 'B');
addVertexList(G, 'C');
addVertexList(G, 'D');
// 添加边:A-B、A-C、B-D
addEdgeList(G, 0, 1);
addEdgeList(G, 0, 2);
addEdgeList(G, 1, 3);
// 打印
printGraphList(G);
return 0;
}
运行结果:
邻接表:
A -> C B
B -> D A
C -> A
D -> B
三、图的遍历
先放一个我当初新手时实现的代码,简单的邻接矩阵,这个遍历不是那种"走迷宫"的模式,存储的数组代表边的连通,而不是迷宫的"墙"或"路"!
cpp
#include <iostream>
#include <queue>
using namespace std;
typedef int Status;
//数组保存图结构
struct Graph
{
int vexnum = 5;//总长
bool visited[5] = { 0 };//访问标记
//数组存储图。a,b,c,d,e 之间的连通关系
//行节点1,列节点2
//(a-b,b-d,d-b,d-c,e-d)
int maze[5][5] = {
// a, b, c, d, e
0, 0, 0, 0, 0, //a (数组第一下标是行)
0, 0, 1, 0, 1, //b
1, 1, 0, 1, 0, //c
0, 1, 1, 0, 1, //d
0, 0, 0, 0, 0, //e
};
bool VisitFunc(int v)
{
if (visited[v]) return false;
visited[v] = true;
cout << (char)(v+'a') << " ";
return true;
}
//深度优先遍历图-从第v个顶点出发
void DFS(int v) {
//访问第v个顶点
if (VisitFunc(v))
{
//对v的尚未访问的邻接顶点w递归调用DFS
for (int j = 0; j < vexnum; j++) {
if (maze[v][j] == 1)//还有连接的路
{
DFS(j);
}
}
}
}
//广度优先遍历图-从第v个顶点出发
void BFS(int Sv) {
std::queue<int > Q;
if (VisitFunc(Sv))
{
for (int j = 0; j < vexnum; ++j) {
if (maze[Sv][j] == 1)//还有连接的路
{
Q.push(j);
}
}
//第一层完毕↑
}
while ( !Q.empty() )
{
int u = Q.front();
Q.pop();//出队后删除
if (VisitFunc(u))//尝试访问
{
for (int j = 0; j < vexnum; j++) {
if (maze[u][j] == 1)//还有连接的路
{
Q.push(j);
}
}
}
}
}
};
int main()
{
int v = 2;
Graph G1,G2;
cout << "DFS:" << endl;
G1.DFS(v);
cout << endl;
cout << "BFS:" << endl;
G2.BFS(v);
system("pause");
}
图的遍历有「深度优先遍历(DFS)」和「广度优先遍历(BFS)」,基于邻接矩阵实现:
3.1 深度优先遍历(DFS)
类似 "走迷宫",沿着一条路走到头,再回溯。
cpp
#include <iostream>
using namespace std;
const int MAX_VERTEX = 100;
struct GraphMatrix {
char vertices[MAX_VERTEX];
int matrix[MAX_VERTEX][MAX_VERTEX];
int vertexNum;
int edgeNum;
};
// (初始化、添加顶点/边的函数同2.1,此处省略)
// 标记顶点是否被访问过
bool visited[MAX_VERTEX] = {false};
// 深度优先遍历(从顶点v开始)
void DFS(GraphMatrix &G, int v) {
// 访问当前顶点
cout << G.vertices[v] << " ";
visited[v] = true;
// 遍历所有邻接顶点
for (int i = 0; i < G.vertexNum; i++) {
// 如果有边且未被访问,递归访问
if (G.matrix[v][i] == 1 && !visited[i]) {
DFS(G, i);
}
}
}
// 测试DFS
int main() {
GraphMatrix G;
initGraphMatrix(G);
addVertex(G, 'A');
addVertex(G, 'B');
addVertex(G, 'C');
addVertex(G, 'D');
addEdge(G, 0, 1);
addEdge(G, 0, 2);
addEdge(G, 1, 3);
cout << "深度优先遍历结果:";
DFS(G, 0); // 从A开始遍历
cout << endl;
return 0;
}
运行结果:
深度优先遍历结果:A B D C
3.2 广度优先遍历(BFS)
类似 "水波扩散",先访问当前顶点的所有邻接顶点,再依次访问它们的邻接顶点(用数组模拟队列)。
cpp
#include <iostream>
using namespace std;
const int MAX_VERTEX = 100;
struct GraphMatrix {
char vertices[MAX_VERTEX];
int matrix[MAX_VERTEX][MAX_VERTEX];
int vertexNum;
int edgeNum;
};
// (初始化、添加顶点/边的函数同2.1,此处省略)
bool visited[MAX_VERTEX] = {false};
// 广度优先遍历(从顶点v开始)
void BFS(GraphMatrix &G, int v) {
// 用数组模拟队列,front是队头,rear是队尾
int queue[MAX_VERTEX];
int front = 0, rear = 0;
// 访问当前顶点,入队
cout << G.vertices[v] << " ";
visited[v] = true;
queue[rear++] = v;
while (front < rear) {
// 出队
int cur = queue[front++];
// 遍历所有邻接顶点
for (int i = 0; i < G.vertexNum; i++) {
if (G.matrix[cur][i] == 1 && !visited[i]) {
cout << G.vertices[i] << " ";
visited[i] = true;
queue[rear++] = i;
}
}
}
}
// 测试BFS
int main() {
GraphMatrix G;
initGraphMatrix(G);
addVertex(G, 'A');
addVertex(G, 'B');
addVertex(G, 'C');
addVertex(G, 'D');
addEdge(G, 0, 1);
addEdge(G, 0, 2);
addEdge(G, 1, 3);
cout << "广度优先遍历结果:";
BFS(G, 0); // 从A开始遍历
cout << endl;
return 0;
}
四、图的相关应用
我们实现两个经典应用:最小生成树(Prim 算法)和最短路径(Dijkstra 算法)。
4.1 最小生成树
连通分量
Prim 算法
从一个顶点出发,每次选 "与当前树相连的权值最小的边",最终生成权值和最小的树(以带权无向图为例)。
cpp
#include <iostream>
#include <climits> // 用INT_MAX表示无穷大
using namespace std;
const int MAX_VERTEX = 100;
const int INF = INT_MAX; // 无穷大(表示无边)
struct GraphMatrix {
char vertices[MAX_VERTEX];
int matrix[MAX_VERTEX][MAX_VERTEX]; // 带权邻接矩阵
int vertexNum;
int edgeNum;
};
// 初始化带权图
void initWeightGraph(GraphMatrix &G) {
G.vertexNum = 0;
G.edgeNum = 0;
for (int i = 0; i < MAX_VERTEX; i++) {
for (int j = 0; j < MAX_VERTEX; j++) {
G.matrix[i][j] = INF;
}
}
}
// 添加带权边(无向图)
void addWeightEdge(GraphMatrix &G, int i, int j, int weight) {
if (i < 0 || i >= G.vertexNum || j < 0 || j >= G.vertexNum) {
cout << "顶点序号非法!" << endl;
return;
}
G.matrix[i][j] = weight;
G.matrix[j][i] = weight;
G.edgeNum++;
}
// Prim算法求最小生成树(从顶点v开始)
void Prim(GraphMatrix &G, int v) {
int minWeight; // 当前最小权值
int u; // 当前选中的顶点
int sum = 0; // 最小生成树的总权值
// 存储"每个顶点到当前生成树的最小权值"
int lowCost[MAX_VERTEX];
// 初始化lowCost
for (int i = 0; i < G.vertexNum; i++) {
lowCost[i] = G.matrix[v][i];
}
lowCost[v] = 0; // 标记v已加入生成树
for (int i = 1; i < G.vertexNum; i++) { // 选n-1条边
minWeight = INF;
u = -1;
// 找当前权值最小的边
for (int j = 0; j < G.vertexNum; j++) {
if (lowCost[j] != 0 && lowCost[j] < minWeight) {
minWeight = lowCost[j];
u = j;
}
}
if (u == -1) {
cout << "图不连通,无法生成最小生成树!" << endl;
return;
}
// 输出选中的边
cout << "选中边:" << G.vertices[v] << " - " << G.vertices[u] << ",权值:" << minWeight << endl;
sum += minWeight;
lowCost[u] = 0; // 标记u加入生成树
// 更新lowCost:用u的边更新其他顶点的最小权值
for (int j = 0; j < G.vertexNum; j++) {
if (lowCost[j] != 0 && G.matrix[u][j] < lowCost[j]) {
lowCost[j] = G.matrix[u][j];
}
}
}
cout << "最小生成树总权值:" << sum << endl;
}
// 测试Prim
int main() {
GraphMatrix G;
initWeightGraph(G);
// 添加顶点
G.vertices[0] = 'A';
G.vertices[1] = 'B';
G.vertices[2] = 'C';
G.vertices[3] = 'D';
G.vertexNum = 4;
// 添加带权边
addWeightEdge(G, 0, 1, 2);
addWeightEdge(G, 0, 2, 3);
addWeightEdge(G, 1, 3, 4);
addWeightEdge(G, 2, 3, 1);
// 运行Prim
Prim(G, 0);
return 0;
}
运行结果:
选中边:A - B,权值:2
选中边:A - C,权值:3
选中边:C - D,权值:1
最小生成树总权值:6
Kruskal 算法
Kruskal 算法与 Prim 算法思路不同 ------按边的权值从小到大选边,避开环,直到选够 n-1 条边(n 为顶点数)。核心:用「并查集」判断是否形成环(新手友好版实现,无 STL)。
cpp
#include <iostream>
#include <climits>
using namespace std;
const int MAX_VERTEX = 100;
const int MAX_EDGE = 1000;
const int INF = INT_MAX;
// 边的结构体(存储起点、终点、权值)
struct Edge {
int start; // 起点序号
int end; // 终点序号
int weight; // 权值
};
// 图的结构体(顶点+边集)
struct Graph {
char vertices[MAX_VERTEX]; // 顶点数组
Edge edges[MAX_EDGE]; // 边集数组
int vertexNum; // 顶点数
int edgeNum; // 边数
};
// 并查集数组(记录每个顶点的父节点,用于判断环)
int parent[MAX_VERTEX];
// 初始化并查集(每个顶点的父节点是自己)
void initUnionFind(int n) {
for (int i = 0; i < n; i++) {
parent[i] = i;
}
}
// 查找顶点v的根节点(带路径压缩,优化效率)
int findRoot(int v) {
if (parent[v] != v) {
parent[v] = findRoot(parent[v]); // 路径压缩
}
return parent[v];
}
// 合并两个顶点的集合
void unionSet(int a, int b) {
int rootA = findRoot(a);
int rootB = findRoot(b);
if (rootA != rootB) {
parent[rootB] = rootA;
}
}
// 交换两条边(用于冒泡排序)
void swapEdge(Edge &a, Edge &b) {
Edge temp = a;
a = b;
b = temp;
}
// 对边按权值从小到大排序(冒泡排序,新手易理解)
void sortEdges(Edge edges[], int edgeNum) {
for (int i = 0; i < edgeNum - 1; i++) {
for (int j = 0; j < edgeNum - 1 - i; j++) {
if (edges[j].weight > edges[j+1].weight) {
swapEdge(edges[j], edges[j+1]);
}
}
}
}
// Kruskal算法求最小生成树
void Kruskal(Graph &G) {
int sum = 0; // 最小生成树总权值
int selectedEdge = 0; // 已选中的边数
initUnionFind(G.vertexNum);
sortEdges(G.edges, G.edgeNum); // 边按权值排序
cout << "Kruskal选中的边:" << endl;
for (int i = 0; i < G.edgeNum && selectedEdge < G.vertexNum - 1; i++) {
int start = G.edges[i].start;
int end = G.edges[i].end;
int weight = G.edges[i].weight;
// 若起点和终点不在同一集合(无环),则选中这条边
if (findRoot(start) != findRoot(end)) {
cout << G.vertices[start] << " - " << G.vertices[end] << ",权值:" << weight << endl;
sum += weight;
unionSet(start, end);
selectedEdge++;
}
}
if (selectedEdge < G.vertexNum - 1) {
cout << "图不连通,无法生成最小生成树!" << endl;
} else {
cout << "最小生成树总权值:" << sum << endl;
}
}
// 测试Kruskal
int main() {
Graph G;
// 初始化顶点
G.vertices[0] = 'A';
G.vertices[1] = 'B';
G.vertices[2] = 'C';
G.vertices[3] = 'D';
G.vertexNum = 4;
// 初始化边集(无向图,每条边只存一次)
G.edges[0] = {0, 1, 2}; // A-B,权2
G.edges[1] = {0, 2, 3}; // A-C,权3
G.edges[2] = {1, 3, 4}; // B-D,权4
G.edges[3] = {2, 3, 1}; // C-D,权1
G.edgeNum = 4;
Kruskal(G);
return 0;
}
运行结果:
Kruskal选中的边:
C - D,权值:1
A - B,权值:2
A - C,权值:3
最小生成树总权值:6
4.2 最短路径
Dijkstra 算法
求一个顶点到其他所有顶点的最短路径(带权有向 / 无向图,权值非负)。
cpp
#include <iostream>
#include <climits>
using namespace std;
const int MAX_VERTEX = 100;
const int INF = INT_MAX;
struct GraphMatrix {
char vertices[MAX_VERTEX];
int matrix[MAX_VERTEX][MAX_VERTEX];
int vertexNum;
int edgeNum;
};
// (初始化、添加顶点/边的函数同4.1,此处省略)
// Dijkstra算法求最短路径(从顶点v开始)
void Dijkstra(GraphMatrix &G, int v) {
int dist[MAX_VERTEX]; // dist[i]:v到i的最短距离
bool visited[MAX_VERTEX] = {false}; // 标记是否已确定最短路径
// 初始化dist
for (int i = 0; i < G.vertexNum; i++) {
dist[i] = G.matrix[v][i];
}
dist[v] = 0;
visited[v] = true;
for (int i = 1; i < G.vertexNum; i++) { // 确定n-1个顶点的最短路径
int minDist = INF;
int u = -1;
// 找当前未确定的顶点中,dist最小的
for (int j = 0; j < G.vertexNum; j++) {
if (!visited[j] && dist[j] < minDist) {
minDist = dist[j];
u = j;
}
}
if (u == -1) break; // 无连通路径
visited[u] = true;
// 更新dist:通过u中转,看是否能缩短路径
for (int j = 0; j < G.vertexNum; j++) {
if (!visited[j] && G.matrix[u][j] != INF && dist[u] + G.matrix[u][j] < dist[j]) {
dist[j] = dist[u] + G.matrix[u][j];
}
}
}
// 输出结果
cout << G.vertices[v] << "到各顶点的最短路径:" << endl;
for (int i = 0; i < G.vertexNum; i++) {
if (dist[i] == INF) {
cout << G.vertices[v] << " -> " << G.vertices[i] << ":无路径" << endl;
} else {
cout << G.vertices[v] << " -> " << G.vertices[i] << ":" << dist[i] << endl;
}
}
}
// 测试Dijkstra
int main() {
GraphMatrix G;
initWeightGraph(G);
G.vertices[0] = 'A';
G.vertices[1] = 'B';
G.vertices[2] = 'C';
G.vertices[3] = 'D';
G.vertexNum = 4;
addWeightEdge(G, 0, 1, 2);
addWeightEdge(G, 0, 2, 5);
addWeightEdge(G, 1, 2, 1);
addWeightEdge(G, 1, 3, 4);
addWeightEdge(G, 2, 3, 2);
// 运行Dijkstra
Dijkstra(G, 0);
return 0;
}
运行结果:
A到各顶点的最短路径:
A -> A:0
A -> B:2
A -> C:3
A -> D:5
Floyd 算法
Floyd 算法是「多源最短路径算法」------ 一次性求出所有顶点之间的最短路径(核心:动态规划,三重循环,新手易实现)。
cpp
#include <iostream>
#include <climits>
using namespace std;
const int MAX_VERTEX = 100;
const int INF = INT_MAX;
// 带权邻接矩阵图
struct GraphMatrix {
char vertices[MAX_VERTEX];
int matrix[MAX_VERTEX][MAX_VERTEX];
int vertexNum;
int edgeNum;
};
// 初始化带权图
void initWeightGraph(GraphMatrix &G) {
G.vertexNum = 0;
G.edgeNum = 0;
for (int i = 0; i < MAX_VERTEX; i++) {
for (int j = 0; j < MAX_VERTEX; j++) {
G.matrix[i][j] = (i == j) ? 0 : INF; // 自己到自己距离为0,否则无穷大
}
}
}
// 添加带权边(有向图,若无向图则双向赋值)
void addWeightEdge(GraphMatrix &G, int i, int j, int weight) {
if (i < 0 || i >= G.vertexNum || j < 0 || j >= G.vertexNum) {
cout << "顶点序号非法!" << endl;
return;
}
G.matrix[i][j] = weight;
G.edgeNum++;
}
// Floyd算法求所有顶点间最短路径
void Floyd(GraphMatrix &G) {
// dist[i][j]:i到j的最短距离
int dist[MAX_VERTEX][MAX_VERTEX];
// 初始化dist为邻接矩阵
for (int i = 0; i < G.vertexNum; i++) {
for (int j = 0; j < G.vertexNum; j++) {
dist[i][j] = G.matrix[i][j];
}
}
// 核心:k为中转顶点,i为起点,j为终点
for (int k = 0; k < G.vertexNum; k++) {
for (int i = 0; i < G.vertexNum; i++) {
for (int j = 0; j < G.vertexNum; j++) {
// 避免溢出:跳过INF的情况
if (dist[i][k] != INF && dist[k][j] != INF && dist[i][k] + dist[k][j] < dist[i][j]) {
dist[i][j] = dist[i][k] + dist[k][j];
}
}
}
}
// 输出结果
cout << "所有顶点间的最短路径:" << endl;
// 打印表头
cout << " ";
for (int i = 0; i < G.vertexNum; i++) {
cout << G.vertices[i] << " ";
}
cout << endl;
// 打印每行
for (int i = 0; i < G.vertexNum; i++) {
cout << G.vertices[i] << " ";
for (int j = 0; j < G.vertexNum; j++) {
if (dist[i][j] == INF) {
cout << "∞ ";
} else {
cout << dist[i][j] << " ";
}
}
cout << endl;
}
}
// 测试Floyd
int main() {
GraphMatrix G;
initWeightGraph(G);
// 添加顶点
G.vertices[0] = 'A';
G.vertices[1] = 'B';
G.vertices[2] = 'C';
G.vertices[3] = 'D';
G.vertexNum = 4;
// 添加带权边(有向图)
addWeightEdge(G, 0, 1, 2); // A->B,权2
addWeightEdge(G, 0, 2, 5); // A->C,权5
addWeightEdge(G, 1, 2, 1); // B->C,权1
addWeightEdge(G, 1, 3, 4); // B->D,权4
addWeightEdge(G, 2, 3, 2); // C->D,权2
Floyd(G);
return 0;
}
运行结果:
所有顶点间的最短路径:
A B C D
A 0 2 3 5
B ∞ 0 1 3
C ∞ ∞ 0 2
D ∞ ∞ ∞ 0
3.拓扑排序
拓扑排序(针对有向无环图 DAG)
拓扑排序是「按依赖关系排序」------ 若存在边 A→B,则 A 排在 B 前面(核心:找入度为 0 的顶点,逐步删除)仅适用于有向无环图(DAG),解决依赖排序问题;
cpp
#include <iostream>
using namespace std;
const int MAX_VERTEX = 100;
// 邻接表存储有向图
struct EdgeNode {
int adjVertex;
EdgeNode *next;
};
struct VertexNode {
char data;
int inDegree; // 入度(关键:拓扑排序需要入度)
EdgeNode *firstEdge;
};
struct GraphList {
VertexNode vertices[MAX_VERTEX];
int vertexNum;
int edgeNum;
};
// 初始化图
void initGraphList(GraphList &G) {
G.vertexNum = 0;
G.edgeNum = 0;
for (int i = 0; i < MAX_VERTEX; i++) {
G.vertices[i].inDegree = 0;
G.vertices[i].firstEdge = NULL;
}
}
// 添加顶点
void addVertexList(GraphList &G, char v) {
if (G.vertexNum >= MAX_VERTEX) {
cout << "顶点数超限!" << endl;
return;
}
G.vertices[G.vertexNum].data = v;
G.vertexNum++;
}
// 添加有向边(u->v,v的入度+1)
void addEdgeList(GraphList &G, int u, int v) {
if (u < 0 || u >= G.vertexNum || v < 0 || v >= G.vertexNum) {
cout << "顶点序号非法!" << endl;
return;
}
// 添加边u->v
EdgeNode *newEdge = new EdgeNode;
newEdge->adjVertex = v;
newEdge->next = G.vertices[u].firstEdge;
G.vertices[u].firstEdge = newEdge;
// v的入度+1
G.vertices[v].inDegree++;
G.edgeNum++;
}
// 拓扑排序(数组模拟队列)
void topologicalSort(GraphList &G) {
int queue[MAX_VERTEX]; // 存放入度为0的顶点
int front = 0, rear = 0;
int count = 0; // 记录已排序的顶点数
// 第一步:将所有入度为0的顶点入队
for (int i = 0; i < G.vertexNum; i++) {
if (G.vertices[i].inDegree == 0) {
queue[rear++] = i;
}
}
cout << "拓扑排序结果:";
while (front < rear) {
// 出队一个入度为0的顶点
int cur = queue[front++];
cout << G.vertices[cur].data << " ";
count++;
// 遍历cur的所有邻接顶点,入度-1
EdgeNode *p = G.vertices[cur].firstEdge;
while (p != NULL) {
int adjV = p->adjVertex;
G.vertices[adjV].inDegree--;
// 若入度变为0,入队
if (G.vertices[adjV].inDegree == 0) {
queue[rear++] = adjV;
}
p = p->next;
}
}
// 若排序顶点数≠总顶点数,说明有环
if (count != G.vertexNum) {
cout << "\n图中存在环,无法完成拓扑排序!" << endl;
} else {
cout << endl;
}
}
// 测试拓扑排序
int main() {
GraphList G;
initGraphList(G);
// 添加顶点:A(0), B(1), C(2), D(3), E(4)
addVertexList(G, 'A');
addVertexList(G, 'B');
addVertexList(G, 'C');
addVertexList(G, 'D');
addVertexList(G, 'E');
// 添加有向边:A->B, A->C, B->D, C->D, D->E
addEdgeList(G, 0, 1);
addEdgeList(G, 0, 2);
addEdgeList(G, 1, 3);
addEdgeList(G, 2, 3);
addEdgeList(G, 3, 4);
topologicalSort(G);
return 0;
}
运行结果:
拓扑排序结果:A B C D E
4.关键路径
关键路径(基于拓扑排序,求项目最短完成时间,工程管理常用)
关键路径是**「从起点到终点的最长路径」------ 决定项目的最短完成时间**(核心:求最早开始时间 ve、最晚开始时间 vl,ve=vl 的边为关键活动)。
cpp
#include <iostream>
#include <climits>
using namespace std;
const int MAX_VERTEX = 100;
const int INF = INT_MAX;
// 带权有向边(存储权值,即活动耗时)
struct EdgeNode {
int adjVertex;
int weight; // 活动耗时
EdgeNode *next;
};
struct VertexNode {
char data;
int inDegree;
EdgeNode *firstEdge;
};
struct GraphList {
VertexNode vertices[MAX_VERTEX];
int vertexNum;
int edgeNum;
};
// 初始化、添加顶点/边函数同拓扑排序(边需增加权值)
void initGraphList(GraphList &G) {
G.vertexNum = 0;
G.edgeNum = 0;
for (int i = 0; i < MAX_VERTEX; i++) {
G.vertices[i].inDegree = 0;
G.vertices[i].firstEdge = NULL;
}
}
void addVertexList(GraphList &G, char v) {
if (G.vertexNum >= MAX_VERTEX) return;
G.vertices[G.vertexNum].data = v;
G.vertexNum++;
}
// 添加带权有向边u->v,权值为weight(活动耗时)
void addWeightEdgeList(GraphList &G, int u, int v, int weight) {
if (u < 0 || u >= G.vertexNum || v < 0 || v >= G.vertexNum) return;
EdgeNode *newEdge = new EdgeNode;
newEdge->adjVertex = v;
newEdge->weight = weight;
newEdge->next = G.vertices[u].firstEdge;
G.vertices[u].firstEdge = newEdge;
G.vertices[v].inDegree++;
G.edgeNum++;
}
// 关键路径算法
void criticalPath(GraphList &G) {
int ve[MAX_VERTEX] = {0}; // 顶点的最早发生时间
int vl[MAX_VERTEX]; // 顶点的最晚发生时间
int queue[MAX_VERTEX];
int front = 0, rear = 0;
int count = 0;
// 第一步:拓扑排序,求ve
// 入度为0的顶点入队
for (int i = 0; i < G.vertexNum; i++) {
if (G.vertices[i].inDegree == 0) {
queue[rear++] = i;
}
}
while (front < rear) {
int cur = queue[front++];
count++;
EdgeNode *p = G.vertices[cur].firstEdge;
while (p != NULL) {
int adjV = p->adjVertex;
// 更新ve[adjV]:取最大值
if (ve[cur] + p->weight > ve[adjV]) {
ve[adjV] = ve[cur] + p->weight;
}
G.vertices[adjV].inDegree--;
if (G.vertices[adjV].inDegree == 0) {
queue[rear++] = adjV;
}
p = p->next;
}
}
if (count != G.vertexNum) {
cout << "图中有环,无法求关键路径!" << endl;
return;
}
// 第二步:初始化vl(终点的vl=ve)
int maxVe = ve[0];
for (int i = 1; i < G.vertexNum; i++) {
if (ve[i] > maxVe) maxVe = ve[i];
}
for (int i = 0; i < G.vertexNum; i++) {
vl[i] = maxVe;
}
// 第三步:逆拓扑排序,求vl
// 重新入队(逆序,用栈模拟,数组实现)
int stack[MAX_VERTEX], top = -1;
for (int i = 0; i < G.vertexNum; i++) {
stack[++top] = queue[i];
}
while (top >= 0) {
int cur = stack[top--];
EdgeNode *p = G.vertices[cur].firstEdge;
while (p != NULL) {
int adjV = p->adjVertex;
// 更新vl[cur]:取最小值
if (vl[adjV] - p->weight < vl[cur]) {
vl[cur] = vl[adjV] - p->weight;
}
p = p->next;
}
}
// 第四步:找关键活动(e=lf,即ve[cur]+weight=vl[adjV])
cout << "关键路径(关键活动):" << endl;
for (int i = 0; i < G.vertexNum; i++) {
EdgeNode *p = G.vertices[i].firstEdge;
while (p != NULL) {
int adjV = p->adjVertex;
int e = ve[i]; // 活动最早开始时间
int l = vl[adjV] - p->weight; // 活动最晚开始时间
if (e == l) { // 关键活动
cout << G.vertices[i].data << " -> " << G.vertices[adjV].data
<< "(耗时:" << p->weight << ")" << endl;
}
p = p->next;
}
}
cout << "项目最短完成时间:" << maxVe << endl;
}
// 测试关键路径
int main() {
GraphList G;
initGraphList(G);
// 添加顶点:A(0), B(1), C(2), D(3), E(4), F(5)
addVertexList(G, 'A');
addVertexList(G, 'B');
addVertexList(G, 'C');
addVertexList(G, 'D');
addVertexList(G, 'E');
addVertexList(G, 'F');
// 添加带权边(活动耗时)
addWeightEdgeList(G, 0, 1, 2); // A->B,2
addWeightEdgeList(G, 0, 2, 3); // A->C,3
addWeightEdgeList(G, 1, 3, 4); // B->D,4
addWeightEdgeList(G, 2, 3, 2); // C->D,2
addWeightEdgeList(G, 3, 4, 3); // D->E,3
addWeightEdgeList(G, 3, 5, 2); // D->F,2
addWeightEdgeList(G, 4, 5, 1); // E->F,1
criticalPath(G);
return 0;
}
运行结果:
关键路径(关键活动):
A -> C(耗时:3)
C -> D(耗时:2)
D -> E(耗时:3)
E -> F(耗时:1)
项目最短完成时间:9
五、实际比赛例题(略)
CCF