--- tags: 資料結構 --- # 資料結構 第六章 圖形 ![](https://i.imgur.com/Vr4nDco.jpg) ## 一、圖形介紹 ### (一)七橋問題 ![](https://i.imgur.com/dY8dCX6.jpg) 討論四個島嶼七座橋(將所有邊走過一次不重複),將島嶼表示為頂點橋表示為邊,【參考離散數學】尤拉推導每個點degree(分支)需要皆為偶數,否則無法完成,或是(2點degree為奇數其餘為偶數) ### (二)圖形分別 #### 1、有向圖與無向圖 ![](https://i.imgur.com/7JxEaBi.jpg) ##### G~1~、G~2~為無向圖(0→1,1→0相同),G~3~為有向圖(0→1,1→0不同) #### 2、簡單圖與多重圖 ![](https://i.imgur.com/ktOYTva.jpg) ##### G=<V,E>:圖形若G中任二點之間至多一邊相連,稱G為simple graph,否則稱G為mutigraph ### (三)子圖 #### 1、定義:G=<V,E>,則S為G的subgraph表示S = <V',E'>,其中V'屬於V,E'屬於E #### 2、圖示 ![](https://i.imgur.com/e9MDe5D.jpg) ### (四)連通圖 #### 1、連通:無向圖中任兩點都有路經可以到達 #### 2、連通元件:圖形中內的圖形(元件、部分)為連通 ![](https://i.imgur.com/AjeypUS.jpg) #### 3、強連通:有向圖中任兩點都有路經可以到達 #### 4、強連通元件:圖形中內的圖形(元件)皆為強連通【以圖G~3~為例】 ![](https://i.imgur.com/48i58Ns.jpg) ### (五)圖形資料抽象型態 【變數設定】for all graph belong to Graph, v1, v2, and v3 belong to Vertices (1)Graph Create():創造一個空圖 (2)Graph InsertVertex(graph, v1, v2):將點V插入圖形中點(indegree=0) (3)Graph InsertEdge(graph, v1, v2):加入一個邊在v1,v2之間 (4)Graph DeleteVertex(graph, v):刪除一個點與連接在點上的所有邊。 (5)Graph DeleteEdge(graph, v1, V2) (6)Boolean isEmpty(graph):前提假設圖等於空回傳True,前提假設不成立則回傳False (7)List Adjacent(graph, v):回傳所有與V點相連的所有頂點。 ### (六)圖形表示方法 #### 以下圖為例 ![](https://i.imgur.com/EkSl2xg.jpg) ![](https://i.imgur.com/GzsQgBr.jpg) #### 1、相鄰矩陣 x,y軸為各點有邊相連的話則為1(N個點則需準備N^2個位置) ![](https://i.imgur.com/6gml9gy.jpg) #### 2、相鄰串列 準備一個陣列(n點準備N格),每個點若有邊相鄰則連結1個Link至該點 ![](https://i.imgur.com/mZMneMe.jpg) #### 3、相鄰多重串列 ##### (1)節點結構 ![](https://i.imgur.com/YAHuNZ9.jpg) ##### (2)以G~1~為例 ###### 邊名稱 邊的左右2點 討論左點其他連結的邊 討論右點其他連結的邊 ![](https://i.imgur.com/hx80qIa.jpg) ### 二、圖形操作 #### (一)深度搜尋 ##### 1、程式碼 【類似迷宮走錯退回原位,故使用Stack】 void dfs(int v) { node_pointer w; visited[v] = True; printf("%5d",v); for (w = graph[v]; w; w = w ->link) if(!visited[w->vertex]) dfs(w->vertex); } ##### 2、時間複雜度分析 ###### (1)若使用adjacency lists 則需走訪O(2*e)經過所有點(2為常數),故時間複雜度為O(e) ###### (2)若使用adjacency matrix 則需走訪(n^2^)格個儲存格,故時間複雜度為O(n^2^) ##### 3、圖例: ![](https://i.imgur.com/0DPF1td.jpg) ##### DFS走訪: (1) V~0~,V~1~,V~3~,V~7~,V~4~,V~5~,V~2~,V~6~ (2) V~0~,V~2~,V~5~,V~7~,V~4~,V~1~,V~3~,V~6~ (3) V~0~,V~1~,V~4~,V~7~,V~5~,V~2~,V~6~,V~3~ ...etc (不唯一) #### (二)廣度搜尋 ##### 1、程式碼 【廣度搜尋以鄰近相連點優先,故使用Queue,先進先出概念,優先拜訪相鄰點】 void bfs(int v) { node_pointer w; queue_pointer front,rear; front = rear = NULL; printf("%5d",v); visited[v] = True; addq(&front, &rear, v); while (front) { V = deleteq(&front); for (w = graph[v]; w; w = w->link) if (!visited[w->vertex]) { printf("%5d", w->vertex); addq(&front,&rear,W->vertex); visited[w->vertex] = true; } ) } ##### 2、時間複雜度分析 ###### (1)若使用adjacency lists: 每個點都會進入Queue(佇列一次)每次delete都會走訪該點的邊,將邊連有連到的頂點插入佇列中 每個點都走訪連至的邊(2E = degree和),故時間複雜度為O(2*E),(2為常數)故為O(E) ###### (2)若使用adjacency matrix: 則需走訪所有儲存空間(n^2^),故時間複雜度為O(n^2^) ##### 3、圖例: ![](https://i.imgur.com/0DPF1td.jpg) ##### BFS走訪: (1) V~0~,V~1~,V~2~,V~3~,V~4~,V~5~,V~6~,V~7~ (2) V~0~,V~2~,V~1~,V~5~,V~6~,V~3~,V~4~,V~7~ (3) V~0~,V~1~,V~4~,V~3~,V~2~,V~6~,V~5~,V~7~ ...etc (不唯一) ##### (三)連通元件 ###### 1、使用DFS or BFS ###### 依G~4~為例為2個連通圖但2圖不相連,故使用BFS or DFS 無法一次拜訪完所有的點,利用此特性,來確認此圖有幾個連通元件。 ###### 2、程式碼 void connected (void) { int i; for (i = 0;, i < n; i++) if(!visited[i]) { dfs(i); printf("\n"); } } ##### 3、時間複雜度分析 ###### (1)adjacency lists: 利用DFS需要O(e)的時間,使用迴圈走訪每個點O(n),故找到所有連通元件需要O(n+e) ###### (2)adjacency matrix: 確認連接的組件需要O(n^2) ##### (四)生成樹 ###### 1、圖示(n=4為例) ![](https://i.imgur.com/GssOyBD.jpg) //Tree不含cycle,邊數 = 頂點數-1 ![](https://i.imgur.com/Z6eSS68.jpg) 以深度搜尋創造的生成樹稱為深度生成樹 以廣度搜尋創造的生成樹稱為廣度生成樹 ###### 2、圖示(深、廣度生成樹) ![](https://i.imgur.com/1B3FpfA.jpg) 與BFS和DFS一樣不唯一 ##### (五)雙向連接元件與樞紐點 ##### 1、樞紐點(Articulation Points) 此點若刪除後導致原圖不連通(變成2個(含)以上的連通元件),此點稱之樞紐點。 ##### 2、雙向連通圖(Biconnected Graph) 途中不含任何樞紐點(EX:cycle) ##### 3、雙向連通元件(Biconnected Components) 圖形中最大的雙聯通圖形 ##### 4、圖示: ![](https://i.imgur.com/B4JEugY.jpg) ##### 5、DFS生成樹 dfn(3)=0 【3為起點】 DFS: 3、4、2、1、0、5、6、7、9、8 ##### 6、圖示 ![](https://i.imgur.com/dVVsBcr.jpg) ##### 7、邊的特性 【定義】 未被訪過的點:白色 被拜訪過的點:灰色 已無法再拜訪:黑色 | 邊名稱 | back edge | Tree edge | forward edge | cross edge | |:------:|:-------------:|:-------------:|:-------------:|:-------------:| | 特性 | 灰色點→灰色點 | 灰色點→白色點 | 灰色點→黑色點 | 灰色點→黑色點 | | 無向圖 | 有 | 有 | 沒有 | 沒有 | | 有向圖 | 有 | 有 | 有 | 有 | ##### //forward edge & cross edge 要區別以發現拜訪時間判斷 ##### 8、dfn & low ###### (1)dfn:計算出每個點被追蹤的順序(ex: dfn(a)=3 > dfn(b)=4 故a比b先追蹤) ###### (2)low:計算出此點的子樹沿著backedge所能到達的祖先,最小的dfn值 ![](https://i.imgur.com/RcEgxZ6.jpg) ###### (3)程式碼: void dfnlow(int u, int v) { node_pinter ptr; int w; dfn[u]=low[u] = num++; for(ptr = graph[u]; ptr; ptr = ptr->link) { w= ptr->vertex; if(dfn[w]<0) { dfnlow(w,u); low[u] = MIN2(low[u],low[w]); } else if(w != V) low[u] = MIN2(low[u],dfn[w]); } } ##### 9、找出雙連通元件 先判斷樞紐點則可以找到所有雙連通元件 ###### 程式碼: void bicon(int u, int v) { node_pointer ptr; int x,y,z; dfn[u] = low[u] =num++; for(ptr = graph[u]; ptr; ptr = ptr->link) { w= ptr->vertex; if (v != w && dfn[w] < dfn[u]) add(&top,u,w); if(dfn[w]<0) { bicon(w,u); low[u] = MIN2(low[u],low[w]); if (low[w] >= dfn[u]) { printf("New biconnected component:"); do { delete(&top, &x, &y); printf("<%d,%d>,x,y"); } while(!((x == u) && (y ==w))); printf("\n"); } } else if (w != v) low[u] =MIN2(low[u],dfn[w]); } } ###### (1)樹根若有2個或以上的子點 ###### (2)頂點u非樹根,頂點存在子點v,滿足LOW(v)≧DFN(u)時,頂點u為樞紐點。 ### 三、最小成本生成樹(Minmim cost panning trees) 找出最小成本生成樹最常見的3種演算法(Kruskal’s Algo、Prim’s Algo、Soilin’s Algo),皆為貪婪演算法。 【定義】 1.權重在於邊上 2.使用貪婪演算法(故邊數取最少): 邊數 = 點數-1 3.為樹的型態不含有cycle #### (一)Kruskal’s Algorithm ##### 1、步驟: step1:將權重最小的邊加入 step2:檢查是否產生cycle step3:直到加入n-1條邊結束 ##### 2、圖示: ![](https://i.imgur.com/4ySMtqU.jpg) ##### 3、程式碼: T = {}; while (T contains less than n-1 edges && E is not empty) { choose a least cost edge (v,w) from E; delete (v,w) from E; if ((v,w) does not create a cycle in T) add (v,w) to T; else discard (v,w); } if (T contains fewer than n-1 edges) printf("No spanning tree\n"); #### (二)Prim’s Algorithm ##### 1、步驟: step1:每次加入一個點至新的集合 step2:找出最小權重的邊加入新的集合 step3:直到新集合內蒐集滿所有點即完成 ##### 2、圖示: ![](https://i.imgur.com/9Ehp0uG.jpg) ##### 3、程式碼: T ={}; TV = {0}; while (T contains fewer than n-1 edges) { let (u, v) be a least cost edge such that u belong to TV and V not belong to TV; if (there is no such edge) break; add v to TV; add (u, v) to T; } if (T contains fewer than n-1 edges) printf("No spanning tree\n"); #### (三)Soilin’s Algorithm ##### 1、步驟: step1:視每個點為一個森林 step2:加入每個權重最小的邊,使森林串聯起來(並檢查是否產生cycle) step3:串聯到只剩一個樹即完成 ##### 2、圖示: ![](https://i.imgur.com/BalP8sD.jpg) ### 四、最短路徑 #### (一)單一點至所有點的距離(Single Source All Destinations) ![](https://i.imgur.com/ZU54WsN.jpg) ##### 1、Dijkstra's algo 由最近的點開始,依序找出每個點的最短路徑【不允許負邊】【不允許負環】 --- step1: 假設a -> j 路徑長度為10 【2種可能】 【可能一】: 加入新點u, a -> j 仍為最短路徑 【可能二】: 加入新點u, a -> j > a->u->j ,則取代最短路徑 step2: 直到所有點都加入後,即可獲得從a -> j的最短路徑 ##### 2、Bellmen's algo 討論起終點最短距離,中間經過1個點、2個點至n-1個點【允許負邊】【不允許負環】 --- step1: 假設a -> j 中間經過1~n-1個點,路徑長度為10 【2種可能】 step2: 選出最短路徑 #### (二)所有點至其他點的距離(All Pairs Shortest Paths) ##### 1、Floyd-Warshall 求任兩點的最短距離【允許負邊】【不允許負環】 將頂點編號討論起終點距離(經過V1、V2....Vn)的距離,選最短路徑 --- step1: 將所有點編號(假設有n個點) step2: 討論假設a -> j pass1:可經過V1 最短距離。 pass2:可經過V1、V2最短距離。 ... pass(n-2):可經過V1....Vn-2最短距離。 step3:討論完可經過所有點即為最短距離 ##### 2、Johnson's algo 在圖中多加入一個點連至所有點權重為0【允許負邊】【不允許負環】 在使用bellmen'algo,無負邊也可使用Dijkstra's algo //稀疏矩陣中效果比Floyd-warshall好 O(V*E+V^2logV) #### (三)遞移包(Transitive Closure) ##### 1、A^+^、A^*^圖示: ![](https://i.imgur.com/UqauMjW.jpg) ###### A^*^ = A^+^ U I ### 五、網絡與拓樸排序 #### (一)點為活動之網絡AOV(Activity On Vertex Networks) ##### 1、定義: 有向圖G其中頂點表示任務或活動,而邊表示任務之間的優先級關係。 ##### 2、圖示及表示: ![](https://i.imgur.com/cZRSenR.jpg) ![](https://i.imgur.com/gHoT3H2.jpg) ##### 3、拓樸排序: ![](https://i.imgur.com/74rm0ZJ.jpg) #### (二)邊為活動之網絡AOE(Activity On Edge (AOE) Networks) ##### 1、條件 (1)邊代表活動且不能有迴圈 (2)單一起終點 ##### 2、圖例及表示 ![](https://i.imgur.com/Am0DGOv.jpg) ![](https://i.imgur.com/i5Rqtyq.jpg) ##### 3、臨界活動 活動最早開始時間與最晚開始時間一樣的話為關鍵(臨界)活動 關鍵路徑上的所有活動都是關鍵活動,提前完成非關鍵活動(不在關鍵路徑的活動)並不能加快工程的進度。 ![](https://i.imgur.com/9UwfhAl.jpg) > 參考資料:Fundamentals of Data Structures in C by Ellis Horowitz, Sartaj Sahni, Susan Anderson-Freed