`
kmplayer
  • 浏览: 508664 次
  • 性别: Icon_minigender_1
  • 来自: 北京
社区版块
存档分类
最新评论

并查集

阅读更多
1,什么是并查集?
并查集,就是Union-Find Set,也称不相交集合 (Disjoint Set),用于处理一些不相交集合(Disjoint Sets)的合并及查询问题,如其求无向图的连通分量个数等.最完美的应用当属:实现Kruskar算法求最小生成树.


2,并查集的主要操作有:

(1)makeSet(x):把每一个元素初始化为一个集合
初始化后每一个元素的父亲节点是它本身,每一个元素的祖先节点也是它本身(也可以根据情况而变).

(2)findSet(x):查找一个元素所在的集合
查找一个元素所在的集合,其精髓是找到这个元素所在集合的祖先.这个才是并查集判断和合并的最终依据.
判断两个元素是否属于同一集合,只要看他们所在集合的祖先是否相同即可。

(3)Union(x,y):合并x,y所在的两个集合
合并两个不相交集合操作很简单:
利用Find_Set找到其中两个集合的祖先,将一个集合的祖先指向另一个集合的祖先。

3,并查集的优化:

(1)Find_Set(x)时 路径压缩
寻找祖先时我们一般采用递归查找,但是当元素很多亦或是整棵树变为一条链时,每次Find_Set(x)都是O(n)的复杂度,有没有办法减小这个复杂度呢?
这就是路径压缩,即当我们经过"递推"找到祖先节点后,"回溯"的时候顺便将它的子孙节点都直接指向祖先,这样以后再次Find_Set(x)时复杂度就变成O(1)了.
(2)Union(x,y)时 按秩合并
即合并的时候将元素少的集合合并到元素多的集合中,这样合并之后树的高度会相对较小。

实现代码:
#defince VMAX 100 //节点个数
int father[VMAX]; //father[x]:元素x的父节点
int rank[VMAX]; //rank[x]:x元素的秩

//初始化操作
void makeSet(int n)
{
    for(i = 0; i < n; i++)
    {
        father[i] = i; //根据实际情况指定的父节点可变化
        rank[i] = 0; //根据实际情况初始化秩也有所变化
    }
}

//查找操作:两种实现方式
//递归实现
int findSetR(int x)
{
    if(x!=father[x])
        //通过回溯,完成路径压缩
        father[x]=findSetR(father[x]);
    return father[x];
}
//非递归实现
int findSet(int x)
{
    int r=x;
    while(r!=father[r])
        r=father[r];

    //路径压缩:把x上面的所有节点的父节点都指向祖先节点
    int temp;
    while(x!=r)
    {
        temp=father[x];
        father[temp]=r;
        x=temp;
    }
    return r;
}

//合并操作
void UnionR(int x,int y)
{
    x=findSetR(x);
    y=findSetR(y);
    if(x==y)
        return;
    if( rank[x]>rank[y] ) //通常将元素少的集合合并到元素多的集合
        father[y]=x;
    else
        father[x]=y;
    if( rank[x]==rank[y] )
        rank[y]++;
}

void Union(int x,int y)
{
    x=findSet(x);
    y=findSet(y);
    if(x==y)
        return;
    if( rank[x]>rank[y] ) //通常将元素少的集合合并到元素多的集合
        father[y]=x;
    else
        father[x]=y;
    if( rank[x]==rank[y] )
        rank[y]++;
}


4,应用
(1)http://acm.pku.edu.cn/JudgeOnline/problem?id=1611
#include<iostream>
using namespace std;

int n, m, i, j;
int father[30005], num[30005];

void makeSet(int n)
{
    for(i = 0; i < n; i++)
    {
        father[i] = i; //使用本身做根
        num[i] = 1; //记录集合的数目
    }
}
int findSetR(int x)
{
    if(father[x] != x) //合并后的树的根是不变的
    {
        father[x] = findSetR(father[x]);
    }
    return father[x];
}

//非递归实现
int findSet(int x)
{
    int r=x;
    while(r!=father[r])
        r=father[r];

    //路径压缩:把x上面的所有节点的父节点都指向祖先节点
    int temp;
    while(x!=r)
    {
        temp=father[x];
        father[temp]=r;
        x=temp;
    }
    return r;
}

void Union(int a, int b)
{
    int x = findSet(a);
    int y = findSet(b);
    if(x == y)
    {
        return;
    }
    if(num[x] <= num[y])
    {
        father[x] = y;
        num[y] += num[x];
    }
    else
    {
        father[y] = x;
        num[x] += num[y];
    }
}

void UnionR(int a, int b)
{
    int x = findSetR(a);
    int y = findSetR(b);
    if(x == y)
    {
        return;
    }
    if(num[x] <= num[y])
    {
        father[x] = y;
        num[y] += num[x];
    }
    else
    {
        father[y] = x;
        num[x] += num[y];
    }
}

int main()
{
    while(scanf("%d %d", &n, &m)!=EOF && n != 0)
    //学生数 小组数
    {
        makeSet(n);
        for(i = 0; i < m; i++)
        {
            int count, first, b;
            //小组成员数目 成员编号0到n-1
            scanf("%d %d",&count, &first);
            for(j = 1; j < count; j++)
            {
                scanf("%d",&b);
                Union(first,b);
            }
        }
        //0所在集合的数目,就是可能人群数
        printf("%d\n",num[findSet(0)]);
    }
    return 0;
}

(2)http://acm.pku.edu.cn/JudgeOnline/problem?id=2524
分析:实质就是求连通分量的个数
思路:每合并一次,将个数减1,即可得到最后结果.
#include<iostream>
using namespace std;

int father[50005], rank[50005];
int maxN;
void makeSet(int n)
{
    for(int i = 0; i < n; i++)
    {
        father[i] = i; 
        rank[i] = 0; 
    }
}

int findSet(int x)
{
    int r=x;
    while(r!=father[r])
        r=father[r];
    int temp;
    while(x!=r)
    {
        temp=father[x];
        father[temp]=r;
        x=temp;
    }
    return r;
}

void Union(int x,int y)
{
    x=findSet(x);
    y=findSet(y);
    if(x==y)
        return;
    maxN--;
    if( rank[x]>rank[y] ) 
        father[y]=x;
    else
        father[x]=y;
    if( rank[x]==rank[y] )
        rank[y]++;
}

int main()
{
    int Case = 1;
    int m;
    //总人数 问题数
    while(scanf("%d %d", &maxN, &m),maxN!=0)
    {
        makeSet(maxN);
        int a, b;
        for(int i = 0; i < m; i++)
        {
            //a和b信仰相同
            scanf("%d %d",&a, &b);
            a--,b--;
            Union(a,b);
        }
        printf("Case %d: %d\n",Case++,maxN);
    }
    return 0;
}

(3)http://acm.pku.edu.cn/JudgeOnline/problem?id=2236
思路:并查集。修复一台电脑就标记为修复过了,然后跟其他已经修复过的电脑进行合并,但是合并的条件必须是两台电脑的距离小于D。当要测试两台电脑时,就是判断他们的根结点是否一样.
#include <iostream>
#include <math.h>
using namespace std;

#define size 1002

int father[size];
int rank[size];
bool isVisit[size];

struct coordinate
{
    double x;
    double y;
}locate[size];

void makeSet(int x)//创建集合
{
    father[x] = x;
    rank[x] = 1;
    isVisit[x] = false;
}

int findSet(int x)//寻找根结点
{
    int r = x;
    int temp;
    while(r!=father[r])
        r = father[r];
    while (x!=r)
    {
        temp = father[x];
        father[temp] = r;
        x = temp;
    }
    return r;
}

void Union(int a,int b)//合并集合
{
    a = findSet(a);
    b = findSet(b);
    if(rank[a]>rank[b])
        father[b] = a;
    else
        father[a] = b;
    if(rank[a] == rank[b])
        rank[b]++;
}

bool IsOk(int a,int b,long distance)//判断两台电脑是否可以连通
{
    double k;
    k = sqrt((locate[a].x-locate[b].x)*(locate[a].x-locate[b].x)+(locate[a].y-locate[b].y)*(locate[a].y-locate[b].y));
    return k<=distance;
}

int main()
{
    char commander;
    int computer;
    int p1,p2;
    int i;
    long distance;
    cin>>computer>>distance; //pc数目,有效距离
    for(i=1;i<=computer;i++)
        makeSet(i);
    for (i=1;i<=computer;i++) //输入每台pc的位置
        cin>>locate[i].x>>locate[i].y;
    while (cin>>commander)
    {
        if (commander == 'O')
        {
            cin>>p1;
            isVisit[p1] = true;
            /*查看修复的电脑是否可以加入到并查集中*/
            for(i=1;i<=computer;i++)
            {
                if(isVisit[i]&&i!=p1)
                    if(IsOk(p1,i,distance)) //满足通讯条件,合并
                        if(findSet(p1)!=findSet(i))
                            Union(p1,i);
            }
        }
        else
        {
            cin>>p1>>p2;
            if(findSet(p1)==findSet(p2))
                cout<<"SUCCESS"<<endl;
            else
                cout<<"FAIL"<<endl;
        }
    }
    return 0;
}
分享到:
评论
1 楼 soarwindzhang 2012-08-08  
感谢博主的分享,我今天看了您的UFSET非递归的路径压缩时感觉稍微有点小问题,
while(x!=r) 
    { 
        temp=father[x]; 
        father[temp]=r; 
        x=temp; 
    } 
这里的father[temp] = r是不是应该是father[x] = r呢,否则没有达到把他的子孙都连到root上的效果,而且这样一次就停止了,虽然也压缩了一小部分

相关推荐

    数据结构并查集 查询 快速

    数据结构中的并查集(Disjoint Set)是一种用于处理元素集合的分割问题的数据结构,它主要支持两种操作:合并(union)与查找(find)。在实际应用中,尤其是在算法竞赛(ACM)和图论中,它被广泛用于解决快速查询和...

    并查集 第12章 并查集

    ### 并查集知识点概述 #### 一、并查集定义与基本概念 **并查集**(Union-Find Set)是一种数据结构,用于处理一些不相交集合的合并及查询问题,通常用来解决动态连通性问题。并查集支持两种主要的操作:**并操作*...

    并查集 团伙数据

    并查集是一种在图论和算法设计中常用的离散数学结构,主要用于处理一些不相交集合的合并与查询问题。这个压缩包文件“团伙数据”显然包含了一些与并查集算法相关的练习数据和可能的题解,目的是帮助学习者更好地理解...

    并查集入门学习并查集

    综上所述,"并查集入门学习并查集"的资源可能是针对初学者的教程,通过“并查集.pdf”这个文档,你将能够深入理解并查集的基本概念、操作原理以及如何在实际问题中应用。对于希望提升算法能力,尤其是参与ACM竞赛的...

    并查集基础知识讲解

    并查集是一种在大型无向图中查找元素所属连通分量的数据结构,常用于解决合并与查询的问题。它的核心思想是将元素组织成一棵树形结构,每个元素都有一个父节点,根节点代表了一个连通分量。并查集的主要操作包括...

    并查集讲义并查集知识学习讲解

    并查集,在一些有N个元素的集合应用问题中,我们通常是在开始时让每个元素构成一个单元素的集合,然后按一定顺序将属于同一组的元素所在的集合合并,其间要反复查找一个元素在哪个集合中。这一类问题近几年来反复...

    并查集 家谱数据

    并查集是一种在图论和算法设计中常用的离散数学结构,主要用来处理一些不相交集合的合并与查询问题。在本数据压缩包中,主题聚焦于家谱数据,可以推断出这些数据可能包含了家庭成员之间的关系,通过并查集这种数据...

    并查集模版

    ### 并查集模版详解 #### 一、并查集简介 并查集是一种用于处理元素集合的动态数据结构,常被应用于图论、计算机网络、数据挖掘等领域。其核心功能是支持两种操作:合并两个集合(并操作)与查询某个元素所属的...

    并查集C++实现

    并查集是一种在大型无向图中查找连接组件的数据结构,它在计算机科学中有着广泛的应用,尤其是在图论和算法设计中。这个压缩包文件"division"可能包含了一个C++实现并查集的示例代码,特别指出是为Visual Studio ...

    经典并查集PPT 这个比较不错啊

    并查集是一种数据结构,主要用于处理一些不相交集合的合并与查询问题。在并查集中,我们将一组对象划分为多个不相交的集合,每个集合由一个代表元素标识。这个数据结构支持两种主要操作: 1. **Find** 操作:查询...

    算法与数据结构:并查集

    并查集是一种用于处理不相交集合操作的数据结构,它主要包含两个基本操作:查找(Find)和合并(Union)。在并查集中,通常用森林的结构来表示各个集合,每棵树代表一个集合,树的根节点代表集合的标识。 **查找...

    优化并查集

    并查集是一种在大型无向图中查找元素所属集合的数据结构,它主要应用于不经常进行连接,但频繁查询是否属于同一集合的问题。在并查集中,我们通常关注两个操作:`Find` 和 `Union`。`Find` 操作用于确定元素所属的...

    最经典并查集详细讲解

    并查集是一种在分散的数据元素中寻找连接关系的数据结构,常用于处理一些不相交集合的合并与查询问题。在图论中,它可以帮助我们快速判断两个节点是否属于同一个连通分量,或者将两个连通分量合并。在本教程中,我们...

    朱全民-并查集ppt

    并查集是一种数据结构,主要用于处理不相交集合的合并与查询问题。它在很多算法竞赛,如信息学奥林匹克竞赛(OI)和全国青少年信息学奥林匹克联赛(NOIP)中有着广泛的应用。朱全民教授的讲解主要围绕并查集的基本...

    并查集模板

    并查集模板并查集模板并查集模板并查集模板并查集模板并查集模板

    并查集算法PKU解题报告

    并查集是一种用于处理集合合并与查询的抽象数据类型,主要应用于解决连通性问题,例如判断图中的两个节点是否在同一连通分量内。在本解题报告中,我们将探讨并查集算法在PKU(北京大学)的三道题目:PKU1182、PKU...

    并查集算法

    深入理解并查集算法,细致讲解,专业老师,一步到位 。

    并查集学习整理 初学者必备

    并查集是一种在图论和数据结构中广泛使用的算法,主要应用于处理不相交集合的合并与查询。在ACM(国际大学生程序设计竞赛)中,掌握并查集的运用对于解决某些特定类型的问题至关重要。这个压缩包包含了全面的并查集...

    并查集算法思想的算法

    ### 并查集算法思想详解 #### 一、并查集基本概念 并查集是一种数据结构,主要用于处理一些不交集的合并及查询问题,它支持两种操作: 1. **合并**(Union):将两个不同的集合合并成一个集合。 2. **查找**...

    并查集C/C++代码实现

    ### 并查集C/C++代码实现 #### 知识点概述 并查集是一种用于处理元素集合的动态集合问题的数据结构,它支持两种基本的操作:**并**和**查**。并查集的主要用途是判断一个元素是否属于某个集合,并且能够有效地将两...

Global site tag (gtag.js) - Google Analytics