并查集 Union-Find Set

并查集 Union-Find Set

    并查集:(union-find sets)是一种简单的用途广泛的集合. 并查集是若干个不相交集合,能够实现较快的合并和判断元素所在集合的操作,应用很多,如其求无向图的连通分量个数、最小公共祖先、带限制的作业排序,还有最完美的应用:实现Kruskar算法求最小生成树。

 

一般采取树形结构来存储并查集,在合并操作时可以利用树的节点数(加权规则)或者利用一个rank数组来存储集合的深度下界--启发式函数,在查找操作时进行路径压缩使后续的查找操作加速。这样优化实现的并查集,空间复杂度为O(N),建立一个集合的时间复杂度为O(1),N次合并M查找的时间复杂度为O(M Alpha(N)),这里Alpha是Ackerman函数的某个反函数,在很大的范围内这个函数的值可以看成是不大于4的,所以并查集的操作可以看作是线性的。
它支持以下三种操作:
-Union (Root1, Root2) //合并操作;把子集合Root2和子集合Root1合并.要求:Root1和 Root2互不相交,否则不执行操作.
-Root(x) //搜索操作;搜索元素x所在的集合,并返回该集合的名字--根节点.
-MakeSet (N) //构造函数。将并查集中s个元素初始化为s个只有一个单元素的子集合.

-对于并查集来说,每个集合用一棵树表示。
-集合中每个元素的元素名分别存放在树的结点中,此外,树的每一个结点还有一个指向其双亲结点的指针。  
       -为简化讨论,忽略实际的集合名,仅用表示集合的树的根来标识集合。

Union分为两个版本,Union1是将parent域充分利用,其中Parent为正数时表示该节点的父节点下标,为负数时表示该节点为一个根节点,其绝对值为该集合包含的节点总数。  Union2版本是将rank利用,保存树的高度。

Del1 是狭义上的删除操作。(一次删除后不再使用) Del2是广义上的删除操作,利用了一个Mapping数组来保存映射信息

当使用Del2的时候, 各个函数的调用方式是:Union1(Mapping[a],Mapping[b]);MakeSet(N) 如果显式调用Root,也需要Mapping[x]; Root(Mapping[x]);

 

综上,一般不涉及删除操作,使用的是MakeSet(N)  Root(x) Union1()

如果涉及删除操作,使用的是MakeSet(N) Root(Mapping[x])  Union1(Mapping[x],Mapping[y]) ,Del2[x]; //注意这里是Del[x];


reshape()来统计各个并查集分量块,当操作都进行完了之后,才进行统计的!

 


Sosi实现:应用的版本不同。。

#include <iostream>
#include <vector>
using namespace std;

#define MAXN 500001
struct
{
    int parent;       //标记父节点,如果parent为负数则表示是父节点,负数的绝对值表示此块内节点数目
    int rank;         //rank 为树高
    bool flag;        //标记是否被删除 0 未被删除,1 被删除  如果引入删除操作,rank树高性质被破坏
}UFS[MAXN];           //UnionFindSet   UFS
int Mapping[MAXN];     //映射数组,为了进行删除元素。
vector<int> UFSSet[MAXN];
int Dmax;

//其中Parent为正数时表示该节点的父节点下标,为负数时表示该节点为一个根节点,其绝对值为该集合包含的节点总数。
//rank表示权值,在不同问题中有不同的含义。
void MakeSet(int N)             /* 初始化 */
{
    for(int i=0;i<N;i++)             //0-indexed
    {
        UFS[i].parent=-1;        /* 开始每个节点单独构成一个集合 */
        UFS[i].rank=0;           /* 权值视具体情况付初值 */
        UFS[i].flag=0;
        Mapping[i]=i;
    }
}

int Root(int x)     /* 查找包含接点x的集合的根节点 */
{
    int i=x,temp;
    while(UFS[i].parent>=0)
        i=UFS[i].parent;
    while(i!=x)     /* 压缩路径以提高以后检索效率 */
    {
        temp=UFS[x].parent;            
        UFS[x].parent=i;                        //直接将x的祖先命为根节点
        x=temp;                                 //继续处理x的原祖先
    }
    return i;
}

