图论
最小生成树
一.Kruskal
1.定理:
任意一棵最小生成树一定包含无向图中权值最小的边。
2.步骤:
(1)建立并查集,每个点单独构成集合。
(2)边从小到大排序,依次扫描每条边。
(3)
(4)否则,合并
(5)所有边扫完后,即为答案。
3.时间复杂度:
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)拓展
3.时间复杂度:
二叉堆优化:
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)初始化:
(2)找出一个未被标记的,
(3)扫描
(4)重复(2)(3),直到所有节点都被标记。
2.时间复杂度:
二叉堆优化:
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.原理:
对于图中的某一条边
2.步骤:
基于迭代思想。
(1)扫描所有边
(2)重复上述步骤,直到没有更新操作发生。
3.时间复杂度:
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)取出队头节点
(3)重复上述步骤,直至队列为空。
2.时间复杂度:
随机图:
特殊构造图:
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 判负环
若经过
若
二.SPFA 判负环
1.设
2.记录每个点入队的次数,次数达到
差分约束
1.原理:
将约束条件
2.步骤:
(1)先求一组负数解,之后增加0节点,令
(2)
Tarjan 与无向图连通性
零.前置知识
1.
2.
(1)初始
(2)若在搜索树上
若无向边
一.桥(割边)
1.定义:
从图中删去边
2.判定法则
无向边
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.定义:
从图中删去节点
2.判定法则:
(1)
(2)
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)图中任意两点都同时包含在至少一个简单环中。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 单线程的Redis速度为什么快?
· 展开说说关于C#中ORM框架的用法!
· Pantheons:用 TypeScript 打造主流大模型对话的一站式集成库
· SQL Server 2025 AI相关能力初探
· 为什么 退出登录 或 修改密码 无法使 token 失效