【算法导论-36】并查集(Disjoint Set)具体解释
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("
");
}
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("
");
}
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)具体解释的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 1000平方厨房面积应配多少厨师?
- 下一篇: 蘑菇家常做法?