算法之并查集

概论

定义:
并查集是一种树型的数据结构,用于处理一些不相交集合的合并及查询问题(即所谓的并、查)。比如说,我们可以用并查集来判断一个森林中有几棵树、某个节点是否属于某棵树等。

主要构成:
并查集主要由一个整型数组pre[ ]和两个函数find( )、join( )构成。
数组 pre[ ] 记录了每个点的前驱节点是谁,函数 find(x) 用于查找指定节点 x 属于哪个集合,函数 join(x,y) 用于合并两个节点 x 和 y 。

作用:
并查集的主要作用是求连通分支数(如果一个图中所有点都存在可达关系(直接或间接相连),则此图的连通分支数为1;如果此图有两大子图各自全部可达,则此图的连通分支数为2……)

代码详解

const int  N = 1005					//指定并查集所能包含元素的个数(由题意决定)
int pre[N];     					//存储每个结点的前驱结点 
int rank[N];    					//树的高度 
void init(int n)     				//初始化函数,对录入的 n个结点进行初始化 
{
    
    
    for (int i = 0; i < n; i++) {
    
    
        pre[i] = i;     			//每个结点的上级都是自己 
        rank[i] = 1;    			//每个结点构成的树的高度为 1 
    }
}
int find(int x)     	 		    //查找结点 x的根结点 
{
    
    
    if (pre[x] == x) return x;  		//递归出口:x的上级为 x本身,则 x为根结点 
    return find(pre[x]); 			//递归查找 
}

int find(int x)     				//改进查找算法:完成路径压缩,将 x的上级直接变为根结点,那么树的高度就会大大降低 
{
    
    
    if (pre[x] == x) return x;		//递归出口:x的上级为 x本身,即 x为根结点 
    return pre[x] = find(pre[x]);   //此代码相当于先找到根结点 rootx,然后 pre[x]=rootx 
}

bool isSame(int x, int y)      		//判断两个结点是否连通 
{
    
    
    return find(x) == find(y);  	//判断两个结点的根结点(即代表元)是否相同 
}

bool join(int x, int y)
{
    
    
    x = find(x);						//寻找 x的代表元
    y = find(y);						//寻找 y的代表元
    if (x == y) return false;			//如果 x和 y的代表元一致,说明他们共属同一集合,则不需要合并,返回 false,表示合并失败;否则,执行下面的逻辑
    if (rank[x] > rank[y]) pre[y] = x;		//如果 x的高度大于 y,则令 y的上级为 x
    else								//否则
    {
    
    
        if (rank[x] == rank[y]) rank[y]++;	//如果 x的高度和 y的高度相同,则令 y的高度加1
        pre[x] = y;						//让 x的上级为 y
    }
    return true;						//返回 true,表示合并成功
}

一道例题-小h去旅游

题目描述
小h想去p地旅游,p地有很多个旅游点,旅游点间有些有道路相连,有些没有。小h有若干询问,每个询问有两个数字,表示两个旅游点之间是否有道路相连,a->b->c也意味着a和c相连

输入
第一行三个数字n,m和q表示有n个旅游点(1到n),m条道路,q个查询
后m行每行两个数字u,v表示u,v两个旅游点有道路相连
后q行每行两个数字a,b查询a旅游点和b旅游点间是否有道路相连
(n<=10000,m<=10000,q<=10000)

输出
每个查询输出是否相连,是输出YES反之输出NO

样例输入 Copy
3 2 3
1 2
2 3
3 2
2 1
1 3

样例输出 Copy
YES
YES
YES

#include <iostream>
using namespace std;

int n, m, q;
int pre[10005];

int Find(int x)
{
    
    
    if (pre[x] == x)
        return x;
    return pre[x] = Find(pre[x]);
}
int main()
{
    
    
    scanf("%d %d %d", &n, &m, &q);
    for (int i = 0; i < n; i++)
        pre[i] = i;
    while (m--)
    {
    
    
        int u, v;
        scanf("%d %d", &u, &v);
        int fx = Find(u), fy = Find(v);
        if (fx != fy)
            pre[fx] = fy;
    }
    while (q--)
    {
    
    
        int a, b;
        scanf("%d %d", &a, &b);
        if (Find(a) == Find(b))
            cout << "YES" << endl;
        else
            cout << "NO" << endl;
    }return 0;
}

总结

1、用集合中的某个元素来代表这个集合,则该元素称为此集合的代表元;

2 、一个集合内的所有元素组织成以代表元为根的树形结构;

3 、对于每一个元素 x,pre[x] 存放 x 在树形结构中的父亲节点(如果 x 是根节点,则令pre[x] = x);

4 、对于查找操作,假设需要确定 x 所在的的集合,也就是确定集合的代表元。可以沿着pre[x]不断在树形结构中向上移动,直到到达根节点。

因此,基于这样的特性,并查集的主要用途有以下两点:
1、维护无向图的连通性(判断两个点是否在同一连通块内,或增加一条边后是否会产生环);
2、用在求解最小生成树的Kruskal算法里。

一般来说,一个并查集对应三个操作:
1、初始化( Init()函数 )
2、查找函数( Find()函数 )
3、合并集合函数( Join()函数 )

Guess you like

Origin blog.csdn.net/qq_52297656/article/details/119840163