并查集

并查集:合并,查找,集合。

每个集合都有至少一个元素,其中有一个代表该集合,以下简称老大,能通过查找的方法能找到集合的老大,集合只能两两合并,合并后老大会变成一个。

 

用一个数组par来应用并查集,一开始所有元素都是各自独立的,每个元素自成一个集合,再通过合并进行操作。

把集合看做帮派,代表元素看做老大,其他元素看做成员。

 

int par[11];

void init()///初始化:此前先定义全局数组int par[11];
{
    for(int i=0;i<11;i++)
        par[i]=i;
}

int find1(int x)
{
    if(x==par[x])
        return x;
    else
        return par[x]=find1(par[x]);
}

void unite(int x,int y)
{
    int xx=find1(x);
    int yy=find1(y);///x老大的下标是xx;y老大的下标是yy
    if(xx!=yy)///老大的下标和内容永远一样
        par[xx]=yy;///认yy做老大,把x的老大xx的内容改成yy,改后下标和内容不同,xx不再是老大了。
      ///par[yy]=xx;//认xx做老大
}

 

 

比如par数组

下标 0  1  2  3  4  5  6  7  8  9  10

内容 0  1  2  2  4  5  3  7  6  9  10

明显数组下标和内容不一样的有3 6 8;其实他们早已被合并了,先看如何查找老大。

查找3的老大:

find1(3)
{
    return par[3]=find1(par[3]=2)
                    {
                        return 2;
                    }
}

查找6的老大:

find1(6)
{
    return par[6]=find1(par[6]=3)
                    {
                        return par[3]=find1(par[3]=2);
                                        {return 2;}    
                    }
}

查找8的老大:

find1(8)
{
    return par[8]=find1(par[8]=6);
                    {
                        return par[6]=find1(par[6]=3);
                                    {
                                        return par[3]=find1(par[3]=2);
                                                        {return 2;}
                                    }
                    }
}

数组下标就是成员,数组内容就是成员的老大,8的老大是6,6的老大是3,3的老大是2,2的老大是2,依次查找,所以2,3,6,8属于一个集合,2是这个集合的老大。

 

并查集只能两两合并。

接下来实际运用一下。

 

城市交通线

Time Limit: 2000/1000ms (Java/Others)

Problem Description:

A国有n座城市,编号为1~n,这n个城市之间没有任何交通线路,所以不同城市的居民之间不能互通,所以A国的人打算为这n个城市建立k条交通线
(两座城市之间可以有两条及以上的线路),交通线只能贯穿两座城市p, q(存在p=q的情况,不用考虑但会占用一条交通线), 建立交通线后两
座城市的居民可以互通。 例如:建立1, 4后再建立4, 2,则1和2是互通的(1—>4—>2)。建立完k条交通线后,接着有m次询问,问居住在两个城市a, b的居民能否互通。

Input:

输入数据有多组,每一组的第一行包含两个整数n (0<n<=5000), k (0<k<=10000) , 接下来有k行,每行包含两个整数p,q (0<p,q<=n),
代表城市p,q之间建立一条交通线。再接着一个整数m (0<m<=n) 代表有m次询问,接下来有m行,每行包含两个整数a, b(0<a,b<=n) 询问
城市a,b之间的居民能否互通 (同一个城市的居民能互通)。

Output:

两座城市的居民可以互通则输出“YES”,否则输出“NO”,每个输出占一行。

Sample Input:

6 5
1 4
4 2
2 4
5 6
4 1
4
1 2
1 3
1 6
4 4

Sample Output:

YES
NO
NO
YES

题目来源:http://acm.gdufe.edu.cn/Problem/read/id/1505
代码:
#include<bits/stdc++.h>
using namespace std;
int par[5050];
void init()
{
    for(int i=0;i<5050;i++)
        par[i]=i;
}

int find1(int x)
{
    if(x==par[x])
        return x;
    else
        return par[x]=find1(par[x]);
}

void unite(int x,int y)
{
    int xx=find1(x);
    int yy=find1(y);
    if(xx!=yy)
        par[xx]=yy;
}
int main()
{
    int n,k,q;
    while(cin>>n>>k)///城市个数,交通线数
    {
        init();
        int x,y;
        while(k--)
        {
            cin>>x>>y;
            unite(x,y);
        }
        cin>>q; ///询问个数
        int a,b;
        while(q--)
        {
            cin>>a>>b;
            if(find1(a)==find1(b))///老大相同就是可以互通的
                cout<<"YES\n";
            else
                cout<<"NO\n";
        }
    }
    return 0;
}

 

