不相交集类

简介:

不相交集类是将一些元素合并为不相交的各个集合。在同一个集合中的元素两两等价,不同集合中的元素不等价。

1.等价关系

等价关系必须满足下面三个性质:
(1):自反性,对于集合S中的任意元素a,a R a;(R为定义的关系,比如R为<=, >=等等)
(2);对称性,a R b当且仅当b R a
(3):传递性,若a R b且b R c,则a R c

2.动态等价性问题

集合S中元素a的等价类是集合S的一个子集,该等价类中包含所有与a有等价关系的元素。所以为确定a是否等价b,只需要验证a和b是否属于同一个等价类中。

find操作,它返回包含给定元素的等价类集合的名字。比如:find(a) == find(b),那么a和b处于同一个集合中。

添加操作,如果想添加关系a ~ b,那么首先要判断a和b是否有关系(可以通过find操作验证它们是否在同一个等价类中)。如果a和b不在同一个等价类中,那么要使用求并操作union,这种操作将含有a和b的两个等价类合并为一个等价类。把这种算法称为不相交集合的求并/查找算法。该算法是动态的,因为在算法的执行过程中,集合可以通过union操作而发生改变。

解决动态等价性问题的方案有两种,第一种方案可以保证指令find能够以常数最坏情形运行时间执行,而另一种方案可以保证操作union能够以常数最坏情形运行时间执行。但是两个操作不能同时以常数最坏情形时间执行。

第一种方案:为使find操作快,可以在一个是数组中保存每个元素的等价类的名字。此时find就是简单的O(1)查找。设执union(a,b),并设a在等价类i中,b在等价类j中,此时我们扫描该数组,将所有的i都改变为j。union的时间复杂度为O(N);

第二种方案:将所有在同一个等价类中的元素放到一个链表中,更新的时候不用搜索整个数组。如果我们还要跟踪每个等价类的大小,并在执行union时将较小的等价类的名字改为较大的等价类的名字。

3.基本数据结构

可以将每个元素都看作是一棵独立的树,每个集合的名字用树根表示,可以用一个数组就可以实现该思路。将所有的元素用一个数组表示,数组每个成员s[i]表示元素i的父亲,如果i是根,那么s[i]=-1;

[cpp] view plaincopy

<embed id="ZeroClipboardMovie_1" src="https://csdnimg.cn/public/highlighter/ZeroClipboard.swf" loop="false" menu="false" quality="best" bgcolor="#ffffff" width="16" height="16" name="ZeroClipboardMovie_1" align="middle" allowscriptaccess="always" allowfullscreen="false" type="application/x-shockwave-flash" pluginspage="http://www.macromedia.com/go/getflashplayer" flashvars="id=1&width=16&height=16" wmode="transparent" style="box-sizing: border-box;">

  1. vector<int> s;//存放每个元素的根节点或父节点

对元素x的一次find(x)操作通过返回包含x的树的根而完成。

合并操作union(root1, root2),将一棵树的父节点链接到另一棵树的根节点合并两棵树。

下面是具体的例子:

初始化元素集合:0,1,2,3,4,5,6,7

image

合并操作union(4,5),将一棵树的父节点链接到另一棵树的根节点合并两棵树。

image

合并操作union(6,7),将一棵树的父节点链接到另一棵树的根节点合并两棵树。

image
image

4.灵巧求并算法

上面不相交集类中的合并函数是想当随意的,它通过使第二棵树成为第一棵树的子树而完成合并。

第一种改进方法:

对其进行简单的改进是借助任意的方法打破现在的随意性,使得总是较小的树成为较大的树的子树。将这种方法称为按大小求并。

如果不是按大小求并,那么随着合并的进行,某些集合树的深度会增加太多(大于logN),这意味这find操作的执行时间为O(logN)。

为了实现按大小合并的方法,需要记住每棵树的大小,可以让每个根元素包含它的树的大小的负值。合并的时候首先检查树的大小,将较小的树成为较大树的子树,新的树的大小为两棵树大小的和。

按大小合并的例子步骤:

image
image
image

第二种改进方法:

按高度求并,它同样保证所有的树的深度最多为O(logN)。我们跟踪每棵树的高度而不是大小并执行合并使得浅的树成为深的树的子树。只有两棵深度相等的树求并的时候树的高度才增加(树的深度加1)。

为了实现按高度求并,需要记住每棵树的高度,可以让每个根元素包含它的树的高度的负值。只有合并的两棵树高度相等的时候才需要更新树的高度(根元素的值减去1)。

按树高度合并的步骤:

image
image
image

源代码:

/*************************************************************************
    > File Name: DisjointSets.cpp
    > Author: 
    > Mail: 
    > Created Time: 2016年04月25日 星期一 11时22分48秒
 ************************************************************************/

#include <iostream>
#include <vector>
using namespace std;


/********************************************
*    类名称:不相交集合类DisjSets
********************************************/
class DisjSets{
    public:
        explicit DisjSets(int numElements);
        ~DisjSets(){}
        
        int find(int x) const;//查找
        void unionSets(int root1, int root2);//合并
        void unionSetsBySize(int root1, int root2);//按树大小合并
        void unionSetsByHeight(int root1, int root2);//按树高度合并
        void print();//输出各个不相交集合类中元素
    private:
        void print(int x);
    private:
        vector<int> s;//存放每个元素的根节点或父节点
};


