并查集间单个节点的转移(UVa 11987 Almost Union-Find)

从来没有这么艰难地完成一道算法题过!经过8次失败之后总算提交成功了!所以决定写一篇博文,对并查集的相关内容做一些总结。

普通并查集的操作无非是两种,find_set(x)即找到节点x所在的集合的代表节点,或者是union_set(x,y),即将x和y所在的两个集合合并起来。如下图所示,有左右两个并集

通常,我们会选用并查集中父节点为自己的元素作为这个并查集的代表,例如图中的节点a和节点e。那么,我们如何通过集合中的一个节点找到该节点所在集合的代表节点呢?其实很简单,例如上图中的d节点,它首先通过指针找到b,然后b又通过指针找到a,最后发现a的指针是指向自身的,那么a就是该并查集的代表节点了。可是现在有个问题,当一个并查集的节点数非常庞大怎么办?从一个距离代表节点a非常遥远的节点找到a,那递归的层次会变得非常可怕。而且再次查找时又要重复上述的递归过程,因此效率是非常低的。于是,在找到例如d所在集合的代表节点之后,我们就将d的指针直接指向a,而不再指向a了,这就是所谓的路径压缩。这样再下次查找d时只要一次递归就可以了。下面是代码描述:

int find_set(int x)
{
    if(fa[x]!=x)//fa[x]相当于图中的指针,例如,现在图中fa[d]就等于b
        fa[x]=find_set(fa[x]);//路径压缩
    return fa[x];
}

现在该解决两个并查集合并的问题了。从上图中我们可以轻而易举地发现两个并查集合并的规律。其实就是将其中一个并查集的代表节点,如右图中的e,它原本是指向自身的,现在将它指向左边并查集中的一个节点就可以了(并不一定需要如图中所示指向代表节点a)。此时我们再寻找例如g所在的根节点时,它依旧会调用find_set不断向前查找,但是并不会在原来的代表节点e停止递归,因为此时它已经不再指向自身了。最终,f将找到新的代表节点a。那么对于其他节点的操作也是类似的。下面是合并的代码表示:

void union_set(int x,int y)
{
    if(find_set(x)==find_set(y))//已经在同一并查集中
        return;
    else
        fa[find_set(x)]=fa[find_set(y)];//将x所在并查集的代表节点指向y所在并查集的代表节点
    return ;
}

