图论(更新中)

本文详细介绍了图论中的最短路算法,包括Dijkstra、Bellman-Ford、SPFA等,并探讨了各种算法的适用范围。此外,还讲解了最小生成树的Kruskal和Prim算法,以及并查集的应用,帮助读者深入理解图论在解决实际问题中的应用。
摘要由CSDN通过智能技术生成

最短路

建图

邻接矩阵

这个东西就非常的easy,只需要用一个二维数组 f x , y f_{x,y} fx,y 表示 x x x y y y之间的距离
上升到几何层面,就是建立了一个二维的 n ∗ n n*n nn的矩阵,每个点表示从下标为它 x x x坐标的点,到下表为它 y y y坐标点之间的距离即可
好的方面在于它存边的过程非常简易,当 n n n的数据范围小的时候用起来会很方便
弊端就在于非常浪费空间,当 n n n的大小上升到 10000 10000 10000以上,就会出现MLE

code
	cin>>x>>y>>z;
	f[x][y]=z;
邻接表

在建图时最常用的就是通过邻接表,将邻接矩阵中浪费的空间大大减少,需要的空间即为边的数量
邻接表是由链表拓展而来
用一个head数组来表示链表的表头,并用 t o t tot tot来表示当前的是第几条边
通过表头 h e a d [ x ] head[x] head[x],很容易定位到第 x x x类对应的链表,从而访问从点 x x x出发的所有边
当连接一条新的边时,我们可以将这条新边的一端连接到此时出发点的表头正连接的位置,再将出发点的表头连到这条新边另一端上
假设 1 1 1已经与 2 2 2相连,那么接下来将 1 1 1 3 3 3相连的过程如图所示
在这里插入图片描述
通过这样的方式,就可以建立出 n n n条链表来代表每个点所连接的边
存边过程中可以开三个数组来分别代表边权,它所连向的点的下标和这条边的下标。
当然也可以直接三合一存在结构体中,这样的存储方式被称为链式前向星

code
struct Star
{
   
	int nxt,val,to;
}edge[M];
void Lian(int x,int y,int z)
{
   
	tot++;
	edge[tot].to=y;
	edge[tot].val=z;
	edge[tot].nxt=head[x];
	head[x]=tot;
}

此代码中有些细节需要注意,当存的图为无向图时,结构体的大小需要开到 2 ∗ M 2*M 2M。使用万能头时,next作为关键字,不可以使用(编译能过)
当遍历一个点所连接的所有点时,就需要发挥链表的用处,从出发点的头所连接的第一条边开始遍历,不断遍历当前这条边后的下一条边。
具体实现见下面代码

code
for(int i=head[x];i;i=edge[i].nxt)
{
   
	int y=edge[i].to,z=edge[i].val;//y表示这条边所连接点,z表示这条边的边权
}

依然有一个小细节,在for循环的终止条件中,由于每条边的下标一开始都为0,但在存边过程中,都被赋予了不为 0 0 0的一个下标,所以当遍历到下标为 0 0 0的边时,则表示 x x x连接的所有边都已经遍历完了
但如果 t o t tot tot是从 0 0 0开始来给边赋予下标,那这条下标为 0 0 0的边就没有了意义,我们可以进行一个操作,将所有边的初值赋为 − 1 -1 1,这样在遍历时就可以把终止条件改为 i ! = 1 i!=1 i=1,使下标为 0 0 0的边又有了价值

vector

有一种更简单且不是特别浪费空间的存图方法就是使用vector
直接将与 x x x相连的点存到 x x x的vector数组中,直接遍历这个vector数组,就可以遍历它所连接的所有点

code
//存图
vector<node> edge[N];
struct Star
{
   
	int val,to;
};
void add(int x,int y,int z)
{
   
	node a;
	a.val=z,a.to=y;
	edge[x].push_back(a);
}
//遍历
for(int i=0;i<edge[x].size();i++)
{
   
	node a=edge[x][i];
	int y=a.to,z=a.val;//y表示这条边所连接点,z表示这条边的边权
}

单源最短路问题

求出从一个点到达其他所有点的最短路径为多少
洛谷题目链接
我们接下来就针对这道题目进行讲解

Dijkstra

Dijkstra用到的主要是贪心策略。
利用一个dis数组来存储源点到每个顶点的最短距离,源点的最短距离显然为0,初始化其他所有点的dis为正无穷
找出dis数组中最短的点,这个点到源点的距离即为当前的dis值(全局最小值不可能再被其他节点更新),我们将这个点锁定,用vis数组将其标记,不再改变它的值,最后再将这个点向外扩展,这样我们就完成了一个顶点的最短路问题,通过这样的方式执行 n n n次,就可以解决单源最短路问题

