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. MySQL Pool

    创建连接池 function SqlPool() { this.flag = true;//是否连接过 this.pool = mysql.createPool({ host : 'localhost ...

  2. iOS Scheme 跳转主流实现方案

    iOS Scheme跳转主流实现方案主要是路由跳转,目前iOS常用路由框架是JLRouter.HHRouter.MGJRouter. 但是这些路由库都各有不足,首先是JLRouter,用不到的功能繁多 ...

  3. 今日SGU 5.15

    最近事情好多,数据库作业,没天要学2个小时java,所以更新的sgu就比较少了 SGU 131 题意:给你两种小块一种,1*1,一种2*2-1*1,问你填满一个m*n的矩形有多少钟方法,n和m小于等于 ...

  4. 具体解释window.location

    window.location 对象所包括的属性 hash//从井号 (#) 開始的 URL(锚) host//主机名和当前 URL 的port号 hostname//当前 URL 的主机名 href ...

  5. 自己写unicode转换ascii码,wchar*到char*

    对于ascii码的char事实上就是unicode码wchar的首个字节码, 如wchar[20] = "qqqq"; 在内存中排码事实上是char的'q' '\0'这类.因此我们 ...

  6. {head first} --- networking 3

    Chapter 10 无线网络 续 .. Chapter 11 网络安全 Chapter 12 设计网络 涉及到物理的设计网络布局等.我没细看. 附录

  7. LeetCode Implement strStr()(Sunday算法)

    LeetCode解题之Implement strStr() 原题 实现字符串子串匹配函数strStr(). 假设字符串A是字符串B的子串.则返回A在B中首次出现的地址.否则返回-1. 注意点: - 空 ...

  8. .Net中常用的几种ActionResult

    1.ViewResult 表示一个视图结果,它根据视图模板产生应答内容.对应得Controller方法为View. 2.PartialViewResult 表示一个部分视图结果,与ViewResult ...

  9. Spark scheduler

    触发Spark scheduler的入口是调用者代码中的action操作,如groupByKey,first,take,foreach等操作.这些action操作最终会调用SparkContext.r ...

  10. springboot扫描通用的依赖模块

    将实际使用类的启动类改为如下形式启动: public class OrderApplication { public static void main(String[] args) { Object[ ...