并查集和最小生成树

并查集

初始化\(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;
}
posted @ 2023-01-08 20:37  Zeoy_kkk  阅读(42)  评论(0编辑  收藏  举报