文章目录
- 图
- 定义
- 存储结构
- 邻接矩阵
- 邻接表
- 遍历
- 深度优先搜索
- 广度优先搜索
- 应用
- 最小生成树
- 构造最小生成树(**M**inimum **S**panning **T**ree,简称MST)
- 最短路径
- 拓扑排序
- 拓扑排序的方法
- 关键路径
图
定义
多对多的关系。
无向图:每条边都没有方向
有向图:每条边都有方向
完全图:任意两个点都有一条边相连(无向完全图:n个顶点,有n(n-1)/2条边;有向完全图:n个顶点,有n(n-1)条边)
稀疏图:有很少边(无向图)或弧(有向图)的图(e<nlogn)
邻接:有边/弧相连的两个顶点之间的关系。
存在(vi , vj),则称vi和vj互为邻接点
存在<vi , vj>,则称vi邻接到, vj邻接于vi
顶点的度:与该顶点相关联的边的数目。
在有向图中,顶点的度等于该顶点的入度与出度之和。
顶点v的入度是以v为终点的有向边的条数。
顶点v的出度是以v为始点的有向边的条数。
连通图(强连通图:有向图):从无(有)向图G=(V , {E})中,若对任何两个顶点v、u都存在从v到u的路径,则称G为连通图(强连通图)
存储结构
图的存储结构主要使用的分为两种:邻接矩阵、邻接表。
邻接矩阵
无向图的邻接矩阵表示法
有向图的邻接矩阵表示法
注:在有向图的邻接矩阵中,
第i行含义:以结点vi为尾的弧(即出度边);
第i列含义:以结点vi为头的弧(即入度边)。
顶点i的度 = 第i行元素之和 + 第i列元素之和
代码实现
#define MVNum 100 //最大顶点数
typedef struct{ //图的结构体定义char vexs[MVNum]; //存放顶点的一维数组int arcs[MVNum][MVNum]; //邻接矩阵int vexnum,arcnum; //图的顶点数和边数
}MGraph;void CreatMGraph(MGraph *G){scanf("%d %d",&G->vexnum,&G->arcnum); // 输入顶点数和边数getchar();for(int i = 0;i<G->vexnum;i++){scanf("%c",&G->vexs[i]);}for(int i = 0;i<G->vexnum;i++){for(int j = 0;j<G->vexnum;j++){G->arcs[i][j] = 0; // 初始化矩阵为0}}getchar();for(int i = 0;i<G->arcnum;i++){ // 构造邻接矩阵char v1,v2;scanf("%c %c",&v1,&v2);getchar();int x = locate(G,v1);int y = locate(G,v2);G->arcs[x][y] = 1; // 有路径则矩阵为1G->arcs[y][x] = 1; // 无向图具有对称性,路径具有互通性(有向图则去除这行代码)}
}int locate(MGraph *G,char v){ // 查找图中顶点v,存在则返回下标for(int i = 0;i<G->vexnum;i++){if(v == G->vexs[i]){return i;}}return -1; // 否则返回-1
}
邻接表
无向图的邻接表如此实现,其特点为:
- 邻接表不唯一
- 若无向图中有n个结点、e条边,则其邻接表需要n个头结点和2e个表结点,适宜存储稀疏图。
- 无向图中顶点vi的度为第i个单链表中的节点数。
代码实现
#define MVNum 100 //最大顶点数
typedef struct ArcNode{ //表结点int adjvex; //邻接点的位置struct ArcNode *nextarc; //指向下一个表结点的指针}ArcNode;
typedef struct VNode{char data; //顶点信息ArcNode *firstarc; //指向第一个表结点的指针
}VNode, AdjList[MVNum]; //AdjList表示邻接表类型
typedef struct{AdjList vertices; //头结点数组int vexnum, arcnum; //图的顶点数和边数
}ALGraph;void CreatMGraph(ALGraph &G){ // 使用邻接表创建无向图int x, y;char v1, v2;cin >> G.vexnum >> G.arcnum;for(int i=0; i<G.vexnum; i++){cin >> G.vertices[i].data;G.vertices[i].firstarc=NULL;//初始化 }for(int i=0; i<G.arcnum; i++){cin >> v1 >> v2;int x = locate(G,v1);int y = locate(G,v2);//头插法 ArcNode* p = (ArcNode*)malloc(sizeof(ArcNode));p->adjvex = y;p->nextarc = G.vertices[x].firstarc;G.vertices[x].firstarc = p;p = (ArcNode*)malloc(sizeof(ArcNode)); // 无向图具有对称性p->adjvex = x;p->nextarc = G.vertices[y].firstarc;G.vertices[y].firstarc = p;}
}int locate(ALGraph G,char v){ // 获取v头结点的下标for(int i = 0;i<G.vexnum;i++){if(G.vertices[i].data==v){return i;}}return -1;
}
邻接矩阵与邻接表的区别
区别/类型 | 邻接矩阵 | 邻接表 |
---|---|---|
唯一确定的无向图 | 唯一(行列号与顶点编号一致) | 不唯一(链表次序与顶点编号无关) |
空间复杂度 | O(n 2 ^2 2) | O(n+e) |
用途 | 多用于稠密图 | 多用于稀疏图 |
遍历
图的遍历主要分为两种:广度优先搜索(DFS)、深度优先搜索(BFS)
注:访问过程中可以使用visited数组来记录访问状态(如,被访问过则该坐标下的visited的值为1,否则为0),防止一个顶点被多次重复访问。
深度优先搜索
遍历方法:
- 访问图中某一个起始顶点v后,从v出发,访问其任一邻接顶点w1。
- 再从w1出发,访问与w1邻接但
未被访问
过的顶点w2。 - 然后再从w2出发,重复第二步直至到达所有的邻接顶点都被访问过的顶点u为止。
- 从u顶点回退一步至刚访问过的顶点,看是否还有其它未被访问的邻接节点。
- 如果有,则访问此顶点,重复2、3步。
- 如果没有,则重复第4步。
- 重复5、6步直至连通图中所有顶点都被访问过为止。
邻接矩阵表示的无向图深度优先搜索(伪代码)
#define MVNum 100 //最大顶点数
typedef struct{ //图的结构体定义char vexs[MVNum]; //存放顶点的一维数组int arcs[MVNum][MVNum]; //邻接矩阵int vexnum,arcnum; //图的顶点数和边数
}MGraph;void DFS(MGraph G,int v){ // 访问第v个顶点cout << v;visited[v] = true;for(int i = 0;i<G.vexnum;i++){if(G.arcs[v][i]!=0 && (!visited[i])){dfs(G,i); // i是v的邻接点,如果i未被访问,则递归调用dfs}}
}
广度优先搜索
遍历方法:
- 从图的某一个节点开始,首先依次访问该节点的所有邻接顶点vi1、vi2、vi3,… ,vin。
- 再按这些顶点被访问的先后次序依次访问它们想邻接的所有未被访问的顶点。
- 重复上述步骤直至所有顶点均被访问为止。
按广度优先非递归遍历图(伪代码)
#define MVNum 100 //最大顶点数
typedef struct{ //图的结构体定义char vexs[MVNum]; //存放顶点的一维数组int arcs[MVNum][MVNum]; //邻接矩阵int vexnum,arcnum; //图的顶点数和边数
}Graph;void BFS(Graph G,int v){ // 按照广度优先非递归遍历连通图Gcout << v;visited[v] = true; // 访问第v个顶点InitQueue(Q); // 使用队列辅助遍历,初始化队列QEnQueue(Q,v); // v进队while(!QueueEmpty(Q)){ // 队列非空DeQueue(Q,u); // 队头元素出队并赋值给ufor(w = FirstAdjVex(G,u);w>=0;w = NextAdjVex(G,u,w)) // 依次找弧if(!visited[w]){ // w为u的尚未访问过的邻接顶点cout << w;visited[w] = true;EnQueue(Q,w); // w进队}}
}
DFS与BFS算法效率比较:
- 空间复杂度相同,都是O(n)(借用了堆栈或队列)
- 空间复杂度只与存储结构(邻接矩阵或邻接表)有关,而与搜索路径无关。
应用
图的应用分为了主要四个部分:最小生成树、最短路径问题、拓扑排序、关键路径问题。
最小生成树
定义
:生成树是指所有顶点均由边连接在一起,但不存在回路的图。
一个图可以有许多棵不同的生成树。
所有生成树都具有以下共同特点:
- 生成树的顶点个数与图的顶点数相同。
- 生成树是图的极小连通子图,去掉一条边则非连通。
- 一个有n个顶点的连通图的生成树有 n-1 条边。
- 在生成树中再加一条边必然会形成一个回路。
设图G=(V, E)是个连通图,当从图任一顶点出发遍历图 G 时,将
边集E(G)分成两个集合T(G)和B(G)。其中T(G)是遍历图时所经过的边的集合,B(G) 是遍历图时未经过的边的集合。显然,G1(V, T)是图 G
的极小连通子图。即子图G1是连通图 G 的生成树。
两种遍历所形成的生成树:
最小生成树:给定一个无向网络,在该网的所有生成树中,使得各边权值之和最小的那颗生成树称为该网的最小生成树,也叫做最小代价生成树。
构造最小生成树(Minimum Spanning Tree,简称MST)
构造最小生成树的算法有很多,其中多数的算法都利用了MST的性质。其中主要的两种算法,一种是Prim算法、另一种是Kruskal算法。
MST性质
:N= (V, E)是一个连通网,U是顶点集V的一个非空子集。若边(u,v)是一条具有最小权值的边,其中u∈U, v∈V-U,则必存在一棵包含边(u, v)的最小生成树。
普里姆(Prim)算法
算法思路:
- 设N=(V , E)是连通图,是N上最小生成树中边的集合。
- 初始化令U={u0},(u0∈V),TE={}。
- 在所有u∈V,v∈V-U的边(u , v)∈E中,找一条代价(权值)最小的边(u0 , v0)。
- 将(u0 , v0)并入集合TE,同时v0并入U。
代码实现
#include <iostream>
#include <climits>
using namespace std;#define MVNum 100
#define MaxInt INT_MAXtypedef struct {char vexs[MVNum];int arcs[MVNum][MVNum];int vexnum, arcnum;
} AMGraph;struct edge {char adjvex;int lowcost;
} closedge[MVNum];int LocateVex(AMGraph G, char v) {for (int i = 0; i < G.vexnum; ++i) {if (G.vexs[i] == v) {return i;}}return -1; // 如果未找到对应的顶点,返回-1表示错误
}void CreateUDN(AMGraph& G) {cout << "请输入顶点个数和边个数:" << endl;cin >> G.vexnum >> G.arcnum;cout << "请输入顶点信息:" << endl;for (int i = 0; i < G.vexnum; ++i) {cin >> G.vexs[i];}// 初始化邻接矩阵for (int i = 0; i < G.vexnum; ++i) {for (int j = 0; j < G.vexnum; ++j) {G.arcs[i][j] = MaxInt;}}// 输入边的信息cout << "请输入边的信息(起点、终点、权值):" << endl;for (int k = 0; k < G.arcnum; ++k) {char start, end;int weight;cin >> start >> end >> weight;int i = LocateVex(G, start);int j = LocateVex(G, end);G.arcs[i][j] = weight;G.arcs[j][i] = weight; // 无向图,权值对称}
}int Min(AMGraph G) {int i;int index = -1;int min = MaxInt;for (i = 0; i < G.vexnum; ++i) {if (!closedge[i].lowcost) {min = closedge[i].lowcost;index = i;}}return index;
}void MiniSpanTree_Prim(AMGraph G, char u) {int k, j, i;char u0, v0;k = LocateVex(G, u);for (j = 0; j < G.vexnum; ++j) {if (j != k) {closedge[j].adjvex = G.vexs[k];closedge[j].lowcost = G.arcs[k][j];}}closedge[k].lowcost = 0;for (i = 1; i < G.vexnum; ++i) {k = Min(G);u0 = closedge[k].adjvex;v0 = G.vexs[k];cout << u0 << "->" << v0 << endl;closedge[k].lowcost = 0;for (j = 0; j < G.vexnum; ++j) {if (G.arcs[k][j] < closedge[j].lowcost) {closedge[j].adjvex = G.vexs[k];closedge[j].lowcost = G.arcs[k][j];}}}
}int main() {AMGraph G;CreateUDN(G);char u;cin >> u;MiniSpanTree_Prim(G, u);return 0;
}
克鲁斯卡尔(Kruskal)算法
算法思路:
- 设连通网N= (V E),令最小生成树初始状态为只有n个顶点而无边的非连通图T=(V { }),
每个顶点自成一个连通分量。 - 在E中选取代价最小的边,若该边依附的顶点落在T中不同的连通分量上(即:不能形成环),
则将此边加入到T中;否则,舍去此边,选取下条代价最小的边。 - 依此类推,直至T中所有顶
点都在同一连通分量上为止。
代码实现
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;#define MVNum 100
#define MaxInt 32767struct Edge {int start;int end;int weight;
};typedef struct {char vexs[MVNum];int vexnum, arcnum;vector<Edge> edges;
} AMGraph;int parent[MVNum]; // 并查集数组int Find(int x) {if (parent[x] == x)return x;return Find(parent[x]);
}void Union(int x, int y) {int rootX = Find(x);int rootY = Find(y);if (rootX != rootY)parent[rootY] = rootX;
}bool CompareEdges(Edge a, Edge b) {return a.weight < b.weight;
}void CreateUDN(AMGraph& G) {cout << "请输入顶点个数和边个数:" << endl;cin >> G.vexnum >> G.arcnum;cout << "请输入顶点信息:" << endl;for (int i = 0; i < G.vexnum; ++i) {cin >> G.vexs[i];}// 输入边的信息cout << "请输入边的信息(起点、终点、权值):" << endl;for (int k = 0; k < G.arcnum; ++k) {char start, end;int weight;cin >> start >> end >> weight;Edge edge;edge.start = start - 'A';edge.end = end - 'A';edge.weight = weight;G.edges.push_back(edge);}
}void MiniSpanTree_Kruskal(AMGraph G) {// 初始化并查集数组for (int i = 0; i < G.vexnum; ++i) {parent[i] = i;}// 对边按权值进行排序sort(G.edges.begin(), G.edges.end(), CompareEdges);int edgeCount = 0; // 记录选中的边数int treeWeight = 0; // 记录最小生成树的权值cout << "最小生成树的边:" << endl;for (int i = 0; i < G.arcnum; ++i) {int start = G.edges[i].start;int end = G.edges[i].end;int weight = G.edges[i].weight;if (Find(start) != Find(end)) { // 判断边的两个顶点是否属于同一个连通分量Union(start, end); // 合并连通分量cout << char(start + 'A') << " - " << char(end + 'A') << " : " << weight << endl;treeWeight += weight;edgeCount++;if (edgeCount == G.vexnum - 1) // 边数达到最小生成树的边数上限,停止构造break;}}cout << "最小生成树的权值:" << treeWeight << endl;
}int main() {AMGraph G;CreateUDN(G);MiniSpanTree_Kruskal(G);return 0;
}
注:最小生成树可能不是唯一的。
两种算法进行比较
算法名 | Prim算法 | Kruskal算法 |
---|---|---|
算法思想 | 选择点 | 选择边 |
时间复杂度 | O(n 2 ^2 2) (n为顶点数) | O(eloge) (e为边数) |
适应范围 | 稠密图 | 稀疏图 |
最短路径
在有向网中A点(源点)到达B点(终点)的多条路径中,寻找一条各边权值之和最小的路径,称为最短路径。与最小生成树不同的是,最短路径上不一定包含 n 个顶点,也不一定包含 n - 1 条边。
两种常见的最短路径问题:单源最短路径、所有顶点间的最短路径。
迪杰斯特拉(Dijkstra)算法 - 单源最短路径
算法思路:
- 初始化:先找出从源点v0到各终点vk的直达路径(v0 , vk),即通过一条弧到达的路径。
- 选择:从这些路径中找出一条长度最短的路径(v0 , u)。
- 更新:然后对其余各条路径进行适当调整:若在图中存在弧(u , vk),且(v0 , u) + (u , vk) < (v0 , vk),则以路径(v0 , u , vk)代替(v0 , vk)。在调整后的各条路径中,再找长度最短的路径,依次类推。
代码实现
#include <iostream>
#include <vector>
#include <climits>
using namespace std;#define MVNum 100
#define MaxInt INT_MAXtypedef char VerTexType;
typedef int ArcType;typedef struct {VerTexType vexs[MVNum];ArcType arcs[MVNum][MVNum];int vexnum, arcnum;
} AMGraph;int LocateVex(AMGraph G, VerTexType v) {for (int i = 0; i < G.vexnum; ++i) {if (G.vexs[i] == v)return i;}return -1; // 未找到对应顶点
}void CreateUDN(AMGraph& G) {cout << "请输入顶点个数和边个数:" << endl;cin >> G.vexnum >> G.arcnum;cout << "请输入顶点信息:" << endl;for (int i = 0; i < G.vexnum; ++i) {cin >> G.vexs[i];}// 初始化邻接矩阵for (int i = 0; i < G.vexnum; ++i) {for (int j = 0; j < G.vexnum; ++j) {if (i == j)G.arcs[i][j] = 0;elseG.arcs[i][j] = MaxInt;}}// 输入边的信息cout << "请输入边的信息(起点、终点、权值):" << endl;for (int k = 0; k < G.arcnum; ++k) {char start, end;int weight;cin >> start >> end >> weight;int i = LocateVex(G, start);int j = LocateVex(G, end);G.arcs[i][j] = weight;G.arcs[j][i] = weight; // 无向图的边是双向的}
}void Dijkstra(AMGraph G, int v0, int* dist, int* path) {int n = G.vexnum;vector<bool> visited(n, false); // 记录顶点是否已被访问dist[v0] = 0; // 初始顶点的最短路径为0for (int i = 0; i < n; ++i) {int minDist = MaxInt;int u = -1; // 选取的顶点// 选取距离最短且未被访问的顶点for (int j = 0; j < n; ++j) {if (!visited[j] && dist[j] < minDist) {minDist = dist[j];u = j;}}if (u == -1)break; // 所有顶点已被访问,结束算法visited[u] = true; // 标记顶点u为已访问// 更新以顶点u为中间顶点的最短路径for (int v = 0; v < n; ++v) {if (!visited[v] && G.arcs[u][v] != MaxInt && dist[u] + G.arcs[u][v] < dist[v]) {dist[v] = dist[u] + G.arcs[u][v];path[v] = u;}}}
}void DisplayPath(AMGraph G, int v0, int dest, int* path) {if (dest != v0) {DisplayPath(G, v0, path[dest], path);cout << " -> ";}cout << G.vexs[dest];
}int main() {AMGraph G;CreateUDN(G);VerTexType start, dest;cout << "请输入起点和终点:" << endl;cin >> start >> dest;int v0 = LocateVex(G, start);int v1 = LocateVex(G, dest);if (v0 == -1 || v1 == -1) {cout << "起点或终点不存在!" << endl;return 0;}int* dist = new int[G.vexnum]; // 记录起点到各顶点的最短路径长度int* path = new int[G.vexnum]; // 记录最短路径中各顶点的前驱顶点for (int i = 0; i < G.vexnum; ++i) {dist[i] = MaxInt;path[i] = -1;}Dijkstra(G, v0, dist, path);cout << "从起点 " << start << " 到终点 " << dest << " 的最短路径为:";DisplayPath(G, v0, v1, path);cout << endl;cout << "最短路径长度为:" << dist[v1] << endl;delete[] dist;delete[] path;return 0;
}
弗洛伊德(Floyd)算法 - 所有顶点间的最短路径(了解)
求最短路径步骤:
- 初始时设置一个n阶方阵,
令其对角线元素为0,若存在弧
<Vi , Vj>,则对应元素为权值;
否则为∞。 - 逐步试着在原直接路径中增加中间顶点,若加入中间顶点后路径变短,则修改之;否则,维持原值。所有顶点试探完毕,算法结束。
拓扑排序
在一个AOV图(顶点表示活动,有向边表示活动之间的先后关系的图)没有回路的前提下,我们将全部活动排列成一个线性序列,使得若AOV网中有弧<i , j>存在,则在这个序列中,i一定排在j的前面,具有这种性质的线性序列称为拓扑有序序列,相应的拓扑有序排序的算法称为拓扑排序。
拓扑排序的方法
- 在有向图中选择一个没有前驱的顶点并输出。
- 从图中删除该顶点和所有以它为尾的弧。
- 重复1、2步,直至全部顶点均已输出(或者当图中不存在无前驱的顶点为止)。
检测AOV网中是否存在环的方法:
对有向图构造其顶点的拓扑排序有序序列,若网中所有顶点都在它的拓扑有序序列中,则AOV网必定不存在环。否则就有环。
代码实现
#include <iostream>
#include <vector>
#include <queue>
using namespace std;#define MVNum 100typedef char VerTexType;
typedef struct ArcNode {int adjvex;struct ArcNode* next;
} ArcNode;typedef struct VNode {VerTexType data;ArcNode* firstarc;
} VNode, AdjList[MVNum];typedef struct {AdjList vertices;int vexnum, arcnum;
} ALGraph;int LocateVex(ALGraph G, VerTexType v) {for (int i = 0; i < G.vexnum; ++i) {if (G.vertices[i].data == v)return i;}return -1; // 未找到对应顶点
}void CreateDG(ALGraph& G) {cout << "请输入顶点个数和有向边个数:" << endl;cin >> G.vexnum >> G.arcnum;cout << "请输入顶点信息:" << endl;for (int i = 0; i < G.vexnum; ++i) {cin >> G.vertices[i].data;G.vertices[i].firstarc = NULL;}cout << "请输入有向边的信息(起点、终点):" << endl;for (int k = 0; k < G.arcnum; ++k) {VerTexType start, end;cin >> start >> end;int i = LocateVex(G, start);int j = LocateVex(G, end);ArcNode* arcNode = new ArcNode;arcNode->adjvex = j;arcNode->next = G.vertices[i].firstarc;G.vertices[i].firstarc = arcNode;}
}bool TopologicalSort(ALGraph G, vector<VerTexType>& result) {vector<int> indegree(G.vexnum, 0); // 保存顶点的入度queue<int> q; // 保存入度为0的顶点// 计算每个顶点的入度for (int i = 0; i < G.vexnum; ++i) {ArcNode* p = G.vertices[i].firstarc;while (p != NULL) {indegree[p->adjvex]++;p = p->next;}}// 将入度为0的顶点入队列for (int i = 0; i < G.vexnum; ++i) {if (indegree[i] == 0)q.push(i);}while (!q.empty()) {int v = q.front();q.pop();result.push_back(G.vertices[v].data); // 将顶点加入结果列表// 更新与顶点v相邻的顶点的入度ArcNode* p = G.vertices[v].firstarc;while (p != NULL) {int adjvex = p->adjvex;indegree[adjvex]--;if (indegree[adjvex] == 0)q.push(adjvex);p = p->next;}}// 判断是否存在环路if (result.size() != G.vexnum)return false;return true;
}int main() {ALGraph G;CreateDG(G);vector<VerTexType> result;if (TopologicalSort(G, result)) {cout << "拓扑排序结果:";for (int i = 0; i < result.size(); ++i) {cout << result[i] << " ";}cout << endl;} else {cout << "图中存在环路,无法进行拓扑排序!" << endl;}return 0;
}
关键路径
将工程计划表示为边表示活动的网络,即AOE网
,用顶点表示事件,弧表示活动,弧的权表示活动持续时间。路径长度是路径上各活动持续时间之和。
关键路径指路径长度最长的路径。
代码演示
#include <iostream>
#include <vector>
#include <algorithm>
#include <queue>
using namespace std;#define MVNum 100
#define INF 999999typedef char VerTexType;
typedef struct ArcNode {int adjvex;int weight;struct ArcNode* next;
} ArcNode;typedef struct VNode {VerTexType data;int early; // 最早开始时间int late; // 最晚开始时间int duration; // 持续时间ArcNode* firstarc;
} VNode, AdjList[MVNum];typedef struct {AdjList vertices;int vexnum, arcnum;
} ALGraph;int LocateVex(ALGraph G, VerTexType v) {for (int i = 0; i < G.vexnum; ++i) {if (G.vertices[i].data == v)return i;}return -1; // 未找到对应顶点
}void CreateAOE(ALGraph& G) {cout << "请输入顶点个数和有向边个数:" << endl;cin >> G.vexnum >> G.arcnum;cout << "请输入顶点信息:" << endl;for (int i = 0; i < G.vexnum; ++i) {cin >> G.vertices[i].data;G.vertices[i].early = 0;G.vertices[i].late = INF;G.vertices[i].duration = 0;G.vertices[i].firstarc = NULL;}cout << "请输入有向边的信息(起点、终点、持续时间):" << endl;for (int k = 0; k < G.arcnum; ++k) {VerTexType start, end;int duration;cin >> start >> end >> duration;int i = LocateVex(G, start);int j = LocateVex(G, end);ArcNode* arcNode = new ArcNode;arcNode->adjvex = j;arcNode->weight = duration;arcNode->next = G.vertices[i].firstarc;G.vertices[i].firstarc = arcNode;}
}void TopologicalSort(ALGraph G, vector<int>& result) {vector<int> indegree(G.vexnum, 0); // 保存顶点的入度queue<int> q; // 保存入度为0的顶点// 计算每个顶点的入度for (int i = 0; i < G.vexnum; ++i) {ArcNode* p = G.vertices[i].firstarc;while (p != NULL) {indegree[p->adjvex]++;p = p->next;}}// 将入度为0的顶点入队列for (int i = 0; i < G.vexnum; ++i) {if (indegree[i] == 0)q.push(i);}while (!q.empty()) {int v = q.front();q.pop();result.push_back(v); // 将顶点加入结果列表// 更新与顶点v相邻的顶点的入度ArcNode* p = G.vertices[v].firstarc;while (p != NULL) {indegree[p->adjvex]--;if (indegree[p->adjvex] == 0)q.push(p->adjvex);p = p->next;}}
}void CriticalPath(ALGraph G) {vector<int> result;TopologicalSort(G, result);// 计算最早开始时间for (int i = 0; i < G.vexnum; ++i) {int v = result[i];ArcNode* p = G.vertices[v].firstarc;while (p != NULL) {int w = p->adjvex;if (G.vertices[v].early + p->weight > G.vertices[w].early)G.vertices[w].early = G.vertices[v].early + p->weight;p = p->next;}}// 计算最晚开始时间和关键路径for (int i = G.vexnum - 1; i >= 0; --i) {int v = result[i];if (G.vertices[v].firstarc == NULL) {G.vertices[v].late = G.vertices[v].early;} else {ArcNode* p = G.vertices[v].firstarc;while (p != NULL) {int w = p->adjvex;if (G.vertices[w].late - p->weight < G.vertices[v].late)G.vertices[v].late = G.vertices[w].late - p->weight;p = p->next;}}G.vertices[v].duration = G.vertices[v].late - G.vertices[v].early;// 输出关键活动if (G.vertices[v].early == G.vertices[v].late) {ArcNode* p = G.vertices[v].firstarc;while (p != NULL) {int w = p->adjvex;if (G.vertices[v].late == G.vertices[w].early - p->weight) {cout << "(" << G.vertices[v].data << ", " << G.vertices[w].data << ") ";}p = p->next;}}}
}int main() {ALGraph G;CreateAOE(G);CriticalPath(G);return 0;
}