最近又把图论的所有算法重新复习一遍,因为这里的算法模版都比较复杂,所以现在重新整理一遍。
在图论中一共有一下这几种问题:
一、最短路径问题
1.没有负权边
在没有负权边的情况下,我们就使用Dijkstra算法,如果是稠密图,我们就使用矩阵来存储边,如果是稀疏图,我们就是用邻接表来存储图。
最经典的Dijkstra算法:
int Dijkstra(){
memset(dist, 0x3f, sizeof dist);
dist[1] = 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;
}
for(int j = 1; j <= n; j ++){
dist[j] = min(dist[j], dist[t] + g[t][j];
}
st[t] = true;
}
if(dist[n] == 0x3f3f3f3f) return -1;
return dist[n];
}
2.有负权边(但没有负权回路)
一种比较简单的就是Bellman_ford算法,该算法非常暴力,直接使用两次循环。这个算法有两个优点:1.他可以做哪些,给你具体了一条路径下只能有几条边的限制。2.该算法实现起来非常简单,对于边的存储,只需要使用结构体就可以。缺点就是:1.时间复杂度太高了。
假设我们现在已经使用dist[a]
来更新 dist[b]
如果我们还要使用 dist[b]
来更新 dist[c]
就相当于本次遍历了两条边。而Bellman-Ford算法每一次只允许遍历一条边。这就违反了算法的定义;所以要memcpy(backup, dist, sizeof dist);
来拷贝上一次的数组。这样就是为了保证每一次,只遍历一个点。
核心代码如下:
int bellman_ford(){
memset(dist, 0x3f, sizeof dist);
dist[1] = 0;
for(int i = 0; i < n; i ++ ){
memcpy(backup, dist ,sizeof dist);
for(int i = 0; i < n; i ++){
int a = edges[i].a, b = edges[i].b, w = edges[i].w;
dist[b] = min(dist[b], dist[a] + w);
}
}
if(dist[n] > 0x3f3f3f3f / 2) return -1;
return dist[n];
}
第二种就是spfa算法,该算法是对Bellman_ford算法的一种优化,该算法使用了一个 queue和一个bool数组,队列用来存储每一次跟新过的点,而bool数组用来判断更新的点是否在队列中如果在队列中,就不用重复地加入到队列,这样可以降低时间复杂度。
核心代码如下:
int spfa(){
memset(dist, 0x3f, sizeof dist);
dist[1] = 0;
st[1] = true;
queue<int> q;
q.push(1);
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];
}
if(!st[j]){
q.push(j);
st[j] = true;
}
}
}
if(dist[n] == 0x3f3f3f3f) return -1;
return dist[n];
}
3.最后还有一个非常特殊的算法就是Floyd算法(多元汇最短路)
该算法的思想就是把每一个点都当做中转点来遍历一遍,具体代码如下
这是自己以前写过的博客,就懒得在抄一遍了。
AcWing 854. Floyd求最短路 - AcWing
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]);
}
}
}
}
二、最小生成树问题
1.稠密图
使用Prim算法
int Prim(){
//先初始化为 `正无穷`
memset(dist, 0x3f, sizeof dist);
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(i && dist[t] == INF) return INF;
//如果是第一次迭代,就不进行此操作
if(i) res += dist[t];
//把当前选中的点 t 加入到集合 S 中
st[t] = true;//始终从集合 S 中选择
//更新其他边到集合 S 的距离
printf("开始更新 dist 数组 第 %d 次\n" , (i + 1));
for(int j = 1; j <= n; j ++){
dist[j] = min(dist[j], g[t][j]);
}
}
return res;
}
2.稀疏图
使用Kruskal算法
#include <iostream>
#include <algorithm>
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(){
scanf("%d%d", &n, &m);
for(int i = 0; i < m; i ++){
int a, b, w;
scanf("%d%d%d", &a, &b, &w);
edges[i] = {a, b, w};
}
int t = kruskal();
if(t == INF) puts("impossible");
else printf("%d\n", t);
return 0;
}
三、二分图
二分图经典算法,染色法,染色法就是一个简单的DFS。话不多说
代码献上
#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(){
scanf("%d%d", &n, &m);
memset(h, -1, sizeof h);
while(m --){
int a, b;
scanf("%d%d", &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");
}
二分图的最大匹配算法:
代码献上:
#include <iostream>
#include <algorithm>
#include <cstring>
using namespace std;
const int N = 510, M = 100010;
int n1, n2, m;
int h[N], e[M], ne[M], idx;
int match[N];
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(){
scanf("%d%d%d", &n1, &n2, &m);
memset(h, -1, sizeof h);
while(m -- ){
int a, b;
scanf("%d%d", &a, &b);
add(a, b);
}
int res = 0;
for(int i = 1; i <= n1; i ++){
memset(st, false, sizeof st);
if(find(i)) res ++;
}
printf("%d\n", res);
return 0;
}