• Tidak ada hasil yang ditemukan

併查集と狀態空間搜索

N/A
N/A
Protected

Academic year: 2023

Membagikan "併查集と狀態空間搜索"

Copied!
8
0
0

Teks penuh

(1)

併查集と狀態空間搜索

FruitTea

2015 年 9 ⽉ 23 ⽇(⽕曜⽇)

1 Disjoint Sets 互斥集

Introduction

Disjoint Sets,⼜稱互斥集,代表⼀群集合兩兩交集皆為空集合的狀況,常常⽤來處理

「分類」問題。因為功能實⽤、實作簡單,在資訊競賽中很常被使⽤。

實作Disjoint Sets的資料結構我們可以稱她為併查樹 (Union-find Tree)或是併查森林(很 多棵樹嚴格上來說要叫做森林)。基本上我們會以⼀顆併查樹代表⼀個集合。

⼀個基本的併查樹我們必須實作兩種操作,Find和Union。

1.1 Find 查詢

我們有⼀個陣列P[x] 代表 x的 parent是誰,如果 x 是併查樹的樹根(root),我們就把x

的parent當作是⾃⼰。初始狀態下所有的P[x] = x。(⼀開始⼤家都屬於各⾃不同的集合)

如果我們想要知道 x在哪個集合中 (x的root 是誰),我們只要遞迴 P[x]就可以得到答案 了。

Algorithm 1: Find 1 f u n c t i o n Find ( x )

2 i f x ≠ p [ x ] then

3 r e t u r n Find ( p [ x ] )

4 else

5 r e t u r n x

6 end i f

7 end f u n c t i o n

I

(2)

可是這樣每次找⼀個點,最慘的狀況下可能會花 O(N) 的時間,所以我們引⼊⼀個稱 為”Path Compression”的概念,速度可以快⾮常多。

Path Compression其實就是在遞迴的過程中,讓所有路徑上的點「直接」連到root。代碼

如下。

Algorithm 2: Find & Path Compression 1 f u n c t i o n Find ( x )

2 i f x ≠ p [ x ] then

3 p [ x ] ← Find ( p [ x ] ) 4 r e t u r n Find ( p [ x ] )

5 else

6 r e t u r n x

7 end i f

8 end f u n c t i o n

1.2 Union 合併

想要合併兩個集合,也就是合併兩顆併查樹,只要把 A樹的樹根接到 B樹的樹根就可以 了。

Algorithm 3: Union 1 f u n c t i o n Union ( x , y )

2 p [ Find ( x ) ] ← Find ( y ) 3 end f u n c t i o n

合併上也有⼀個優化的⽅法叫做”Union By Rank”,也就是把⼩樹合併到⼤樹上,這樣需 要更動的點會⽐較少。

1.3 總結

併查樹可以讓我們在處理分類和合併的問題時很輕鬆的完成。如果我們同時使⽤”Union By Rank”和”Path Compression”,併查樹的複雜度是O(N × α(N)),其中α(N)是阿克曼函 數的反函數。不過就算不⽤”Union By Rank”,複雜度也只是退化到O(N × lg N),在競賽 領域中,我們會遇到的併查樹的操作,α(N) 和lg N 都不會太⼤,所以基本上我們可以把 併查樹的複雜度當作O(N)來使⽤。⾄於”Union By Rank”對於併查樹來說會⽐較⿇煩 (需

要多紀錄rank)⼜不會快很多,所以平常不太需要。

II

(3)

1.4 Exercises!!

1. <TIOJ 1312 家族>

給你很多組數字a, b,代表a與b在同個家族。現在給你⼀個數字k,求k所在的家 族中編號最⼩的是誰?

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。若當前給的關係與之前

的關係⽭盾,代表此組關係是假的。求總共有多少組關係是假的?

4. <TIOJ 1833 陽炎眩亂>

有⼀群蘿莉機器⼈,分成很多部隊,每個部隊都有⼀個⼥王。題⽬可能要求你合併兩 個部隊或是查詢某隻蘿莉機器⼈隸屬於哪個⼥王。

2 Search 搜尋

Introduction

搜索,暴⼒的找答案,也就是要試過每個可能性後再⼀個⼀個檢查。雖然是暴⼒,但是寫 法好或不好差別還是很⼤的喔!

2.1 Enumeration 枚舉

嘗試所有的可能,然後⼀⼀檢查,找出答案。

2.2 State Space Search 狀態空間搜索

先把問題轉成某種狀態,⽐如你要去學校上學,⼀開始在位置(0, 0)要到位置(s, t)。現在 的位置即為狀態。

