《演算法》筆記 2 動態連通性問題

2022-06-05 18:00:12 字數 3326 閱讀 6294

在基礎部分的最後一節,作者用乙個現實中應用非常廣泛的案例,說明以下幾點:

動態連通性問題的輸入是一列整數對,其中的每個整數都表示乙個某種型別的物件,一對整數對p q可以理解為「p和q是相連的」。相連是一種等價關係,其具有:

程式的目標是過濾掉序列中無意義的整數對,當程式從輸入中讀取了整數對pq時,如果已知的所有整數對都不能說明p和q是相連的,那麼則將這一對整數寫入到輸出中。如果已知的資料可以說明p和q是相連的,那麼程式應該忽略pq這對整數並繼續處理輸入中的下一對整數。

動態連通性問題的實際應用很多,比如:檢查通訊網路中計算機之間是否連通、電子電路中的觸點是否連線或者社交網路中的人是否相識等等。

接下來會統一使用網路方面的術語,將整數稱為觸點、整數對稱為連線、等價類稱為連通分量或分量。

明確需要解決的問題後,就可以抽象出演算法的api了,包括

void union(int p,int q)  //在p q之間新增一條連線,將兩個分量歸併

int find(int p) //返回p所在分量的識別符號

boolean connected(int p int q) //判斷兩個觸點是否在同一分量

int count() //連通分量的數量

通用**

演算法的基本**如下,其中find()、union()方法在各種演算法中有不同的實現。

public class uf 

}public int find(int p)

public int count()

public boolean connected(int p, int q)

public void union(int p, int q)

public static void main(string args)

stdout.println(uf.count() + " components");

}}

quick-find演算法

實現最直接的方法是當p和q連通時,讓id[p]=id[q],所以同一連通分量中的所有觸點在id中的值是全部相同的。find(p)只需返回id[p],但union()方法在將p和q設定為同一連通分量時比較麻煩,需要改變p或q所在分量中所有元素的值。

public int find(int p) 

public void union(int p, int q)

for(int i=0;i這種方法得到的find操作的速度很快,只需要訪問id陣列一次,但union操作由於每次都要掃瞄整個陣列而變得很慢。這裡**中是重新整理q所在的分量,也可以改為重新整理p所在的分量。

分析在分析演算法的成本時,主要考慮的是陣列的訪問次數(包括讀、寫)。

那麼quick-find演算法的成本如何呢?假設問題的規模為n,則陣列的長度為n,那麼每次find()只訪問陣列一次,union()訪問陣列的次數在(n+3)到(2n+1)之間。

開頭的兩次find呼叫訪問陣列2次,for迴圈會訪問陣列n次,然後:

a.在最好的情況下,q所在的連通分量中,只有q乙個成員,內層的if判斷只會成立一次。所以總次數=2+n+1=n+3次。

b.在最壞的情況下,除了元素p,其餘成員都與q在同乙個連通分量中,那麼內層的if判斷會成立n-1次,總次數=2+n+n-1=2n+1。

假設最後只得到乙個連通分量,則至少需要呼叫union方法n-1次,所以最好情況下,union方法的呼叫總次數為(n-1)*(n+3),用~n^2來近似表示,可見在最終得到少數連通分量的場景下,quick-find演算法的執行時間隨問題規模的增長是平方級別的。平方級別、立方級別、指數級別的演算法等都無法用來解決大型問題。

quick-union演算法

接下來的quick-union演算法相比quick-find演算法,其union的速度較快。它也採用相同的資料結構——以觸點作為索引的id陣列,但這次讓每個陣列元素都代表同一連通分量中另乙個分量的名稱,實際上同一連通分量的節點構成了一棵樹,但陣列元素的值等於其索引時,它就是這棵樹的根節點。find()操作返回的就是一棵樹的根節點,如果兩個分量有相同的根節點則表示它們互相連通,否則呼叫union()方法將其中的一棵樹合併到另一顆樹上,就完成了兩個連通分量的歸併。

實現public int find(int p)

return p;

}public void union(int p, int q)

id[proot] = qroot;

count--;

}

分析

quick-union演算法的成本依賴輸入的特點,在最好的情況下,一棵樹只有根節點自己,find()只需訪問陣列一次;而在最壞的情況下,樹的結構為一顆深度為節點數-1,每一層都只有乙個節點,設數的深度為n,這時如果需要查詢最底層節點的根節點,find()方法需要訪問陣列n+(n+1)=2n+1次。

while (p != id[p])
在這種最壞的情況下,為了讓樹的深度最大,每次新的節點都會連線到樹的最底層,輸入整數對是有序的0-1,0-2,0-3等,其中0鏈結接到1,1鏈結到2,2鏈結到3,可見union的訪問次數為:(2n+1)+1+1=2n+3次

public void union(int p, int q) 

id[proot] = qroot; //1次

count--;

}

所以忽略常數3,處理n個節點的訪問次數為2(1+2+...+n)=2n(n-1)/2,用~n^2來近似表示,則最壞情況為平方級別。

加權quick-union演算法

quick-union演算法的速度取決於生成的樹的深度,深度越大速度越慢。union操作時,避免將較大的樹鏈結到較小的樹可以有效控制樹的深度,從而大大改進演算法的效率,這便是加權quick-union演算法。加權quick-union演算法是在quick-union演算法的基礎上,增加乙個陣列來記錄每顆樹的權重(樹包含的節點數量),然後在union操作時將權重小的樹鏈結到權重大的樹。

實現

public void union(int a, int b) 

if (size[aroot] < size[broot]) else

count--;

}

分析

關於加權quick-union演算法的最壞情況,既較要被歸併的兩棵樹的大小總數相等的,且大小都是2的冪(滿二叉樹),此時n個節點的樹的深度為lg(n),結合對quick-union演算法的分析可知加權quick-union演算法的成本增長數量級為對數級別。

綜上,動態連通性問題的求解過程便是乙個定義問題、給出初級演算法的實現、當演算法能解決問題的規模達不到期望時逐步改進演算法的過程。並且用經驗性的分析(和數學分析)驗證改進後的效果,盡量為演算法在最快情況下的效能提供保證,但在處理普通資料時也要有良好的效能。

動態連通性問題

首先定義演算法的api 方法作用 uf int n 初始化觸點及其他資料 int find int p 返回p所在連通分量的識別符號 int union int p,int q 在p和q之間新增一條線 int count 返回連通分量的數量 boolean connected int p,int q...

連通性問題

1 伺服器可以ping通客戶端,說明伺服器和客戶端之間的鏈路是通的。客戶端不能ping通伺服器,很可能是防火牆的原因,包括伺服器本身自帶的防火牆和伺服器與交換機之間的cisco asa 5505防火牆。防火牆的訪問規則中清除禁止ping入之類的規則,或者清除拒絕接收icmp包的規則。2 ping不通...

SOJ 連通性問題

description 關係r具有對稱性和傳遞性。數對p q表示prq,p和q是0或自然數,p不等於q。要求寫乙個程式將數對序列進行過濾,如果乙個數對可以通過前面數對的傳遞性得到,則將其濾去。例如 輸入 輸出 連通性 3 4 3 4 4 9 4 9 8 0 8 0 2 3 2 3 5 6 5 6 2...