101 北一女中 資訊選手培訓營

43
101 北 一女 北北北北北北北 最最最最最最最 Minimum (Cost) Spanning Tree, MST 2012.08. 06 Nan

Upload: mirari

Post on 22-Feb-2016

54 views

Category:

Documents


0 download

DESCRIPTION

101 北一女中 資訊選手培訓營. 最小花費擴張樹 Minimum (Cost) Spanning Tree, MST. 2012.08. 06 Nan. 一張圖的 MST 就是 …. M inimum Cost. 花費最小. S panning. 且包含所有點. T ree. 的樹. Tree 的性質. 每棵樹一定都有一個 root 一棵樹如果有 N 個點 ,那麼樹上一定會有 N-1 條邊 樹上不會存在 cycle 樹上任兩個節點之間的路是唯一的 樹 上的邊可以有權重 (weight ). MST- 問題描述. - PowerPoint PPT Presentation

TRANSCRIPT

Page 1: 101 北一女中 資訊選手培訓營

101 北一女中資訊選手培訓營最小花費擴張樹

Minimum (Cost) Spanning Tree, MST

2012.08. 06 Nan

Page 2: 101 北一女中 資訊選手培訓營

Minimum Cost花費最小

且包含所有點

的樹

一張圖的 MST 就是…

Spanning

Tree

Page 3: 101 北一女中 資訊選手培訓營

Tree 的性質• 每棵樹一定都有一個 root• 一棵樹如果有 N 個點,那麼樹上一定會有 N-1 條邊• 樹上不會存在 cycle• 樹上任兩個節點之間的路是唯一的• 樹上的邊可以有權重 (weight)

Page 4: 101 北一女中 資訊選手培訓營

MST- 問題描述給妳一張圖,圖上有 N 個節點和 M 條邊,每條邊上有權重 (Weight) ,代表建這條邊需要的花費 (Cost) ,問你如何在這張圖上選出一些邊,使得這張圖兩兩之間都有路徑可以到達,並且建邊的總花費最少。

10

20 40

70

100105

60 80

Page 5: 101 北一女中 資訊選手培訓營

問題始源在二次世界大戰過後的波希米亞要重新鋪設電力線,但是因為他們沒有什麼錢了,所以他們希望所有的區域都可以配到電,且鋪設電力線的成本要最低。

Minimum Cost 成本要最低Spanning Tree 原圖所有點的集合 + 原圖所有邊的子集合,邊的子集合要使得整張圖是連通的

= 所有區域都可以配到電

Page 6: 101 北一女中 資訊選手培訓營

問題轉換

10

20 40

70

100105

60 80

區域 節點兩區域之間可建電力線邊 建電力線的花費權重

Page 7: 101 北一女中 資訊選手培訓營

特性觀察可能不是唯一解 10

10 10

10

101010

10 10

10

10 10

1010

10 10

10

10 10

Page 8: 101 北一女中 資訊選手培訓營

特性觀察如果原圖有 cycle ,則拿掉的邊一定會是比較重的邊

10

20 40

70

100105

60 80

Page 9: 101 北一女中 資訊選手培訓營

特性觀察比較小的邊一定會先被挑到

10

20 40

70

100105

60 80

10

20 40

105

把邊的 Weight由小到大排序5

10102040607080

100

Page 10: 101 北一女中 資訊選手培訓營

簡單的方向: Greedy

Page 11: 101 北一女中 資訊選手培訓營

Algorithm I : Prim’s Algorithm

Page 12: 101 北一女中 資訊選手培訓營

Prim’s Algorithm• 使用鄰接矩陣 ( 二維陣列 ) 的時間複雜度是

O(n2)• 概念是 Greedy :盡量選小的,如果會造成

Cycle 就不選 ( 為何會對? )

• 可以從任意一個點開始做 (Why?)

Page 13: 101 北一女中 資訊選手培訓營

Prim’s Algorithm• 使用鄰接矩陣 ( 二維陣列 ) 的時間複雜度是

O(n2)• 概念是 Greedy :盡量選小的,如果會造成

Cycle 就不選 ( 為何會對? )如果只能選 N-1 條邊,那麼選盡量小的,能組成樹( 不會產生 Cycle) 的邊,花費一定最少

• 可以從任意一個點開始做 (Why?)一棵樹可以以任何一個節點作為 root( 每個點一定會至少和一條邊相連接 )

Page 14: 101 北一女中 資訊選手培訓營

範例

挑選第一個點加入正確聯盟,也就是當成這棵樹的root

1

2

64 5

310

