算法学习13:最小生成树 && 二分图(待完成)
文章目录
前言
提示:以下是本篇文章正文内容:
一、最小生成树
1. 普利姆算法(Prim)(朴素版)
给定一个n个点,m条边的“无向图”,可能存在重边和自环,边权可能是负数。
求最小生成树的“树边权重之和”,如果不存在则输出impossible。
1 <= n <= 500, 1 <= m <= 10^5,图中边权的绝对值不超过10000
(稠密图,邻接矩阵)
// 1 <= n <= 500, 1 <= m <= 10^5,
// (稠密图,邻接矩阵)
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 510, INF = 0x3f3f3f;
int n, m;
int g[N][N];// 邻接矩阵
int dist[N];// 这个点到集合的距离
bool st[N];
int prim()
{
// 设置到集合的距离全部为 正无穷
memset(dist, 0x3f3f3f, 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;
// 如果i不为0,且t到集合的距离为+无穷,那么不存在最小生成树
if(i && dist[t] == INF) return INF;
if(i) res += dist[t];
for(int j = 1; j <= n; j ++)
dist[j] = min(dist[j], g[t][j]);
st[t] = true;
}
return res;
}
int main()
{
scanf("%d%d", &n, &m);
memset(g, 0x3f3f3f, sizeof g);
while(m --)
{
int a, b, c;
scanf("%d%d%d", &a, &b, &c);
// 无向图,重边,自环处理
g[a][b] = g[b][a] = min(g[a][b], c);
}
int t = prim();
if(t == INF) puts("impossible");
else printf("%d\n", t);
return 0;
}
2. 克鲁斯卡尔算法(Kruskal)
/*
给定一个边带权的无向图G=(V, E),其中V表示图中点的集合,E表示图中边的集合,n=|V|, m=|E|。
由V中的全部n个顶点和E中n-1条边构成的无相连通子图被称为G的一颗生成树,其中边的权值之和最小的生成树被称为无向图G的最小生成树。
输入:
第一行包含2个整数n和m
接下来m行,每行包含3个整数u,v,w,表示点u和点v之间存在一条权值为w的边。
输出:若存在最小生成树,输出它的边权重之和,否则输出impossible。
数据范围:
1<= n <= 10^5, 1<= m <= 2*10^5
图中涉及的边权的绝对值,均不超过1000.
*/
#include <iostream>
#include <algorithm>
#include <cstring>
using namespace std;
const int N = 200010;
int n, m;
int p[N];// 存储每个元素父结点(最上面的)
// 并查集:返回x的祖宗结点 + 路径优化(递归)
int find(int x)
{
if(p[x] != x) p[x] = find(p[x]);
return p[x];
}
// 按照权重从小到大排序(升序)
struct Edge
{
int a, b, w;
bool operator< (const Edge &W)const
{
return w < W.w;
}
}edges[N];
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};
}
// 按边的权重从小到大排序:
sort(edges, edges + m);
for(int i = 1; i <= n; i ++) p[i] = i;//
int res = 0, cnt = 0;// res:边权值之和, cnt:加入最小生成树的边的数量
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)// 如果a,b不属于同一个结合
{
p[a] = b;// 合并
res += w;
cnt ++;
}
}
if(cnt < n - 1) puts("impossible");
else printf("%d\n", res);
return 0;
}
二、二分图:不存在“奇数环”(就是这个环有奇数条边)
1. 染色法:
/*
给定一个n个点m条边的无向图,图中可能存在重边和自环
请你判断这个图是不是二分图
输入:
第一行包含2个整数n和m
接下来m行,每行包含2个整数u和v,表示点u和点v之间存在一条边
输出:
如果给定的是二分图,输出“Yes”,否则输出“No”
数据范围:1<= n, m <= 10^5
*/
#include <iostream>
#include <algorithm>
#include <cstring>
using namespace std;
const int N = 100010, M = 200010;
int n, m;
int h[N], e[M], ne[M], idx;
int color[N]; // 染色数组:0未染色,1黑色,2白色
// 插入边(邻接表)
void add(int a, int b)
{
e[idx] = b, ne[idx] = h[a], h[a] = idx ++;
}
// u:当前点,c:颜色
bool dfs(int u, int c)
{
color[u] = c;// 染色:相当于“之前”dfs中的状态数组
// 遍历邻接顶点
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);// 由于是无向图,所以每条边需要插入2次
}
bool flag = true;// 判断标志
for(int i = 1; i <= n; i ++)
if(!color[i])// 未被染色
{
if(!dfs(i, 1))// dfs + 染色
{
// 如果返回结果为false
flag = false;
break;
}
}
if(flag) puts("Yes");
else puts("No");
return 0;
}
2. 匈牙利算法:
/*
给定一个二分图,其中左半部分包含n1个点,右半部分包含n2个点,二分图共包含m条边。
数据保证任意一条边的两个端点都不可能在同一部分。
请你求出二分图的最大匹配数。
给定一个二分图G,在G的一个子图M中,M的边集{E}中的任意2条边都不依附于同一个顶点,则称M是一个匹配。(人话就是:左右配对,而且只能配对一次)
多有匹配中包含边数最多的一组匹配是二分图的最大匹配,其边数为最大匹配数。
输入:
第一行包含3个整数n1、n2、m
接下来m行,每行包含2个整数u和v,表示左半部点集中的点u和右半部点集中的点v之间存在一条边
输出:二分图的最大匹配数
数据范围:
1<= n1, n2 <= 500
1<= u <= n1 1<= v <= n2
1 <= m <= 10^5
*/
#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 -1);// 将邻接表头指针置为-1(null)
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;
}
总结
提示:这里对文章进行总结:
💕💕💕