p.s.自用
目录
简单图:无自边和重边
分类:
1.方向性:有向图 无向图
有向边:弧
起点:弧尾
终点:弧头
p.s.无向图可以看成有向图
2.环:带环图 无环图
环:从一个点x出发,沿边走,最终可以回到x
3.边权:无权图 带权图
完全图:图中每两个顶点之间,都存在一条边
完全无向图:有n(n-1)/2条边
完全有向图:有n(n-1)条边
存储结构
p.s.默认下标与数据相同
边集数组【有/无】
适用范围:有向图 & 无向图
点:数组存点集
边:起点 终点 边权 → 结构体数组
优点:代码简单
缺点:不方便
//边集数组
//有向图 && 简单图
int v[1005];//点
struct edge {
int start, end;//s起点,e终点
int w;//权值(可选)
}e[10005];//边
int n, m;//n点,m边
int main()
{
int x, y, w0;
scanf("%d%d", &n, &m);
for (int i = 1; i <= n; i++)
scanf("%d", &v[i]);
for (int i = 1; i <= m; i++)
{
scanf("%d%d%d", &x, &y, &w0);
e[i].start = x;
e[i].end = y;
e[i].w = w0;
}
scanf("%d", &x);
for (int i = 1; i <= m; i++)//查找临界点
{
if (e[i].start == x)
printf("%d ", e[i].end);
if (e[i].end == x)
printf("%d ", e[i].start);
}
}
邻接矩阵【有/无】★
适用范围:有向图 & 无向图
点:数组存点集
边:二位数组(下标是点的标号)(邻接矩阵)
无向图:邻接矩阵是对称矩阵
缺点:如果是稀疏图,会存在空间浪费
//邻接矩阵
//有向图 && 简单图
int v[105];//点
int e[105][105];//邻接矩阵
int n, m;//n点,m边
int main()
{
int x, y, w0;
scanf("%d%d", &n, &m);
for (int i = 1; i <= n; i++)
scanf("%d", &v[i]);
for (int i = 1; i <= m; i++)//与无向图的区别
{
scanf("%d%d%d", &x, &y, &w0);
e[x][y] = w0;
}
scanf("%d", &x);
for (int i = 1; i <= m; i++)//查找临界点
{
if (e[x][i])
printf("%d ", e);
}
}
//邻接矩阵
//无向图 && 简单图
int v[105];//点
int e[105][105];//邻接矩阵
int n, m;//n点,m边
int main()
{
int x, y, w0;
scanf("%d%d", &n, &m);
for (int i = 1; i <= n; i++)
scanf("%d", &v[i]);
for (int i = 1; i <= m; i++)//区别
{
scanf("%d%d%d", &x, &y, &w0);
e[x][y] = e[y][x] = w0;
}
scanf("%d", &x);
for (int i = 1; i <= m; i++)//查找临界点
{
if (e[x][i])
printf("%d ", e);
}
}
邻接表【有/无】 → 链式前向星★
适用范围:有向图 & 无向图
点:数组存点集
无向图:
边:把每个顶点的临界点构成线性表,由于每个点的邻接点个数不同,邻接表用 单链表 存储
有向图:
边:区别于无向图,只保存 出边(邻接表) / 入边(逆邻接表)
组成
int n, m;//n点,m边
//每个点后的单链表
typedef struct listNode {
int num;//邻接点编号
int w;//边权
struct listNode* next;
}ENode;
//点
struct vNode {
int data;//点的编号
struct listNode* first;//指向单链表的第一个节点
}g[105];
边的存储
//建立点x和点y之间的边
ENode* insert(int x, int y, int w0)
{
//初始化
ENode* s = (ENode*)malloc(sizeof(ENode));
s->num = y;
s->w = w0;
//头插
s->next = g[x].first;
g[x].first = s;
return s;
}
度的查找
//查找x的度
int findDegree(int x)
{
int d = 0;
ENode* p = g[x].first;
while (p != NULL)
{
d++;
p = p->next;
}
return d;
}
参考主代码
//无向图-邻接表
int main()
{
//建立
int x, y, w0;
scanf("%d%d", &n, &m);
for (int i = 1; i <= n; i++)
{
scanf("%d", &x);
g[i].data = x;
g[i].first = NULL;
}
for (int i = 1; i <= m; i++)
{
scanf("%d%d%d", &x, &y, &w0);
ENode* sx = insert(x, y, w0);//在点x的单链表中插入邻接点y
ENode* sy = insert(y, x, w0);//在点y的单链表中插入邻接点x
}
//查找x的度
scanf("%d", &x);
findDegree(x);
}
//有向图-邻接表
int main()
{
//建立
int x, y, w0;
scanf("%d%d", &n, &m);
for (int i = 1; i <= n; i++)
{
scanf("%d", &x);
g[i].data = x;
g[i].first = NULL;
}
for (int i = 1; i <= m; i++)
{
scanf("%d%d%d", &x, &y, &w0);
ENode* sx = insert(x, y, w0);//在点x的单链表中插入邻接点y
}
//查找x的出度
scanf("%d", &x);
findDegree(x);
}
//有向图-逆邻接表
int main()
{
//建立
int x, y, w0;
scanf("%d%d", &n, &m);
for (int i = 1; i <= n; i++)
{
scanf("%d", &x);
g[i].data = x;
g[i].first = NULL;
}
for (int i = 1; i <= m; i++)
{
scanf("%d%d%d", &x, &y, &w0);
ENode* sy = insert(y, x, w0);//在点y的单链表中插入邻接点x
}
//查找x的入度
scanf("%d", &x);
findDegree(x);
}
十字链表【有】
适用范围:有向图
构成:邻接表+逆邻接表
p.s.同一单链表节点存储在 一个节点的出边 和 一个节点的入边 单链表里
组成
int n, m;//n点,m边
//每个点后的单链表
typedef struct listNode {
int vTail;//弧尾编号
int vHead;//弧头编号
int w;//边权
struct listNode* nextTailOut;//弧尾出边
struct listNode* nextHeadIn;//弧头入边
}ENode;
//点
struct vNode {
int data;//点的编号
struct listNode* firstOut;//指向出边单链表的第一个节点
struct listNode* firstIn;//指向入边单链表的第一个节点
}g[105];
边的存储
//建立点x和点y之间的边
ENode* insert(int x, int y, int w0)
{
ENode* s = (ENode*)malloc(sizeof(ENode));
s->vHead = y;//弧头数据
s->vTail = x;//弧尾数据
s->w = w0;
//s插入到弧尾x的出边单链表中(头插)
s->nextTailOut = g[x].firstOut;
g[x].firstOut = s;
//s插入到弧头y的入边单链表中(头插)
s->nextHeadIn = g[y].firstIn;
g[y].firstIn = s;
return s;
}
度的查找
//查找x的出度
int findOutDegree(int x)
{
int d = 0;
ENode* p = g[x].firstOut;
while (p != NULL)
{
d++;
p = p->nextTailOut;
}
return d;
}
//查找x的入度
int findInDegree(int x)
{
int d = 0;
ENode* p = g[x].firstIn;
while (p != NULL)
{
d++;
p = p->nextHeadIn;
}
return d;
}
参考主代码
int main()
{
//建立
scanf("%d%d", &n, &m);
for (int i = 1; i <= n; i++)
{
g[i].data = i;
g[i].firstIn = g[i].firstOut = NULL;
}
int x, y, w0;
for (int i = 1; i <= m; i++)
{
scanf("%d%d%d", &x, &y, &w0);//点x到点y有一条权值是w0的边
ENode* s = insert(x, y, w0);//建立边
}
//查找x的度
scanf("%d", &x);
findOutDegree(x);//找出度
findInDegree(x);//找入度
}
邻接多重表【无】
适用范围:无向图
构成:无向图邻接表 PLUS版(修改了无向邻接表一条边存两次的缺点)
组成
int n, m;//n点,m边
//每个点后的单链表
typedef struct listNode {
int vi, vj;//两个点
int w;//边权
struct listNode* viNext;
struct listNode* vjNext;
}ENode;
//点
struct vNode {
int data;//点的编号
struct listNode* first;
}g[105];
边的存储
//建立点x和点y之间的边
ENode* insert(int x, int y, int w0)
{
ENode* s = (ENode*)malloc(sizeof(ENode));
s->vi = x;
s->vj = y;
s->w = w0;
//vi-头插
s->viNext = g[x].first;
g[x].first = s;
//vj-头插
s->vjNext = g[y].first;
g[y].first = s;
return s;
}
度的查找
//查找x的度
int findDegree(int x)
{
int d = 0;
ENode* p = g[x].first;
while (p != NULL)
{
d++;
if (p->vi == x)
p = p->viNext;
else if (p->vj == x)
p = p->vjNext;
}
return d;
}
参考主代码
int main()
{
//建立
scanf("%d%d", &n, &m);
for (int i = 1; i <= n; i++)
{
g[i].data = i;
g[i].first = NULL;
}
int x, y, w0;
for (int i = 1; i <= m; i++)
{
scanf("%d%d%d", &x, &y, &w0);//点x到点y有一条权值是w0的边
ENode* s = insert(x, y, w0);//建立边
}
//查找x的度
scanf("%d", &x);
findOutDegree(x);//找出度
findInDegree(x);//找入度
}
图的遍历
深度优先遍历(DFS)
优点:空间少
缺点:易超时
应用:查找所有路径
以 右手原则 & 邻接矩阵的存储方式 为基础
char v[105];//点
int e[105][105];//邻接矩阵
int vis[105];//查看是否被访问过
int n, m;//n点,m边
//深度优先遍历
void DFSg(int x)
{
vis[x] = 1;
printf("%c", v[x]);//访问点x
for (int i = 1; i <= n; i++)
if (vis[i] == 0 && e[x][i] == 1)
DFSg(i);
}
void dfs()
{
for (int i = 1; i <= n; i++)
if (vis[i] == 0)
DFSg(i);
}
int main()
{
scanf("%d%d", &n, &m);
getchar();
for (int i = 1; i <= n; i++)
scanf("%c", &v[i]);
int x, y;
for (int i = 1; i <= m; i++)
{
scanf("%d%d", &x, &y);
e[x][y] = e[y][x] = 1;
}
dfs();
}
广度优先遍历(BFS)
缺点:在搜索过程中必须保存搜索过程中的状态,用于判重(队列)
应用:寻找最短路径
以 邻接表的存储方式 为基础
创建
int n, m;//n点,m边
int vis[105];//查看是否被访问过
//每个点后的单链表
typedef struct listNode {
int num;//临界点的下标
struct listNode* next;
}ENode;
//点
struct vNode {
char v;//顶点数据
struct listNode* first;//指向单链表的第一个节点
}g[105];
//建立点x和点y之间的边
void insert(int x, int y)
{
//初始化
ENode* s = (ENode*)malloc(sizeof(ENode));
s->num = y;
//头插
s->next = g[x].first;
g[x].first = s;
}
创建循环队列
//循环队列
typedef struct queueNode {
int data[MVNum];
int front;
int rear;
}qNode;
void initQueue(qNode* q)//初始化
{
q->front = 0;
q->rear = 0;
}
void ppush(qNode* q, int k)//入队
{
if ((q->rear + 1) % MVNum != q->front)//判满
{
q->data[q->rear] = k;
q->rear = (q->rear + 1) % MVNum;
}
}
int ppop(qNode* q)//出队
{
if (q->rear == q->front)//判空
return -1;
int k = q->data[q->front];
q->front = (q->front + 1) % MVNum;
return k;
}
BFS
//广度优先遍历
void bfs()
{
qNode q;
initQueue(&q);
for (int i = 0; i < n; i++)
{
if (vis[i] == 0)
{
//入队:将下标i入队
ppush(&q, i);
vis[i] = 1;
while (q.rear != q.front)//非空
{
int x = ppop(&q);
printf("%c", g[x].v);
ENode* p = g[x].first;
while (p != NULL)
{
if (vis[p->num] == 0)
{
ppush(&q, p->num);
vis[p->num] = 1;
}
p = p->next;
}
}
}
}
}
参考主代码
int main()
{
//建立
scanf("%d%d", &n, &m);
getchar();
for (int i = 0; i < n; i++)
{
scanf("%c", &g[i].v);
g[i].first = NULL;
}
int x, y;
for (int i = 1; i <= m; i++)
{
scanf("%d%d", &x, &y);
insert(x, y);//在点x的单链表中插入邻接点y
insert(y, x);//在点y的单链表中插入邻接点x
}
bfs();
}
相关算法
最小生成树
选择:①稀疏图:Kruskal算法;②稠密图:Prim算法;
应用:①网线架设;②道路铺设;③以太桥接的自动配置;等等
特殊定义:
#define INF 65535 //无穷大
Kruskal算法(克鲁斯卡尔算法)
*考虑角度*
边
*思路*
1.选权值尽可能小的n-1条边 -> 排序(Eg.选择排序算法)
2.选边时,选择的每一条边都得是连接未连通的两个节点,即不能成环 【顶点并查集】
*时间复杂度*
与排序算法有关(边)
组成
//带权无向连通图 -> 邻接矩阵存图 && 边集数组存边
struct edge {
int u, v;//顶点
int w;//权值
}e[10005];
int g[105][105] = { 0 };//邻接矩阵
int n, m;//n个点,m条边
必要函数:排序(选择排序) + 并查集寻父
//选择排序
void SortE(int start, int end)
{
struct edge tmp;
int minn;
for (int i = start; i < end; i++)//排序次数
{
minn = i;
for (int j = i + 1; j <= end; j++)
{
if (e[j].w < e[minn].w)
minn = j;
}
tmp = e[i];
e[i] = e[minn];
e[minn] = tmp;
}
for (int i = start; i <= end; i++)
printf("(%d,%d) %d\n", e[i].u, e[i].v, e[i].w);
printf("\n");
}
//并查集找父(可用路径压缩)
int findF(int f[], int x)
{
while (x != f[x])
x = f[x];
return x;
}
Kruskal排序
//Kruskal排序
void kruskal()
{
int f[105] = { 0 };//并查集
for (int i = 0; i < n; i++)//并查集初始化
f[i] = i;
int numE = 0;//边的条数
int fu, fv;//u和v的父亲
for (int i = 0; i < m; i++)
{
fu = findF(f, e[i].u);
fv = findF(f, e[i].v);
if (fu != fv)//若符合选择条件
{
numE++;
f[fu] = fv;//并查集合并
printf("(%d,%d) %d\n", e[i].u, e[i].v, e[i].w);
}
if (numE == n - 1)
break;
}
}
参考主函数
int main()
{
scanf("%d%d", &n, &m);
for (int i = 0; i < n; i++)//初始化邻接矩阵
{
for (int j = 0; j < n; j++)
{
if (i == j)
g[i][j] = 0;
else
g[i][j] = INF;//点i和点j之间不存在边,假定数值为无限大
}
}
int x, y, wi;
for (int i = 0; i < m; i++)
{
scanf("%d%d%d", &x, &y, &wi);
g[x][y] = g[y][x] = wi;
e[i].u = x;
e[i].v = y;
e[i].w = wi;
}
SortE(0, m - 1);//排序 Eg.选择排序
kruskal();
}
Prim算法(普里姆算法)
*考虑角度*
点
分成两类:①已经选到生成树中的点;②还未选到生成树中的点;
*概念*
点到生成树的最小距离dist:①:0;②:点与生成树中的若干个点直接相连,其最小权值为dist
*思路*
维护一个点到生成树的最小距离dist[],初始化为无穷大
维护一个标记数组 vis[]:vis[i]==0,i点没在生成树中;vis[i]==1,i点在生成树中
1.任选一个点做起点,把起点x加入生成树中:vis[x]=1,dist[x]=0
2.更新x的邻接点到生成树的最小距离
3.执行n-1次循环:
i)找到vis==0,且dist[]最小的点
ii)把点x加入到生成树中
iii)更新x的邻接点到生成树的最小距离
*时间复杂度*
O(n^2) 即与点有关
组成
//带权无向连通图 -> 邻接矩阵存图
int g[105][105] = { 0 };//邻接矩阵
int dist[105] = { 0 };//存最小距离
int vist[105] = { 0 };//标记数组
int n, m;//n个点,m条边
必要函数:求两数的最小值
//求最小值函数
int Min(int a, int b)
{
return a < b ? a : b;
}
Prim算法
//Prim排序
void prim()
{
//第一步:找点
int x;
scanf("%d", &x);//选择第一个点
vist[x] = 1; dist[x] = 0;
//第二步:更新
for (int i = 0; i < n; i++)
dist[i] = Min(dist[i], g[x][i]);
//第三步:循环
int k = -1;//dist[k]最小
int minn = INF;//最小值
for (int i = 0; i < n - 1; i++)
{
minn = INF;
k = -1;
for (int j = 0; j < n; j++)
{
if (vist[j] == 0 && dist[j] < minn)
{
minn = dist[j];
k = j;
}
}
vist[k] = 1;
dist[k] = 0;
printf("点:%d\n", k);
for (int j = 0; j < n; j++)
dist[j] = Min(dist[j], g[k][j]);
}
}
参考主函数
int main()
{
scanf("%d%d", &n, &m);
for (int i = 0; i < n; i++)//初始化邻接矩阵
{
dist[i] = INF;//初始化
for (int j = 0; j < n; j++)
{
if (i == j)
g[i][j] = 0;//自连
else
g[i][j] = INF;//点i和点j之间不存在边,假定数值为无限大
}
}
int x, y, w;
for (int i = 0; i < m; i++)
{
scanf("%d%d%d", &x, &y, &w);
g[x][y] = g[y][x] = w;
}
prim();
}
最短路算法
核心思想:通过寻找中转点得到更短的路径
dijkstra算法【单源最短路径算法】
基于*贪心*
时间复杂度:n^2
缺点:不能解决负边权
dist[i]=l;//0点到i点的目前已知的最短路径的长度是l
*思路*
用离源点最近的点x做中转点,更新其他点,此时,源点到x的最短路径肯定就确定下来了。
①循环n-1次:每次找到一个dist[]值最小的点x,确定点x的最短路径,然后用点x去更新点x的邻接点的最短路径长度。
组成
//邻接矩阵存图
int n, m;//n个点,m条边
int g[105][105] = { 0 };//邻接矩阵
int dist[105] = { 0 };//当前最短路径长度
int vist[105] = { 0 };//1:已访问;0:未访问;
int pre[105] = { 0 };//存储确定最短路径的点的下标
dijkstra算法
//dijkstra算法
void dijkstra(int st)
{
for (int i = 0; i < n; i++)
{
dist[i] = g[st][i];
pre[i] = st;
}
dist[st] = 0;
vist[st] = 1;
pre[st] = st;
for (int i = 0; i < n - 1; i++)
{
int minn = INF;
int minNum = -1;
for (int j = 0; j < n; j++)
{
if (vist[j] == 0 && dist[j] < minn)
{
minn = dist[j];
minNum = j;
}
}
vist[minNum] = 1;
for (int j = 0; j < n; j++)
{
if (vist[j] == 0 && dist[j] > (dist[minNum] + g[minNum][j]))
{
dist[j] = dist[minNum] + g[minNum][j];
pre[j] = minNum;
}
}
}
}
参考主代码
int main()
{
scanf("%d%d", &n, &m);
for (int i = 0; i < n; i++)
{
for (int j = 0; j < n; j++)
{
if (i == j)
g[i][j] = 0;
else
g[i][j] = INF;
}
}
int x, y, w;
for (int i = 0; i < m; i++)
{
scanf("%d%d%d", &x, &y, &w);
g[x][y] = g[y][x] = w;
}
int start, end;
scanf("%d%d", &start, &end);
dijkstra(start);
printf("从%d到%d的最短路径:%d\n", start, end, dist[end]);
printf("具体路径为: ");
int p = end;
while (pre[p] != p)
{
printf("%d ", p);
p = pre[p];
}
printf("%d ", start);
}
Floyd算法【多源最短路径算法】
基于*动态规划*
时间复杂度:n^3
缺点:不能解决负边权回路
*思路*
①确定使用动态规划/DP
对于任意两点i j,若要找更短的路径,通过增加中站点的方式,使路径变短
②找状态,设状态数组
i)不加中转点,求任意两点之间最短路径的距离
-> 邻接矩阵 初始状态:dp[0][i][j] = a[i][j]
ii)加一个中转点,求任意两点之间最短路径的距离
-> dp[1][i][j]
……
-> dp[k][i][j] = ans;//以 前n个点做中转点时,点i点j之间的最短距离
……
…)加n个中转点,求任意两点之间最短路径的距离
-> dp[n][i][j]
③状态转移公式
dp[k-1][i][j] = 确定的数
dp[k][i][j] = min(dp[k-1][i][j], dp[k-1][i][k]+dp[k-1][k][j])
*代码*
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
#define INF 65535 //无穷大
//题面:给定一个带权图,求出图中任意两点的最短路径
int a[105][105] = { 0 };//邻接矩阵
int dp[105][105][105] = { 0 };
int n, m;//点,边
int main()
{
scanf("%d%d", &n, &m);
//初始化
for (int i = 1; i <= n; i++)
{
for (int j = 1; j <= n; j++)
{
if (i == j)
dp[0][i][j] = a[i][j] = 0;
else
dp[0][i][j] = a[i][j] = INF;
}
}
//画图:邻接矩阵(有向边)
int x, y, w;
for (int i = 1; i <= m; i++)
{
scanf("%d%d%d", &x, &y, &w);
dp[0][x][y] = a[x][y] = w;//同时初始化dp数组
}
//Floyd算法-状态转移
for (int k = 1; k <= n; k++)
{
for (int i = 1; i <= n; i++)//起点
{
for (int j = 1; j <= n; j++)//终点
{
dp[k][i][j] = min(dp[k - 1][i][j], dp[k - 1][i][k] + dp[k - 1][k][j]);
}
}
}
}
*优化版*
因为dp[k][][]仅与dp[k-1][][]有关,可将三维数组优化为二位数组dp[i][j]
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
#define INF 65535 //无穷大
//题面:给定一个带权图,求出图中任意两点的最短路径
//优化版
int dp[105][105] = { 0 };
int n, m;//点,边
int main()
{
scanf("%d%d", &n, &m);
//初始化
for (int i = 1; i <= n; i++)
{
for (int j = 1; j <= n; j++)
{
if (i == j)
dp[i][j] = 0;
else
dp[i][j] = INF;
}
}
//画图:邻接矩阵(有向边)
int x, y, w;
for (int i = 1; i <= m; i++)
{
scanf("%d%d%d", &x, &y, &w);
dp[x][y] = w;//同时初始化dp数组
}
//Floyd算法-状态转移
for (int k = 1; k <= n; k++)
{
for (int i = 1; i <= n; i++)//起点
{
for (int j = 1; j <= n; j++)//终点
{
dp[i][j] = min(dp[i][j], dp[i][k] + dp[k][j]);
}
}
}
//输出
for (int i = 1; i <= n; i++)
{
for (int j = 1; j <= n; j++)
{
if (dp[i][j] == INF)
{
printf(" ");
continue;
}
printf("%-2d ", dp[i][j]);
}
printf("\n");
}
}
拓扑排序【前置】
*思路*
引入一个栈,用来保存所有入度为0的点的下标
执行n次循环:
1.执行一次出栈,得到一个入度为0的点x(若栈空,则有环)
2.访问点x
3.从图中删除点x及其出边,即遍历x的出边单链表,将其出边邻接点的入度-1,若一个点的入度为0,则该点入栈
栈
//链式栈
typedef struct stackNode {
int data;//存放节点的下标
struct stackNode* next;
}mystack, * stack;
stack init()//初始化
{
stack s = (mystack*)malloc(sizeof(mystack));
s->next = NULL;
return s;
}
stack ppush(stack s, int e)//入栈
{
mystack* p = (mystack*)malloc(sizeof(mystack));
p->data = e;
p->next = s->next;
s->next = p;
return s;
}
stack ppop(stack s, int* e)//出栈,返回栈顶元素
{
if (s->next == NULL)
{
*e = -1;
return s;
}
stack p = s->next;
*e = p->data;
s->next = p->next;
free(p);
p = NULL;
return s;
}
邻接表存有向图
int n, m;//n点,m边
int indegree[105] = { 0 };//入度数据
char topo[105] = { 0 };//拓扑数组
//每个点后的单链表
typedef struct listNode {
int num;//临界点编号
struct listNode* next;
}ENode;
//点
struct vNode {
char d;//点的编号
struct listNode* first;//指向单链表的第一个节点
}g[105];
必要函数
int find(char x)//找输入字母的下标
{
for (int i = 0; i < n; i++)
if (g[i].d == x)
return i;
}
拓扑排序
//拓扑排序
int topoSort()
{
//创建栈
stack s = init();
for (int i = 0; i < n; i++)
if (indegree[i] == 0)
s = ppush(s, i);
//循环
int e;//出栈的点的下标
int k = 0;//topo数组的下标
for (int i = 0; i < n; i++)
{
s = ppop(s, &e);
if (e == -1)
{
printf("该图是带环图,无拓扑序列\n");
return 0;
}
topo[k++] = g[e].d;
ENode* p = g[e].first;
while (p != NULL)
{
int j = p->num;
indegree[j]--;
if (indegree[j] == 0)
s = ppush(s, j);
p = p->next;
}
}
return 1;
}
参考主函数
int main()
{
//建立
scanf("%d%d", &n, &m);
for (int i = 0; i < n; i++)
{
getchar();
scanf("%c", &g[i].d);
g[i].first = NULL;
}
char x, y;
int xi, yi;
for (int i = 1; i <= m; i++)
{
getchar();
scanf("%c %c", &x, &y);
xi = find(x);
yi = find(y);
indegree[yi]++;//统计入度
ENode* s = (ENode*)malloc(sizeof(ENode));
s->num = yi;
s->next = g[xi].first;
g[xi].first = s;
}
int flag = topoSort();
if (flag)
{
for (int i = 0; i < n; i++)
printf("%c ", topo[i]);
printf("\n");
}
}
关键路径
*相关概念*
AOV网:在DAG中,用顶点表示活动,用无权有向边表示活动之间的次序
-> 工程中小项目的次序
AOE网:在DAG中,用顶点表示事件,用带权有向边表示活动,边权表示活动持续的时间
-> 工程中小项目的次序,每个小项目所需的完成时间,整个工程所需的完成时间
p.s.区别:
事件:瞬时间发生的一件事情/动作。(事件的发生)
活动:一个完整的流程,可能由多个事件组成。(活动开始 活动结束)
关键路径:从起点到终点,最长的路径(们),且由关键活动组成
关键活动:关键路径上的活动,不能延期,且最早开始时间(ETE)==最晚开始时间(LTE)
①事件的最早发生时间(ETV)
起点事件strat的最早发生时间:ETV[s]=0;
其他事件的最早发生时间:基于topo序列去算 ETV[j]=max(ETV[j],ETV[i]+w);
②事件的最晚发生时间(LTV)
终点事件end的最晚发生时间:LTV[e]=ETV[e];
其他事件的最晚发生时间:基于逆topo序列去算 LTV[i]=min(LTV[i],LTV[j]-w);
关系:
假如一个活动是:i -w-> j
ETE = 该边起点事件的ETV ,即 ETE = ETV[i]
LTE = 该边终点事件的LTV - 该边权值 ,即LTE = LTV[j] - w
*时间复杂度*
n + e
链式栈
//链式栈
typedef struct stackNode {
int data;//存放节点的下标
struct stackNode* next;
}mystack, * stack;
stack init()//初始化
{
stack s = (mystack*)malloc(sizeof(mystack));
s->next = NULL;
return s;
}
stack ppush(stack s, int e)//入栈
{
mystack* p = (mystack*)malloc(sizeof(mystack));
p->data = e;
p->next = s->next;
s->next = p;
return s;
}
stack ppop(stack s, int* e)//出栈,返回栈顶元素
{
if (s->next == NULL)
{
*e = -1;
return s;
}
stack p = s->next;
*e = p->data;
s->next = p->next;
free(p);
p = NULL;
return s;
}
邻接表存图
//邻接表存有向图 AOE网
int n, m;//n点,m边
int indegree[105] = { 0 };//入度数据
int topo[105] = { 0 };//拓扑数组:保存节点下标
int ETV[105] = { 0 };//事件的最早发生时间
int LTV[105] = { 0 };//事件的最晚发生时间
//每个点后的单链表
typedef struct listNode {
int num;//临界点编号
int w;//边权
struct listNode* next;
}ENode;
//点
struct vNode {
char d;//点的编号
struct listNode* first;//指向单链表的第一个节点
}g[105];
int find(char x)//找输入字母的下标
{
for (int i = 0; i < n; i++)
if (g[i].d == x)
return i;
}
关键路径 + 拓扑排序
//拓扑排序
void topoSort()
{
//创建栈
stack s = init();//初始化栈
for (int i = 0; i < n; i++)//初始化ETV
ETV[i] = 0;
for (int i = 0; i < n; i++)//找到入度为0的点,入栈
if (indegree[i] == 0)
s = ppush(s, i);
//循环
int e;//出栈的点的下标
int k = 0;//topo数组的下标
for (int i = 0; i < n; i++)
{
s = ppop(s, &e);//出栈
topo[k++] = e;//保存出栈的顶点的下标
ENode* p = g[e].first;
while (p != NULL)
{
int j = p->num;//j就是e的出边邻接点 e-->j
if (ETV[j] < (ETV[e] + p->w))//求ETV
ETV[j] = ETV[e] + p->w;
indegree[j]--;
if (indegree[j] == 0)
s = ppush(s, j);
p = p->next;
}
}
}
//关键路径
void CriticalPath()
{
int end = topo[n - 1];//终点:topo排序的最后一个点
for (int i = 0; i < n; i++)//初始化LTV
LTV[i] = ETV[end];
//根据逆topo序更新LTV
int x;
for (int i = end - 1; i >= 0; i--)
{
x = topo[i];//求x点的最晚发生时间
ENode* p = g[x].first;
while (p != NULL)
{
int j = p->num;//点x的出边临界点下标
if (LTV[x] > (LTV[j] - p->w))
LTV[x] = LTV[j] - p->w;
p = p->next;
}
}
printf("以下是关键活动:\n");
int ETE, LTE;
for (int i = 0; i < n; i++)//遍历
{
for (ENode* p = g[i].first; p != NULL; p = p->next)
{
int j = p->num;//边p的终点j
ETE = ETV[i];
LTE = LTV[j] - p->w;
if (ETE == LTE)
printf("%c %c\n", g[i].d, g[j].d);
}
}
}
参考主代码
int main()
{
//建立
scanf("%d%d", &n, &m);
for (int i = 0; i < n; i++)
{
getchar();
scanf("%c", &g[i].d);
g[i].first = NULL;
}
char x, y;
int xi, yi, wi;
for (int i = 0; i < m; i++)
{
getchar();
scanf("%c %c %d", &x, &y, &wi);
xi = find(x);
yi = find(y);
indegree[yi]++;//统计入度
ENode* s = (ENode*)malloc(sizeof(ENode));
s->num = yi;
s->w = wi;
//头插
s->next = g[xi].first;
g[xi].first = s;
}
topoSort();
CriticalPath();
}