数据结构及算法基础--并查集(union-find)

并查集,在一些有N个元素的集合应用问题中,我们通常是在开始时让每个元素构成一个单元素的集合,然后按一定顺序将属于同一组的元素所在的集合合并,其间要反复查找一个元素在哪个集合中。这一类问题近几年来反复出现在信息学的国际国内赛题中,其特点是看似并不复杂,但数据量极大,若用正常的数据结构来描述的话,往往在空间上过大,计算机无法承受;即使在空间上勉强通过,运行的时间复杂度也极高,根本就不可能在比赛规定的运行时间(1~3秒)内计算出试题需要的结果,只能用并查集来描述。

首先我们给出并查集(union-find)的api:

1242043-20171003110837849-904453171.png

在《algorithm》书中,对与uf的应用直接给出了代码,但是其中find和union方法并没有直接实现,而是空着的。书中代码如下:

image

注意:这里看见有一个StdIn的类作为类似于输入流的作用,这个在java中是不存在的,这是这本书自己构造的一个类。相信以后在这本书中也会有类似的部分,可以在网上找到其中的具体实施代码,我会发送出来的。

为什么不直接实现find和union方法呢,这里会考虑到数据结构和算法复杂度的问题。

我们将其分为:quickunion,quickfind,weightedquickunion,weightedquickunion with path compression;

1)Quick-Find

其中判断p和q两个元素是否连接便是让两个元素的id[]相同。

此时,find()便是直接返回id[p]的值

而union()便是将连接的两个集合的id[]变为一致:当然,我们首先要判断她们是不是已经连接;

image

由上图便可看出,我们union(3,4),得到两者的id[]相同,即id[3]=id[4]=3(id值为连接的集合包含的任意一个数);

得到的代码如下:

package unionFind;
public class QuickFindUF {
private int id[];
private int count;
public QuickFindUF(int N){
count=N;
id=new int[N];
for(int i=0;i<N;i++){
id[i]=i;
}
}
public int count(){
return count;
}
public boolean connected(int p,int q){
return find(p)==find(q);
}
public int find(int p){
return id[p];
}
public void union(int p,int q){
int pid=find(p);
int qid=find(q);
if(pid==qid)return;
for(int i=0;i<id.length;i++){
if(id[i]==pid)id[i]=qid;
}
count--;
}
}
2)Quick-Union

Quick-Union的结构如下图所示:

image

即每一个结点的id为上一个结点。而根节点便是root=id[root];

判断两个元素是否连接则是判断两个元素的根(root)是否相同。

find()则为找到元素的根节点;

union(p,q)即将p的根节点与q的根节点连接。

代码实现如下:

package unionFind;
public class QuickUnionUF {
private int id[];
private int count;
public QuickUnionUF(int N){
id=new int[N];
count=N;
for(int i=0;i<N;i++){
id[i]=i;
}
}
public int count(){
return count;
}
public int find(int i){
while(i!=id[i]){
i=id[i];
}
return i;
}
public void union(int p,int q){
if(find(p)==find(q))return;
id[find(p)]=find(q);
count--;
}
public boolean connected(int p,int q){
return find(p)==find(q);
}
}
3)Weighted Quick-Union

当然,我们如果胡乱的将根节点相互连接,会导致这个树的结构非常糟糕,比如:

image

我们可以看到这个树的结构非常非常糟糕。

为了避免这个情况,我们记录树的大小,并且总是将小的树连接到大的树:

image

使用这种方法可以很大程度的优化树的结构,例如上图的树我们可以变为:

image

具体实现代码如下:

package unionFind;
public class WeightedQuickUnionUF {
private int id[];
private int count;
private int sz[];
public WeightedQuickUnionUF(int N){
count=N;
id=new int[N];
sz=new int[N];
for(int i=0;i<N;i++){
id[i]=i;
sz[i]=1;
}
}
public int find(int p){
while(p!=id[p])p=id[p];
return p;
}
public void union(int p,int q){
int pid=find(p);
int qid=find(q);
if(qid==pid)return ;
if(sz[pid]<sz[qid]){
id[pid]=qid;
sz[qid]+=sz[pid];
} else{
id[qid]=pid;
sz[pid]+=sz[qid];
}
count--;
}
public int count(){
return count;
}
public boolean connected(int p,int q){
return find(p)==find(q);
}
}

4)Weighted Quick-Union with Path Compression

最优情况下,我们希望所有的节点都直接连接到根节点上,但是又不希望像QuickUnion那样大量修改连接,这时,我们可以在检查节点的同时将它与根节点直接连接。

例如,我们对下列并查集进行union(7,3);

image

在采取最优算法下,结果如下:

image

可以看出,我们将遍历到的节点都直接与根节点直接连接,这一切只需要在find内的循环进行修改就可以实现。

具体的代码如下:

package unionFind;
public class WeightedQuickUnionUFWPC {
private int id[];
private int count;
private int sz[];
public WeightedQuickUnionUFWPC(int N){
count=N;
id=new int[N];
sz=new int[N];
for(int i=0;i<N;i++){
id[i]=i;
sz[i]=1;
}
}
public int find(int p){
int root=p;
while(root!=id[root])root=id[root];
while(p!=root){
int x=p;
id[x]=root;
p=id[p];
}
return root;
}
public void union(int p,int q){
int pid=find(p);
int qid=find(q);
if(qid==pid)return ;
if(sz[pid]<sz[qid]){
id[pid]=qid;
sz[qid]+=sz[pid];
} else{
id[qid]=pid;
sz[pid]+=sz[qid];
}
count--;
}
public int count(){
return count;
}
public boolean connected(int p,int q){
return find(p)==find(q);
}
}
这四种方法能够适应不同的情况,但是对于算法复杂度来说,这四种方法就会有很大的差别:

image

对于每一项的得出,《algorithm》给出了很详细的解释,我希望自己能够有时间写一篇文章来细讲一下。(别说了。感觉还有好多坑没填)
转载出处:https://www.cnblogs.com/DSNFZ/articles/7623522.html

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,968评论 6 482
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,601评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 153,220评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,416评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,425评论 5 374
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,144评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,432评论 3 401
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,088评论 0 261
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,586评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,028评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,137评论 1 334
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,783评论 4 324
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,343评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,333评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,559评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,595评论 2 355
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,901评论 2 345

推荐阅读更多精彩内容

  • 1.问题描述: 有N个对象,对象间可以连通。假设有一个命令用来连接两个对象,将两个对象传入该命令就会连接两者,还有...
    luckygong阅读 1,058评论 0 0
  • 妈的,拖延症发作,差点没死掉。。。 动态连通性问题,有很多的点,我们来可以用union方法在两个点之间添加链接,或...
    Alexey阅读 496评论 0 0
  • 1. Java基础部分 基础部分的顺序:基本语法,类相关的语法,内部类的语法,继承相关的语法,异常的语法,线程的语...
    子非鱼_t_阅读 31,587评论 18 399
  • 那天和我妈聊起高中同学聚会,她问我那谁谁回来没有,我不耐烦的跟她解释:“那谁谁不是我的高中同学难道你忘了吗?我们...
    余周周Blink阅读 994评论 0 0
  • 牛郎骑着牛找织女去了
    雨乫雪阅读 141评论 0 0