数据结构学习记录——并查集

        并查集是一种树形数据结构,经常用于处理一些集合之间的操作,例如元素查找、集合合并等等。

        不同集合在并查集中以不同的树来表示,一般每棵树的根节点会作为当前集合的代表元。

        查询两个元素是不是同一个集合里,只需要比较两个元素所在集合的代表元素。

并查集的初始化

假设一开始有n个元素,这些元素初始都是独立的。显然他们构成了n个集合,每个集合的代表元就是这些元素自己。

const int maxn =100010;

int fa[maxn + 1];//fa数组记录每个元素由谁代替
int sz[maxn + 1];//sz数组记录每个集合的元素个数
int dep[maxn + 1];//dep数组记录每个集合的树深度

void initial(int n){
    for(int i = 1;i <= n; i++){
        fa[i] = i;
        sz[i] = dep[i] = 1;
    }
}

集合合并

如果我们要将两个元素x, y所在的集合合并。先找到x, y 对应的代表元(fa等于自己的元素)。将其中一个代表元的fa指向另外一个。

int findset(x){
    if(fa[x] == x)
        return x;
    return findset(fa[x]);
}

void Union(int x, int y){
    int fx = findset(x), fy = findset(y);
    if(fx == fy) return;
    fa[fx] = fy;
}

路径压缩

我们可以缩短并查集中的路径,具体做法就是在查询的过程中,把沿途的每个节点的fa都设为集合代表元。

int findset(int x){
    if(fa[x] == x)
        return x;
    fa[x] = findset(fa[x]);
    return fa[x];
}

//可以简写为

int findset(int x){
    return x == fa[x] ? x : (fa[x] = findset(fa[x]));
}

*启发式合并

在合并集合的时候,我们尽量选择包含元素个数少的集合,将他合并到另一个集合中,使需要改变代表元的元素数量尽可能少。将较小的集合合并到较大的集合中称为启发式合并。

void Union(int x, int y){
    int fx = findset(x), fy = findset(y);
    if(fx == fy) return;
    if(sz[fx] > sz[fy])
        swap(fx, fy);
    fa[fx] = fy;
    sz[fy] += sz[fx];
}

*按深度合并

在每次合并的时候将深度较小的集合并到深度较大的一方,并更新一下新集合的深度。

在路径压缩的时候,有可能会破坏我们维护的深度值,但算法总体复杂度不会变差。

void Union(int x, int y){
    int fx = findset(x), fy = findset(y);
    if(fx == fy) return;
    if(def[fx] > def[fy])
        swap(fx, fy);
    fa[fx] = fy;
    if(def[fx] == def[fy])//只有两颗树深度相等时才会更新
        dep[fy]++;
}

*时间复杂度

易证使用启发式合并,当有n个元素和m次查询时,并查集的时间复杂度为O(m log n)。

一般我们认为并查集的时间复杂度为O(mα(m,n))。其中α是阿克曼函数的反函数,一个很小的常数

例题

1.修路

有 n 个城市,城市的编号为 1 到 n。城市之间已经修好了 m 条道路,第 i 条道路连接了第 xi 和第 yi个城市。请问最少再修几条道路,可以使得所有城市都连通(也就是说,我们可以通过道路从任意一个城市到另一个城市)?

输入格式

第一行两个整数 n,m,代表城市数量和已修道路数。接下来 m行,每行两个整数 x,y,表示一条连接第 x 个和第 y个城市的道路。

输出格式

输出一行一个数表示答案。

数据规模

对于所有数据,保证 1≤n,m≤100000,1≤x,y≤n,x≠y。

#include<bits/stdc++.h>

using namespace std;

int n, m, fa[100001];

int findset(int x){
    if(x == fa[x])
        return x;
    fa[x] = findset(fa[x]);
    return fa[x];
}

int main(){
    scanf("%d%d", &n, &m);
    for(int i = 1; i <= n; i++)
        fa[i] = i;
    for(int i = 1; i <= m; i++){
        int x, y;
        scanf("%d%d", &x, &y);
        int fx = findset(x), fy = findset(y);
        if(fx == fy)
            continue;
        fa[fx] = fy;
    }
    int cnt = 0;
    for(int i = 1; i<= n; i++)
        if(fa[i] == i)
            ++cnt;
    printf("%d", cnt - 1);
}

2.行进路线

小蜗在玩一个游戏,游戏里有一张平面地图,他需要从坐标 (0,0) 点移动到坐标 (xe,ye)点。基于游戏设定,整张地图都十分危险,小蜗不能踏入其中。幸运的是,地图中存在 n个安全区域,第 i 个安全区域是由圆心在坐标 (xi,yi) 点,效果半径为 ri 的圆形信标展开形成的,安全区域可以相互重叠。特别的,除了上述 n 个安全区域,还存在一个圆心坐标为 (0,0) 点,效果半径为 1的安全区域。小蜗只能在安全区域中移动,如果两个安全区域相交或相切,小蜗可以从其中一个安全区域走到另一个安全区域。请问小蜗能不能顺利到达终点?能的话输出 1,不能输出 0

输入格式

输入包含多组测试数据。第一行一个整数 T,表示数据组数。

对于每组数据,第一行两个整数 xe,ye,表示终点坐标。

接下来一行一个整数 n,表示安全区域的总数(不包括特别的那个安全区域)。

接下来 n行,每行三个整数 xi,yi,ri,表示一个安全区域。

输出格式

对于每组数据,输出一行一个数表示答案。

数据规模

对于所有数据,保证 1≤T≤10,1≤n≤1000,−108≤xi,yi,xe,ye≤108,1≤ri≤108。

#include<bits/stdc++.h>

using namespace std;

int n, cnt, ans, dist[100001], f[100001], pre[100001];
vector<int>edges[100001];

inline void dfs(int x){
    for(auto y : edges[x])
        if(y != pre[x]){
            pre[y] =x;
            dist[y] = dist[x] + 1;
            dfs(y);
        }
}

inline void solve(int x){
    ++cnt;
    for(auto y : edges[x])
        if(y != pre[x]){
            pre[y] = x;
            solve(y);    
        }
}

int main(){
    scanf("%d", &n);
    for(int i = 1; i <=n; i++){
        int x, y;
        scanf("%d%d", &x, &y);
        edges[x].push_back(y);
        edges[y].push_back(x);
    }
    for(int i = 1; i <= n; i++){
        f[i] = 0;
        memset(pre, 0, sizeof(pre));
        for(auto y : edges[i]){
            cnt = 0;
            pre[y] = i;
            solve(y);
            f[i] = max(f[i], cnt);
        }
    }

    int idx = 0, v = 1 << 30;
    for(int i = 1; i <= n; i++)
        if(f[i] < v){
            v = f[i];
            idx = i;
        }

    memset(dist, 0, sizeof(dist));
    memset(pre, 0, sizeof(pre));
    pre[idx] = -1;
    dfs(idx);
    ans = 0;
    for(int i = 1; i <= n; i++)
       ans += dist[i];
    printf("%d\n", ans);
}

1LL的使用

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值