以上这些操作其实都非常简单。但是如果我只想将例如上图中的e节点添加到最左边的那个并查集怎么办?见例题(http://uva.onlinejudge.org/index.php?option=com_onlinejudge&Itemid=8&page=show_problem&problem=3138)显然,直接将fa[e]等于a是不行的。因为有好多节点指向e,改变了fa[e],你就要从指向e的所有节点中取出一个代替e作为整个并查集的代表节点,然后将其余指向e的节点指向新的代表节点,而这些操作的时间复杂度将到达0(n)。

那么为什么会出现上述这种情况呢?其实归根到底,原因是我们选取了集合中的一个节点作为代表节点,一旦这个节点不再属于这个集合了,那么整个并查集都将进行调整。因此我们一个非常自然的想法就是,能不能用其他方式来代表一个集合呢?当然可以,如果我们设立一个数组addr[x]专门用来存放并查集的代表节点。而原本指向具体元素的指针fa[x]指向代表节点所在的位置,例如,addr[fa[x]]就是x所在集合的代表节点。当需要将节点x移到y所在的集合时,只要如下操作即可:

fa[x]=fa[y];//改变x指向的代表元素所在的地址

现在,单个节点的删除合并是解决了,那么如何合并整个并查集呢?其实只要将两个并查集的addr[]改成相同就可以了。如下:将地址fa[x]所在的代表元素赋值为fa[y]所在的代表元素。

<pre name="code" class="cpp">addr[fa[x]]=addr[fa[y]];

不过这样做还是会有问题的啦。因为假如,x所在的集合A,和y所在的集合B原先已经合并了。且fa[x]!=fa[y]。那么如上操作addr[fa[x]]是和addr[fa[y]]是相等的。但是,之后再将y所在的集合与z所在的集合C合并。那么addr[fa[y]]=addr[fa[z]]。因为fa[y]!=fa[x],所以addr[fa[x]]仍旧是原来的数值,而不是addr[fa[z]]。因此真正的合并操作是这样的:

int f1=find_set(addr[fa[x]]),f2=find_set(addr[fa[y]]);
addr[f1]=f2;

当addr[x]!=x的时候,说明两个集合合并了,且将其中一个集合的代表节点所在地址的内容变成了另一个集合代表节点的地址,这一点和基础的并查集操作非常类似。这样,并查集的单个节点的删除合并,以及整体的合并问题就都解决了。

其实核心思想无非是不再用并查集中的一个元素代表整个并查集,这样单个节点的移动就不会对集合中的其他节点带来任何影响。而在集合间的合并操作时,只需将其中一个集合的代表节点指向另一个集合的代表节点了,然后用类似于find_set的操作,就可以找到真正的代表节点了。相信去做一下上面链接的那道题目,理解会更加深刻一些。

时间: 2024-08-17 13:45:50

并查集间单个节点的转移(UVa 11987 Almost Union-Find)的相关文章

(hdu step 5.1.5)Dragon Balls(求并查集的根节点、节点数和个结点的移动次数)

题目: Dragon Balls Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 32768/32768 K (Java/Others) Total Submission(s): 562 Accepted Submission(s): 239   Problem Description Five hundred years later, the number of dragon balls will increase unexpected

hdu 2473 Junk-Mail Filter(并查集_虚节点)

感觉有些难的题,刚开始就想到了设立虚节点,但是实现总是出错,因为每次设立了虚节点之后,无法将原节点和虚节点分开,导致虚节点根本无意义. 以上纯属废话,可以忽略…… 题意—— 给定n个点(0, 1, 2, ..., n-1),可进行两种操作:1. 将两个点合并到一个集合中; 2. 将一个点从原有集合中取出.问最后点有几个集合. 很明显的并查集,包含合并,删除操作. 但是,删除某节点的时候,需要保证这个集合中,除了被删除节点的其它节点不变,这点有些难以处理. 我们知道,并查集其实是一棵棵树,我们将树

The Suspects(并查集维护根节点信息)

The Suspects Time Limit: 1000MS   Memory Limit: 20000K Total Submissions: 37090   Accepted: 17980 Description Severe acute respiratory syndrome (SARS), an atypical pneumonia of unknown aetiology, was recognized as a global threat in mid-March 2003. T

3.19 Tarjan算法与并查集解决二叉树节点间最近公共祖先的批量查询问题

[题目]: 如下的Node类是标准的二叉树节点结构: 1 public class Node{ 2 public int value; 3 public Node left; 4 public Node right; 5 6 public Node(int data){ 7 this.value = data; 8 } 9 } 再定义Query类如下: 1 public class Query{ 2 public Node o1; 3 public Node o2; 4 5 public Que

畅通工程(并查集找根节点)

畅通工程 Time Limit: 4000/2000 MS (Java/Others) Memory Limit: 65536/32768 K (Java/Others) Total Submission(s): 203 Accepted Submission(s): 168   Problem Description 某省调查城镇交通状况,得到现有城镇道路统计表,表中列出了每条道路直接连通的城镇.省政府“畅通工程”的目标是使全省任何两个城镇间都可以实现交通(但不一定有直接的道路相连,只要互相间

hdu 1829 A Bug&#39;s Life (基础种类并查集)

先说说种类并查集吧. 种类并查集是并查集的一种.但是,种类并查集中的数据是分若干类的.具体属于哪一类,有多少类,都要视具体情况而定.当然属于哪一类,要再开一个数组来储存.所以,种类并查集一般有两个数组,一个存并查集内的父子关系,一个存各个节点所属的种类关系. 以这道题为例(题意在后面,如果没有读题,可以先看完题在来看这部分)—— 这道题很明显,将bug分成两类,一公一母.但是实际上我们并不关心它是公的还是母的,只关心它们之间是同性还是异性.所以,我们可以设与并查集的根节点同性的为0,反之为1.所

并查集小结

---------------------------------------------------------------------------------------------------by Milkor -----------------------------2015.7.12 int delta[maxn];int f[maxn];void init(){ int i; for(i=0;i<maxn;i++) { f[i] = 1; //init delta }}int fin

hdu-1863畅通工程 最小生成树克鲁斯卡尔算法kruskal(并查集实现)

畅通工程 Time Limit: 1000/1000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others) Total Submission(s): 16994    Accepted Submission(s): 7134 Problem Description 省政府"畅通工程"的目标是使全省任何两个村庄间都可以实现公路交通(但不一定有直接的公路相连,只要能间接通过公路可达即可).经过调查评估,得到的统计表中列出

并查集及其优化

并查集 概述 性质 一种树形结构 并查集算法不支持分割一个集合 元素 代表元 集合中的元素,用来代表这个集合 一个集合内的所有元素组织成以代表元为根的树形结构 parent[x] 对于每一个元素,parent[x]指向x在树形结构上的父亲节点.如果x是根节点,则令parent[x] = x 操作 MakeSet 初始化并查集 设置一个代表 function MakeSet(x) // 参数 => 选定的代表元 x.parent := x Find 确定元素属于哪一个子集,返回元素所属集合的代表元