图论

最小生成树

一.Kruskal

1.定理:

任意一棵最小生成树一定包含无向图中权值最小的边。

2.步骤:

(1)建立并查集,每个点单独构成集合。
(2)边从小到大排序,依次扫描每条边。
(3)xy 属于同一集合,忽略这条边。
(4)否则,合并xy所在的集合,累加边权。
(5)所有边扫完后,即为答案。

3.时间复杂度:

O(mlogm)

4.代码:

点击查看代码
int n,m,f[100005],ans;
struct edge{
	int u;
	int v;
	int w;
}e[200005];
bool cmp(edge a,edge b){
	return a.w<b.w;
}
int get(int x){
	if(f[x]==x) return x;
	return f[x]=get(f[x]);
}
void Kruskal(){
	sort(e+1,e+1+m,cmp);
	for(int i=1;i<=n;i++) f[i]=i; 
	for(int i=1;i<=m;i++){
		int u=get(e[i].u);
		int v=get(e[i].v);
		if(u==v) continue;
		f[u]=v;
		ans+=e[i].w;
	}
	cout<<ans;
}

二.Prim

1.区分:

与 Kruskal 算法(由若干小集合#去合成更大的集合,过程中有若干个集合)不同的是,Prim 算法总是维护最小生成树的一部分(由一个总集合
去拓展点,使该集合更大,过程中始终有一个集合)。

2.步骤:

(1)最初仅确定1号节点属于最小生成树。
(2)类似于蓝白点思想,每次只拓展一个离总集合最近的一个元素。
(3)拓展 n1 次求出答案。

3.时间复杂度:

O(n2)
二叉堆优化:O(mlogn)

4.适用范围:

多用于稠密图,尤其是完全图的最小生成树求解。

5.代码:

点击查看代码
int a[3005][3005],d[3005],n,m,ans;//a用来存边 
bool v[3005];
void Prim(){
	memset(d,0x3f,sizeof(d));
	d[1]=0;
	for(int i=1;i<n;i++){
		int x=0;
		for(int j=1;j<=n;j++)
		    if(!v[j]&&(x==0||d[j]<d[x])) x=j;
		v[x]=1;
		for(int j=1;j<=n;j++)
		    if(!v[j]) d[j]=min(d[j],a[x][j]); 
	}
	for(int i=2;i<=n;i++) ans+=d[i];
	cout<<ans;
}

最短路

一.Dijkstra

1.步骤:

(1)初始化:dist1=0,其余节点的 dist 置为正无穷。
(2)找出一个未被标记的,dist 值最小的节点 x,然后标记节点 x
(3)扫描 x 的所有出边,松弛其他节点的 dist
(4)重复(2)(3),直到所有节点都被标记。

2.时间复杂度:

O(n2)
二叉堆优化:O((n+m)logn)

3.使用限制:

不能有负边。

4.代码:

点击查看代码
int a[3005][3005],dist[3005],n,m;
bool v[3005];
void Dijkstra(){
	memset(d,0x3f,sizeof(d));
	memset(v,0,sizeof(v));
	d[1]=0;
	for(int i=1;i<n;i++){
		int x=0;
		for(int j=1;j<=n;j++)
		    if(!v[j]&&(x==0||d[j]<d[x])) x=j;
		v[x]=1;
		for(int j=1;j<=n;j++)
		    d[j]=min(d[j],d[x]+a[x][j]);
 	}
} 

二.Bellman-Ford

1.原理:

对于图中的某一条边 (x,y,z),有 dist[y]<=dist[x]+z 成立,则称该边满足三角形不等式。若所有边都满足三角形不等式,则 dist 数组就是所求最短路。

2.步骤:

基于迭代思想。
(1)扫描所有边 (x,y,z),若 dist[y]>dist[x]+z,则用 dist[x]+z 更新 dist[y]
(2)重复上述步骤,直到没有更新操作发生。

3.时间复杂度:

O(nm)

4.适用范围:

可处理有负边/负环的最短路,有限路线最短路。

5.代码:

点击查看代码
int a[3005][3005],dist[3005],backup[3005];
struct edge{
	int u;
	int v;
	int w;
}e[200005];
void Bellman_Ford(){
	memset(dist,0x3f,sizeof(dist));
	dist[1]=0;
	for(int i=1;i<=k;i++){
		memcpy(backup,dist,sizeof(dist));
		for(int j=1;j<=m;j++){
			int u=e[i].u;
			int v=e[i].v;
			int w=e[i].w;
			dis[v]=min(dist[v],backup[a]+w);
		} 
	}
}

三.SPFA

1.步骤:

队列优化的 Bellman-Ford 算法(避免了 Bellman-Ford 对不需要扩展的节点的冗余扫描)。
(1)建立一个队列,最初只有起点。
(2)取出队头节点 x,扫描它的所有出边 (x,y,z),若 dist[y]>dist[x]+z,则使用 dist[x]+z 更新 dist[y]。同时,若 y 不在队列中,则把 y 入队。
(3)重复上述步骤,直至队列为空。

2.时间复杂度:

随机图:O(km)k 是一个较小的常数)。
特殊构造图:O(nm)

3.适用范围:

可处理负边。

4.代码:

