算法來自Algorithms一書1.5節,在此備忘。
該書配套網站:http://algs4.cs.princeton.edu/15uf/
算法解決的問題
解決的是動態連通性問題,給定N個點和N個點之間的連通數據,例如:
N = 10(0,1,2,3,4,5,6,7,8,9)
連通數據:
(4,3)
(3,8)
(6,5)
(9,4)
(2,1)
(8,9)
(5,0)
(7,2)
(6,1)
(1,0)
(6,7)
效果圖如下:
問題就是,如何判斷給定的兩個點是連通的?比如上圖中,(8,9)、(1,0)、(6,7)都是連通的。如何判斷這些節點中有多少個連通分量(孤島,如上面的數據構成的節點就存在兩個相互獨立的孤島)?
quick-find算法:
該算法的思路是創建一個長度爲N的數組,數組的每一個元素表示一個點,依靠判斷兩個數組元素的值是否相同來判斷這兩個元素是否是連通的,數組元素的初始值爲點的序號,表示他們互不相連。新加入一個連接時,需要將連接雙方的每一個點的值都改成一致的。
public class QuickFindUF {
private int[] id;
private int count;
public QuickFindUF(int N){
id = new int[N];
for(int i = 0;i<N;i++){
id[i] = i;
}
count = N;
}
public int find(int p){
return id[p];
}
//union q to p
public void union(int p,int q){
int pId = find(p);
int qId = find(q);
if(pId==qId) return;
for(int i = 0;i<id.length;i++){
if(id[i]==qId){
id[i] = pId;
}
}
count--;
}
public boolean connected(int p,int q){
return find(p) == find(q);
}
public int count(){
return count;
}
public static void main(String[] args) {
int N = StdIn.readInt();
QuickFindUF uf = new QuickFindUF(N);
while(!StdIn.isEmpty()){
int p = StdIn.readInt();
int q = StdIn.readInt();
if(uf.connected(p, q)){
continue;
}
uf.union(p, q);
StdOut.println(p + " " + q);
}
StdOut.println(uf.count()+" compontents");
}
}
quick-union算法
quick-union算法在對節點值的定義上有所不同,表示的是父節點,從而把所有節點變成了樹狀結構。
quick-find算法的時間主要浪費在union上,改進的quick-union算法的union方法進行了優化,不需要遍歷所有節點。這主要依賴於find方法,find方法查找的不是節點本身,而是節點所屬的根節點:
public class QuickUnionUF {
private int[] id;
private int count;
public QuickUnionUF(int N){
id = new int[N];
for(int i = 0;i<N;i++){
id[i] = i;
}
count = N;
}
public int find(int p){
while(id[p]!=p){
p = id[p];
}
return id[p];
}
//union q to p
public void union(int p,int q){
int pId = find(p);
int qId = find(q);
if(pId==qId) return;
id[pId] = qId;
count--;
}
public boolean connected(int p,int q){
return find(p) == find(q);
}
public int count(){
return count;
}
public static void main(String[] args) {
int N = StdIn.readInt();
QuickUnionUF uf = new QuickUnionUF(N);
while(!StdIn.isEmpty()){
int p = StdIn.readInt();
int q = StdIn.readInt();
if(uf.connected(p, q)){
continue;
}
uf.union(p, q);
//StdOut.println(p + " " + q);
}
StdOut.println(uf.count()+" compontents");
}
}
加權重的quick-union算法
quick-union的缺點是,在最壞情況的輸入數據時,最後形成的樹可能是畸形的,樹的深度很大,導致find方法查找根節點的時間代價越來越大。加權重的quick-union算法增加了每個節點的權重值。在初始時每個節點的權重是相同的,當節點不斷地聚集,根節點的權重不斷增大。union的時候,根據權重就能將小樹連接到大樹上,不會出現將大樹連接到小樹的情況,從而保證樹的深度不會很大。
public class WeightedQuickUnionUF {
private int[] id;
private int[] weight;
private int count;
public WeightedQuickUnionUF(int N){
System.out.println("size=" +N);
id = new int[N];
weight = new int[N];
for(int i = 0;i<N;i++){
id[i] = i;
weight[i] = 1;
}
count = N;
}
public int find(int p){
while(id[p]!=p){
p = id[p];
}
return id[p];
}
//union q to p
public void union(int p,int q){
int pId = find(p);
int qId = find(q);
if(pId==qId) return;
if(weight[pId] < weight[qId]){
//connect p to q
id[pId] = qId;
weight[qId] += weight[pId];
}else{
//connect q to p
id[qId] = pId;
weight[pId] += weight[qId];
}
count--;
}
public boolean connected(int p,int q){
return find(p) == find(q);
}
public int count(){
return count;
}
public static void main(String[] args) {
int N = StdIn.readInt();
WeightedQuickUnionUF uf = new WeightedQuickUnionUF(N);
while(!StdIn.isEmpty()){
int p = StdIn.readInt();
int q = StdIn.readInt();
if(uf.connected(p, q)){
continue;
}
uf.union(p, q);
//StdOut.println(p + " " + q);
}
StdOut.println(uf.count()+" compontents");
}
}
總的來說,quick-find方法不是基於樹來考慮的,跟多的是基於分組的思想,最終的結果是不同的孤島中的點都保存着相同的組號。quick-union算法則是基於樹的思想,兩個樹想連的時候,不會對樹的子節點進行任何操作,僅僅是對樹的根節點進行調整,這就避免了從新調整節點值的操作。這種情況下,如果再保證樹的深度不是很大,就能迅速提高運行速度。