并查集及并查集的路径压缩

   1.引入什么是并查集?

导引问题犯罪团伙

    警察抓到了n个罪犯,警察根据经验知道他们属于不同的犯罪团伙,却不能判断有多少个团伙,但通过警察的审讯,知道其中的一些罪犯之间相互认识,已知同一犯罪团伙的成员之间直接或间接认识。有可能一个犯罪团伙只有一个人。

    请你根据已知罪犯之间的关系,确定犯罪团伙的数量。已知罪犯的编号从1n

输入:

第一行:n<=10000,罪犯数量),

第二行:m<=100000,关系数量)

以下若干行:每行两个数:Ij,中间一个空格隔开,表示罪犯i和罪犯j相互认识。

输出:一个整数,犯罪团伙的数量。

 

2.抽象的算法:

  开始把n个人看成n个独立集合。

每读入两个有联系的人ij,查找ij所在的集合pq,如果pq是同一个集合,不作处理;如果pq属于不同的集合,则合并pq为一个集合。

最后统计集合的个数即可得到问题的解。

 3.什么是并查集?

并查集是一种树型的数据结构,用于处理一些不相交集合S={S1, S2,,Sn},每个集合Si都有一个特殊元素root[Si],称为集合的代表元.

并查集支持三种操作:

Init(X):集合初始化:把元素xi加到集合Si中。每个集合Si只有一个独立的元素xi,并且元素xi就是集合Si的代表元素。father[xi]=-1(或者xi);  每个结点都是一颗独立的树,-1是该树的代表元素。

                  void Initial(int father[]){

                          for(int i=0;i

                              father[i]=-1;

                   }                           

Find(x):查找:查找xi所在集合Si的代表root[Si]。

               优化:路径压缩。查找出对应得根节点

            int Find(int father[],int x){

                            while(father[x]>=0)

                                         x=father[x];

                            return x;

             }

 

Union(int father[],x,y): 合并:把x和y所在的两个不同集合合并。

         void  Union(int father[],int root1,int root2){

                            father[root2]=root1;//将根root2连接到跟root1下面

          }

 4.并查集的压缩

下面是采用路径压缩的方法查找元素:

int find(int x)       //查找x元素所在的集合,回溯时压缩路径
{


return father[x]==-1?x: father[x]
= find(father[x]); //回溯时的压缩路径
//从x结点搜索到祖先结点所经过的结点都指向该祖先结点

}

   

    上面是一采用递归的方式压缩路径, 但是,递归压缩路径可能会造成溢出栈,下面我们说一下非递归方式进行的路径压缩:(即先找到父节点,再将路径上的所有节点的父节点都设为根节点。)

int find(int x)
{
int
k, j, r;
r
= x;

while(father[r]>=0) //查找跟节点
r = father[r]; //找到跟节点,用r记录下
k = x;

while(k != r) //非递归路径压缩操作
{
j
= parent[k]; //用j暂存parent[k]的父节点

parent[k] = r; //parent[x]指向跟节点
k = j; //k移到父节点
}
return r; //返回根节点的值

}

 

例题:两个人一组,输入人的组数n,再输入n组人。每组的两个人表示这两个人是好朋友,a得好朋友是b,b得好朋友是c,则c也是a得好朋友。求最大的好朋友团体的人数,并输出。

#include
#include
using namespace std;
#define N 10000005
int p[N];
int sum[N];

int Find_set(int x) {//采用了路径压缩
    return p[x]==-1 ? x:p[x]=Find_set(p[x]);
}
int main() {
    int n;
    while (scanf("%d", &n) != EOF) {
        memset(p, -1, sizeof(p));
        for (int i=1; i<=N; i++)
            sum[i] = 1;
        while (n --) {
            int a, b;
            scanf("%d%d", &a, &b);
            a = Find_set(a);
            b = Find_set(b);
            if (a != b) {
                p[a] = b;
                sum[b] += sum[a];
            }
        }
        int ans = 1;
        for (int i=1; i<=N; i++) {
            if (p[i]==-1 && sum[i]>ans)
                ans = sum[i];
        }
        printf("%d\n", ans);
    }
}

 

 

 

 

你可能感兴趣的:(ACM算法)