⾄於從(0, 0) 到(0, 1)就稱為狀態轉移,⽐如南海路也許就是⼀條可以讓你從家裡轉移到

學校的其中⼀個狀態轉移的路徑。

所有的狀態的集合就是狀態空間,在這裡⾯找答案就是所謂的狀態空間搜索。

我們並不知道這個稱之為狀態空間的迷宮⻑怎麼樣,所以我們必須嘗試⾛⾛看,⾄於怎麼

⾛才有效率,我們介紹幾個常⾒的演算法。

III

(4)

2.3 DFS

Depth First Search(深度優先搜索),簡稱DFS。也就是⼀直往前⾛,直到遇到死路就往

後退到上⼀個岔路並且換⼀條⾛,如果所有岔路都⾛完了就再往後退,直到把所有的點都 搜完。跟著下圖的編號順序⾛訪⼀遍就是⼀個DFS 順序的搜尋,當然⼤家的順序可能不 太⼀樣。

實作上,我們只要⽤遞迴就可以輕鬆完成,也可以⽤Stack模擬,可是很⿇煩。並且注意

⼀定要記的紀錄⾛過的點。

Algorithm 4: DFS 1 f u n c t i o n DFS( v )

2 mark v as v i s i t e d

3 f o r a l l u t h a t i s a d j a c e n t t o v do 4 i f u i s n o t v i s i t e d then

5 DFS( u )

6 end i f

7 end f o r

8 end f u n c t i o n

2.4 BFS

Breadth First Search(廣度優先搜索),簡稱BFS,也就是所謂的地毯式搜索。⼀層⼀層的

向外擴⼤搜尋範圍,就像下圖的順序,直到⾛完所有點。當然你不可能⼀次⾛到很多點,

所以下圖的數字代表層數,⾄於同⼀層的順序⼤家可能會不太⼀樣。有⼀個很棒的性質

IV

(5)

是,BFS的層數剛好就是起點到該點的距離。

實作BFS也很簡單,只要把DFS的Stack換成Queue就可以了。

Algorithm 5: BFS 1 f u n c t i o n BFS ( v )

2 enqueue v t o Q (Q i s a queue ) 3 mark v as v i s i t e d

4 while Q i s n o t empty do

5 c ← pop Q

6 f o r a l l u t h a t i s a d j a c e n t t o c do 7 i f u i s n o t v i s i t e d then

8 push u t o Q

9 mark u as v i s i t e d

10 end i f

11 end f o r

12 end while

13 end f u n c t i o n

2.5 BiBFS

雙向的 BFS,通常是在知道起點和終點⽽要找最短路徑時,同時交錯在起點和終點做

BFS,起點做⼀層,終點跟著做⼀層。當⼀個點同時被起點和終點搜到時,我們可以確定

⼀定有⼀條最短路徑,從起點經過這個點到終點。

V

(6)

BiBFS的好處是,⼀般BFS必須不斷把點存進記憶體裡,假設每個點都連到 N個點,那 麼距離d 的BFS就必須存⼊O(Nd)個點。但是,⽤ BiBFS我們兩個BFS的距離都剩下

d

2,所以只要存O(Nd2)個點,省下了不少記憶體。

2.6 IDDFS

IDDFS就是限制深度的DFS,先做⼀個限制深度的DFS,然後不斷加深深度。這樣做的

原因是避免我們⾛進⼀個⼜深⼜⻑的死路,⽩⽩浪費時間。因為限制了深度,到達終點 時,深度剛好可以代表距離。

也許你會覺得這樣⼀直重複⾛⾛過的點很浪費時間,但是從時間複雜度去分析,假設每個 點都連到N個點,1 d層點數總共有O(Nd+1),⽽d+1層卻也有O(Nd+1)個點,所以總共 只差了常數時間⽽已。

換句話說,IDDFS就是⼀個不會浪費空間的BFS,但是⽐BFS難寫,不過時間複雜度不 變。

2.7 A*

A*就是把BFS 裡的 queue換成 priority queue,也就是依照優先度去搜尋,⽽不是到起

點的實際距離。⾄於優先度是甚麼呢,其實可以⾃⼰定義。A*的優先度是起點到終點的 估計距離,也就是我們希望最短路徑優先被搜尋到。

我們給每⼀點⼀個優先度 f(x) = g(x) + h(x),其中g(x) 代表起點到該點的實際距離,h(x) 代表該點到終點的估計距離。我們只要挑f(x)最⼩的⼈先⾛,很快就可以找到最短路徑。