/****************************************************************
*   函数名称:DisjSets(int numElements)
*   功能描述: 构造函数,同时对每个元素进行集合初始化
*   参数列表: numElements是集合中元素的个数
*   返回结果:无
*****************************************************************/
DisjSets::DisjSets(int numElements):s(numElements)
{
    for(unsigned i = 0; i < s.size(); ++i)
        s[i] = -1;
}

/****************************************************************
*   函数名称:print(int x)
*   功能描述: 打印元素x
*   参数列表: x是元素的
*   返回结果:void 
*****************************************************************/
void DisjSets::print(int x)
{
    cout << x << " "; 
    for(unsigned i = 0; i < s.size(); ++i){
        if(s[i] == x)
            print(i);
    }
}
/****************************************************************
*   函数名称:print
*   功能描述: 打印集合中的元素
*   参数列表: 无
*   返回结果:void 
*****************************************************************/
void DisjSets::print()
{
    cout << "输出不相交集合类(每行表示一个相交集合): " << endl;
    cout << "s: ";
    for(unsigned i = 0; i < s.size(); ++i)
        cout << s[i] << " ";
    cout << endl;

    for(unsigned i = 0; i < s.size(); ++i){
        if(s[i] < 0){
            print(i);
            cout << endl;
        }
    }
}

/****************************************************************
*   函数名称:find(int x) const
*   功能描述: 查找元素x处于集合的名字
*   参数列表: x是要查找的元素
*   返回结果:返回元素x的集合名字
*****************************************************************/
int DisjSets::find(int x) const
{
    if(s[x] < 0)
        return x;
    else
        return find(s[x]);
}


/****************************************************************
*   函数名称:unionSets(int root1, int root2)
*   功能描述: 合并两个集合
*   参数列表: root1表示集合1,root2表示集合2
*   返回结果:void 
*****************************************************************/
void DisjSets::unionSets(int root1, int root2)
{
    s[root2] = root1; 
}

/****************************************************************
*   函数名称:unionSetsBySize(int root1, int root2)
*   功能描述: 按集合大小合并两个集合,使得较小的树成为较大树的子树
*   参数列表: root1表示集合1,root2表示集合2
*   返回结果:void 
*****************************************************************/
void DisjSets::unionSetsBySize(int root1, int root2)
{
    if(s[root2] < s[root1]){//root2树比较大
        s[root2] += s[root1];//更新树的大小
        s[root1] = root2;//root1的父节点变为root2
    }
    else{
        s[root1] += s[root2];
        s[root2] = root1;
    }
}


/****************************************************************
*   函数名称:unionSetsByHeight(int root1, int root2)
*   功能描述: 按集合高度合并两个集合,使较浅的树成为较深的树的子树
*   参数列表: root1表示集合1,root2表示集合2
*   返回结果:void 
*****************************************************************/
void DisjSets::unionSetsByHeight(int root1, int root2)
{
    if(s[root2] < s[root1]){//root2树比较高
        s[root1] = root2;//直接合并, root1成为root2树的子树 
    }
    else{//root1树比较高,或相等。

         //如果相等则更新树的高度
        if(s[root1] == s[root2])
            s[root1]--;
         s[root2] = root1;
    }   
}


//测试主函数
int main()
{
    cout << "任意合并: " << endl;
    DisjSets disjSets(8);
    disjSets.unionSets(4, 5);
    disjSets.unionSets(6, 7);
    disjSets.unionSets(4, 6);

    disjSets.print();

    cout << "按大小合并: " << endl;
    DisjSets disjSets2(8);
    disjSets2.unionSetsBySize(4, 5);
    disjSets2.unionSetsBySize(6, 7);
    disjSets2.unionSetsBySize(4, 6);
    disjSets2.unionSetsBySize(3, 4);
    disjSets2.print();


    cout << "按高度合并: " << endl;
    DisjSets disjSets3(8);
    disjSets3.unionSetsByHeight(4, 5);
    disjSets3.unionSetsByHeight(6, 7);
    disjSets3.unionSetsByHeight(4, 6);
    disjSets3.unionSetsByHeight(3, 4);
    disjSets3.print();

    return 0;
}

运行结果为:


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

推荐阅读更多精彩内容

  • 1. 前言 并查集(Union Find Set),也称为不相交集数据结构(Disjointed Set Data...
    kophy阅读 6,640评论 1 7
  • D58沙尘暴 对于我们南方人来说,沙尘暴比较陌生,今天就来好好了解一下。 从中国各月沙尘暴的日数占比来看,4月最多...
    林楚楚楚阅读 990评论 0 0
  • 这周加班很多。Jpass项目上线,金牛项目的技术方案终于敲定下来了,算是有了重大突破和飞跃。关于自我的思考也有很多...
    dear鹿同学阅读 667评论 0 1
  • 一个时代就这么过去了 我很怀念他 泪水融进土地 岁月掩埋芳华 谁也不曾会记起 那年,那月,那日 我,你,还有他
    离岛日阅读 250评论 1 4
  • 我写诗 是的 我写诗 写诗是因为疲惫 路好长 每一个脚印 都那么漫长 每一个脚印 都覆盖了岁月 当岁月缓缓流过 声...
    星冈阅读 265评论 0 0