并查集的使用及例题记录(P1536 & P1551)

本文详细介绍了并查集的概念、图示、算法实现,包括issameset和union方法,以及在P1536村村通和P1551亲戚问题中的应用。通过实例演示了如何利用并查集判断元素归属和合并集合,以及如何解决实际道路连通问题。
摘要由CSDN通过智能技术生成

一、并查集的概述

1. 概念相关

(1) 并查集的引入

问题

有若干个集合,{a},{b},{c},{d},{e}
设计两个方法

  1. 判断两个元素是否在同一个集合中 issameset(a, b)
  2. 将a,b元素所在集合进行合并 union(a, b)

解决方案

给每个集合确定一个代表元素,向上指向某个元素
判断两个元素是否在同一个集合中,就是判断两个元素所在集合的代表元素是否相同
若要合并a,b两个集合的元素,先判断两个元素是否属于一个集合(调用issameset方法),
a,b不属于同一个集合,进行合并操作, 将 b往上指的指针变成a ;找到某个集合的顶(代表元素)把它挂载另外一个元素的顶上
采用往上指的图的方式来表示集合结构

(2) 图示说明

在这里插入图片描述

2. 并查集的板子

//并查集结构,及方法
#include <iostream>
#include <cstring>


using namespace std;

const int maxn = 100;

int par[100]; //父亲  par[x]=x时,x是所在的树的根
int rank[100]; //树的高度

//初始化n个元素
void init(int n)
{
    for(int i=0;i<n;i++)
    {
        par[i] = i;
        rank[i] = 0;
    }
}

//查询树的根
int find(int x)
{
    if(par[x]==x){
        return x;
    }else {
        return par[x] = find(par[x]);  //这种方式,可以将路径进行压缩
    }
}

//合并x和y所属的集合
void unite(int x, int y)
{
    //找到两个元素的根元素
    x = find(x);
    y = find(y);
    if(x==y) return;  //在同一个根上, 属于同一个集合,直接返回

    if(rank[x] < rank[y])  //判断哪个元素所在的树比较高
    {
        par[x] = y;  //x的根变为y
    }
    else
    {
        par[y] = x;  //y的根变成x
        if(rank[x]==rank[y]) rank[x]++;   //两棵树原来就具有相等的高度,将一棵树挂载到另一棵树上,高度需要加1 
    }

}

//判断a,b元素是否属于同一集合
bool same(int x, int y)
{
    return find(x) == find(y);
}

二、并查集的应用

(一)P1536 村村通题解

题目链接

1. 题目的算法考查

并查集

2. 程序设计思想

  • 两个数组 par:用来保存每个村庄所在树的根节点;H:H[i]表示,以i为根节点的树的高度,在合并时使用,将高度小的数合并到高度大的树上面。
  • 将每个村看成一个树的节点,初始化时,每个村子都是一个独立的树,它们的根节点就是它们本身;
  • 根据给出的道路关系,将村庄进行连接,怎样说明两个村子连接呢?让它们的根节点变成一样的即可,用union函数实现。

举例:用数组par保存的是每个村子所在树的根节点,在最开始,编号为1的村子,par[1]=1, 编号为2的村子,par[2]=2. 假设题目给出的道路中,1号村子和2号村子有一条路存在,那么就将2号村子所在树挂载到1号树上,让2号村子的根节点变为1,即合并后par[2]=1。此时1号村子和2号村子的根节点都是1,则它们是相同的。

  • 最后要统计哪些村子没有相连,那什么情况下所有的村子均相连呢?即,所有村子的根节点相同即可。设一个统计变量ret,用来记录还需要修建几条路,遍历每一个村子,检查它和其他村子的根节点是否相同(注意:假设在查看1号村庄时已经对1号村庄和2号村庄之间的关系确定过了,在查看2号村庄与其他村庄时就不必再管1号村庄和2号村庄的关系),如果不同,ret+1,并用unite函数将两个村庄连接。最终ret统计的值,就是还需要修建的道路的数目。

3. 代码

#include <iostream>
#include <cstring>
//https://www.luogu.com.cn/problem/P1536
using namespace std;

const int maxn=10005;
int n, m;  //城镇数目n和道路数目m
int par[maxn]; //用来保存每个城镇所在树的根节点
int H[maxn]; //用来保存树的高度

void init(int n) //初始化
{
    for(int i=1;i<=n;i++)
    {
        par[i] = i;
        H[i] = 1;
    }
}
/*
a可以到达 b,c  那么 a,b,c三个地方的根节点是相同的
*/
int find(int a) //寻找根节点
{
    if(par[a] == a)
        return a;
    else
        return par[a] = find(par[a]);
}

void unite(int a,int b) //对a,b所在集合进行合并   
{
    a = find(a);
    b = find(b);

    if(a==b)
        return;

    if(H[a]>=H[b])
    {
        par[b] = a;
        if(H[a]==H[b]) H[a]++;
    }
    else
        par[a] = b;

}

bool isReach(int a, int b) //判断a,b两个村镇是否可以到达
{
    return find(a) == find(b);
}

void solve()
{
    //接收道路的关系
    int a, b;
    //输入道路相连的数据
    for(int i=0;i<m;i++)
    {
        cin >> a >> b;
        unite(a,b);   //将a和b进行合并
    }
    //判断还需要几条路才能相连
    //遍历每一个村镇
    int ret=0;
    for(int i=1;i<=n;i++)
    {
        for(int j=i+1;i<=n;i++)
        {
            if(!isReach(i,j))  //两个村镇没有相连,则需要一条道路
            {
                ret++;
                unite(i,j);
            }

        }
    }

    cout << ret << endl;

}
int main()
{
    while(true)
    {
        cin >> n;
        if(n==0)
            break;
        cin >> m;
        memset(par,0,sizeof(par));
        memset(H, 0, sizeof(par));
        init(n);  //初始化
        //输入两个数
        solve();
    }
    return 0;
}

(二)P1551亲戚

题目链接

1. 代码


#include <iostream>
#include <cstring>
using namespace std;

const int maxn = 5005;
int par[maxn]; //根节点 par[i]=2  第i个元素的根节点为2
int treeHigh[maxn]; //表示树的高度 treeHigh[i] 表示以i为根节点的树的高度

//初始化 n 个元素
void init(int n)
{
    for(int i=1;i<=n;i++)
    {
        par[i] = i;
        treeHigh[i] = 1;
    }
}
//查询树根
int find(int x)
{
    if(par[x]==x)
        return x;
    else
    {
        par[x] = find(par[x]);
        return par[x];
    }
}

//合并
void unite(int x, int y)
{
    //先找到x,y元素的根节点
    x = find(x);
    y = find(y);
    if(x == y) return;
    if(treeHigh[x] >= treeHigh[y])
    {
        par[y] = x;  //进行合并,将y的根变成x的根
        if(treeHigh[x] == treeHigh[y]) treeHigh[x]++;
    }
    else
    {
        par[x] = y; //x的根节点变为y
    }
}
bool isSameSet(int x, int y)
{
    return find(x) == find(y);
}
int main()
{
    int n, m, p;
    int a, b;
    cin >> n >> m >> p;
    init(n);  //对这n个人进行初始化
    //输入m个关系
    for (int i = 0; i < m; i++)
    {
        cin >> a >> b;
        //对a, b进行合并
        unite(a, b);  //亲戚合并
    }
    
    for(int j=0;j<p;j++)
    {
        cin >> a >> b;
        if(isSameSet(a,b))
            cout << "Yes" << endl;
        else
            cout << "No" << endl;
    }

    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值