WiKi

Disjoint是“不相交”的意思。Disjoint Set高效地支持集合的合并(Union)和集合内元素的查找(Find)两种操作,所以Disjoint Set中文翻译为并查集。

就《算法导论》21章来讲,主要设计这几个知识点:

 用并查集计算图的连通区域;

 推断两个顶点是否属于同一个连通区域;

 链表实现并查集;

 Rooted tree实现并查集;

 Rooted tree实现并查集时採用rank方法和路径压缩算法。

《算法导论》21.4给出了一个结论:总计m个MAKE-SET、UNION、FIND-SET操作。当中MAKE-SET的个数为n,则採用rank和路径压缩算法实现的并查集最坏时间复杂度是O(m α(n) )。当中α是Ackerman函数的某个反函数,这个函数的值能够看成是不大于4。所以,并查集的三种典型操作的时间复杂度是线性的

相关资料

并查集的维基百科

并查集的java实现

这里依据《算法导论》的21.3节的伪代码,实现了一个泛型的并查集。输出时,打印节点及其集合的代表元素(即根元素。representative)。

import java.util.ArrayList;
import java.util.List;
import java.util.TreeSet; /**
* <p>并查集的实现<p/>
* <p>參考:《算法导论》21.3节<p/>
* <p>created by 曹艳丰<p/>
* <p>2016-08-31<p/>
*
* */
public class DisjointSet<T> {
private List<Node> forests;//全部节点
public DisjointSet(){
forests=new ArrayList<Node>();
}
/**
* 内部类,并查集的rooted node
* */ private class Node{
Node parent;
int rank;
T t;
private Node(T t){
parent=this;
rank=0;
this.t=t;
}
}
//向森林中加入节点
public void makeSet(T t){
Node node=new Node(t);
forests.add(node);
}
//将包括x和包括y的两个集合进行合并
public void union(T x,T y){
Node xNode=isContain(x);
Node yNode=isContain(y);
if (xNode!=null&&yNode!=null) {
link(findSet(xNode), findSet(yNode));
}
}
//查找到节点node的根节点
public Node findSet(Node node){
if (node!=node.parent) {
//路径压缩,參考《算法导论》插图21.5
node.parent=findSet(node.parent);
}
return node.parent;
}
//查找到节点node的根节点
public Node findSet(T t){
Node node=isContain(t);
if (node==null) {
throw new IllegalArgumentException("不含该节点! ");
}else {
return findSet(node);
} }
//将两个根节点代表的集合进行连接
private void link(Node xNode,Node yNode){
if (xNode.rank>yNode.rank) {
yNode.parent=xNode;
}else {
xNode.parent=yNode;
if (xNode.rank==yNode.rank) {
yNode.rank+=1;
}
}
}
//森林是否包括这个节点
private Node isContain(T t){
for (Node node : forests) {
if (node.t.equals(t)) {
return node;
}
}
return null;
}
@Override
public String toString() {
// TODO Auto-generated method stub
if (forests.size()==0) {
return "并查集为空!";
}
StringBuilder builder=new StringBuilder();
for (Node node : forests) {
Node root=findSet(node);
builder.append(node.t).append("→").append(root.t);
builder.append("\n");
} return builder.toString();
}
}

然后測试一下

public class Main{

    public static void main(String[] args) {
// TODO Auto-generated method stub DisjointSet<String> disjointSet=new DisjointSet<String>();
disjointSet.makeSet("cao");
disjointSet.makeSet("yan");
disjointSet.makeSet("feng");
disjointSet.union("cao", "yan");
disjointSet.union("cao", "feng");
System.out.println(disjointSet.toString());
}
}

输出格式,元素→代表元素

cao→yan
yan→yan
feng→yan

表明3个节点的代表元素一致。即处于一个集合中。