点击查看代码
const int N=100010,M=1000010;
int head[N],ver[N],edge[M],Next[M],d[N];
int n,m,tot;
queue<int> q;
bool v[N];
void add(int x,int y,int z){
	ver[++tot]=y;
	edge[tot]=z;
	Next[tot]=head[x];
	head[x]=tot;
}
void Spfa(){
	memset(d,0x3f,sizeof(d));
	d[1]=0;v[1]=1;
	q.push(1);
	while(!q.empty()){
		int x=q.front();
		q.pop();
		for(int i=head[x];i;i=Next[i]){
			int y=ver[i],z=edge[i];
			if(d[y]>d[x]+z){
				d[y]=d[x]+z;
				if(!v[y]) q.push(y),v[y]=1;
			}
		}
	}
}

四.Floyd

负环

负环判定

一.Bellman-Ford 判负环

若经过 n 次迭代,算法仍未结束(仍有能产生更新的边),则图中存在负环。

n1 轮迭代以内,算法结束(所有边满足三角形不等式),则图中无负环。

二.SPFA 判负环

1.设 cnt[x] 表示从1到 x 的最短路径包含的边数,cnt[1]=0。当执行更新 dist[y]=dist[x]+z 时,同样更新 cnt[y]=cnt[x]+1。此时发现 cnt[y]>=n,则图中有负环。若算法正常结束,则图中没有负环。
2.记录每个点入队的次数,次数达到 n 时说明有负环。

差分约束

1.原理:

将约束条件 XiXj<=ck 变形为 Xi<=Xj+ck。这与单源最短路径问题中的三角形不等式 disty<=distx+z 非常相似。因此,可以把变量 Xi 看作有向图的一个节点 i 连一条长度为 ck 的有向边。

2.步骤:

(1)先求一组负数解,之后增加0节点,令 X0=0,这样就多了 N 个形如 XiX0<=0 的约束条件,应该从节点0向每个节点 i 连一条长度为0的有向边。
(2)dist[0]=0,以0为起点求单源最短路。若图中存在负环,则给定的差分约束系统无解。否则,Xi=dist[i]就是差分约束系统的一组解。

Tarjan 与无向图连通性

零.前置知识

1.dfnx->时间戳(访问的先后顺序)。
2.lowx->追溯值
(1)初始 lowx=dfnx考虑从 x 出发的每条边 (x,y)
(2)若在搜索树上 xy 的父节点,则令 lowx=min(lowx,lowy)
若无向边 (x,y) 不是搜索树上的边,则令 lowx=minlowx,dfny)

一.桥(割边)

1.定义:

从图中删去边 e 之后,G 分裂成两个不相连的子图,则称 eG 的桥或割边。

2.判定法则

无向边 (x,y) 是桥,当且仅当搜索树上存在 x 的一个子节点 y,满足:
dfn[x]<low[y]

3.代码:

点击查看代码
const int SIZE=100010;
int head[SIZE],ver[SIZE*2],Next[SIZE*2];
int dfn[SIZE],low[SIZE],n,m,tot,sum;
bool bridge[SIZE*2];
void add(int x,int y){
	ver[++tot]=y;
	Next[tot]=head[x];
	head[x]=tot;
} 
void tarjan(int x,int in_edge){
	dfn[x]=low[x]=++dfc;
	for(int i=head[x];i;i=Next[i]){
		int y=ver[i];
		if(!dfn[y]){
			tarjan(y,i);
			low[x]=min(low[x],low[y]);
			if(low[y]>dfn[x])
			    bridge[i]=bridge[i^1]=1; 
		}else if(i!=(in_edge^1))
		    low[x]=min(low[x],dfn[y]);
	}
}
for(int i=1;i<=n;i++) if(!dfn[i]) tarjan(i,0);

二.割点

1.定义:

从图中删去节点 x,以及所有与 x 关联的边之后,G 分裂成两个或两个以上不相连的子图,则称 xG 的割点。

2.判定法则:

(1)x 不是根节点,则 x 是个点当且仅当搜索树上存在 x 的一个子节点 y 满足 dfn[x]<=low[y]
(2)x 是根节点,则 x 是割点当且仅当搜索树上存在至少两个子节点 y1,y2 满足上述条件。

3.代码:

点击查看代码
const int SIZE=100010;
int head[SIZE],ver[SIZE*2],Next[SIZE*2];
int dfn[SIZE],low[SIZE],stack[SIZE];
int n,m,tot,num,root;
bool cut[SIZE];
void add(int x,int y){
	ver[++tot]=y;
	Next[tot]=head[x];
	head[x]=tot;
}
void tarjan(int x){
	dfn[x]=low[x]=++num;
    int flag=0;
    for(int i=head[x];i;i=Next[i]){
    	int y=ver[i];
		if(!dfn[y]){
			tarjan(y);
			low[x]=min(low[x],low[y]);
			if(low[v]>=dfn[x]){
				flag++;
				if(x!=root||flag>1) cnt[x]=true;
			}
		}else low[x]=min(low[x],dfn[y]); 
	}  
}

三.无向图的双连通分量

1.定理

一张无向连通图是“点双连通图”,当且仅当满足下面两个条件之一:
(1)图中顶点数不超过2。
(2)图中任意两点都同时包含在至少一个简单环中。

posted @   Travller  阅读(32)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 单线程的Redis速度为什么快?
· 展开说说关于C#中ORM框架的用法!
· Pantheons:用 TypeScript 打造主流大模型对话的一站式集成库
· SQL Server 2025 AI相关能力初探
· 为什么 退出登录 或 修改密码 无法使 token 失效
点击右上角即可分享
微信分享提示