并查集

来源:互联网 发布:自动顶贴软件 编辑:程序博客网 时间:2024/06/05 15:12
并查集是一种很基本的数据结构,可我以前竟不知道。最近做pku的acm online judge题遇到了,用起来很顺手,觉得这是一种很有用有数据结构。


       并查集是一种可以方便地进行以下三种操作的数据结构:


       合并两个集合;将一元素并入另一集体;判断两个元素是否属于同一个集合。


       例如,可以用数组很方便地实现一个并查集,对一个含有n个元素的并查集,可以用一个长度为n的数组实现,主要设计以下四种操作:


        初始化并查集:每个元素赋一不同的值,时间复杂度为O(n)。


        合并两个集合A和B:将所有标记为B集体的元素的标记变为A集体的标记,时间复杂度为O(n)。


        将一元素a并入集合A:时间复杂度为O(1)。


        判断两元素是否属于同一集体:只须比较标记,时间复杂度为O(1).

并查集的定义: 并查集是一种简单的集合,它支持三种操作: MakeSet(x):创建一个只包含一个元素x的并查集 Find(x, S):判断x是否在集合S中 Union(A, B):合并两个并查集A和B 并查集与树可以将每一棵树都看成是元素的集合,从而可以用树来表示并查集. 1 4 2 5 6 3 7 MakeSet 一个元素可以看成是只有根结点的树 1 Find 判断一个元素x是否在并查集S中,只需找到x的根结点,看它是否等于S的根结点 1 4 2 5 6 3 7 Union 合并两个并查集,只需把其中一个根结点变成另一个根结点的儿子即可 1 4 2 6 3 用数组表示树 1 4 2 5 6 3 7 5 2 1 1 2 1 0 7 6 5 4 3 2 1 父亲表示法用数组表示森林 1 4 2 5 6 3 7 5 2 0 1 2 0 0 7 6 5 4 3 2 1 父亲表示法并查集常用的数据结构我们一般对所有的元素从1到n编号,这样我们就可以用元素的编号来表示这个元素.同时使用一个数组来存放相应的并查集. 我们用树的根结点来表示整棵树,即整个集合

类型定义
type AllElements = array [1..MAX] of Integer; 
DisjointSet = Integer; {根结点} 




MakeSet function MakeSet(x: Integer):DisjointSet; 
begin
p[x] := 0; {设p是AllElements类型} 
MakeSet := x;
end; 




Find function getRoot(x: Integer): Integer; 
begin
 if p[x] = 0 then getRoot := x 
else getRoot := getRoot(p[x]); 
end; 




function Find(x: Integer; S: DisjointSet):Boolean; 
begin 
Find := getRoot(x) = s; 
end; 




Union function Union(A, B: DisjointSet): DisjointSet; 
begin 
p[B] := A; 
Union := A; 
end; 

时空复杂度分析 O(1) O(h) O(1) 空间复杂度 O(1) O(h) O(1) 时间复杂度 Union Find MakeSet 并查集的路径压缩我们看到,Find的时间复杂度取决于树的高度.在实际中,由于多次Union操作,容易导致树的高度越来越大,从而降低Find的执行效率. 
实际上在并查集中,树的具体结构并不重要,只要维持树所包含的结点不变即可(见图,两者所表示的集合等价).因此我们想到在getRoot的同时优化树的结构. 1 4 2 5 6 3 7 1 4 2 5 6 3 7 优化的getRoot function getRoot(x: Integer):Integer; begin if p[x] = 0 then getRoot := x else begin p[x] := getRoot(p[x]); getRoot := p[x]; end; end; 例1 已知有n(<=10000)个国家组成了两大集团,这两大集团之间相互对立.现在提供给你有关情报,请你判断哪些情报是与先前的情报矛盾的. 输入第一行是整数n,接下来若干行每行都是一个情报,由3个数组成.前两个数表示不同的国家,第3个数是0或者1,表示这两个国家是友好的或者对立的.最后一行是3个0表示结束. 对于每行输入,输出Reject表示该行提供的轻薄与前面已知的情报矛盾,否则输出Accept. 


例2 你和朋友玩一个游戏.他在纸上写一个长度为n(<=10000)的01串.然后你可以选择任意一段,你的朋友会告诉你这一段里所有数字之和是奇数还是偶数.你怀疑你的朋友作弊,故意告诉你错误的答案.现在你需要用一个程序来帮助你. 输入第一行是整数n,接下来若干行每行包括3个数.前两个数是你所选择的区间,第3个数是0或者1表示这个区间内所有数字之和是偶数还是奇数.最后一行是3个0表示结束. 输出第一个与前面的回答矛盾的答案的编号,如果所有答案都互不矛盾,输出-1. 


以下内容为并查集实现的程序代码:


int set[MAXN],rank[MAXN];


int FindSet(int x)


{


    if(set[x]!=x)


        set[x]=FindSet(set[x]);


    return set[x];


}
void MakeSet(int x)


{


    set[x]=x;


    rank[x]=0;


}

void Link(int a,int b)


{


    if(rank[a]>rank[b])


        set[b]=a;


    else if(rank[a]<rank[b])


        set[a]=b;


    else


    {


        set[a]=b;


        rank[b]++;


    }


}


void Union(int a,int b)


{


    Link(FindSet(a),FindSet(b));


}

原创粉丝点击