数据结构:图

p.s.自用

目录

存储结构

边集数组【有/无】

邻接矩阵【有/无】★

邻接表【有/无】 → 链式前向星★

组成

边的存储

度的查找

参考主代码

十字链表【有】

组成

边的存储

度的查找

参考主代码

邻接多重表【无】

组成

边的存储

度的查找

参考主代码

图的遍历

深度优先遍历(DFS)

广度优先遍历(BFS)

创建

创建循环队列

BFS

参考主代码

相关算法

最小生成树

Kruskal算法(克鲁斯卡尔算法)

组成

必要函数:排序(选择排序) + 并查集寻父

Kruskal排序 

参考主函数

Prim算法(普里姆算法)

组成

必要函数:求两数的最小值

Prim算法

参考主函数

最短路算法

dijkstra算法【单源最短路径算法】

组成

dijkstra算法

参考主代码

Floyd算法【多源最短路径算法】

*优化版*

拓扑排序【前置】

邻接表存有向图

必要函数

 拓扑排序

参考主函数 

关键路径

链式栈

邻接表存图

关键路径 + 拓扑排序

参考主代码


简单图:无自边和重边

分类:
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();
}

  • 63
    点赞
  • 39
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值