20 40

70

100105

60 80

*只要一個節點被上了橘色,就代表他加入了「正確聯 盟」,也就是 root 到它的路徑已經確定了

1 2 3 4 5 6

0 inf inf inf inf inf

Page 15: 101 北一女中 資訊選手培訓營

範例

看看所有和正確聯盟中的點相連的邊,挑出最小的連過去

1

2

64 5

310

20 4070

100105

60 80

*紅色代表最小的邊,咖啡色代表已經確定存在的邊

1 2 3 4 5 6

0 20 40 inf inf inf

1 2 3 4 5 6

0 20 40 10 5 10更新後:

Page 16: 101 北一女中 資訊選手培訓營

範例

看看所有和正確聯盟中的點相連的邊,挑出最小的連過去

1

2

64 5

310

20 40

100105

60 80

如果有兩條邊可以讓正確聯盟連到該點則保留較小權重的邊即可

1 2 3 4 5 6

0 20 40 10 5 10

Page 17: 101 北一女中 資訊選手培訓營

範例

看看所有和正確聯盟中的點相連的邊,挑出最小的連過去

1

2

64 5

310

20 40

100105

*有相等的就隨意挑

1 2 3 4 5 6

0 20 40 10 5 10

Page 18: 101 北一女中 資訊選手培訓營

範例

看看所有和正確聯盟中的點相連的邊,挑出最小的連過去

1

2

64 5

310

20 40

100105

1 2 3 4 5 6

0 20 40 10 5 10

Page 19: 101 北一女中 資訊選手培訓營

範例

看看所有和正確聯盟中的點相連的邊,挑出最小的連過去

1

2

64 5

310

20 40

105

1 2 3 4 5 6

0 20 40 10 5 10

Page 20: 101 北一女中 資訊選手培訓營

範例

所有的點都加入正確聯盟之後,就做完了!

1

2

64 5

310

20 40

105

1 2 3 4 5 6

0 20 40 10 5 10

Page 21: 101 北一女中 資訊選手培訓營

演算法描述1. 初始化,開一個陣列存放正確聯盟到各個點的最近距離2. 將第一個點無條件加入正確聯盟3. 每次都找目前距離正確聯盟最近的點,將之加入正確聯盟4. 更新因該點而使得正確聯盟到其距離更近的點

( 前提是此點還沒有被走過且新加進去的點有路可到此點 )5. 重複 3 & 4 直到所有的點都加入正確聯盟

Page 22: 101 北一女中 資訊選手培訓營

其他一些處理• 要知道總花費?開一個變數,邊做的時候就邊計算• 要知道選了那些邊?開一個陣列,紀錄每個點是透過誰加入正確聯盟• 要知道是哪些路有通,並依序印出改用一個鄰接矩陣紀錄,邊做的時候邊把有通的路標起來,最後用

DFS跑一次即可• 在邊很少時,更快的方法找最小值?鄰接串列 +Minimum Heap

Page 23: 101 北一女中 資訊選手培訓營

另外要注意的• 有可能存在不連通的點 ( 要注意是否有說為連通圖 )• 有可能會有多重邊 ( 要看題目敘述,是否有說是簡單圖或兩個點之間只有一條路 ) ,如果有多重邊只要留下 cost 最小的邊即可• 整個複雜度是 O(V2) ,用鄰接串列 &Heap 可加速到 O((V+E)lgV)

Page 24: 101 北一女中 資訊選手培訓營

#include <string.h> // 為了用 memset 這個 function 所以要 include 這個#define INF 2147483647 // 用 int 的最大值做為無限大int graph[N][N]; // 假設我們有 N 個點。這裡存的是邊 (i,j) 的花費 ( 無向邊 ) // 沒有邊時的花費就是 INFint cost[N]; // 記錄目前要把第 i 個點加入正確聯盟所需要的花費int last[N]; // 記錄第 i 個點是透過誰加入了正確聯盟 ( 等於是存在 edge(last[i], i))int choosed[N]; // 記錄是否已經加入了正確聯盟int fin_cnt; // 記錄已經加入正確聯盟的點的個數int total_cost; // 記錄總花費void init(){ // 初始化 // memset 會把整塊記憶體空間都填上零,有歸零作用 ( 但不能用來歸成除了 0 和 -1 之外的其他值 ) 。 memset(choosed, 0, sizeof(choosed)); // last = -1 代表自己就是 root ,一開始所有點都是自己的 root memset(last, -1, sizeof(last)); // 以 idx=0 的點作為 root 開始看花費 cost[0] = 0; choosed[0] = 1; int i; for ( i = 1 ; i < N ; i++ ){ cost[i] = graph[0][i]; // 如果有邊 cost 就會是該條邊,反之則會是 INF if ( cost[i] != INF ) last[i] = 0; } fin_cnt = 1; // 一開始只有一個點在正確聯盟裡}

