培訓- 2 素集合とグラフ探索
素集合とグラフ探索
Kuroineko Ginnoneko
September 29, 2013
1 Disjoint Sets
Introduction
互斥集就是一些兩兩交集皆為空集合的集合,常常被用在處理「互斥」或是「分 類」的問題上。
實作 Disjoint set 時我們用樹來表示集合,一棵樹就是一個集合,以根代表整
個集合。
1.1 Find
我們有一個陣列p[x] 代表x的parent 是誰,若x是root 則它的parent設為 自身,一開始每個 p[x] =x。
若要找尋x 所屬的集合則迭代過 p[x]陣列就行了。
Algorithm 1Naive Find
1: function Find(x)
2: if x ̸=p[x] then
3: returnFind(p[x])
4: else
5: returnx
6: end if
7: end function
find 的過程中若引入一個稱為"Path Compression" 的概念,則速度會加快非 常多。作法就是把遞迴途經的元素的parent 順便設成root。
I
1.2 Union 素集合とグラフ探索
Algorithm 2Find using path compression
1: function Find(x)
2: if x ̸=p[x] then
3: p[x]← Find(p[x])
4: returnp[x]
5: else
6: returnx
7: end if
8: end function
1.2 Union
把 x 和 y 所在的集合合併,只要把其中一個樹根指向另一個樹根就好了,就像 樹的合併一樣。
Algorithm 3Union
1: function Union(x, y)
2: p[Find(x)]← Find(y)
3: end function
如果把每一棵樹的高度順便紀錄下來,合併的時候把小樹合併到大樹下,也就 是"Union By Rank",則複雜度會在低一點點。
1.3 Conclusion
若 是 同 時 使 用 path compression 和 union by rank 兩 種 優 化, 則 Dis- joint set 的複雜度是 O(n×α(n)),其中 α(n) 是阿克曼函數的反函數,若是只 使用 path compression 作為優化,則複雜度是 O(n × log∗n)。 由於 α(n) 和 log∗n 的成長速率都非常小,且在競賽賽上 n 通常都不會太大,因此操作 Dis- joint set整體的複雜度估計為 O(n),而且 union by rank 其實加了跟沒加一樣,
寫起來又很麻煩,我從來沒用過。
1.4 Exercises
1. <TIOJ 1312>家族
給你很多組數字 a, b,代表 a 與b 在同個家族。
現在給你一個數字 k,求 k 所在的家族中編號最小的是?
II
2. Search 素集合とグラフ探索
2. <POJ 2492>雌蟲雄蟲
給你很多對關係 x, y,代表蟲x 跟蟲y 為異性。
請輸出是否有任何矛盾 (也就是有蟲為雌雄同體,ex:(x, y)、(y, z)、(z, x))。
3. <POJ 1182>食物鏈
總共有 A、B、C 三種天使,A 吃 B,B 吃 C,C 吃 A。
依序給你很多組關係 d, x, y,若 d= 1 則代表天使 x 跟 y 是同類,d = 2則代 表天使 x 吃y。
若當前給的關係與之前的關係矛盾,代表此組關係是假的。
求總共有多少組關係是假的?
2 Search
Introduction
用電腦解決問題都是暴力,但搜索是最純粹的暴力。幾乎嘗試所有的可能去解決 一個問題,但是寫的好跟不好卻會有非常大的差別。
2.1 Enumeration
枚舉,完完全全的嘗試所有的可能,然後一一檢驗。
2.2 State Space Search
狀態空間搜索,把問題轉換成某種狀態,像是你 t = 0 時,在某起點 (0,0),要 以最少時間走到迷宮的終點(tx, ty),你就可以設計狀態為「你現在在迷宮的 (x, y),
經過多少時間,你早上吃了什麼」這樣子,但早餐吃什麼對於解決這個問題並不重 要,那你記錄「你早上吃了什麼」就會是多餘的資訊,你就可以把它從狀態當中砍 掉。減少多餘的資訊,留下有用的資訊,是演算法競賽很重要的技巧。
而你可以從一個狀態轉移到另一個狀態,譬如說「你現在在(0,3),經過 3s」可 以轉移到「你現在在(1,3),經過 4s」這樣。你可以形象化成有一堆圈圈,一開始 你站在某個圈圈,你可以走到某一些圈圈,但也有些圈圈走不到,像是「你現在在
(100,100),經過 0s」就無法,而題目可以轉化成要問你「你可以走到」,且「符合
某個特質」的圈圈當中,讓「某一性質最優化」的圈圈。以這題為例就是找出符合
「你現在在(tx, ty)」,且讓「經過時間」最小化的圈圈。
轉化成這樣子後,要解決這個問題的方法,最簡單的就是枚舉所有你可以走到的 圈圈,看他符不符合該特質,然後看他的某性質是否是最佳的,至於要如何枚舉你 可以走到的圈圈就要引入下面兩個方法DFS, BFS。
III
2.3 DFS 素集合とグラフ探索
2.3 DFS
DFS 就是 Depth First Search,往深的地方一直走,只要可以走就一直往下 走,如果發現連出去的都是走過了的,就往回退,譬如說狀態空間如下圖,走的順 序就如同所標的號碼那樣。不過其實可以有很多不同的走法,反正不管怎樣都可以 走到所有的圈圈。
4 3
2
1
7 8
6 5
實作的話,你可以亂做,譬如說記錄某個圈圈的前一個圈圈是誰,這樣你走不下 去時,就退回你前一個圈圈。假如你退到了起點,那麼你其實就把所有可以走到的 點都走完了。或是可以直接用Stack 模擬,但用 Stack模擬是一件很鳥的事情。
Algorithm 4DFS
1: function DFS(v)
2: mark v as visited
3: for allu that is adjacent tov do
4: if u is not visitedthen
5: DFS(u)
6: end if
7: end for
8: end function
2.4 BFS
BFS 就是 Breadth First Search,他是一層一層的擴展,一次走到全部你可 以走到的點,譬如說狀態空間如下圖,走的順序就如同所標的號碼那樣。不過其實
IV
2.5 BiBFS 素集合とグラフ探索
你不可能一次處理一堆點,所以會有一個「裡順序」(跟「裡人格」是同樣的道理), 反正不管怎樣都可以走到所有的圈圈。比較特別的是,BFS 所走出來的順序,剛剛 好會是該點跟起點的距離,這是一個非常好的性質。
3 2
2
1
2 3
2 2
實作的話,還是可以亂做,搞個 Queue就好了。
Algorithm 5BFS
1: function BFS(v)
2: enqueue v to Q ▷ Q is a queue
3: mark v as visited
4: whileQ is not empty do
5: c← dequeue Q
6: for allu that is adjacent tocdo
7: if u is not visitedthen
8: enqueueu to Q
9: marku as visited
10: end if
11: end for
12: end while
13: end function
2.5 BiBFS
雙向 BFS,這是第一個不會走完全部點的搜索法,這種不會走完全部點的搜索
法,通常都用在已知終點,然後問你從起點到終點的距離是多少。雙向 BFS就是,
V
2.6 IDDFS 素集合とグラフ探索
名符其實的從起點與終點一起來的BFS,起點拓展一層、終點拓展一層、起點再拓 展一層...,當起點拓展一層後,發現到有某些圈圈同時被起點也被終點拓展,那麼 這些圈圈中一定有一個圈圈,使得他到起點的距離+他到終點的距離=起點到終點 的距離(這裡的距離是指兩個圈圈的最短距離)。假設每個圈圈都連到 n 個圈圈,
當你擴展到d 層時,就要儲存 O(nd)個圈圈到 Queue 裡,等你找到終點時,電腦 就爆炸了,使用BiBFS,就可以只要擴展 d/2層,只要儲存 O(nd2),爽爽AC。
2.6 IDDFS
(第二個)IDDFS 就是,不斷的重複有限制深度的DFS,然後慢慢把限制深度 加深。因為有限制,所以當你走到終點時,那個深度限制就等於起點跟終點的距離,
跟 BFS 是類似的。你可能會覺得這樣會不斷走到走過的點很糟,但是假設每個圈 圈都連到 n 個圈圈,第 1 ~ d 層總共有 O(nd+1),第 d+1 層自己就也有 O(nd+1) 個了,所以重走前面的根本沒差。比起 BFS 較難寫,可以想成是用編程複雜度換 取空間複雜度,卻不會增加時間複雜度的演算法。
2.7 A*
(第三個)A* 其實很直覺,就是將 BFS 裡用的 queue 改成 priority_queue。
而依照什麼作為priority?就依照你覺得從該點出發,走到終點,大概距離是多少,
再加上起點到該點的實際距離,做估算。設g(x)為起點到該點的實際距離,h(x)為 該點到終點的猜測距離,我們設他的priority 為 f(x) = g(x) +h(x)。而顯然每次 都選這個f(x)最小的人做擴展,直覺上這樣就會更快找到最短距離。
但其實這會出現一些問題,你希望他像 BFS 一樣,可以一走到終點時,走過來 的那條路,就會是實際上的最短路,不然,你還是得走遍整個圖,才有辦法得知最 短距離。可以證明(用簡單的反證法),只要你的 h(x)不會高估了 (>) 你的實際距 離,一走到終點時,你就知道他是最短路了,這就是 feasible性質。
2.8 IDA*
(第四個)IDA* 的概念比 A*簡單,當你在進行 IDDFS時,如果深度(有沒有 發現這其實就是 g(x))超過限制深度時,你就會砍掉他,不繼續走下去。而 IDA*
就是,如果預測到達深度(也就是 f(x))超過限制深度時,你就砍掉他。可以發現
你的 h(x) 也要符合 feasible 性質,不然你會毫無來由的砍掉一個明明 OK 的路
徑,這樣速度會瞬間下降超級多,因為當你增加深度後,要搜索的點數會暴增,而 且有可能找到的答案是錯的。
VI
2.9 Conclusion 素集合とグラフ探索
2.9 Conclusion
搜索其實是一門很奧祕(人品)的學問,常常都要使用一些神奇的剪枝,像是 IDA* 其實就是剪枝的一種,雖然競賽很少碰到搜索,可是以後在解決一些實際的 問題時,搜索是非常重要的。(有時候可以搞一些 Random,算一下複雜度可能就 發現降低了)
2.10 Exercises
1. <經典問題 >數獨問題 輸出所有數獨的解。
2. <TIOJ 1573> 15-puzzle
給你一個 4*4 的盤面,裡面有 1 ~ 15 跟一個空格,你每次可以將一個與空 格相鄰的數字移到空格上,問最少要幾步才能走到指定盤面。
3. <SGU 149> Computer Network
給你一棵樹,問樹的直徑(任兩點最大距離)。(N <= 1000000)
4. <SGU 125> Shtirlits
有一個 N ×N 矩陣 A,每一個數字都介於 0 ~ 9,他給你另一個 N ×N 矩 陣 B,Bij 代表Aij 的周圍有幾個比他大的數字。請輸出任一個可行的矩陣A,
若不存在,則輸出"NO SOLUTION"。(N <= 3)
5. <Ural 1006> Square Frames
給你一張寬50 長20的圖(如下),這張圖是由不超過 15個正方框架一個一 個擺上去的,輸出一個框架序列,使得產生的圖跟他給的圖一樣。
VII