并查集和最小生成树
并查集
初始化\(O(n)\)
int fa[N], szp[N], sze[N], loop[N]; //fa根节点,szp点的数量,sze边的数量,loop自环的数量
int n, m; //n代表点数,m代表边数
void init() //初始化
{
for (int i = 1; i <= n; ++i)
fa[i] = i, szp[i] = 1, sze[i] = 0, loop[i] = 0;
}
查找:已实现路径压缩\(O(\alpha (n))\)
int find(int x)
{
return x == fa[x] ? x : fa[x] = find(fa[x]);
}
合并\(O(\alpha (n))\)
void merge(int x, int y)
{
int fx = find(x);
int fy = find(y);
sze[fy]++;
if (fx != fy)
{
fa[fx] = fy;
sze[fy] += sze[fx];
szp[fy] += szp[fx];
loop[fy] += loop[fx];
}
}
最小生成树(minimum spanning tree)
基础知识
图是一个二元组\(G(V(G),E(G)),V(G)\)代表点的集合,E(G)代表边的集合
对于V中的每个元素,我们可以称他为顶点或者节点
一个没有固定根结点的树称为 无根树(unrooted tree)。无根树有几种等价的形式化定义:
1.有n个结点,n-1条边 的连通无向图
2.无向无环的连通图
3.任意两个结点之间有且仅有一条简单路径的无向图
4.任何边均为桥的连通图
5.没有圈,且在任意不同两点间添加一条边之后所得图含唯一的一个圈的图
在无根树的基础上,指定一个结点称为 根,则形成一棵 有根树(rooted tree)。
生成树:一个连通无向图的生成子图,同时要求是树。也即在图的边集中选择n-1条,将所有顶点连通。
那么 最小生成树 就是边权和最小的生成树
注意:只有连通图才有生成树,而对于非连通图,只存在生成森林。
Kruskal算法:适合点多边少的稀疏图\(O(mlogm)\)
基本思想
本质为贪心算法,从边角度考虑问题,我们按照边权由小到大排序,然后我们进行加边。
怎么加边呢?我们首先利用并查集查找两个点是否在同一个集合中(也称一颗树),如果不在,那么他们一定属于两个不同的集合(相当于两个森林),然后我们使用并查集合并即可;如果两个点在同一集合中,我们就不需要操作了
array<int, 3> edge[N*100];
int num, ans;
void Kruskal()
{
sort(edge+1,edge+m+1);
num = 0,ans=0;
for (int i=1;i<=m;++i)
{
auto [w,u,v] = edge[i];
if (find(u)!=find(v))
{
ans+=w;
num++;
merge(u,v);
}
}
}
int main(void)
{
Zeoy;
int t = 1;
// cin >> t;
while (t--)
{
cin >> n >> m;
for (int i = 1, u, v, w; i <= m; ++i)
{
cin >> u >> v >> w;
edge[i] = {w, u, v};
}
}
return 0;
}
Prim算法:适合点少边多的稠密图\(O(n^2+m)\)
基本思想
从点角度考虑问题,利用邻接矩阵记录节点之间的权值,任选一个节点开始,然后找一个没有在集合中并且距离集合最近的(也就是到集合的权值最小)点加入集合,然后利用这个点去更新其他不在集合里的点到集合的距离,实际上把所有点分为两个集合,一个是已经加入最小生成树的集合,另一个是还未加入最小生成树的集合,重复以上步骤直到最小生成树集合中存在n-1条边
#include <bits/stdc++.h>
#define Zeoy std::ios::sync_with_stdio(false), std::cin.tie(0), std::cout.tie(0)
#define all(x) (x).begin(), (x).end()
#define endl '\n'
using namespace std;
typedef long long ll;
typedef pair<int, int> pii;
typedef pair<ll, ll> pll;
const int inf = 0x3f3f3f3f;
const ll INF = 0x3f3f3f3f3f3f3f3f;
const int mod = 1e9 + 7;
const double eps = 1e-9;
const int N = 1e5 + 10;
int n, m; // n代表点数,m代表边数
int num, ans;
const int inf = 0x3f3f3f3f;
int dis[N], vis[N]; //dis表示到最小生成树集合的最短距离,vis代表这个节点在不在最小生成树集合中
int g[N][N]; //邻接矩阵存图
void Prim()
{
for (int i = 1; i <= n; ++i)
{
dis[i] = inf; //初始化距离为inf
vis[i] = 0;
}
dis[1] = 0; //选第一个点加入集合
for (int i = 1; i <= n; ++i)
{
int x = 0;
for (int j = 1; j <= n; ++j)
{
if (!vis[j] && (x == 0 || dis[j] < dis[x])) //找到距离集合距离最小的点
x = j;
}
vis[x] = 1;
for (int j = 1; j <= n; ++j) //更新其他未在集合中的点到集合的距离
{
if (!vis[i])
{
dis[i] = min(dis[i], g[x][j]);
}
}
}
}
int main(void)
{
Zeoy;
int t = 1;
// cin >> t;
while (t--)
{
cin >> n >> m;
for (int i = 1; i <= n; ++i)
{
for (int j = 1; j <= n; ++j)
{
g[i][j] = inf;
g[i][i] = 0;
}
}
for (int i = 1, u, v, w; i <= m; ++i)
{
cin >> u >> v >> w;
g[u][v] = g[v][u] = min(g[u][v], w);
}
ans = 0;
for (int i = 1; i <= n; ++i)
{
if (dis[i] == inf) //生成不了生成树
break;
else
ans += dis[i];
}
cout << ans << endl;
}
return 0;
}