但这样的算法复杂度为 O ( n 2 ) O(n^2) O(n2),非常的拉胯,考虑优化

两个 n n n的复杂度分别消耗于对 n n n个节点进行操作,每次操作找出 n n n个节点的最小dis值,后面的这个循环是可以进行优化的
我们可以使用二叉堆来优化这个过程,迅速的查找出最小的dis值,这样就可以在 O ( ( n + m ) l o g n ) O((n+m)logn) O((n+m)logn)的复杂度解决此题,这个复杂度已经是飞快了
Dijkstra却也有弊端,就是无法处理负的边权

code
#include<bits/stdc++.h>
using namespace std;
const int maxn=1e6+10;
int n,m,s,tot=0;
int head[maxn],vis[maxn];
long long dis[maxn];
//链式前向星存图
struct Star
{
   
	int to,next;
	long long val;
}edge[maxn];
void Lian(int x,int y,int z)
{
   
	tot++;
	edge[tot].to=y;
	edge[tot].next=head[x];
	edge[tot].val=z;
	head[x]=tot;
}
//堆优化 
struct Point
{
   
	int dd,p;
	bool operator<(const Point &x) const{
   
		return dd>x.dd;
	}
};
void dijkstra()
{
   
	for(int i=1;i<=n;i++) dis[i]=(1<<31)-1;//dis数组赋初值
	priority_queue <Point> que;
	dis[s]=0;
	Point a;
	a.p=s;
	a.dd=0;
	que.push(a);
	while(!que.empty())
	{
   
		a=que.top();
		que.pop();
		int x=a.p;
		if(vis[x]) continue;
		vis[x]=1;//确定这个点的dis值后,就将这个点标记,不再更新它的值
		for(int i=head[x];i;i=edge[i].next)
		{
   
			int y=edge[i].to;
			if(dis[y]>dis[x]+edge[i].val)//如果这条边连接后更加优秀,就更新dis[y]
			{
   
				dis[y]=dis[x]+edge[i].val;
				a.p=y;
				a.dd=dis[y];
				que.push(a);
			}
		}
	}
}
int main()
{
   
	cin>>n>>m>>s;
	for(int i=1;i<=m;i++)
	{
   
		int u,v,w;
		cin>>u>>v>>w;
		Lian(u,v,w);
	}
	dijkstra();
	for(int i=1;i<=n;i++)
	{
   
		cout<<dis[i]<<' ';
	}
	return 0;
}
Bellman-Ford & SPFA

Bellman-Ford算法的流程为
扫描所有边 ( x , y , z ) (x,y,z) (x,y,z),如果 d i s [ y ] > d i s [ x ] + z dis[y]>dis[x]+z dis[y]>dis[x]+z,那么就更新 d i s [ y ] dis[y] dis[y]的值,一直这样搞下去,迟早可以将所有点的最短路都更新完毕
时间复杂度为 O ( n m ) O(nm) O(nm)

显然这是一个很low的算法
如同Dijkstra一样,在它的基础上也可以进行优化,优化后的算法即为SPFA

在我们不断扫描一些边的过程中,有些节点的dis值已经锁死不会再变了,我们却还是一直在扫描与它相连的边,判断能不能将这些边更新,这个操作是冗余的,大大浪费了时间复杂度
所以我们可以拿一个队列优化,当这个节点被更新后,我们再去判断与它相连的边是否能够改变
否则执行操作就没有什么价值
当队列为空,即所有点都确定了最小dis值
需要注意的是,为了防止一个节点重复的进入队列浪费时间复杂度,我们可以拿一个标记数组判断每个点是否在队列中,如果已经处于队列中则没必要再将它放入

#include<bits/stdc++.h>
using namespace std;
int n,m,s;
const int maxn=1e6+10;
int head[maxn],tot=0,dis[maxn],vis[maxn];
//链式前向星存图
struct Star
{
   
	int nxt,to,val;
}edge[maxn];
void Lian(int x,int y,int z)
{
   
	tot++;
	edge[tot].nxt=head[x];
	edge[tot].to=y;
	edge[tot].val=z;
	head[x]=tot;
}
void Spfa()
{
   
	for(int i=1;i<=n;i++) dis[i]=1e9;//更新dis初值
	queue <int> q;
	q.push(s);
	vis[s]=1;
	dis[s]=0;
	while(!q.empty())
	{
   
		int x=q.front();
		q.pop();
		for(int i=head[x];i;i=edge[i].nxt)
		{
   
			int y=edge[i].to;
			if(dis[y]>dis[x]+edge[i].val)
			{
   
				dis[y]=dis[x]+edge[i].val;
				if(!vis[y])
				{
   
					vis[y]=1;//进队列将其标记
					q.push(y);
				}	
			}
		}
		vis[x
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值