How Many Tables

Today is Ignatius' birthday. He invites a lot of friends. Now it's dinner time. Ignatius wants to know how many tables he needs at least. You have to notice that not all the friends know each other, and all the friends do not want to stay with strangers.

One important rule for this problem is that if I tell you A knows B, and B knows C, that means A, B, C know each other, so they can stay in one table.

For example: If I tell you A knows B, B knows C, and D knows E, so A, B, C can stay in one table, and D, E have to stay in the other one. So Ignatius needs 2 tables at least.

InputThe input starts with an integer T(1<=T<=25) which indicate the number of test cases. Then T test cases follow. Each test case starts with two integers N and M(1<=N,M<=1000). N indicates the number of friends, the friends are marked from 1 to N. Then M lines follow. Each line consists of two integers A and B(A!=B), that means friend A and friend B know each other. There will be a blank line between two cases.
OutputFor each test case, just output how many tables Ignatius needs at least. Do NOT print any blanks.
Sample Input
2
5 3
1 2
2 3
4 5

5 1
2 5
Sample Output
2
4

题意:生日聚会上,认识的人坐一起。如果A和B认识,坐一张桌子上,如果B又和C认识,可以认为A和C也认识,坐一起。求最后有多少张桌子?
思路:这也是一道裸题,通过并查集将这些人联系在一起,每张桌子看做一个集合,总会有一个老大,查找老大数量就是桌子数量。

代码:
#include<stdio.h>
#include<algorithm>
#include<iostream>
using namespace std;

int par[5050];
void init()
{
    for(int i=0;i<5050;i++)
        par[i]=i;
}

int find1(int x)
{
    if(x==par[x])
        return x;
    else
        return par[x]=find1(par[x]);
}

int unite(int x,int y)
{
    int xx=find1(x);
    int yy=find1(y);//x老大的下标是xx;y老大的下标是yy
    if(xx!=yy)//老大的下标和内容永远一样
        par[xx]=yy;//认yy做老大
      return 0;
}
int main()//裸题
{
    int t,n,k,sum,flag;
    cin>>t;
    while(t--){
    while(cin>>n>>k)
    {
        sum=0;
        init();
        int x,y;
        while(k--)
        {
            cin>>x>>y;
            unite(x,y);
        }

        for(int i=1;i<=n;i++)
            if(par[i]==i)///老大下标和内容一样
                sum++;
        cout<<sum<<"\n";

    }
    }
    return 0;
}

 


 再看一道变相并查集的题目。(题目不好复制粘贴,直言题意了)

题意:每次输入两个数字,用一条边把他们联系起来,最后求环的个数,比如:

 

 

 

像这样的图,有2个环,(15,5,9,11)和(7,10,16);

解题思路:每合并两个顶点,他们的边数加1,最后,环中都是两条边的顶点符合要求。

代码:

#include<iostream>
#include<stdio.h>
using namespace std;
int par[1000100];
int bian[1000100];
int temp[1000100];
void init(int x)
{
    for(int i=1;i<=x;i++)
        {par[i]=i;bian[i]=0;}///一开始边数都是0
}
int find1(int x)
{
    if(x==par[x])
        return x;
    else
        return par[x]=find1(par[x]);
}
int unite(int x,int y)
{
    int xx=find1(x);
    int yy=find1(y);
    if(xx!=yy)
        par[xx]=yy;

      return 0;
}
int main()
{
    int n,k,boss;
    while(cin>>n>>k)///点数,关系数
    {
        boss=0;
        init(n);
        while(k--)
        {
            int a,b;
            cin>>a>>b;
            bian[a]++;
            bian[b]++;
            unite(a,b);
        }
        for(int i=1;i<=n;i++)
            temp[i]=par[i];///复制一个合并后数组的副本,等下修改数据
        for(int i=1;i<=n;i++)
            if(bian[i]!=2)///如果这个节点边数不为2
                temp[find1(i)]=-1;
///把这个节点的老大废掉,改成-1,改动是在副本里改动,查找在原数组里查找,
///如果是在原数组里改动,有些成员的老大被改成-1了,不好继续查找
                
        for(int i=1;i<=n;i++)
            if(temp[i]==i)///老大还没废掉的说明它的所有成员符合要求,只有两条边
            boss++;
            cout<<boss<<endl;
    }
    return 0;
}

 

 

 


posted @ 2018-07-30 16:35  守林鸟  阅读(244)  评论(0编辑  收藏  举报