不過這樣真的會是最短路徑嗎?我們可以證明,因為我們不會⾼估h(x),也就是我們只要 保證該點到終點的實際距離⼤於等於h(x),這樣的話如果我們由⼀條路徑A⾛到終點,另 外⼀條f(x)⽐這條路徑⼤的路徑B,實際距離⼀定⽐路徑 B的 f(x)更⼤,所以路徑 A就 是最短路徑了。

2.8 IDA*

IDA*就是 IDDFS 加上 A*,也就是限制f(x) 的⻑度。當我們做IDDFS 時,深度(剛好就

是A* 裡提到的g(x))超過限制就砍掉。現在我們⼀樣做IDDFS,只是改成 f(x)超過⻑度

限制就砍掉(也就是不⽤g(x)超過限制,如果估計⻑度超過就可以先砍掉了。)

因為我們保證到終點的實際⻑度⼤於等於h(x),所以我們不會不⼩⼼砍掉可以⾛的路徑。

2.9 總結

搜尋其實是很神秘⼜很吃⼈品的領域,透過簡單的DFS、BFS去拓展,想辦法剪枝(就像

IDA*),或是利⽤⼀些性質,甚⾄Random⼀下,都有機會降低複雜度。

VI

(7)

之後還會介紹⼀堆神奇的狀態空間搜索,基本上所有的問題都可以透過搜索解出來,把搜 索學好可以幫助你解決很多無論是競賽還是⽣活上的問題。(ex:遊戲中的 bot⾛路可以

⽤A*解決,算⾼維度函數可以⽤爆搜剪枝驗算答案等等。)

附註:因為台灣的⽐賽很雷,常常演算法⽐賽會變成爆搜⽐賽,練好搜尋絕對是好處多於 壞處的。

2.10 Exercises!!

1. <TIOJ 1025 數獨問題>

計算數獨的答案。

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. < 經典問題>

給⼀個 M × N 的格⼦矩形,每個格⼦不是1就是 0,你每次可以對⼀個格⼦進⾏操

作,把那個格⼦和他周圍的格⼦1 變成0、0變成 1,問你⾄少要做幾次才能使所有 格⼦都變成0。(M, N ≤ 15)

6. <TIOJ 1099 射⼿座之⽇>

對⼀個三維空間的座標 (x, y, z),你可以做它兩件事:

(a)把x, y, z 任意調換

(b)把(x, y, z) 變成(2y−x+1, 2x−y−1, z) 問有沒有可能把座標從 (x1, y1, z1)變成(x2, y2, z2)。

7. <TIOJ 1022 跑跑卡恩⾞>

有⼀個⾼低起伏的地圖,你可以在⾼度差不超過5的格⼦之間移動。請問從起點到終 點最短要⾛多遠。

8. <TIOJ 1013 Fire in the forest>

在⼀張給定的地圖中,給你不同的起點和終點,還有⼀個起⽕點,你的速度和⽕的速 度⼀樣,⽽⽕會不斷向四個⽅向延燒。請問你來得急在被⽕燒到前跑到終點嗎?

VII

(8)

9. <TIOJ 1226 H 遊戲>

給你⼀個有向無環圖,問你從起點到每個終點的所有可能的路徑的路徑⻑和。

10. <TIOJ 1250 番茄蹲>

有N個⼈排成⼀排,⼀開始都站著,主持⼈可以選擇⼀個數字k,使k的倍數編號的

⼈蹲下,如果已經蹲下就站起來。請你列出經過M個回合後所有可能的狀況。

VIII

Referensi

Dokumen terkait

這樣的題目主要的重點有兩個,第一個重點從小數字著手,系統性 的舉出所有情形,第二個是針對固定的n值,列出所有的可能情形, 分析每個人能不能分辨的狀況,針對這個重點,最重要的就是列出 所有的可能,如果沒有列出所有的可能,就無法分辨出誰可以或不 可以完全知道,另外,系統性的把狀況用表格列出來,有助於分析 的效率,大多數作答的同學對於題意的理解及重點的掌握還可以多

反證法在一些存在性的問題上是一個很好的證明方式。在本題目中,命題: 「必能找出兩相鄰的數,它們的差不小於5」,它的否定是「對所有相鄰的兩 數,它們的差都小於5」,只要說明否定命題是錯的,即得證。一般而言, 說明一個命題是錯的方式是找出一個反例,所以我們可以舉1、81間格最多 的一個情況作為反例。