<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
1.優秀的演演算法因為能夠解決實際問題而變得更為重要;
2.高效演演算法的程式碼也可以很簡單;
3.理解某個實現的效能特點是一個挑戰;
4.在解決同一個問題的多種演演算法之間進行選擇時,科學方法是一種重要的工具;
5.迭代式改進能夠讓演演算法的效率越來越高效;
動態連線:輸入是一對整數對的序列,其中每個整數代表某種型別的物件(或觸點),我們將整數對p q 解釋為意味著p連線到q。我們假設“連線到”是等價關係:
等價關係將物件劃分為多個等價類 或連線的元件。等價類稱為連通分量或分量。
我們的目標是編寫一個程式,以從序列中過濾掉多餘的對:當程式從輸入中讀取整數對 p q時,只有在該對點不等價的情況下,才應將對寫入到輸出中,並且將p連線到q。如果等價,則程式應忽略整數對pq 並繼續讀取下對。
動態連通性問題的應用:
在更高的抽象層次上,可以將輸入的所有整數看做屬於不同的數學集合。
設計演演算法的第一個任務就是精確地定義問題。
演演算法解決的問題越大,它完成任務所需的時間和空間可能越多。我們不可能預先知道這其間的量化關係,通常只會在發現解決問題很困難,或是代價巨大,或是發現演演算法所提供的資訊比原問題所需要的更加有用時修改問題。例如,連通性問題只要求我們的程式能夠判斷出給定的整數對是否相連,但並沒有要求給出兩者之間的通路上的所有連線。這樣的要求更難,並會得出另一組不同的演演算法。
為了定義和說明問題,先設計一份API 來封裝基本操作: 初始化,連線兩個觸點,查詢某個觸點的分量 ,判斷兩個觸點是否屬於同一分量,分量的數量:
/// <summary> /// 動態連通API /// </summary> public interface IUnionFind { /// <summary> /// 連線 /// </summary> /// <param name="p"></param> /// <param name="q"></param> void Union(int p, int q); /// <summary> /// 查詢觸點 p 的分量識別符號 /// </summary> /// <param name="p"></param> /// <returns></returns> int Find(int p); /// <summary> /// 判斷兩個觸點是否處於同一分量 /// </summary> /// <param name="p"></param> /// <param name="q"></param> /// <returns></returns> bool Connected(int p, int q); /// <summary> /// 連通分量的數量 /// </summary> /// <returns></returns> int Count(); }
為解決動態連通性問題設計演演算法的任務轉化為實現這份API:
資料結構的性質會直接影響演演算法的效率。這裡,以觸點為索引,觸點和連線分量都是用 int 值表示,將會使用分量中某個觸點的值作為分量的識別符號。所以,一開始,每個觸點都是隻含有自己的分量,分量識別符號為觸點的值。由此,可以初步實現一部分方法:
public class FirstUnionFind:IUnionFind { private int[] id;//* 分量id 以觸點作為索引 private int count;//分量數量 public FirstUnionFind(int n) { count = n; id = new int[n]; for (var i = 0; i < n; i++) { id[i] = i; // 第一個 i 作為觸點,第二個 i 作為觸點的值 } } public int Count() { return count; } public bool Connected(int p, int q) { return Find(p) == Find(q); } public int Find(int p) { } public void Union(int p, int q) { } }
Union-find 的成本模型 是陣列的存取次數(無論讀寫)。
quick-find 演演算法是保證當且僅當 id[p] 等於 id[q] 時,p 和 q 是連通的。也就是說,在同一個連通分量中的所有觸點在 id[ ] 中的值全部相等。
所以Find 方法只需返回id[q],Union 方法需要先判斷Find(p) 是否等於Find(q) ,若相等直接返回;若不相等,需要將 q 所在的連通分量中所有觸點的 id [ ] 值全部更新為 id[p]。
public class QuickFindUF: IUnionFind { private int[] id;//* 分量id 以觸點作為索引 private int count;//分量數量 public QuickFindUF(int n) { count = n; id = new int[n]; for (var i = 0; i < n; i++) { id[i] = i; // 第一個 i 作為觸點,第二個 i 作為觸點的值 } } public int Count() { return count; } public bool Connected(int p, int q) { return Find(p) == Find(q); } public int Find(int p) { return id[p]; } public void Union(int p, int q) { var pID = Find(p); var qID = Find(q); if (pID == qID) return; for (var i = 0; i < id.Length; i++) { if (id[i] == qID) id[i] = pID; } count--; //連通分量減少 } public void Show() { for(var i = 0;i<id.Length;i++) Console.WriteLine("索引:"+i+",值:"+ id[i] ); Console.WriteLine("連通分量數量:"+count); } }
Find() 方法只需存取一次陣列,所以速度很快。但是對於處理大型問題,每對輸入 Union() 方法都需要掃描整個陣列。
每一次歸併兩個分量的 Union() 方法存取陣列的次數在 N+3 到 2N+1 之間。由程式碼可知,兩次 Find 操作存取兩次陣列,掃描陣列會存取N次,改變其中一個分量中所有觸點的值需要存取 1 到 N - 1 次(最好情況是該分量中只有一個觸點,最壞情況是該分量中有 N - 1個觸點),2+N+N-1。
如果使用quick-find 演演算法來解決動態連通性問題並且最後只得到一個連通分量,至少需要呼叫 N-1 次Union() 方法,那麼至少需要 (N+3)(N-1) ~ N^2 次存取陣列,是平方級別的。
quick-union 演演算法重點提高 union 方法的速度,它也是基於相同的資料結構 -- 已觸點為索引的 id[ ] 陣列,但是 id[ ] 的值是同一分量中另一觸點的索引(名稱),也可能是自己(根觸點)——這種聯絡成為連結。
在實現 Find() 方法時,從給定觸點,連結到另一個觸點,知道到達根觸點,即連結指向自己。同時修改 Union() 方法,分別找到 p q 的根觸點,將其中一個根觸點連結到根觸點。
public class QuickUnionUF : IUnionFind { private int[] id; private int count; public QuickUnionUF(int n) { count = n; id = new int[n]; for (var i = 0; i < n; i++) { id[i] = i; // 第一個 i 作為觸點,第二個 i 作為觸點的值 } } public int Count() { return count; } public bool Connected(int p, int q) { return Find(p) == Find(q); } public int Find(int p) { while (p != id[p]) p = id[p]; return p; } public void Union(int p, int q) { var pRoot = Find(p); var qRoot = Find(q); if (pRoot == qRoot) return; id[pRoot] =qRoot; count--; //連通分量減少 } public void Show() { for (var i = 0; i < id.Length; i++) Console.WriteLine("索引:" + i + ",值:" + id[i]); Console.WriteLine("連通分量數量:" + count); } }
id[ ] 陣列用父連結的形式表示一片森林,用節點表示觸點。無論從任何觸點所對應的節點隨著連結查詢,最後都將到達含有該節點的根節點。初始化陣列之後,每個節點的連結都指向自己。
定義:一棵樹的大小是它的節點的數量。樹中一個節點的深度是它到根節點的路徑上連結數。樹的高度是它的所有節點中的最大深度。
quick-union 演演算法比 quick-find 演演算法更快,因為它對每對輸入不需要遍歷整個陣列。
分析quick-union 演演算法的成本比 quick-find 演演算法的成本要困難,因為quick-union 演演算法依賴於輸入的特點。在最好的情況下,find() 方法只需存取一次陣列就可以得到一個觸點的分量表示;在最壞情況下,需要 2i+1 次陣列存取(i 時觸點的深度)。由此得出,該演演算法解決動態連通性問題,在最佳情況下的執行時間是線性級別,最壞情況下的輸入是平方級別。解決了quick-find 演演算法中 union() 方法總是線性級別,解決動態連通性問題總是平方級別。
quick-union 演演算法中 find() 方法存取陣列的次數為 1(到達根節點只需存取一次) 加上 給定觸點所對應節點的深度的兩倍(while 迴圈,一次讀,一次寫)。union() 存取兩次 find() ,如果兩個觸點不在同一分量還需加一次寫陣列。
假設輸入的整數對是有序的 0-1, 0-2,0-3 等,N-1 對之後N個觸點將全部處於相同的集合之中,且得到的樹的高度為 N-1。由上可知,對於整數對 0-i , find() 存取陣列的次數為 2i + 1,因此,處理 N 對整數對所需的所有存取陣列的總次數為 3+5+7+ ......+(2N+1) ~ n^2
簡單改動就可以避免 quick-union演演算法 出現最壞情況。quick-union演演算法 union 方法是隨意將一棵樹連線到另一棵樹,改為總是將小樹連線到大樹,這需要記錄每一棵樹的大小,稱為加權quick-union演演算法。
程式碼:
public class WeightedQuickUnionUF: IUnionFind { int[] sz;//以觸點為索引的 各個根節點對應的分量樹大小 private int[] id; private int count; public WeightedQuickUnionUF(int n) { count = n; id = new int[n]; sz = new int[n]; for (var i = 0; i < n; i++) { id[i] = i; // 第一個 i 作為觸點,第二個 i 作為觸點的值 sz[i] = 1; } } public int Count() { return count; } public bool Connected(int p, int q) { return Find(p) == Find(q); } public int Find(int p) { while (p != id[p]) p = id[p]; return p; } public void Union(int p, int q) { var pRoot = Find(p); var qRoot = Find(q); if (pRoot == qRoot) return; if (sz[pRoot] < sz[qRoot]) { id[pRoot] = qRoot; } else { id[qRoot] = pRoot; } count--; //連通分量減少 } public void Show() { for (var i = 0; i < id.Length; i++) Console.WriteLine("索引:" + i + ",值:" + id[i]); Console.WriteLine("連通分量數量:" + count); } }
加權 quicj-union 演演算法最壞的情況:
這種情況,將要被歸併的樹的大小總是相等的(且總是 2 的 冥),都含有 2^n 個節點,高度都正好是 n 。當歸並兩個含有 2^n 個節點的樹時,得到的樹含有 2 ^ n+1 個節點,高度增加到 n+1 。
節點大小: 1 2 4 8 2^k = N
高 度: 0 1 2 3 k
k = logN
所以加權 quick-union 演演算法可以保證對數級別的效能。
對於 N 個觸點,加權 quick-union 演演算法構造的森林中的任意節點的深度最多為logN。
對於加權 quick-union 演演算法 和N 個觸點,在最壞情況下 find,connected 和 union 方法的成本的增長量級為 logN。
對於動態連通性問題,加權 quick-union 演演算法 是三種演演算法中唯一可以用於解決大型問題的演演算法。加權 quick-union 演演算法 處理 N 個觸點和 M 條連線時最多存取陣列 c M logN 次,其中 c 為常數。
三個演演算法處理一百萬個觸點執行時間對比:
三個演演算法效能特點:
在檢查節點的同時將它們直接連線到根節點。
實現:為 find 方法新增一個迴圈,將在路徑上的所有節點都直接連結到根節點。完全扁平化的樹。
研究各種基礎問題的基本步驟:
到此這篇關於C#並查集(union-find)演演算法的文章就介紹到這了。希望對大家的學習有所幫助,也希望大家多多支援it145.com。
相關文章
<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
综合看Anker超能充系列的性价比很高,并且与不仅和iPhone12/苹果<em>Mac</em>Book很配,而且适合多设备充电需求的日常使用或差旅场景,不管是安卓还是Switch同样也能用得上它,希望这次分享能给准备购入充电器的小伙伴们有所
2021-06-01 09:31:42
除了L4WUDU与吴亦凡已经多次共事,成为了明面上的厂牌成员,吴亦凡还曾带领20XXCLUB全队参加2020年的一场音乐节,这也是20XXCLUB首次全员合照,王嗣尧Turbo、陈彦希Regi、<em>Mac</em> Ova Seas、林渝植等人全部出场。然而让
2021-06-01 09:31:34
目前应用IPFS的机构:1 谷歌<em>浏览器</em>支持IPFS分布式协议 2 万维网 (历史档案博物馆)数据库 3 火狐<em>浏览器</em>支持 IPFS分布式协议 4 EOS 等数字货币数据存储 5 美国国会图书馆,历史资料永久保存在 IPFS 6 加
2021-06-01 09:31:24
开拓者的车机是兼容苹果和<em>安卓</em>,虽然我不怎么用,但确实兼顾了我家人的很多需求:副驾的门板还配有解锁开关,有的时候老婆开车,下车的时候偶尔会忘记解锁,我在副驾驶可以自己开门:第二排设计很好,不仅配置了一个很大的
2021-06-01 09:30:48
不仅是<em>安卓</em>手机,苹果手机的降价力度也是前所未有了,iPhone12也“跳水价”了,发布价是6799元,如今已经跌至5308元,降价幅度超过1400元,最新定价确认了。iPhone12是苹果首款5G手机,同时也是全球首款5nm芯片的智能机,它
2021-06-01 09:30:45