图的连通区域计算`

《算法导论》21.1节的伪代码。这里给出连通区域计算的样例。图的数据结构採用“【算法导论-35】图算法JGraphT开源库介绍 “中的无向图。

private static void connectedComponents(){
UndirectedGraph<String, DefaultEdge> g =
new SimpleGraph<>(DefaultEdge.class); String v1 = "v1";
String v2 = "v2";
String v3 = "v3";
String v4 = "v4"; // add the vertices
g.addVertex(v1);
g.addVertex(v2);
g.addVertex(v3);
g.addVertex(v4); // add edges to create a circuit
g.addEdge(v1, v2);
g.addEdge(v2, v3); //连通区域计算
//參考《算法导论》21.1节
DisjointSet<String> disjointSet=new DisjointSet<String>();
for ( String v : g.vertexSet()) {
disjointSet.makeSet(v);
} // for ( DefaultEdge e : g.edgeSet()) {
// String source=e.getSource();//protected訪问类型
// String target=e.getTarget();//protected訪问类型
// if (disjointSet.findSet(source)!=disjointSet.findSet(target)) {
// disjointSet.union(source, target);
// }
// } if (disjointSet.findSet(v1)!=disjointSet.findSet(v2)) {
disjointSet.union(v1, v2);
}
if (disjointSet.findSet(v2)!=disjointSet.findSet(v3)) {
disjointSet.union(v2, v3);
}
System.out.println(disjointSet.getSetCounter()); }

输出

v1→v2
v2→v2
v3→v2
v4→v4

v1、v2、v3的代表元素一致。表明三者在一个集合中,即三者连通。

v4是另外一个集合。

实例应用

举个样例,某人结婚时宴请宾客,A来宾认识B来宾,B来宾认识C来宾,则A、B、C安排在一桌。

A来宾认识B来宾,且A、B的熟人及其熟人的熟人(熟人链)不包括C,则C与A、B不在一桌。问。须要多少桌子才干满足要求呢?

这个样例事实上就是连通区域的详细到社交关系的1度、2度……n度关系。

略微改动并查集的实例,加入集合的计数setCounter,每次makeset时递增,union时递减。这样就得到最后的集合个数。

import java.util.ArrayList;
import java.util.List;
import java.util.TreeSet; /**
* <p>并查集的实现<p/>
* <p>參考:《算法导论》21.3节<p/>
* <p>created by 曹艳丰<p/>
* <p>2016-08-31<p/>
*
* */
public class DisjointSet<T> {
private List<Node> forests;//全部节点
private int setCounter;//集合计数
public DisjointSet(){
forests=new ArrayList<Node>();
setCounter=0;
} public int getSetCounter() {
return setCounter;
} /**
* 内部类,并查集的rooted node
* */ private class Node{
Node parent;
int rank;
T t;
private Node(T t){
parent=this;
rank=0;
this.t=t;
}
}
//向森林中加入节点
public void makeSet(T t){
Node node=new Node(t);
forests.add(node);
setCounter++;
}
//将包括x和包括y的两个集合进行合并
public void union(T x,T y){
if (x.equals(y)) {
throw new IllegalArgumentException("Union的两个元素不能相等。");
}
Node xNode=isContain(x);
Node yNode=isContain(y);
if (xNode!=null&&yNode!=null) {
link(findSet(xNode), findSet(yNode));
setCounter--;
}
}
//查找到节点node的根节点
public Node findSet(Node node){
if (node!=node.parent) {
//路径压缩,參考《算法导论》插图21.5
node.parent=findSet(node.parent);
}
return node.parent;
}
//查找到节点node的根节点
public Node findSet(T t){
Node node=isContain(t);
if (node==null) {
throw new IllegalArgumentException("不含该节点! ");
}else {
return findSet(node);
} }
//将两个根节点代表的集合进行连接
private void link(Node xNode,Node yNode){
if (xNode.rank>yNode.rank) {
yNode.parent=xNode;
}else {
xNode.parent=yNode;
if (xNode.rank==yNode.rank) {
yNode.rank+=1;
}
}
}
//森林是否包括这个节点
private Node isContain(T t){
for (Node node : forests) {
if (node.t.equals(t)) {
return node;
}
}
return null;
}
@Override
public String toString() {
// TODO Auto-generated method stub
if (forests.size()==0) {
return "并查集为空!";
}
StringBuilder builder=new StringBuilder();
for (Node node : forests) {
Node root=findSet(node);
builder.append(node.t).append("→").append(root.t);
builder.append("\n");
} return builder.toString();
}
}

连通区域的计算,只是这里输出的是集合个数。

private static void connectedComponents(){
UndirectedGraph<String, DefaultEdge> g =
new SimpleGraph<>(DefaultEdge.class); String v1 = "v1";
String v2 = "v2";
String v3 = "v3";
String v4 = "v4"; // add the vertices
g.addVertex(v1);
g.addVertex(v2);
g.addVertex(v3);
g.addVertex(v4); // add edges to create a circuit
g.addEdge(v1, v2);
g.addEdge(v2, v3); //连通区域计算
//參考《算法导论》21.1节
DisjointSet<String> disjointSet=new DisjointSet<String>();
for ( String v : g.vertexSet()) {
disjointSet.makeSet(v);
} // for ( DefaultEdge e : g.edgeSet()) {
// String source=e.getSource();//protected訪问类型
// String target=e.getTarget();//protected訪问类型
// if (disjointSet.findSet(source)!=disjointSet.findSet(target)) {
// disjointSet.union(source, target);
// }
// } if (disjointSet.findSet(v1)!=disjointSet.findSet(v2)) {
disjointSet.union(v1, v2);
}
if (disjointSet.findSet(v2)!=disjointSet.findSet(v3)) {
disjointSet.union(v2, v3);
}
System.out.println(disjointSet.getSetCounter()); }

输出是2。

【算法导论-36】并查集(Disjoint Set)具体解释的更多相关文章

  1. 【算法与数据结构】并查集 Disjoint Set

    并查集(Disjoint Set)用来判断已有的数据是否构成环. 在构造图的最小生成树(Minimum Spanning Tree)时,如果采用 Kruskal 算法,每次添加最短路径前,需要先用并查 ...

  2. 并查集(Disjoint Set)

    在一些有N个元素的集合应用问题中,我们通常是在开始时让每个元素构成一个单元素的集合,然后按一定顺序将属于同一组的元素所在的集合合并,其间要反复查找一个元素在哪个集合中.这一类问题其特点是看似并不复杂, ...

  3. 【数据结构】【计算机视觉】并查集(disjoint set)结构介绍

    1.简述 在实现多图像无序输入的拼接中,我们先使用surf算法对任意两幅图像进行特征点匹配,每对图像的匹配都有一个置信度confidence参数,来衡量两幅图匹配的可信度,当confidence> ...

  4. hdu 4641 K-string SAM的O(n^2)算法 以及 SAM+并查集优化

    链接:http://acm.hdu.edu.cn/showproblem.php?pid=4641 题意:有一个长度为n(n < 5e4)的字符串,Q(Q<=2e5)次操作:操作分为:在末 ...

  5. hdu 1233(还是畅通project)(prime算法,克鲁斯卡尔算法)(并查集,最小生成树)

    还是畅通project Time Limit: 4000/2000 MS (Java/Others)    Memory Limit: 65536/32768 K (Java/Others) Tota ...

  6. POJ 2421 Constructing Roads (Kruskal算法+压缩路径并查集 )

    Constructing Roads Time Limit: 2000MS   Memory Limit: 65536K Total Submissions: 19884   Accepted: 83 ...

  7. hdu 4641K-string SAM的O(n^2)算法 以及 SAM+并查集优化

    转载:http://www.cnblogs.com/hxer/p/5675149.html 题意:有一个长度为n(n < 5e4)的字符串,Q(Q<=2e5)次操作:操作分为:在末尾插入一 ...

  8. 距离LCA离线算法Tarjan + dfs + 并查集

    距离B - Distance in the Tree 还是普通的LCA但是要求的是两个节点之间的距离,学到了一些 一开始我想用带权并查集进行优化,但是LCA合并的过程晚于离线计算的过程,所以路径长度会 ...

  9. 并查集(Disjoint Set Union,DSU)

    定义: 并查集是一种用来管理元素分组情况的数据结构. 作用: 查询元素a和元素b是否属于同一组 合并元素a和元素b所在的组 优化方法: 1.路径压缩 2.添加高度属性 拓展延伸: 分组并查集 带权并查 ...

随机推荐

  1. Idea下mybatis的错误—Module not specified

    IDEA下使用maven的mybatis常见错误 错误类型一:导入项目引起的错误Module not specified 错误提示:idea Error Module not specified. 错 ...

  2. jQuery.inArray和splice删除数组元素

    不知道数组下标的情况下,删除数组对应元素.实例: var arrays = ['a','b','c','d']; arrays.splice($.inArray('c',arrays),1); ale ...

  3. 深入浅出WPF 第一部分(3)

    3.2.3 属性元素 <Grid HorizontalAlignment="Center" VerticalAlignment="Center"> ...

  4. 48.Cookie 管理

    转自:http://www.runoob.com/nodejs/nodejs-express-framework.html 我们可以使用中间件向 Node.js 服务器发送 cookie 信息,以下代 ...

  5. 74.QT窗口实现类的封装

    #include "mainwindow.h" #include <QApplication> #include <windows.h> //定义一个窗口类 ...

  6. 39.C语言操作数据库

    一.准备工作: sqlite3工具集:链接:https://pan.baidu.com/s/1mjufXZa 密码:2ui7 安装步骤: 打开如下文件夹,找到sqlite3.dll,并放入系统目录 2 ...

  7. MyBatis映射

    mybatis-config.xml映射文件 <?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE ...

  8. 【例题 8-15 UVA - 12174】Shuffle

    [链接] 我是链接,点我呀:) [题意] 在这里输入题意 [题解] 枚举第一段的范围[0..i] (0<=i<s) 然后看看[i+1..i+s-1],[i+s,i+s+s-1]..这些区间 ...

  9. 查看oracle数据库的启动时间

    Oracle的sys用户下有个视图v_$instance,该视图只有一行数据.通过SQL语名可查询其内容: select * from sys.v_$instance 此视图可查看很多东西,如实例名, ...

  10. gdal读写图像分块处理

    转自赵文原文 gdal读写图像分块处理(精华版) Review: 用gdal,感觉还不如直接用C++底层函数对遥感数据进行处理.因为gdal进行太多封装,如果你仅仅只是Geotif等格式进行处理,IO ...