DFS
BFS
树与图的存储
树是一种特殊的图,与图的存储方式相同。对于无向图,看成特殊有向图,存储边a->b,b->a。
方式一:邻接矩阵(适合稠密图)
//a,b间有一条长度为w的边
cin >> a >> b >> x;
g[a][b] = min(g[a][b] , x);//可能a,b间的边输入两次,取最短的边
方式二:邻接图表(适合稀疏图)
//对于每一个节点k,开一个单链表,存储这个点能走到哪些点,h[k]存储这个链表的头节点
int h[N],e[M],ne[M],idx;
//添加一个边a->b
void add(int a, int b)
{
e[idx] = b; ne[idx] = h[a]; h[a] = idx++;
}
//初始化
memset(h,-1,sizeof h);
idx = 0;
树与图的遍历
深度优先遍历
模板
int dfs(int u)
{
st[u] = true;
for(int i=h[u]; i!=-1; i=ne[i])
{
int j = e[i];
if(!st[j]) dfs(j);
}
}
题目:树的重心(找出树的重心,删除这个点得到的最大连通块最小)
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
const int N = 100010,M=2*N;
int h[N],e[M],ne[M],idx;
int n;
bool st[N];
int ans=N;
void add(int a,int b)
{
e[idx] = b; ne[idx] = h[a]; h[a] = idx++;
}
//以u为根结点的子树有多少个结点
int dfs(int u)
{
st[u] = true;
int res=0,sum=1;//res表示删除当前结点最大的连通块结点数,sum表示当前子数的结点数
for(int i=h[u]; i != -1; i=ne[i])
{
int j=e[i];
if(!st[j])
{
int s=dfs(j);
res=max(res,s); //子数也是一个连通块,找最大连通块数
sum+=s;
}
}
res=max(res,n-sum);
ans=min(ans,res);
return sum;
}
int main()
{
memset(h, -1, sizeof h);//初始化
cin>>n;
for(int i=0;i<n-1;i++)
{
int a,b;
cin>>a>>b;
add(a,b);add(b,a);
}
dfs(1);//从任意小于n的结点开始都行
cout<<ans<<endl;
return 0;
}
广度优先遍历
模板
void bfs()
{
queue<int> q;
q.push(1);
st[1]=true;
while(q.size())
{
int t=q.front();
q.pop();
for(int i=h[t];i!=-1;i=ne[i])
{
int j=e[i];
if(!st[j])
{
st[j]=true;
q.push(j);
}
}
}
}
题目:图中点的层次(输出从1号点到n号点的最短距离)
#include<cstring>
#include<iostream>
#include<algorithm>
#include<queue>
using namespace std;
const int N=100010,M=2*N;
int h[N],e[M],ne[M],idx;
int n,m;
int d[N];//每个结点i到1的最短距离
void add(int a,int b)
{
e[idx]=b;ne[idx]=h[a];h[a]=idx++;
}
int bfs()
{
memset(d,-1,sizeof d);
queue<int> q;
d[1]=0;
q.push(1);
while(q.size())
{
//取出队头
int t=q.front();
q.pop();
//用队头更新所有队头能到达的点的距离
for(int i=h[t];i!=-1;i=ne[i])
{
int j=e[i];
if(d[j]==-1)
{
d[j]=d[t]+1;
q.push(j);
}
}
}
return d[n];
}
int main()
{
memset(h,-1,sizeof h);
cin>>n>>m;
for(int i=0;i<m;i++)
{
int a,b;
cin>>a>>b;
add(a,b);
}
cout<<bfs()<<endl;
return 0;
}
拓扑排序
步骤:①找入度为0的点存入队列中。
②当队列不为空,取出队头,将所有队头能到达的点入度减1;
③入度减为零的点入队。
题目:有向图求拓扑结构
#include<iostream>
#include<algorithm>
#include<cstring>
#include<queue>
using namespace std;
const int N=10e+5;
const int M=2*N;
int h[N],e[M],ne[M],idx;
int n,m;
int d[N];//入度
int top[N],cnt;
queue<int> q;//队列中存储入度为0的点
void add(int a,int b)
{
e[idx]=b;ne[idx]=h[a];h[a]=idx++;
}
bool topsort()
{
for(int i=1;i<=n;i++)
if(!d[i]) q.push(i);
while(q.size())
{
auto t=q.front();
top[cnt++]=t;
q.pop();
for(int i=h[t];i!=-1;i=ne[i])
{
int j=e[i];
d[j]--;
if(!d[j]) q.push(j);
}
}
//如果都入队了,说明存在拓扑结构,否则不存在拓扑结构
return cnt==n;
}
int main()
{
cin>>n>>m;
int a,b;
memset(h,-1,sizeof h);
while(m--)
{
cin>>a>>b;
add(a,b);
d[b]++;
}
if(!topsort())
puts("-1");
else{
for(int i=0;i<n;i++) printf("%d ",top[i]);
}
return 0;
}
最短路问题
Dijkstra求最短路I(朴素版) O(n^2)
不能有负环,求单源最短路径,没有负权边,适合稠密图,时间复杂度O(n^2)
步骤:①初始化所有点,到1的距离为最大值。
②找到未入队的点的距离1最近的点t。
③用点更新其他所有点的距离。
题目:迪杰斯特拉求最短路
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
const int N=510;
int g[N][N],dist[N];
int n,m;
bool st[N];
int dijkstra()
{
//初始化所有点到1的距离为最大值
memset(dist,0x3f,sizeof dist);
dist[1]=0;
for(int i=0;i<n-1;i++)
{
int t=-1;
//找到没有入队的距离第一个点最近的点t
for(int j=1;j<=n;j++)
{
if(!st[j] && (t==-1||dist[t]>dist[j]))
t=j;
}
//用点t更新其他点
for(int j=1;j<=n;j++)
{
dist[j]=min(dist[j],g[t][j]+dist[t]);
}
st[t]=true;
}
if(dist[n]==0x3f3f3f3f) return -1;
else return dist[n];
}
int main()
{
cin>>n>>m;
//将所有点之间的距离初始化为最大值
memset(g,0x3f,sizeof g);
for(int i=0;i<m;i++)
{
int a,b,c;
cin>>a>>b>>c;
g[a][b]=min(g[a][b],c);
}
cout<<dijkstra()<<endl;
return 0;
}
Dijkstra求最短路II(堆优化版) O(mlogn)
单源最短路径,无负权边,适合稀疏图,时间复杂度O(mlogn)
优先队列保存的是当前未确定最小距离的点。
#include<cstring>
#include<iostream>
#include<algorithm>
#include<queue>
using namespace std;
const int N=1000010;
int h[N],e[N],ne[N],w[N],idx;
int n,m;
typedef pair<int,int> PII;
int dist[N];
bool st[N];
void add(int a,int b,int c)
{
e[idx]=b;ne[idx]=h[a];w[idx]=c;h[a]=idx++;
}
int Dijkstra()
{
priority_queue<PII,vector<PII>,greater<PII>> heap;
memset(dist,0x3f,sizeof dist);
dist[1]=0;
heap.push({0,1});
while(heap.size())
{
auto t=heap.top();
heap.pop();
int ver=t.second;int distance=t.first;
if(st[ver]) continue;
st[ver]=true;
for(int i=h[ver];i!=-1;i=ne[i])
{
int j=e[i];
if(dist[j]>dist[ver]+w[i])
{
dist[j]=dist[ver]+w[i];
heap.push({dist[j],j});
}
}
}
if(dist[n]==0x3f3f3f3f) return -1;
return dist[n];
}
int main()
{
memset(h,-1,sizeof h);
cin>>n>>m;
for(int i=0;i<m;i++)
{
int a,b,c;
cin>>a>>b>>c;
add(a,b,c);
}
cout<<Dijkstra()<<endl;
return 0;
}
Bellman-ford O(mn)
求单源最短路,有负权边,时间复杂度O(mn) (算法简单)
可以用结构体存储所有边的值合两端点。
算法步骤:
for n次(允许走的边数)
for 所有边 a,b,w (松弛操作)
dist[b] = min(dist[b],back[a] + w)
题目:有边数限制的最短路
#include <iostream>
#include <algorithm>
#include <cstring>
using namespace std;
const int N=510,M=100010;
int n,m,k;
int dist[N],last[N];
struct Edge
{
int a,b,c;
}edge[M];
void bellman_ford()
{
memset(dist,0x3f,sizeof dist);
dist[1]=0;
for(int i=0;i<k;i++)
{ //防止串联
memcpy(last,dist,sizeof dist);
for(int j=0;j<m;j++)
{
auto e=edge[j];
dist[e.b]=min(dist[e.b],last[e.a]+e.c);
}
}
}
int main()
{
cin>>n>>m>>k;
for(int i=0;i<m;i++)
{
int a,b,c;
cin>>a>>b>>c;
edge[i]={a,b,c};
}
bellman_ford();
if(dist[n]>0x3f3f3f3f/2) cout<<"impossible"<<endl;
else{
cout<<dist[n]<<endl;
}
return 0;
}
Spfa 一般是O(m),最差是O(mn)
1.求单源最短路,有负权边,时间复杂度O(mn),是Bellman_ford算法的优化。
Bellman_ford算法会遍历所有的边,但是有很多的边遍历了其实没有什么意义,我们只用遍历那些到源点距离变小的点所连接的边即可,只有当一个点的前驱结点更新了,该节点才会得到更新;因此考虑到这一点,我们将创建一个队列每一次加入距离被更新的结点。
Bellman_ford算法可以存在负权回路,是因为其循环的次数是有限制的因此最终不会发生死循环;但是SPFA算法不可以,由于用了队列来存储,只要发生了更新就会不断的入队,因此假如有负权回路请你不要用SPFA否则会死循环。
给定一个 𝑛个点 𝑚 条边的有向图,图中可能存在重边和自环, 边权可能为负数。但不存在负权回路。
题目:spfa求最短路
#include <iostream>
#include <algorithm>
#include <cstring>
#include<queue>
using namespace std;
typedef pair<int,int> PII;//到源点的距离,下标号
const int N=100010;
int n,m;
int h[N],e[N],ne[N],idx,w[N];
int dist[N];
bool st[N];
void add(int a,int b,int c)
{
e[idx]=b,ne[idx]=h[a],w[idx]=c;h[a]=idx++;
}
int spfa()
{
queue<PII> q;
memset(dist,0x3f,sizeof dist);
dist[1]=0;
q.push({0,1});
st[1]=true;
while(q.size())
{
auto x=q.front();
q.pop();
int t=x.second;
st[t]=false;
for(int i=h[t];i!=-1;i=ne[i])
{
int j=e[i];
if(dist[j]>dist[t]+w[i])
{
dist[j]=dist[t]+w[i];
if(!st[j])
{ st[j]=true;
q.push({dist[j],j});
}
}
}
}
return dist[n];
}
int main()
{
cin>>n>>m;
memset(h,-1,sizeof h);
for(int i=0;i<m;i++)
{
int a,b,c;
cin>>a>>b>>c;
add(a,b,c);
}
int t = spfa();
if (t == 0x3f3f3f3f) puts("impossible");
else printf("%d\n", t);
}
spfa判断负环
求负环一般使用SPFA算法,方法是用一个cnt数组记录每个点到源点的边数,一个点被更新一次就+1,一旦有点的边数达到了n那就证明存在了负环。
不用初始化dist,只更新有负环的dist.
#include <cstring>
#include <iostream>
#include <algorithm>
#include <queue>
using namespace std;
const int N = 2010, M = 10010;
int n, m;
int h[N], w[M], e[M], ne[M], idx;
int dist[N], cnt[N];
bool st[N];
void add(int a, int b, int c)
{
e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx ++ ;
}
bool spfa()
{
queue<int> q;
for (int i = 1; i <= n; i ++ )
{
st[i] = true;
q.push(i);
}
while (q.size())
{
int t = q.front();
q.pop();
st[t] = false;
for (int i = h[t]; i != -1; i = ne[i])
{
int j = e[i];
if (dist[j] > dist[t] + w[i])
{
dist[j] = dist[t] + w[i];
cnt[j] = cnt[t] + 1;
if (cnt[j] >= n) return true;
if (!st[j])
{
q.push(j);
st[j] = true;
}
}
}
}
return false;
}
int main()
{
scanf("%d%d", &n, &m);
memset(h, -1, sizeof h);
while (m -- )
{
int a, b, c;
scanf("%d%d%d", &a, &b, &c);
add(a, b, c);
}
if (spfa()) puts("Yes");
else puts("No");
return 0;
}
作者:yxc
链接:https://www.acwing.com/activity/content/code/content/48499/
来源:AcWing
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
Floyd(On^3)
求多源汇最短路
给定一个n个点m条边的有向图,图中可能存在重边和自环,边权可能为负数。
再给定k个询问,每个询问包含两个整数x和y,表示查询从点x到点y的最短距离,如果路径不存在,则输出“impossible”。
数据保证图中不存在负权回路。
#include <iostream>
#include <algorithm>
using namespace std;
const int N=210,M=2e5+10,INF=1e9;
int n,m,k,x,y,z;
int d[N][N];
void floyd()
{
for(int k=1;k<=n;k++)
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
d[i][j]=min(d[i][j],d[i][k]+d[k][j]);
}
int main()
{
cin>>n>>m>>k;
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
if(i==j) d[i][j]=0;
else d[i][j]=INF;
while(m--)
{
cin>>x>>y>>z;
d[x][y]=min(d[x][y],z);
}
floyd();
while(k--)
{
cin>>x>>y;
if(d[x][y]>INF/2) puts("impossible");
else cout<<d[x][y]<<endl;
}
return 0;
}
最小生成树
Prim算法
dist代表到集合的距离。每次寻找到集合距离最近的点,用这个点更新其他点到集合的距离
#include <iostream>
#include <algorithm>
#include <cstring>
using namespace std;
const int N=510,INF=0x3f3f3f3f;
int g[N][N],dist[N];
int n,m;
bool st[N];
int prim()
{
memset(dist,0x3f,sizeof dist);
dist[1]=0;
int res=0;
for(int i=0;i<n;i++)
{
int t=-1;//找距离集合最近的点
for(int j=1;j<=n;j++)
if(!st[j] && (t==-1 || dist[t]>dist[j]))
t=j;
if(dist[t]==INF) return 0x3f3f3f3f;//这种情况代表不连通
res+=dist[t];
st[t]=true;
for(int j=1;j<=n;j++)//更新其他点到集合的距离
dist[j]=min(dist[j],g[t][j]);
}
return res;
}
int main()
{
cin>>n>>m;
memset(g,0x3f,sizeof g);
while(m--)
{
int a,b,c;
cin>>a>>b>>c;
g[a][b]=g[b][a]=min(g[a][b],c);
}
int t=prim();
if(t==INF) puts("impossible");
else cout<<t<<endl;
return 0;
}
Kruskal (Omlogm)
用到了并查集
求最小生成树,算法步骤:
①将所有边按权重大小排序
②枚举每条边a b
if b和a不连通,将这条边加入集合中
#include <iostream>
#include <algorithm>
#include <cstring>
using namespace std;
const int N=100010,M=200010,INF=0x3f3f3f3f;
int n,m;
int p[N];
struct Edge
{
int a,b,w;
bool operator< (const Edge &W)const
{
return w<W.w;
}
}edges[M];
int find(int x)
{
if(p[x]!=x) p[x]=find(p[x]);
return p[x];
}
int kruskal()
{
sort(edges,edges+m);
for(int i=1;i<=n;i++) p[i]=i;
int res=0,cnt=0;
for(int i=0;i<m;i++)
{
int a=edges[i].a,b=edges[i].b,w=edges[i].w;
a=find(a),b=find(b);
if(a!=b)
{
p[a]=b;
res+=w;
cnt++;
}
}
if(cnt < n-1) return INF;
return res;
}
int main()
{
cin>>n>>m;
for(int i=0;i<m;i++)
{
int a,b,c;
cin>>a>>b>>c;
edges[i]={a,b,c};
}
int t=kruskal();
if(t==INF) puts("impossible");
else cout<<t<<endl;
return 0;
}
二分图
一个图是二分图,当且仅当图中不含有奇数环(奇数环是指环中边的数量是奇数)
染色法O(n+m)
一个图是二分图一定能被正确染色,不是二分图就不会被正确染色。
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
const int N=100010,M=200010;
int n,m;
int h[N],e[M],ne[M],idx;
int color[N];
void add(int a,int b)
{
e[idx]=b;ne[idx]=h[a];h[a]=idx++;
}
bool dfs(int u,int c)
{
color[u]=c;
for(int i=h[u];i!=-1;i=ne[i])
{
int j=e[i];
if(!color[j])
{
if(!dfs(j,3-c)) return false;
}
else if(color[j]==c) return false;
}
return true;
}
int main()
{
memset(h,-1,sizeof h);
cin>>n>>m;
while(m--)
{
int a,b;
cin>>a>>b;
add(a,b);add(b,a);
}
//染色法
bool flag=true;
//考虑到非连通图的情况,保证遍历到每一个点
for(int i=1;i<=n;i++)
{
if(!color[i])
{
if(!dfs(i,1))
{
flag=false;
break;
}
}
}
if(flag) puts("Yes");
else puts("No");
return 0;
}
匈牙利算法 O(nm) 实际时间复杂度远小于O(nm)
题目:二分图的最大匹配
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N=510,M=100010;
int n1,n2,m;
int match[N];
int h[N],e[M],ne[M],idx;
bool st[N];
void add(int a,int b)
{
e[idx]=b;ne[idx]=h[a],h[a]=idx++;
}
//能否成功匹配
bool find(int x)
{
for(int i=h[x];i!=-1;i=ne[i])
{
int j=e[i];
if(!st[j])
{
st[j]=true;
if(match[j]==0 || find(match[j]))
{
match[j]=x;
return true;
}
}
}
return false;
}
int main()
{
cin>>n1>>n2>>m;
memset(h,-1,sizeof h);
while(m--)
{
int a,b;
cin>>a>>b;
add(a,b);
}
int res=0;
//遍历所有左侧点
for(int i=0;i<=n1;i++)
{ //所有右侧点都没有访问过
memset(st,false,sizeof st);
if(find(i)) res++;
}
cout<<res<<endl;
return 0;
}