简单并查集QwQ
过了好几个月了,把并查集复习一下QwQ
按子树中的节点数合并的方法 来源于:http://www.cnblogs.com/cyjb/p/UnionFindSets.html by CYJB
模板:
按节点数合并
1 procedure init(); 2 begin 3 for i:= 1 to n do uset[i]:=-1; 4 end; 5 6 7 function find(apple:longint):longint; 8 begin 9 if (uset[apple]<0) then exit(apple); 10 11 uset[apple]:=find(uset[apple]); 12 find:=uset[apple]; 13 end; 14 { 15 //非递归版本 16 function find(apple:longint):longint; 17 var p,t:longint; //p=pin, t=temp 18 begin 19 p:=apple; 20 while (uset[p]>=0) do p:=uset[p];//找根节点 21 while (apple<>p) do 22 begin 23 t:=uset[apple];//备份他的爸爸 24 uset[apple]:=p;//更新当前指向根节点 25 apple:=t; //使下一次迭代来更新它爸爸 26 end; 27 exit(apple); 28 end; 29 } 30 procedure union(aa,bb:longint); 31 var 32 t1,t2:longint; 33 begin 34 t1:=find(aa); 35 t2:=find(bb); 36 if t1=t2 then exit; 37 if uset[t1]<uset[t2] then begin 38 inc(uset[t1],uset[t2]); //统计根节点的点的个数 39 uset[t2]:=t1; //合并树 40 end 41 else begin 42 inc(uset[t2],uset[t1]); //统计根节点的点的个数 43 uset[t1]:=t2; //合并树 44 end; 45 end;
路径压缩+启发式合并:
procedure init(); begin for i:= 1 to n do father[i]:=i; for i:= 1 to n do rank[i]:=0; end; function find(apple:longint):longint; begin if father[apple]<>apple then father[apple]:=find(father[apple]); exit(father[apple]); end; procedure union(aa,bb:longint); begin t1:=find(aa); t2:=find(bb); if t1=t2 then exit; if rank[t1]>rank[t2] then father[t2]:=t1 else begin father[t1]:=t2; if rank[t1]=rank[t2] then inc(rank[t2]); end; end;
主要思想以及注意事项:
初始化father
先读入a,b, 然后找两个点的father, 然后合并(b接着a)
else : 在luogu看到有这么一个挺好的想法,就是不用初始化,在find过程里面如果father[apple]<>0就find(father[apple])否则返回apple
两个优化:
1 .路径压缩
并查集运用树形结构,一个集合便是一棵树,根节点就是father
方法: 找father的时候沿途可以顺带给该集合内的所有元素记录下找到的father ,不增加时间复杂度,却使得今后find的操作比较快。 // 更新所有子树的father
注意:在判断时还是要每次都要在递归找一次father再判断//所谓优化只是减少一点检查时递归查找的路径
假设下面的情况,我们现在如果要给1和2连起来,那么只会更新2的father为1,后面3和4的father还是2, 那优化有什么用呢,假设之前还有4和3的连线,如果按照原始法judge时是4-3-2-1,而优化版的很明显是4-2-1
2.启发式合并按秩合并
让深度小的数成为深度较大的树的子树——将比较矮的树作为子树,将它的树根指向较高树的树根。
find+路径压缩: 【事实证明递归查找比非递归查找的版本快,别问我怎么知道。。。】
function find(apple:longint):longint; begin if father[apple]<>apple then father[apple]:=find(father[apple]); exit(father[apple]); end;
union+启发式合并按秩合并:
procedure union(aa,bb:longint); begin t1:=find(aa); t2:=find(bb); if t1=t2 then exit; if rank[t1]>rank[t2] then father[t2]:=t1 //如果有统计子树(集合)节点数记得加上,下面也是 else begin father[t1]:=t2; if rank[t1]=rank[t2] then inc(rank[t2]); end; end;
judge:
function judge(x,y:longint):Boolean; begin if find(x)=find(y) then exit(true) else exit(false); end;
其他
除了按秩合并,并查集还有一种常见的策略,
就是按集合中包含的元素个数(或者说树中的节点数)合并,
将包含节点较少的树根,指向包含节点较多的树根。
这个策略与按秩合并的策略类似,同样可以提升并查集的运行速度,而且省去了额外的 rank 数组。
这样的并查集具有一个略微不同的定义,
即若 uset 的值是正数,则表示该元素的父节点(的索引);若是负数,则表示该元素是所在集合的代表(即树根),
而且值的相反数即为集合中的元素个数。//指的是值为负数的情况
小树指向大树 !!!!!
procedure init(); begin for i:= 1 to n do uset[i]:=-1; end; function find(apple:longint):longint; begin if (uset[apple]<0) then exit(apple); uset[apple]:=find(uset[apple]); find:=uset[apple]; end; { //非递归版本 function find(apple:longint):longint; var p,t:longint; //p=pin, t=temp begin p:=apple; while (uset[p]>=0) do p:=uset[p];//找根节点 while (apple<>p) do begin t:=uset[apple];//备份他的爸爸 uset[apple]:=p;//更新当前指向根节点 apple:=t; //使下一次迭代来更新它爸爸 end; exit(apple); end; } procedure union(aa,bb:longint); var t1,t2:longint; begin t1:=find(aa); t2:=find(bb); if t1=t2 then exit; if uset[t1]<uset[t2] then begin inc(uset[t1],uset[t2]); //统计根节点的点的个数 uset[t2]:=t1; //合并树 end else begin inc(uset[t2],uset[t1]); //统计根节点的点的个数 uset[t1]:=t2; //合并树 end; end;
例题:
简单入门: luogu P1551亲戚 简单并查集裸题
luogu P1892 团伙 注意怎样处理敌人的敌人就可以了
luogu P1455 搭配购买 01背包+并查集
格子游戏 理解题意+维度转换
luogu P2814 家谱 还没AC。。。。
打击犯罪 暴力+并查集+逆向思维