1.最小生成树
1.1普利姆算法(Prim)
1.1.1朴素版的普利姆算法(Prim) O(n^2)—稠密图
例题:
图解:
代码
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
const int N = 510,INF = 0x3f3f3f3f;
int n,m;
int g[N][N];//存储图
int dist[N];//存储各个节点到生成树的距离
bool st[N];//节点是否被加入到生成树中
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];
//不是第一个点,
//那么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,0x3f,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;
}
Prim算法
dist[i]
距离设置为无穷大
dist[1]=0
for i in 0..n-1
- 找到不在
s
集合中,距离s
集合最近的点t
- 将这个点
t
放入集合中 - 利用这个点
t
, 更新不在集合中的点
Prim算法与Dijkstra算法的区别
Dijkstra算法是更新不在集合中的点 离起点
的距离
dist[j]=min(dist[j], dist[t]+g[t][j])
Prim是更新不在集合中的点 离集合S
的距离
dist[j] = min(dist[j], g[t][j])
1.1.2堆优化版的普利姆算法(Prim)O(mlogn) —稀疏图
1.2克鲁斯卡尔算法(Kruskal)O(mlogm)
Kruskal算法 (解决稀疏图) O(mlog(m))O(mlog(m))
- 将所有边按照权重从小到大排序 O(mlog(m))O(mlog(m))
- 枚举每条边(a, b, 权重c) O(m)O(m)
if a, b 两点不连通
将a, b边加入集合中
第二步与 连通块中点的数量 相似
使用并查集,查询两个结点是否属于一个集合, 合并两个结点
例题
代码
#include<iostream>
#include<algorithm>
using namespace std;
const int N = 200010;
int n,m;
int p[N];//p是并查集里面的p数组
//结构体存储所有边
struct Edge
{
int a,b,w;
//重载小于好,方便排序
bool operator< (const Edge &W)const
{
return w < W.w;
}
}edges[N];
//并查集模板
int find(int x)
{
//如果x不是祖宗结点
if(p[x] != x) p[x] = find(p[x]);
return p[x];
}
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;
//从小到大枚举所以边
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存储最小生成树中所有树边的权重之和
res += w;
cnt++;//cnt存储当前加入多少条边
}
}
//判断是否连通
if(cnt < n - 1) puts("impossible");
else printf("%d\n",res);
return 0;
}
2.二分图
2.1染色法 O(n + m)
划分为两个集合,集合内部没有边。
代码
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 100010 * 2;
int e[N], ne[N], idx;//邻接表存储图
int h[N];
int color[N];//保存各个点的颜色,0 未染色,1 是红色,2 是黑色
int n, m;//点和边
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;//u的点成 c 染色
//遍历和 u 相邻的点
for(int i = h[u]; i!= -1; i = ne[i])
{
int b = e[i];
if(!color[b])//相邻的点没有颜色,则递归处理这个相邻点
{
if(!dfs(b, 3 - c)) return false;//(3 - 1 = 2, 如果 u 的颜色是2,则和 u 相邻的染成 1)
//(3 - 2 = 1, 如果 u 的颜色是1,则和 u 相邻的染成 2)
}
else if(color[b] && color[b] != 3 - c)//如果已经染色,判断颜色是否为 3 - c
{
return false;//如果不是,说明冲突,返回
}
}
return true;
}
int main()
{
memset(h, -1, sizeof h);//初始化邻接表
cin >> n >> m;
for(int i = 1; i <= m; i++)//读入边
{
int a, b;
cin >> a >> b;
add(a, b), add(b, a);
}
for(int i = 1; i <= n; i++)//遍历点
{
if(!color[i])//如果没染色
{
if(!dfs(i, 1))//染色该点,并递归处理和它相邻的点
{
cout << "No" << endl;//出现矛盾,输出NO
return 0;
}
}
}
cout << "Yes" << endl;//全部染色完成,没有矛盾,输出YES
return 0;
}
2.2匈牙利算法 O(nm),实际运行时间一般远小于O(nm)
最多确定多少对恋爱关系(关系非常正的那样)
匈牙利算法准则:待字闺中,据为己有;名花有主,求他放手。
例题:
代码实现:
#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
const int N = 510 , M = 100010;
int n1,n2,m; // n1男生,n2女生,m心仪线路
int h[N],ne[M],e[M],idx;
bool st[N]; // 作用:判重.男生(1-n1)找对象的时候是否已经考虑过这个女生
int match[N]; // 右边指向左边,match[j]=a,表示女孩j的现男友是a
void add(int a , int b)
{
e[idx] = b, ne[idx] = h[a], h[a] = idx++;
}
// x来配对,能否找到另一半
bool find(int x)
{
// 遍历所有心仪女生
for(int i = h[x] ; i != -1 ;i = ne[i])
{
int j = e[i]; // 获取我心仪女生编号都是啥(大数据系统main那里帮add的)
if(!st[j]) // 这个女生我还没考虑过(考虑过就不要考虑了)
{
st[j] = true; // 那就考虑一下她
if(!match[j]||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 ++) // 帮n1个男生找对象
{
memset(st,false,sizeof st); // 保证每个女生被所有心仪她的男生都考虑一遍
if(find(i))
res++; // 最多可以确定多少对恋爱关系
}
printf("%d\n",res);
return 0;
}