/*
Union1 的优点是能把并查集数某颗树的节点数找到,在parent域中
且rank域空缺,可以去掉
如果使用,可以赋给rank域额外信息
*/
void Union1(int a,int b)     /* 合并a和b所在的集合 */
{
    int x,y;
    x=Root(a);
    y=Root(b);
    if(x==y) return;
    if(UFS[x].parent<UFS[y].parent)  
        UFS[x].parent+=UFS[y].parent,UFS[y].parent=x;    /* 始终将较小树作为较大树的子树 */
    else
        UFS[y].parent+=UFS[x].parent,UFS[x].parent=y;

}
/*
Union 2 的优点是把树高可以统计出来。。此时rank被占用
*/
void Union2(int a, int b)     //将rank较大的作为父节点
{
    int x = Root(a), y = Root(b);
    if( x == y ) return ;
    if( UFS[x].rank > UFS[y].rank ) UFS[y].parent = x;
    else{
        UFS[x].parent = y;
        if( UFS[x].rank == UFS[y].rank ) UFS[y].rank++;
    }
}
//此时的删除只是狭义上的删除,看1号节点被删除之后,当下一次被添加的时候是什么性质了
//此时需要维护一个映射表!
//当新来一个元素之后,我们首先都是先进行映射!Mapping[]将所有的标号i修改成为Mapping[i];
//初始化Mapping为i;
void Del1(int x)           
{
    if(UFS[x].parent==-1)            //独立顶点
        UFS[x].flag=true;
    else
    {
        if(UFS[x].parent>=0)         //叶子节点
        {
            UFS[x].flag=true;
            int i=x;
            while(UFS[i].parent>=0)
                i=UFS[i].parent;
            UFS[i].parent+=1;
        }
        else
            UFS[x].parent+=1,UFS[x].flag=true;      //并查集集合顶点
    }
}
void Del2(int x)   
{
    int initialx=x;               //便于修改一开始的x

    if(UFS[x].flag==true)         //已被删除
        x=Mapping[x];

    if(UFS[x].parent==-1)            //独立顶点
        UFS[x].flag=true;
    else
    {
        if(UFS[x].parent>=0)         //叶子节点
        {
            UFS[x].flag=true;
            int i=x;
            while(UFS[i].parent>=0)
                i=UFS[i].parent;
            UFS[i].parent+=1;
        }
        else
            UFS[x].parent+=1,UFS[x].flag=true;      //并查集集合顶点
    }

    Mapping[initialx]=Dmax;
    Mapping[Dmax]=Dmax;
    UFS[Dmax].parent=-1;
    UFS[Dmax].rank=0;
    UFS[Dmax].flag=0;

    Dmax++;
}
void reshape()
{
    //我们默认此处存储为0-indexed
    int UFSBlock=0;
    for(int i=0;i<Dmax;i++)
    {
        if(UFS[i].parent<-1||((UFS[i].parent==-1) && (UFS[i].flag==0)))  //如果一个节点被删除
        {
            UFSBlock++;
            UFSSet[0].push_back(i);
        }
    }
    for(int i=0;i<Dmax;i++)
    {
        if(UFS[i].flag==0)             //i号节点未被删除
        {
            int x=Root(i);             //找到i号根节点
            for(int j=0;j<UFSSet[0].size();j++)         //根节点编号
            {
                if(x==UFSSet[0][j])
                {
                    UFSSet[j+1].push_back(i);
                    break;
                }
            }
        }
    }
    cout<<" 索引编号:";
    for(int i=0;i<UFSSet[0].size();i++)
        cout<<UFSSet[0][i]<<"    ";
    cout<<endl;

    for(int i=1;i<=UFSBlock;i++)
    {
        cout<<" BLOCK "<<i<<endl;
        for(int j=0;j<UFSSet[i].size();j++)
        {
            cout<<UFSSet[i][j]<<" ";
        }
        cout<<endl;
    }
    cout<<"UFSBlock Num "<<UFSBlock<<endl;
}

/*
1 如果涉及到删除操作,那么各个函数的调用方式是:
Union(Mapping[a],Mapping[b]);
MakeSet(N)
如果显式调用Root,也需要Mapping[x]; Root(Mapping[x]);
*/
int main()
{
    int N=7;  //7个点
    MakeSet(N);
    Dmax=N;
    Union1(Mapping[1],Mapping[0]);
    Union1(Mapping[1],Mapping[2]);
    Union1(Mapping[3],Mapping[4]);
    Union1(Mapping[1],Mapping[4]);
    Del2(0);
    Del2(5);
    Del2(0);
    Del2(0);
    Union2(Mapping[0],Mapping[5]);
    for(int i=0;i<Dmax;i++)
    {
        if(UFS[i].flag==1)
        {
            cout<<"Index"<<i<<" be deleted"<<endl;
            cout<<"Index  "<<i<<"  parent   "<<UFS[i].parent<<"   rank  "<<UFS[i].flag<<endl;
        }
        else
            cout<<"Index  "<<i<<"  parent   "<<UFS[i].parent<<"   rank  "<<UFS[i].flag<<endl;

    }
    reshape();
    return 0;
}

你可能感兴趣的:(并查集 Union-Find Set)