2017/7/31训练日记(并查集基础)

来源:互联网 发布:http 服务器默认端口 编辑:程序博客网 时间:2024/06/07 08:40

今天服务器故障没怎么a题,a了两道广搜的水题。

看了看dp的基础课件和几个例题,下午花了半天时间来看的数据结构并查集部分,数据结构部分有关并查集的题目还蛮多的,而且相当的好用。

并查集是有合并集合和查找集合两种操作的一种数据结构中的算法。

并查集一般用途:

1.维护无向图的连通性,判断两个点是否处于同一个连通块内、还有判断增加一条边是否会成环;

2.用在求解最小生成树的算法中;

并查集一般包括一个整形数组和两个函数;

整形数组p用来存放记录前导点或者说父节点

函数find用来查找,join用来合并集合;

int p[10000];//记录前导父节点;

int find(int x)//查找集合;

{

 int r=x;//我们拿一个整形数据r来寻找集合;

 while(p[r]!=r)//如果r的父节点不等于他本身;

  r=p[r];//那么我们把r的父节点赋给r,即返回到父节点;

  int i,j;

  i=x;

  while(i!=r)

  {

  j=p[i];//临时记录下i的父节点;

  p[i]=r;//将父节点变更为r;

  i=j;//将i值赋为i的父节点;

  }

 return r;

}

void join(int x,int y)//判断x,y是否是连通的

{

int fx=find(x),fy=find(y);//fx为x的父节点,fy为y的父节点;

if(fx!=fy)//判断x和y的父节点是否为同一个,如果是就不用管,如果不是;

{

p[fx]=fy;//将fy设为fx的父节点,来达到合并集合的目的;

}

}

例题:

Description
若某个家族人员过于庞大,要判断两个是否是亲戚,确实还很不容易,给出某个亲戚关系图,求任意给出的两个人是否具有亲戚关系。 规定:x和y是亲戚,y和z是亲戚,那么x和z也是亲戚。如果x,y是亲戚,那么x的亲戚都是y的亲戚,y的亲戚也都是x的亲戚。
Input
第一行:三个整数n,m,p,分别表示有n个人,m个亲戚关系,询问p对亲戚关系。 以下m行:每行两个数Mi,Mj,1< =Mi,Mj< =N,表示Mi和Mj具有亲戚关系。 接下来p行:每行两个数Pi,Pj,询问Pi和Pj是否具有亲戚关系。

分析:

我们可以给每个人建立一个集合,集合的元素值有他自己,表示最开始时他不知道任何人是它的亲戚。以后每次给出一个亲戚关系a, b,则a和他的亲戚与b和他的亲戚就互为亲戚了,将a所在集合与b所在集合合并。对于样例数据的操作全过程如下:
输入关系 分离集合
初始状态(举个栗子)
(2,4) {2,4}
(5,7) {2,4} {5,7}
(1,3) {1,3} {2,4} {5,7}
(8,9) {1,3} {2,4} {5,7} {8,9}
(1,2) {1,2,3,4} {5,7} {8,9}
(5,6) {1,2,3,4} {5,6,7} {8,9}
(2,3) {1,2,3,4} {5,6,7} {8,9}
最后我们得到3个集合{1,2,3,4}, {5,6,7}, {8,9},于是判断两个人是否亲戚的问题就变成判断两个数是否在同一个集合中的问题。

实际上这种方法就是并查集,由于我们关心的只是两个人之间是否连通,至于他们是如何连通的,以及每个圈子内部的结构是怎样的,甚至根节点是谁,并不重要。、

算法需要以下几个子过程:
(1) 开始时,为每个人建立一个集合;
(2) 得到一个关系后a,b,合并相应集合;
(3) 此外我们还需要判断两个人是否在同一个集合中,这就涉及到如何标识集合的问题。我们可以在每个集合中选一个代表标识集合,因此我们需要一个子过程给出每个集合的代表元。于是判断两个人是否在同一个集合中,即两个人是否为亲戚。


有关于并查集的问题基本上就是如上面所述,大多数的并查集问题基本上都能解决,再有就是优化算法了--路径压缩。

每次查找的时候,如果路径较长,则修改信息,以便下次查找的时候速度更快。

实现方法呢,第一步,找到根结点。
第二步,修改查找路径上的所有节点,将它们都指向根结点。

也就是说我们每找到一个根节点,就按照题意将同一路径上的节点全都变成根节点的子节点以减少查找时间。

具体的实现代码(其实我还没有参透该代码的实现原理QAQ,但做到并查集问题要想实现优化的话简单更改下下面的代码就行)

#include<bits/stdc++.h>
using namespace std;
const int M = 1e3 + 10 ;
int n , m ;
int folk[M] ;
int dsu (int u) { 
        return u == folk[u] ? u : folk[u] = dsu (folk[u]) ;
}
int main () {
        int n ;
        scanf ("%d" , &n) ;
        for (int i = 0 ; i < n ; i ++) folk[i] = i ;
        scanf ("%d" , &m) ;
        while (m --) {
                int u , v ;
                scanf ("%d%d" , &u , &v) ;
                int _u = dsu (u) , _v = dsu (v) ;
                if (_u != _v) {
                        folk[v] = _u ;
                }
        }
        for (int i = 0 ; i < n ; i ++) dsu (i) ;
        return 0 ;
}

看懂了上面的知识的话基本上做一些简单的并查集的题是没有什么问题了,关于路径压缩的代码还要找时间再研究一下原理。

原创粉丝点击