邻接:有边/弧相连的两个顶点之间的关系。
存在(vi , vj),则称vi和vj互为邻接点
存在i , 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;
}
}
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;
G->arcs[y][x] = 1;
}
}
int locate(MGraph *G,char v){
for(int i = 0;i<G->vexnum;i++){
if(v == G->vexs[i]){
return i;
}
}
return -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];
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){
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){
cout << v;
visited[v] = true;
for(int i = 0;i<G.vexnum;i++){
if(G.arcs[v][i]!=0 && (!visited[i])){
dfs(G,i);
}
}
}
广度优先搜索
遍历方法:
- 从图的某一个节点开始,首先依次访问该节点的所有邻接顶点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){
cout << v;
visited[v] = true;
InitQueue(Q);
EnQueue(Q,v);
while(!QueueEmpty(Q)){
DeQueue(Q,u);
for(w = FirstAdjVex(G,u);w>=0;w = NextAdjVex(G,u,w))
if(!visited[w]){
cout << w;
visited[w] = true;
EnQueue(Q,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
#include
using namespace std;
#define MVNum 100
#define MaxInt INT_MAX
typedef 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;
}
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
#include
#include
using namespace std;
#define MVNum 100
#define MaxInt 32767
struct 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
#include
#include
using namespace std;
#define MVNum 100
#define MaxInt INT_MAX
typedef 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;
else
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;
}
}
void Dijkstra(AMGraph G, int v0, int* dist, int* path) {
int n = G.vexnum;
vector<bool> visited(n, false);
dist[v0] = 0;
for (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;
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,若存在弧
i , Vj>,则对应元素为权值;
否则为∞。
- 逐步试着在原直接路径中增加中间顶点,若加入中间顶点后路径变短,则修改之;否则,维持原值。所有顶点试探完毕,算法结束。
代码实现
#include
#include
const int INF = 999999;
void multiply(std::vector<std::vector<int>>& a, std::vector<std::vector<int>>& b, std::vector<std::vector<int>>& c, int n) {
std::vector<std::vector<int>> temp(n, std::vector<int>(n, INF));
for (int i = 0; i < n; ++i) {
for (int j = 0; j < n; ++j) {
for (int k = 0; k < n; ++k) {
if (a[i][k] != INF && b[k][j] != INF) {
temp[i][j] = std::min(temp[i][j], a[i][k] + b[k][j]);
}
}
}
}
for (int i = 0; i < n; ++i) {
for (int j = 0; j < n; ++j) {
c[i][j] = temp[i][j];
}
}
}
void floyd(std::vector<std::vector<int>>& graph, int num_vertices) {
std::vector<std::vector<int>> dist(num_vertices, std::vector<int>(num_vertices));
for (int i = 0; i < num_vertices; ++i) {
for (int j = 0; j < num_vertices; ++j) {
dist[i][j] = graph[i][j];
}
}
for (int k = 0; k < num_vertices; ++k) {
multiply(dist, dist, dist, num_vertices);
}
for (int i = 0; i < num_vertices; ++i) {
for (int j = 0; j < num_vertices; ++j) {
if (dist[i][j] == INF) {
std::cout << "INF ";
} else {
std::cout << dist[i][j] << " ";
}
}
std::cout << std::endl;
}
}
int main() {
int num_vertices = 4;
std::vector<std::vector<int>> graph = {
{0, 3, INF, 7},
{8, 0, 2, INF},
{5, INF, 0, 1},
{2, INF, INF, 0}
};
floyd(graph, num_vertices);
return 0;
}
拓扑排序
在一个AOV图(顶点表示活动,有向边表示活动之间的先后关系的图)没有回路的前提下,我们将全部活动排列成一个线性序列,使得若AOV网中有弧存在,则在这个序列中,i一定排在j的前面,具有这种性质的线性序列称为拓扑有序序列,相应的拓扑有序排序的算法称为拓扑排序。
拓扑排序的方法
- 在有向图中选择一个没有前驱的顶点并输出。
- 从图中删除该顶点和所有以它为尾的弧。
- 重复1、2步,直至全部顶点均已输出(或者当图中不存在无前驱的顶点为止)。
检测AOV网中是否存在环的方法:
对有向图构造其顶点的拓扑排序有序序列,若网中所有顶点都在它的拓扑有序序列中,则AOV网必定不存在环。否则就有环。
代码实现
#include
#include
#include
using namespace std;
#define MVNum 100
typedef 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;
for (int i = 0; i < G.vexnum; ++i) {
ArcNode* p = G.vertices[i].firstarc;
while (p != NULL) {
indegree[p->adjvex]++;
p = p->next;
}
}
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);
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
#include
#include
#include
using namespace std;
#define MVNum 100
#define INF 999999
typedef 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;
for (int i = 0; i < G.vexnum; ++i) {
ArcNode* p = G.vertices[i].firstarc;
while (p != NULL) {
indegree[p->adjvex]++;
p = p->next;
}
}
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);
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;
}