本文主要內容:(與樹類似)
一、圖的概念
二、圖的重中之重——兩種重要儲存結構
三、樹的升級擴充應用:最小生成樹
四、本節應用習題
五、個人反思與未來計劃
一、圖的基本概念:
(1)圖的定義:
圖(Graph)是由頂點的有窮非空集合和頂點之間邊的集合組成,通常表示為:G(V,E),其中,G表示一個圖,V是圖G中頂點的集合,E是圖G中邊的集合。
注意:線性表中可以沒有元素,稱為空表。樹中可以沒有結點,叫做空樹。但是在圖中不允許沒有頂點,可以沒有邊。
(2)圖的基本術語:
· 無向邊:若頂點Vi和Vj之間的邊沒有方向,稱這條邊為無向邊,用 (Vi,Vj)
來表示。
· 無向圖:圖中任意兩個頂點的邊都是無向邊。
· 有向邊:若從頂點Vi到Vj的邊有方向,稱這條邊為有向邊,也稱為弧,用 <Vi, Vj>
來表示,其中 Vi 稱為弧尾,Vj 稱為弧頭。
· 有向圖:圖中任意兩個頂點的邊都是有向邊。
· 簡單圖:不存在自環(頂點到其自身的邊)和重邊(完全相同的邊)的圖。
· 稀疏圖;有很少條邊或弧的圖稱為稀疏圖,反之稱為稠密圖。
· 權:表示從圖中一個頂點到另一個頂點的距離或耗費。
· 網:帶有權重的圖。
· 度:與特定頂點相連線的邊數。
· 出度、入度:有向圖中的概念,出度表示以此頂點為起點的邊的數目,入度表示以此頂點為終點的邊的數目。
· 連通圖:任意兩個頂點都相互連通的圖。
· 極大連通子圖:包含竟可能多的頂點(必須是連通的),即找不到另外一個頂點,使得此頂點能夠連線到此極大連通子圖的任意一個頂點。
· 連通分量:極大連通子圖的數量。
· 強連通圖:此為有向圖的概念,表示任意兩個頂點a,b,使得a能夠連線到b,b也能連線到a 的圖。
· 連通圖的生成樹:一個極小連通子圖,它含有原圖中全部定點,但只有足以構成一棵樹的 n-1 條變,這樣的連通子圖稱為連通圖的生成樹。
· 最小生成樹:此生成樹的邊的權重之和是所有生成樹中最小的。
(3)圖的兩種遍歷方式:
· 深度優先遍歷:(DFS常利用遞迴思想)
首先從圖中某個頂點v0出發,訪問此頂點,然後依次從v相鄰的頂點出發深度優先遍歷,直至圖中所有與v路徑相通的頂點都被訪問了;若此時尚有頂點未被訪問,則從中選一個頂點作為起始點,重複上述過程,直到所有的頂點都被訪問。
· 廣度優先遍歷:(BFS常利用佇列+佇列不為空迴圈思想)
首先,從圖的某個頂點v0出發,訪問了v0之後,依次訪問與v0相鄰的未被訪問的頂點,然後分別從這些頂點出發,廣度優先遍歷,直至所有的頂點都被訪問完。
二、圖的重中之重 —— 已學習的兩種儲存結構:
(1)鄰接矩陣:
圖的鄰接矩陣的儲存方式是用兩個陣列來表示圖。一個一維陣列儲存圖中頂點資訊,一個二維陣列(稱鄰接矩陣)儲存圖中的邊或弧的資訊。
優缺點:
· 優點:結構簡單,操作方便
· 缺點:對於稀疏圖,這種實現方式將浪費大量的空間。
(2)鄰接表:
鄰接表是一種將陣列與連結串列相結合的儲存方法。其具體實現為:將圖中頂點用一個一維陣列儲存,每個頂點Vi的所有鄰接點用一個單連結串列來儲存。這種方式和樹結構中孩子表示法一樣。
對於有向圖其鄰接表結構如下:
優缺點:
· 優點:本演算法的時間複雜度為 O(N + E),其中N、E分別為頂點數和邊數,鄰接表實現比較適合表示稀疏圖。
· 缺點:操作繁瑣
注:還有一種十字連結串列儲存結構,暫未教學,等學習熟練之後再單獨拿來寫博
三、圖的升級擴充之一:最小生成樹 (貪心演算法):
(1)最小生成樹的概念:
圖的生成樹是它的一棵含有所有頂點的無環連通子圖。一棵加權圖的最小生成樹(MST)是它的一棵權值(所有邊的權值之和)最小的生成樹。
(2)最小生成樹的兩種實現演算法:
· 普里姆演算法(Prim)
實現過程:
從頂點0開始,首先將頂點0加入到樹中(標記),頂點0和其它點的橫切邊(這裡即為頂點0的鄰接邊)加入優先佇列,將權值最小的橫切邊出隊,加入生成樹中。此時相當於也向樹中新增了一個頂點2,接著將集合(頂點1,2組成)和另一個集合(除1,2的頂點組成)間的橫切邊加入到優先佇列中,如此這般,直到佇列為空。
· 克魯斯卡爾演算法(Kruskal)
實現過程:
按照邊的權重順序來生成最小生成樹,首先將圖中所有邊加入優先佇列,將權重最小的邊出隊加入最小生成樹,保證加入的邊不與已經加入的邊形成環,直到樹中有V-1到邊為止。
注:具體實現程式碼暫未學習,僅瞭解實現過程,後續增加。
四、本章習題練習:
拯救007:(DFS)
在老電影“007之生死關頭”(Live and Let Die)中有一個情節,007被毒販抓到一個鱷魚池中心的小島上,他用了一種極為大膽的方法逃脫 —— 直接踩著池子裡一系列鱷魚的大腦袋跳上岸去!(據說當年替身演員被最後一條鱷魚咬住了腳,幸好穿的是特別加厚的靴子才逃過一劫。) 設鱷魚池是長寬為100米的方形,中心座標為 (0, 0),且東北角座標為 (50, 50)。池心島是以 (0, 0) 為圓心、直徑15米的圓。給定池中分佈的鱷魚的座標、以及007一次能跳躍的最大距離,你需要告訴他是否有可能逃出生天。 輸入格式: 首先第一行給出兩個正整數:鱷魚數量 N(≤100)和007一次能跳躍的最大距離 D。隨後 N 行,每行給出一條鱷魚的 (x,y) 座標。注意:不會有兩條鱷魚待在同一個點上。 輸出格式: 如果007有可能逃脫,就在一行中輸出"Yes",否則輸出"No"。 輸入樣例 1: 14 20 25 -15 -25 28 8 49 29 15 -35 -2 5 28 27 -29 -8 -28 -20 -35 -25 -20 -13 29 -30 15 -35 40 12 12 輸出樣例 1: Yes 輸入樣例 2: 4 13 -12 12 12 12 -12 -12 12 -12 輸出樣例 2: No
題目有一點點小坑,剛開始還過了五個測試點,只差一個測試點,但還好一兩小時肝一下debug出來了,下面簡單說一下題意:
有一個人在一個圓內,半徑為7.5(這是個坑,不是15噢)單位,然後可以從圓內往外跳,但只有固定的幾個點可以跳,而且能不能跳過去看這個人的最大跳躍距離。只要能跳出100*100的大矩形則說明可以逃生輸出Yes,否則No
題目大意就是這樣啦,簡單擬定一下思路:
1、對於每一個點,用一個struct去實現其儲存結構,儲存其x、y座標、能夠跳到的點的編號、能夠跳到點的數目、能否成為起跳點、能否成為終止點。
2、輸入完x、y座標點之後,掃一遍全點,連結能跳的點,並且判斷能否成為起跳點,能否成為終止點。
3、特判一下當最大可跳距離大於42.5時,可以不用跳到鱷魚,可以直接跳出矩形。
4、實現DFS深搜遞迴,打上vis陣列。
現在我們來動手試一下吧~
首先,標頭檔案,因為是ACMer選手,習慣了C語言的寫法,各位小夥伴不必在意噢,只需要把scanf輸入的東西換成cin,printf輸出的東西換成cout就搞定了
#include<stdio.h> #include<math.h> #include<string.h> #define MAX 999
第二步,開始建立圖結點的結構體:按上面我思路說的,儲存其x、y座標、能夠跳到的點的編號、能夠跳到點的數目、能否成為起跳點、能否成為終止點。
typedef struct ArcNode{ int x; int y; int num; int Next[MAX]; bool out = false; bool in = false; }ArcNode;
第三步,基本變數申明以及函式原型申明:
int N,maxJump,ans,vis[MAX]; void DFS(ArcNode *p,int i); void buildGraph(ArcNode *&p); void buildArc(ArcNode *&p); void searchInOut(ArcNode *&p);
第四步,對Main主函式進行模組化函式構建:
int main() { ArcNode *G; //建立結點 buildGraph(G); //匯入結點內容 if(maxJump >= 42.5) //特判 { printf("Yes\n"); return 0; } buildArc(G); //掃一遍所有點,構造邊,使能互相跳的點結合起來 searchInOut(G); //掃一遍所有點,判斷是否能夠成為起跳點和終止點 ans = -1; //答案初始化 for(int i = 1;i<=N;i++) if(G[i].in) //如果這個點是起跳點 { memset(vis,0,sizeof(vis)); //重置vis陣列 DFS(G,i); //開始深搜 } if(ans == -1) printf("No\n"); //列印答案 else printf("Yes\n"); return 0; }
第五步,匯入結點,比較簡單:一個輸入匯入就可以了。
void buildGraph(ArcNode *&p) { scanf("%d %d",&N,&maxJump); p = new ArcNode[N+1]; for(int i = 1;i<=N;i++) scanf("%d %d",&p[i].x,&p[i].y); }
第六步,連結各點,實現儲存可以互跳的點,這裡需要兩層for迴圈的遍歷,可能效率優點不高,但簡單粗暴
void buildArc(ArcNode *&p) { for(int i = 1;i<=N;i++) { for(int u = 1;u<=N;u++) { if(u == i) continue; if((maxJump * maxJump) >= (p[i].x-p[u].x)*(p[i].x-p[u].x)+(p[i].y-p[u].y)*(p[i].y-p[u].y)) //這裡除了用sqrt還可以直接用平方比較,誤差會小一點 { p[i].num++; p[i].Next[p[i].num] = u; //將可互跳的邊匯入 } } } }
第七步,搜尋起始點和終止點,也是比較簡單的,一個距離公式就好了。
void searchInOut(ArcNode *&p) { for(int i = 1;i<=N;i++) { if((maxJump + 7.5)*(maxJump + 7.5) >= p[i].x*p[i].x+p[i].y*p[i].y) p[i].in = true; if(maxJump >= 50 - abs(p[i].x) || maxJump >= 50 - abs(p[i].y)) p[i].out = true; } }
第八步,巢狀一下DFS遞迴就好了,注意遞迴的終止條件。
void DFS(ArcNode *p,int i) { vis[i] = 1; if(p[i].out == true) //如果搜到了這個點是終止點,說明這個人可以跳出去,那麼就給答案變數做個標記。 ans = 1; if(p[i].num == 0) //遞迴終止條件 return; for(int u = 1; u<=p[i].num; u++) if(!vis[p[i].Next[u]])//防止遞迴迴圈需要一個標記陣列 DFS(p,p[i].Next[u]); }
這樣整個程式就完成啦~ 完整程式碼貼上:
1 #include<stdio.h> 2 #include<math.h> 3 #include<string.h> 4 #define MAX 999 5 6 typedef struct ArcNode{ 7 int x; 8 int y; 9 int num; 10 int Next[MAX]; 11 bool out = false; 12 bool in = false; 13 }ArcNode; 14 15 int N,maxJump,ans,vis[MAX]; 16 void DFS(ArcNode *p,int i); 17 void buildGraph(ArcNode *&p); 18 void buildArc(ArcNode *&p); 19 void searchInOut(ArcNode *&p); 20 int main() 21 { 22 ArcNode *G; 23 buildGraph(G); 24 if(maxJump >= 42.5) 25 { 26 printf("Yes\n"); 27 return 0; 28 } 29 buildArc(G); 30 searchInOut(G); 31 ans = -1; 32 for(int i = 1;i<=N;i++) 33 if(G[i].in) 34 { 35 memset(vis,0,sizeof(vis)); 36 DFS(G,i); 37 } 38 if(ans == -1) printf("No\n"); 39 else printf("Yes\n"); 40 return 0; 41 } 42 void buildGraph(ArcNode *&p) 43 { 44 scanf("%d %d",&N,&maxJump); 45 p = new ArcNode[N+1]; 46 for(int i = 1;i<=N;i++) 47 scanf("%d %d",&p[i].x,&p[i].y); 48 } 49 void buildArc(ArcNode *&p) 50 { 51 for(int i = 1;i<=N;i++) 52 { 53 for(int u = 1;u<=N;u++) 54 { 55 if(u == i) continue; 56 if((maxJump * maxJump) >= (p[i].x-p[u].x)*(p[i].x-p[u].x)+(p[i].y-p[u].y)*(p[i].y-p[u].y)) 57 { 58 p[i].num++; 59 p[i].Next[p[i].num] = u; 60 } 61 } 62 } 63 } 64 void searchInOut(ArcNode *&p) 65 { 66 for(int i = 1;i<=N;i++) 67 { 68 if((maxJump + 7.5)*(maxJump + 7.5) >= p[i].x*p[i].x+p[i].y*p[i].y) 69 p[i].in = true; 70 if(maxJump >= 50 - abs(p[i].x) || maxJump >= 50 - abs(p[i].y)) 71 p[i].out = true; 72 } 73 } 74 void DFS(ArcNode *p,int i) 75 { 76 vis[i] = 1; 77 if(p[i].out == true) 78 ans = 1; 79 if(p[i].num == 0) 80 return; 81 for(int u = 1; u<=p[i].num; u++) 82 if(!vis[p[i].Next[u]]) 83 DFS(p,p[i].Next[u]); 84 }
下面將具體的匯入結點和DFS搜尋過程列印出來,大家可以看看他的步驟流程:
五、個人反思及未來計劃:
有一天老師跟我說了一句話,讓我一直留著比較深的印象:
是啊,從開學到現在一直有不少人告訴我,大家能夠專心幹一件事,你真的就是很棒的人了。
而我,在上學期加了五個社團,Quanta、Eddy、數挖、ACM、招協,拖著班長,拖著五個兼職,我也不知道我怎麼活下來的,大概是想把自己忙成狗 吧,把一些傷心的事情忘得一乾二淨。
下學期收了收心,退了幾個社團,僅留ACM和數挖,班長的事情隨著英劇的結束事情也少了不少,兼職也拖剩了一個,慢慢收心,大概上學期各個方面的接觸,也讓我逐步摸清了未來的發展方向。
就這樣吧,努力計劃做好每天該乾的事情,不負身邊人對我的期望,好好對待每一個人,去努力的帶給他們快樂,帶給自己快樂,帶來更多的動力。
大二,大概有了方向了,嗯,努力幹下去。
(1)圖比較抽象的資料結構上基礎有些不牢,進一步學習普里姆演算法、克魯斯卡爾演算法、迪傑斯特拉演算法等,特別是碰到鏈式儲存的指標使用的時候,需要找時間給自己多加強這方面的學習。
(2)ACM集訓隊每天幾道題,每週寫一篇部落格。
(3)完成論文標解並準備好論文演講『Improving patch-based scene text script identification with ensembles of conjoined networks』
(4)完成論文標解並準備好論文演講『漢老雙語命名實體識別及對齊方法研究_韓銳』