基础图论
树与图的存储和遍历
树与图的 存储
因为树是无环连通图,所以可以只看图。而图分为无向图和有向图,无向图每条边可以表示为两条有向边,所以可以只看有向图。
1.邻接表
用多个单链表构成的邻接表存储这个点指向的边(即出度),多用于稀疏图(n== m)。
1 2 3 4 5 6 7 8 9 10
| const int N=100010,M=N*2; int h[N],e[M],ne[M],idx; void add(int a,int b) { e[idx]=b;ne[idx]=h[a],h[a]=idx++; } int main() { memset(h,-1,sizeof h); }
|
2.邻接矩阵
用一个二维数组存储点边的关系,多用于稠密图(n == m^2)。
g[a] [b]指从a指向b的边,为0即为无边,非0即为有边(值可以存成权值等)
缺点:不能存重边。
树与图的 遍历
DFS
同普通DFS
AcWing 846. 树的重心
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41
| #include<bits/stdc++.h> using namespace std; const int N=100010,M=N*2; int h[N],e[M],ne[M],idx; int ans=N,st[N]; int n; void add(int a,int b) { e[idx]=b;ne[idx]=h[a];h[a]=idx++; } int dfs(int u) { st[u]=1; int sum=1,res=0; 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() { cin>>n; memset(h,-1,sizeof h); for(int i=1;i<n;i++) { int a,b;cin>>a>>b; add(a,b);add(b,a); } dfs(1); cout<<ans; return 0; }
|
BFS
同普通BFS
AcWing 847. 图中点的层次
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44
| #include<bits/stdc++.h> using namespace std; const int N=100010,M=N*2; int h[N],e[M],ne[M],idx; int d[N]; int n,m;
void add(int a,int b) { e[idx]=b;ne[idx]=h[a];h[a]=idx++; }
int bfs() { queue<int> q; q.push(1); memset(d,-1,sizeof d); d[1]=0; 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)continue; d[j]=d[t]+1; q.push(j); } } return d[n]; } int main() { cin>>n>>m; memset(h,-1,sizeof h); for(int i=1;i<=m;i++) { int a,b;cin>>a>>b; add(a,b); } cout<<bfs(); return 0; }
|
常用算法
拓扑排序
有向无环图。(bfs)
做法
类比多元bfs
- 将所有入度为0的点放入queue。
- 遍历queue每个点,将点所有出度对应的点入读-1。
- 将入读为0的点再放到queue中。
- 最后判断所有的点入度是否都是0。
AcWing 848. 有向图的拓扑序列
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46
| #include<bits/stdc++.h> using namespace std; const int N=100010; int h[N],e[N],ne[N],idx; int d[N],c[N],num; int n,m; queue<int> q; void add(int a,int b) { e[idx]=b;ne[idx]=h[a];h[a]=idx++; } void bfs() { while(q.size()) { int t=q.front(); q.pop(); c[++num]=t; for(int i=h[t];i!=-1;i=ne[i]) { int j=e[i]; d[j]--; if(!d[j])q.push(j); } } } int main() { cin>>n>>m; for(int i=1;i<=n;i++)h[i]=-1; for(int i=1;i<=m;i++) { int a,b;cin>>a>>b; add(a,b); d[b]++; } for(int i=1;i<=n;i++)if(!d[i])q.push(i); bfs(); if(num!=n)cout<<"-1"; else { for(int i=1;i<=n;i++)cout<<c[i]<<' '; } }
|
最短路
详见最短路。
总结
image-20230412204030858
Dijkstra(朴素)
思路:
- n次循环,每次找到点集外最近的点
- 更新所有点最近距离
Dijkstra(堆优化)
思路:
- n次循环,每次找到点集外最近的点(小根堆实现)
- 更新所有点最近距离
bellman-ford算法
思路:
- n次循环,循环所有边。
- 循环所有边时更新所有边
spfa算法
思路:
1 2 3 4 5 6 7 8 9
| queue <- 初点
while(queue不空) t <- q.front(); q.pop(); // 更新t的所有出边 t -w-> b (如果b变小了)queue <- b
|
Floyd
思路
1 2 3 4
| for(k 1~n) for(i 1~n) for(j 1~n) g[i][j] = min(g[i][j], g[i][k] + g[k][j]);
|
最小生成树
Prim
Kruskal
其它
染色法判定二分图
匈牙利算法