简介:

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

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;

vector 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

#include

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 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

不相交集java_不相交集类相关推荐

  1. 蓝桥杯日期计算java_日期类的使用(java)-蓝桥杯

    蓝桥杯日期问题常考,java提供了日期类很方便: //日历类 Calendar c = Calendar.getInstance(); // 获取实例化对象 Date date =c.getTime( ...

  2. base64码 java_工具类:Java将图片变成base64码

    一个可以将图片转成base64编码的工具类/** * Copyright (c) 2011-2017, 玛雅牛 (myaniu AT gmail dot com). * * Licensed unde ...

  3. 第十二章:Java_常用类

    1. String类: 不可变的字符序列(如:String str = "atguigu"; str += "javaEE")底层使用char[]存放.Stri ...

  4. 第九章:Java_枚举类和注解

    一.枚举类 1.如何自定义枚举类. 枚举类:类的对象是有限个的,确定的. 1.1 私有化类的构造器,保证不能在类的外部创建其对象 1.2 在类的内部创建枚举类的实例.声明为:public static ...

  5. 31.基类的公有成员在派生类中的访问权限由 决定.java_基类的公有成员在派生类中的访问权限由派生方式决定。()...

    金属弯曲试验用以检验金属承受规定弯曲程度为__性能,并显示其__. 莫尔法测定Cl-含量时,要求介质的pH值在6.5~10范围内,若酸度过高则 取标示量为25mg的盐酸氯丙嗪片20片,除去糖衣后精密称 ...

  6. Java_比较器枚举类和注解集合泛型

    文章目录 Java_比较器 Java_自然排序:java.lang.Comparable Java_Comparable的实现 Java_定制排序java.util.Comparator Java_S ...

  7. 论文阅读-2 | Meta-Learning with Task-Adaptive Loss Function for Few Shot Learning

    论文目录 0 概述 0.1 论文题目 0.2 摘要 1 简介 2 相关的工作 3 提出的方法 3.1 前言 3.1.1 提出问题 3.1.2 模型无关元学习 Model-agnostic meta-l ...

  8. Android - JNI环境搭建和简单案例入门

    一.简单概念: JNI:Java Native Interface.实现java 与本地语言(android是linex系统开发的,语言是c/c++) 的相互调用. NDK:Native Develo ...

  9. Java练习:实现分数之间进行加、减、乘除操作,并用分数形式显示运算结果

    Java 在进行分数之间运算时,结果都是以小数的形式进行显示的: 如下代码演示: public class Rational_Test {public static void main(String[ ...

最新文章

  1. Python基础18-常用模块之os、sys、json、pickle、shelve、xml、re、logging、configparse、hashlib等
  2. python asyncio与aiohttp_python链家网异步IO爬虫,使用asyncio、aiohttp和aiomysql
  3. Mysql 5.7 json
  4. vue-beauty 的v-data-table数据单元不换行
  5. 6-1 线性表元素的区间删除 (10 分)
  6. 中无法打开源文件_Safari浏览器无法打开,如何解决Safari在Big Sur中崩溃的情况...
  7. 项目背景怎么描述_项目工作总结报告怎么写?搞定通用规范模型先
  8. chrome下载提示网络错误
  9. schtasks /run 拒绝访问
  10. [渝粤教育] 新乡医学院 医学微生物学 参考 资料
  11. 汉语教学备课工具推荐
  12. Tomcat启动时报错:A child container failed during start解决方案-clean
  13. 【wpf】如果让Bingding 如何让后台数据强制更新界面
  14. HTML5网页好看的一些特效
  15. 使用 SSM 框架实现发送手机短信验证码
  16. 02-关于画布、Artboard(画板)
  17. Others1_教你快速玩转chrome浏览器
  18. 国人造「人镜分离望远镜」,手机大屏能拍照、能录像…500mm长焦距,月坑都能看清楚...千元入...
  19. C语言中 * “星号”的九种用法
  20. lora 与 485 双线备份式通讯

热门文章

  1. ArcGIS Server 9.3 beta 体验三 -- 简阅 ADF 框架
  2. Lucene入门与使用(一) [转]
  3. Bailian4100 进程检测【排序】
  4. 百练(十三~十六)题解
  5. JSK-369 字符逆序【入门】
  6. Bailian3195 最大公约数【数论】
  7. CCF201403-2 窗口(100分)
  8. 【证明】—— 斐波那契
  9. NVIDIA 显卡信息(CUDA信息的查看)
  10. 高观点下的高等数学(数学分析、线性代数)