标签:
说道并查集,大家一定对于以多叉树状结构为基础的并查集并不陌生最常见的两种写法如下
1 private function getfat(x:longint):longint; 2 begin 3 while x<>c[x] do x:=c[x]; 4 exit(x); 5 end;
1 private function getfat(x:longint):longint; 2 begin 3 if x<>c[x] then exit(getfat(c[x])) else exit(x); 4 end;
其实并查集的核心操作就是一个寻根操作。代码都很短,也很不容易写错,原理也很明显——假如两个点已经到了一个块里的话,那么由于在树结构中,则两者必然有个共同的祖先节点——根节点。。。于是我就这样子学会了并查集
直到某一天在网上看到某神犇在Pascal程序中采用了如下写法——
1 private function getfat(x:longint):longint; 2 begin 3 if x<>c[x] then c[x]:=getfat(c[x]); 4 exit(c[x]); 5 end;
这个里面的程序和之前的第一段程序相当类似,但有着很大的区别——这个里面不单单是用递归的方式找到了根节点,同时又变更着各个中间各级父亲节点的父节点——实际上都变成了树的根节点,也就是说每个getfat操作之后,得到的树在此点所涉及的支链上都已经被撸到了根节点上(HansBug:喂喂喂别想歪了),这样子对于一再的重复操作可以起到减少树的高度的神奇作用。
说了这些,于是来一个小小的实验比对下——
准备:一个并查集程序,实现功能——输入N、M、T,表示原来有N堆,M次操作,T(1表示用循环并查集;2表示用简单递归并查集;3表示用最下面的新方法);接下来输入M行,每行"z x y","1 x y"表示将x和y所在块合并,"2 x y"表示查看x和y在不在一起。就是这样一个简单的问题
程序如下:(本程序中均不开inline优化,同时均在我家的烂电脑上进行测试,速度比较逗——这么告诉你们吧,我曾经不止一次提心吊胆地把我在本机上单组数据就运行了快2秒的程序交上BZOJ,结果一瞬间AC,时间是1000ms多一点,吓尿了)
1 var 2 i,j,k,l,m,n,t:longint; 3 c:array[0..1000000] of longint; 4 function getfat1(x:longint):longint; 5 begin 6 while x<>c[x] do x:=c[x]; 7 exit(x); 8 end; 9 function getfat2(x:longint):longint; 10 begin 11 if x<>c[x] then exit(getfat2(c[x])) else exit(x); 12 end; 13 function getfat3(x:longint):longint; 14 begin 15 if x<>c[x] then c[x]:=getfat3(c[x]); 16 exit(c[x]); 17 end; 18 function getfat(x,y:longint):longint; 19 begin 20 case y of 21 1:exit(getfat1(x)); 22 2:exit(getfat2(x)); 23 3:exit(getfat3(x)); 24 end; 25 end; 26 begin 27 readln(n,m,t); 28 for i:=1 to n do c[i]:=i; 29 for i:=1 to m do 30 begin 31 readln(j,k,l); 32 case j of 33 1:c[getfat(k,t)]:=c[getfat(l,t)]; 34 2:writeln(c[getfat(k,t)]=c[getfat(l,t)]); 35 end; 36 end; 37 end.
于是开始很开心的对拍:
1.N=1000 M=1000000 —— 结果发现,循环实现的并查集居然最好,大约1.2s左右,然后两种递归实现的并查集都差不多少,大约都1.3s左右,呵呵呵呵。。。神奇
2.N=100 M=10000000 ——现在的数据一定更容易产生高大的树,一方面合并增多了,另一方面原始的块数少了,也就意味着块会更加集中。。。
于是结果是——最新的方法明显强大,大约8-10秒,而这个时候循环实现的并查集还是比递归的快,大约12s,而递归的就要至少13s啦.。。
其实——在原来的并查集中每次合并操作(c[getfat(x)]:=getfat(y);)都已经将一棵树的根节点直接连接到了另一棵树的根节点上,两棵树实现了简单合并,但是对于两点是否在同一块内的访问操作时,原来的程序没有减少高度,也就是说对于同一个树的状态访问多少次都是一样要跑那么高,这样子完全可以构造出很高大的一棵树(通过节节合并不难构造出),每次都会爬好高的树,当然慢啦。。。而这个将每次的树直接缩节后,那么高的树就只需要跑一次了,以后直接一下子就升到根节点上啦!!!哥就是这么霸气!!!
总结:事实证明了新方法的强大,在关于树的问题上,对于树结构的不断优化也必将不断提高算法的速度——这是关于树的问题亘古不变的一个定则!!!最重要的是有get了一个新技能,可以更开心的刷题啦哈哈哈哈^_^
(附1:随机数生成器)
1 var 2 i,j,k,l,m,n:longint; 3 a:array[0..10000000,1..3] of longint; 4 begin 5 readln(n,m); 6 randomize; 7 for i:=1 to m do 8 begin 9 a[i,1]:=random(2)+1; 10 a[i,2]:=random(n)+1; 11 a[i,3]:=random(n)+1; 12 end; 13 assign(output,‘bxj1.txt‘); 14 rewrite(output); 15 writeln(n,‘ ‘,m,‘ ‘,1); 16 for i:=1 to m do writeln(a[i,1],‘ ‘,a[i,2],‘ ‘,a[i,3]); 17 close(output); 18 assign(output,‘bxj2.txt‘); 19 rewrite(output); 20 writeln(n,‘ ‘,m,‘ ‘,2); 21 for i:=1 to m do writeln(a[i,1],‘ ‘,a[i,2],‘ ‘,a[i,3]); 22 close(output); 23 assign(output,‘bxj3.txt‘); 24 rewrite(output); 25 writeln(n,‘ ‘,m,‘ ‘,3); 26 for i:=1 to m do writeln(a[i,1],‘ ‘,a[i,2],‘ ‘,a[i,3]); 27 close(output); 28 end.
(附2:bat小程序,用于对拍,fuckbxj.exe表示随机数生成器)
1 @echo off 2 set /a s=0 3 :1 4 set /a s=s+1 5 echo Test %s% 6 rem 此处两个数分别表示N和M 7 echo 100 10000000|fuckbxj.exe 8 echo 循环求并查集 9 echo.|time 10 type bxj1.txt|bxj.exe >bxj1.out 11 echo.|time 12 echo 递归求并查集 13 echo.|time 14 type bxj2.txt|bxj.exe >bxj2.out 15 echo.|time 16 echo 优化递归并查集 17 echo.|time 18 type bxj3.txt|bxj.exe >bxj3.out 19 echo.|time 20 fc bxj1.out bxj2.out 21 fc bxj1.out bxj3.out 22 pause 23 goto 1
标签:
原文地址:http://www.cnblogs.com/HansBug/p/4296675.html