Page 25: 101 北一女中 資訊選手培訓營

void prim(){ int min; // 用來存這一輪找到的花費最小值 int min_idx; // 用來存這一輪找到花費最小的是哪個點 int i; while ( fin_cnt < N ) { // 如果小於 N 代表還沒找完 min = INF; // 初始化成 INF ,用來找最小值 min_idx = -1; // 初始化成 -1 ,之後用來判別有沒有找到新的可用的點 for ( i = 1 ; i < N ; i++ ){ // 跑過所有點,找最小值 if ( choosed[i] == 1 ) // 已經在正確聯盟裡就不考慮 continue; if ( cost[i] < min ){ min_idx = i; min = cost[i]; } } if ( min_idx == -1 ) break; // 如果沒找到代表此圖找不到 spanning tree choosed[min_idx] = 1; // 標記 min_idx 這個點進入了正確聯盟 total_cost += cost[min_idx]; // 加上加入這個點的 cost fin_cnt++; // fin_cnt增加一,代表多了一個點已經確定 // 看看還沒有被選的點,有沒有點能夠透過 min_idx 這個點而更近的 for ( i = 1 ; i < N ; i++ ){ if ( choosed[min_idx] == 1 ) continue; // 被選過的就跳過 if ( graph[min_idx][i] < cost[i] ){ // 有更近就更新 last[i] = min_idx; cost[i] = graph[min_idx][i]; } } }}

Page 26: 101 北一女中 資訊選手培訓營

Algorithm II: Kruskal’s Algorithm

Page 27: 101 北一女中 資訊選手培訓營

Kruskal’s Algorithm• 一樣是 Greedy• 用到剛剛從小到大排序抓較小的方法• 以邊為主體:對於每條邊,紀錄起點、終點、花費• 先將邊依照 cost 排序,由小到大一條一條做,在選時判斷:如果會造成 Cycle 就不選該條邊,並記錄每條邊有沒有被選到• 可以想成許多的 MST 在做 Union

用 Disjoint Set 來做 Union-Find輔助之!• 整體時間複雜度是 O(ElogE)

Page 28: 101 北一女中 資訊選手培訓營

範例1

2

64 5

310

20 40

70

100105

5 80

5

5

10

10

20

40

70

80

100

先將所有邊從小到大排序好

1 2 3 4 5 6

-1 -1 -1 -1 -1 -1

Page 29: 101 北一女中 資訊選手培訓營

範例1

2

64 5

310

20 40

70

100105

5 80

5

5

10

10

20

40

70

80

100

由小到大開始做,不會造成 Cycle 就選起來

1 2 3 4 5 6

-1 -1 -1 -1 -1 -1

Find()操作在節點 parent 為 -1 時會傳回節點本身編號

2 != 5 Union

2

Page 30: 101 北一女中 資訊選手培訓營

範例1

2

64 5

310

20 40

70

100105

5 80

5

5

10

10

20

40

70

80

100

由小到大開始做,不會造成 Cycle 就選起來

1 2 3 4 5 6

-1 -1 -1 -1 2 -1

2 != 4 Union

2

Page 31: 101 北一女中 資訊選手培訓營

範例1

2

64 5

310

20 40

70

100105

5 80

5

5

10

10

20

40

70

80

100

由小到大開始做,不會造成 Cycle 就選起來

1 2 3 4 5 6

-1 -1 -1 2 2 -1

2

2 != 6 Union

Page 32: 101 北一女中 資訊選手培訓營

範例1

2

64 5

310

20 40

70

100105

5 80

5

5

10

10

20

40

70

80

100

出現 Cycle 代表左右兩點本來就是在同一個集合裡了,要判斷可以用 Disjoint Sets 的操作。

出現 Cycle 了!這條邊不選!

1 2 3 4 5 6

-1 -1 -1 2 2 2

2 == 2 Don’t Union

Page 33: 101 北一女中 資訊選手培訓營

範例1

2

64 5

310

20 40

70

100105

5 80

5

5

10

10

20

40

70

80

100

1 2 3 4 5 6

-1 -1 -1 2 2 2

1 != 2 Union

2

Page 34: 101 北一女中 資訊選手培訓營

範例1

2

64 5

310

20 40

70

100105

5 80

5

5

10

10

20

40

70

80

100

1 2 3 4 5 6

2 -1 -1 2 2 2

2 != 3 Union

2

Page 35: 101 北一女中 資訊選手培訓營

範例1

2

64 5

310

20 40

70

100105

5 80

5

5

10

10

20

40

70

80

100出現 Cycle 了!這條邊不選!

1 2 3 4 5 6

2 -1 2 2 2 2

2 == 2 Don’t Union

Page 36: 101 北一女中 資訊選手培訓營

範例1

2

64 5

310

20 40

70

100105

5 80

5

5

10

10

20

40

70

80

100出現 Cycle 了!這條邊不選!

1 2 3 4 5 6

2 -1 2 2 2 2

2 == 2 Don’t Union

Page 37: 101 北一女中 資訊選手培訓營

範例1

2

64 5

310

20 40

70

100105

5 80

5

5

10

10

20

40

70

80

100出現 Cycle 了!這條邊不選!

1 2 3 4 5 6

2 -1 2 2 2 2

2 == 2 Don’t Union

Page 38: 101 北一女中 資訊選手培訓營

範例1

2

64 5

310

20 40

70

100105

5 80

5

5

10

10

20

40

70

80

100

做完了!

1 2 3 4 5 6

2 -1 2 2 2 2

Page 39: 101 北一女中 資訊選手培訓營

演算法描述1. 將所有的邊按照 cost 排序2. 對所有的點作 Disjoint set 的初始化3. 從 cost 小的到 cost 大的,枚舉每條邊4. 對於每條邊,看看該條邊所連接的兩點是否屬於同的 Set( 代表有 Cycle) ,如果不同則將該條邊標上使用,並 Union 他們5. 重複 4 直到所有的邊都枚舉完

Page 40: 101 北一女中 資訊選手培訓營

其他一些處理• 要知道總花費?

開一個變數,邊做的時候就邊計算• 要知道是哪些路有通,並依序印出

把所有有使用的邊印出來即可• 要注意是否所有的點都有連通

(才算整張圖存在一個 MST)

Page 41: 101 北一女中 資訊選手培訓營

#include <stdlib.h> // 為了用 qsort 所以要 include 這個#include <string.h> // 為了用 memset 這個 function 所以要 include 這個int st[M], ed[M], cost[M];// 假設我們有 M 條邊。 edge(st[i], ed[i]) 的 cost 為 cost[i]int ind[M]; // 用來索引排序的陣列int choosed[M]; // 用來記該邊是否有被選到int set_r[N]; // 用來記 disjoint set 中每個點的 parent 的陣列int total_cost;

void init(){ // 初始化 memset(choosed, 0, sizeof(choosed)); qsort(ind, M, sizeof(int), comp); // comp裡請以 cost 由小到大排 int i; for ( i = 0 ; i < N ; i++ ){ set_r[i] = -1; // disjoint set 初始化 } total_cost = 0;}void kruskal(){ int i, vRt1, vRt2; for ( i = 0 ; i < M ; i++ ){ vRt1 = findRoot(st[ind[i]]); // disjoint set 的操作 : 找 root vRt2 = findRoot(ed[ind[i]]); if ( vRt1 != vRt2 ){ // 如果 root 不同 => 代表在不同 set=> 做 union set_r[vRt2] = vRt1; choosed[ind[i]] = 1; // 標上選了這條邊 total_cost += cost[ind[i]]; // 加上這條邊的 cost } }}

int findRoot(int idx){ if ( set_r[idx] == -1 ) return idx; return (set_r[idx] = findRoot(set_r[idx])); }

Page 42: 101 北一女中 資訊選手培訓營

一些參考資料• http://en.wikipedia.org/wiki/Prim%27s_algorithm• http://en.wikipedia.org/wiki/Kruskal%27s_algorithm• http://www.csie.ntnu.edu.tw/~u91029/

SpanningTree.html

• http://en.wikipedia.org/wiki/Minimum_spanning_tree• http://www.cyut.edu.tw/~ckhung/b/al/graph.php

Page 43: 101 北一女中 資訊選手培訓營

看完影片你應該要知道• 什麼是 Spanning Tree• MST 的問題定義是什麼• Prim 演算法的操作過程• 「正確聯盟」的概念• Prim 演算法和 Heap 的搭配• Kruskal 演算法的操作過程• Kruskal 演算法儲存圖的方式• Kruskal 演算法和 Disjoint Sets 的搭配• 如何印出兩種演算法的結果 ( 總值跟選那些邊 )