1、首先這里講了圖的定義以及圖的基本術語
圖的基本術語有:(1)子圖;(2)無向完全圖和有向完全圖;(3)稀疏圖和稠密圖;
(4)權和網 :帶權的圖通常稱為網;
(5)鄰接點;(6)度、入度和出度;
(7)路徑和路徑長度:
🔺路徑長度是一條路徑上經過的邊或弧的數目;
(8)回路或環:第一個頂點和最后一個頂點相同的路徑稱為回路或者環;
(9)簡單路徑、簡單回路或簡單環:1)序列中頂點不重復出現的路徑稱為簡單路徑。
2)除了第一個頂點和最后一個頂點之外,其余頂點不重復出現的回路,稱為簡單回路或簡單環;
(10)連通、連通圖和連通分量;(如果連通分量是圖本身,則此圖是連通圖);
(11)強連通圖和強連通分量:(針對有向圖)
強連通分量:極大強連通子圖稱作有向圖的強連通分量;
🔺(12)連通圖的生成樹:一個極小連通子圖,它含有圖中全部頂點,但只有足以構成一棵樹的n-1條邊,這樣的連通子圖稱為連通圖的生成樹。
(13)有向樹和生成森林
2、圖的存儲結構
1、鄰接矩陣(實際上就是一個二維數組)
利用鄰接矩陣創建無向圖
1 const int maxn = 1e5+5;//這里根據題目而定; 2 const int INF = 0x3f3f3f3f;//定義無窮大; 3 int vexnum , arcnum //定義點數和邊數; 4 int G[maxn][maxn]; 5 int v1,v2,w;//輸入的兩個頂點和權值; 6 scanf("%d%d",&vexnum,&arcnum); 7 for(int i = 0 ; i < vexnum ;i++) 8 { 9 for(int j = 0 ; j < vexnum ;j++) 10 { 11 if(i==j) 12 { 13 G[i][j] = 0; 14 }else 15 G[i][j] = INF; 16 } 17 } 18 for(int i = 0 ; i < arcnum ;i++) 19 { 20 scanf("%d%d%d",&v1,&v2,&w); 21 G[v1][v2] = w; 22 G[v2][v1] = w;//因為是無向圖,所以正反兩邊都得建一次; 23 }
2、鄰接表(實際上就是一維數組+指針)
(1)表頭結點表;
表頭結點包括數據域和鏈域兩部分;
(2)邊表;
邊表包括鄰接點域、數據域和鏈域三部分;
采用鄰接表創建無向圖
1 const int maxn = 1e5+5;//這里視題目而定; 2 struct ArcNode{ 3 int adjv;//鄰接點,這里我假設為整型; 4 int w; //權值; 5 struct ArcNode *next1; //指向下一個結點的指針; 6 }p1,p2; 7 struct VNode{ 8 int data; //表頭的數據;這里假設為整型; 9 ArcNode *firstarc; //第一個指向的結點; 10 }G[maxn]; 11 int vexnum , arcnum; 12 int v1,v2,w; 13 int main() 14 { 15 scanf("%d%d",&vexnum,&arcnum);//輸入頂點數和邊數; 16 for(int i = 0 ; i < vexnum;i++) 17 { 18 scanf("%d",&G[i].data); 19 G[i].firstarc = NULL; 20 } 21 for(int i = 0 ; i < arcnum ;i++) 22 { 23 scanf("%d%d%d",&v1,&v2,&w); 24 p1 = new ArcNode;//創建一個空間 25 p1->adjv = v2; //下面便是讀入數據; 26 p1->next1 = G[i].firstarc; 27 p1->w = w; 28 G[i].firstarc = p1; 29 p2 = new ArcNode; 30 p2->adjv = v1; 31 p2->next1 =G[i].firstarc; 32 p2->w = w; 33 G[i].firstarc = p2; 34 } 35 }
3、圖的遍歷
1、深度優先搜索(dfs)
總結起來就三段話
1)遞歸;2)是否被訪問過,用vis標記;3)連通分量(除非是一張連通圖,不然一次dfs訪問的是一個連通分量);
(1)采用鄰接矩陣表示圖的深度優先搜索遍歷
1 const int maxn = 5e5+5;//根據具體題目分析; 2 int G[maxn][maxn];//圖為鄰接矩陣類型,從第v個頂點出發深度優先搜索遍歷圖; 3 bool vis[maxn]; 4 int vexnum;//點數; 5 void dfs(int v) 6 { 7 printf("%d",v); 8 vis[v] = 1; //標記為被訪問過; 9 for(int i = 0 ; i < vexnum ;i++) 10 { 11 if(G[v][i]!=0&&!vis[i]) //如果有邊且未被訪問過; 12 { 13 vis[i] = 1; //標記為被訪問過; 14 dfs(i); //深搜; 15 } 16 } 17 }
2、利用鄰接表表示圖的深搜;
1 const int maxn = 1e5+5;//這里視題目而定; 2 struct ArcNode{ 3 int adjv;//鄰接點,這里我假設為整型; 4 int w; //權值; 5 struct ArcNode *next1; //指向下一個結點的指針; 6 }p1,p2; 7 struct VNode{ 8 int data; //表頭的數據;這里假設為整型; 9 ArcNode *firstarc; //第一個指向的結點; 10 }G[maxn]; 11 int vexnum , arcnum; 12 int v1,v2,w; 13 bool vis[maxn]; 14 ArcNode p; 15 void dfs(int v) 16 { 17 printf("%d",v); 18 vis[v] = 1; 19 p = G[v].firstarc; 20 int w ; 21 while(p!=NULL) 22 { 23 w = p->adjv; 24 if(!vis[w]) 25 dfs(w); 26 p = p->next1; 27 } 28 } 29 int main() 30 { 31 scanf("%d%d",&vexnum,&arcnum);//輸入頂點數和邊數; 32 for(int i = 0 ; i < vexnum;i++) 33 { 34 scanf("%d",&G[i].data); 35 G[i].firstarc = NULL; 36 } 37 for(int i = 0 ; i < arcnum ;i++) 38 { 39 scanf("%d%d%d",&v1,&v2,&w); 40 p1 = new ArcNode; 41 p1->adjv = v2; 42 p1->next1 = G[i].firstarc; 43 p1->w = w; 44 G[i].firstarc = p1; 45 p2 = new ArcNode; 46 p2->adjv = v1; 47 p2->next1 =G[i].firstarc; 48 p2->w = w; 49 G[i].firstarc = p2; 50 } 51 }
2、廣度優先搜索(bfs)
1 void bfs(int v) //bfs函數; 2 { 3 vis[v] = 1; //一進來就標記為訪問過; 4 queue<int>q; //實際上bfs就是層序遍歷,需要用到隊列; 5 int tmp; //一個暫時的變量; 6 q.push(v); //入隊; 7 while(!q.empty())//當隊列為非空時; 8 { 9 tmp = q.front(); //取出隊頭元素; 10 printf(" %d",tmp); //輸出; 11 q.pop();//記得出隊,不然會變成死循環; 12 for(int i = 0 ; i < N ;i++) 13 { 14 if(G[tmp][i]==1&&vis[i]==0)//如果有邊,而且沒被訪問過; 15 { 16 vis[i] = 1; //標記已被訪問; 17 q.push(i); //如果滿足條件則入隊; 18 } 19 } 20 } 21 22 }
4、圖的應用
1、最小生成樹
在一個連通網的所有生成樹中,各邊的代價之和最小的那棵生成樹稱為該連通網的最小代價生成樹,簡稱最小生成樹;
有兩個算法 1、普里姆算法 和克魯斯卡爾算法
1)普里姆算法
普里姆算法的步驟
從圖中某一個頂點出發(這里選V0
),尋找它相連的所有結點,比較這些結點的權值大小,然后連接權值最小的那個結點。(這里是V1
)然后將尋找這兩個結點相連的所有結點,找到權值最小的連接。(這里是V5
).重復上一步,知道所有結點都連接上。
2)克魯斯卡爾
邊思想,不斷找不同集合的最小邊連接,直到所有點都在同個集合;
最小生成樹的代碼在這里先不說了,我自己竟然寫的是並查集,以后有機會再說吧;
接下來還學習了最短路徑
單源最短路徑迪傑斯特拉算法:
(1)首先先解釋一下單源最短路徑:
1)容易的解釋:指定一個點(源點)到其余各個頂點的最短路徑,也叫做“單源最短路徑”
2)官方解釋:給定一個帶權有向圖G=(V,E),其中每條邊的權是一個實數。另外,還給定V中的一個頂點,稱為源。現在要計算從源到其他所有各頂點的最短路徑長度。這里的長度就是指路上各邊權之和。這個問題通常稱為單源最短路徑問題。
(2)解釋一下Dijkstra算法:
例如求A點到B、C、D、E、F頂點的最短路徑;
我們可以先這樣設想:
1)先把所有的點到另一個點的長度全部初始化為無窮大 ,本身到本身則初始化為0,再輸入數值;
第一:將所有點到點初始化為無窮大
代碼大致如下:
1 const int INF = 0x3f3f3f3f; //為無窮大; 2 int G[2000][2000]; 3 int N ;//N為點的個數; 4 5 for( i = 1 ;i <= N ;i++) 6 { 7 for( j = 1 ;j <= N ;j++) 8 { 9 G[i][j] = INF; 10 } 11 }
也可用下面這種:
1 #include<string.h> 2 const int INF = 0x3f3f3f3f; 3 int N; //點的個數; 4 int G[2000][2000]; 5 memset(G,INF,sizeof(G));
第二:將本身到本身初始化為0 ;因為本身到本身的距離就為0;
1 for(int i = 1 ;i <= N; i++) 2 { 3 G[i][i] = 0; //本身到本身距離為0; 4 }
第三:輸入數據:
對應下面的表格:
代碼實現大概如下:
1 int M; //M為邊的個數; 2 int x , y; // x ,y 為同一邊的兩個點; 3 int D; //D 為題目給的邊的權值; 4 for( i = 1; i <= M ;i++) 5 { 6 cin>>x>>y>>D; 7 8 if(G[x][y]>D) 9 { 10 G[x][y] = D; 11 G[y][x] = D; 12 } 13 14 } 15 16
17 //上面我們已經把點到其他點的距離初始化為無窮大,把本身到本身初始化為0;
//那么上面這個循環的化,我們可以把題目給的兩點之間的權值輸入;
18 //題目給的權值一定為正值,所以比0大,故本身到本身的距離還是維持為0;
//而小於無窮大,所以用上面那個循環可把邊的權值輸入;
2)我們還需用一個一維數組d來存儲A頂點到其余各個頂點的初始路程
下面我們來模擬一下:
這是輸入最初數據的表格:
模擬:
1)既然是從A點到其余各個頂點的最短路徑,那就先找一個離A號頂點最近的頂點。 A到B的距離最小
所以d[2]的值就已經從“估計值”變成了“確定值”,目前離A頂點最近的是B頂點。
2)既然選了B點,接下來看B頂點有哪些出邊呢,有B->E和B->C兩條邊。
先討論B->E能否讓A頂點到E頂點的路程變短;
A->B->E :d[2]+e[2][5]表示從A到B到E的距離;d[2]是A到B頂點的路程,e[2][5]表示B到E的距離; 12+7 = 19;
A->E :d[5] = 16;
我們發現 d[2]+e[2][5]=19,d[5] = 16; 所以 d[5] < d[2]+e[2][5];
所以A到E目前的最短距離為16;
d
因此d[5]更新為 16;(這個過程有個專業術語叫做:松弛);
d
再判斷B->C
A->B->C:d[2]+e[2][3] 表示A到B到C的距離;d[2]是A到B頂點的路程,e[2][3]表示B到C 的距離;12+10 = 22;
A->C:A沒有直接到C ,所以為無窮大;
22<無窮大
所以A到C目前的最短距離為22;d[3] = 22;
這樣從A到B的當前最短路徑就更新完了;
3)找除B外離A最近的點:由圖可知是F點;
由圖可知F的出邊有兩條:F->E, F->D;
先討論 F->E能否讓A到E的距離變短;
A->F->E :d[6]+e[6][5] = 14+ 9 = 23;
A->E :d[5] = 16;
所以d[5] < d[6]+e[6][5];
所以當前A到E的最短距離為16; d[5] = 16;
再討論E->D的距離能否讓A到D的距離變短
A->E->D : d[5]+e[5][4] = 16+2=18;
A->D : 無窮大;
所以A到D的當前最短距離更新為18; d[4] = 18;
4)找除B和F外離A最近的點:即E點
E的出邊有C和D,看E->C和E->D能否使A到C的距離變短,使A到D的距離變短;
先討論E->D
A->E->D :d[5]+e[5][4] = 16+2 = 18
之前更新后的AD d[4] = 22;
所以AD 當前最短變為18; d[4] = 18;
討論E->C,看是否讓AC變短
A->E->C :d[5]+e[5][3] = 16+6 =22;
原更新的AC為22;
所以當前AC最短為22; d[3] = 22;
5)找除B,E,F外離A最近的點,即D
D的出邊只有D->C;
看D->C,能否讓AC變短;
A->D->C d[4]+e[4][3] = 18+5 = 23;
原AC 22;
所以AC當前最短為22; d[3] = 22;
6)最后剩下C
全部找完,確定了A到各個點的最短路徑;
還有做了老師布置的一道作業和實踐題
作業題:
給定一個有N個頂點和E條邊的無向圖,請用DFS和BFS分別列出其所有的連通集。假設頂點從0到N−1編號。進行搜索時,假設我們總是從編號最小的頂點出發,按編號遞增的順序訪問鄰接點。
輸入格式:
輸入第1行給出2個整數N(0<N≤10)和E,分別是圖的頂點數和邊數。隨后E行,每行給出一條邊的兩個端點。每行中的數字之間用1空格分隔。
輸出格式:
按照"{ v1 v2 ... vk }"的格式,每行輸出一個連通集。先輸出DFS的結果,再輸出BFS的結果。
輸入樣例:
8 6
0 7
0 1
2 0
4 1
2 4
3 5
輸出樣例:
{ 0 1 4 2 7 }
{ 3 5 }
{ 6 }
{ 0 1 2 7 4 }
{ 3 5 }
{ 6 }
代碼如下:
1 #include<iostream> 2 #include<stdio.h> 3 #include<string.h> 4 #include<queue> 5 using namespace std; 6 7 int N ,M ; //輸入點數和邊數; 8 int G[15][15];//將圖存在鄰接矩陣中,因為題目N較小,綜合下來用鄰接矩陣方便; 9 bool vis[15];//用來記錄是否已被訪問過; 10 int x , y; 11 void dfs(int v) //dfs函數; 12 { 13 vis[v] = 1; //一進來就標記為被訪問過; 14 for(int i = 0 ; i < N ;i++) //遍歷; 15 { 16 if(G[v][i]==1&&vis[i]==0) //如果有邊而且還未沒被訪問過; 17 { 18 vis[i] = 1; //標記被訪問過; 19 printf(" %d",i); //並輸出; 20 dfs(i);//利用遞歸繼續深搜; 21 } 22 } 23 } 24 void bfs(int v) //bfs函數; 25 { 26 vis[v] = 1; //一進來就標記為訪問過; 27 queue<int>q; //實際上bfs就是層序遍歷,需要用到隊列; 28 int tmp; //一個暫時的變量; 29 q.push(v); //入隊; 30 while(!q.empty())//當隊列為非空時; 31 { 32 tmp = q.front(); //取出隊頭元素; 33 printf(" %d",tmp); //輸出; 34 q.pop();//記得出隊,不然會變成死循環; 35 for(int i = 0 ; i < N ;i++) 36 { 37 if(G[tmp][i]==1&&vis[i]==0)//如果有邊,而且沒被訪問過; 38 { 39 vis[i] = 1; //標記已被訪問; 40 q.push(i); //如果滿足條件則入隊; 41 } 42 } 43 } 44 45 } 46 int main() 47 { 48 scanf("%d%d",&N,&M);//輸入點數和邊數; 49 for(int i = 1 ; i <= M ;i++) 50 { 51 scanf("%d%d",&x,&y); 52 G[x][y] = 1;//輸入鄰接矩陣,注意這是一張無向圖,所以正反都得輸入; 53 G[y][x] = 1; 54 } 55 for(int i = 0 ;i < N ;i++) //將所有點遍歷一遍,讓所有連通分量都有被遍歷過; 56 { 57 if(vis[i]==0)//如果沒有被訪問過; 58 { 59 printf("{");//格式; 60 printf(" %d",i); 61 dfs(i);//深搜; 62 printf(" }\n"); 63 } 64 } 65 memset(vis,0,sizeof(vis));//注意此時應將所有點標記成未訪問過,進行廣搜,memset的頭文件為<string.h>; 66 for(int i = 0 ;i < N ;i++) 67 { 68 if(vis[i]==0)//如果沒被訪問過; 69 { 70 printf("{"); 71 bfs(i);//廣搜; 72 printf(" }\n"); 73 } 74 } 75 return 0; 76 }
實踐題:
在老電影“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
解題思路:這是在我畫圖之后想到的,先深搜找連通分量,然后將連通分量染色,然后看是否有連通分量滿足題意
第一個flag1是判斷是否連接矩形,我判斷的是直線距離,
其實本應該判斷的是以連通塊某點為圓心以那個最大距離為半徑與矩形是否有交點,
但是其實判斷直線距離就好了,flag2是判斷連通塊的點是否與小島圓有交點
大概思路如上,由這張圖思考而來的;

代碼如下:
1 #include<iostream> 2 #include<stdio.h> 3 #include<cmath> 4 using namespace std; 5 6 int N ; //鱷魚的個數; 7 int maxdis; //跳的最大距離; 8 struct zb{ 9 int u; //點的橫坐標; 10 int v; //點的縱坐標; 11 }G[105];//圖中點的信息 12 double map[105][105];//圖中點與點的距離; 13 double getdis(int i ,int j) //獲得兩點間的距離; 14 { 15 double d = sqrt((G[i].u-G[j].u)*(G[i].u-G[j].u)+(G[i].v-G[j].v)*(G[i].v-G[j].v)); 16 return d; 17 } 18 int tp1 , tp2; //輸入的橫坐標和縱坐標; 19 bool vis[105]; //標記點是否被訪問過; 20 int color[105]; //用於將連通塊染色; 21 int count1 = 1; //用於染色的記數, 22 23 void dfs(int x) //深搜,找連通塊; 24 { 25 vis[x] = 1; //標記該點訪問過; 26 for(int i = 1 ; i<= N ;i++) 27 { 28 if(x!=i&&vis[i]==0&&map[x][i]<=maxdis)//如果另一點還沒被訪問過,且兩點間距離小於最大距離; 29 { 30 color[i] = count1; //將連通的染為同種顏色; 31 vis[i] = 1; //標記訪問過; 32 dfs(i); //繼續深搜; 33 } 34 } 35 } 36 37 int main() 38 { 39 scanf("%d%d",&N,&maxdis); //輸入鱷魚的個數和最大距離; 40 for(int i = 1 ; i <= N ;i++) 41 { 42 scanf("%d%d",&tp1,&tp2); 43 G[i].u = tp1; //將輸入的橫坐標賦給圖的點的橫坐標; 44 G[i].v = tp2; //將輸入的縱坐標賦給圖的點的縱坐標; 45 } 46 for(int i = 1 ; i <= N ;i++) 47 { 48 for(int j = 1 ; j <= N ;j++) 49 { 50 if(i!=j) 51 { 52 map[i][j] = map[j][i] = getdis(i,j);//將距離存成圖中的邊; 53 } 54 } 55 } 56 for(int i = 1 ; i <= N ;i++) 57 { 58 if(vis[i]==0) 59 { 60 color[i] = count1 ; //染色; 61 dfs(i); 62 count1++; //代表不同的連通塊,染不同的顏色; 63 } 64 } 65 66 67 int flag1 = 0 ,flag2 = 0; //flag1標記是否與矩形相連,flag2標記是否與小島圓相連; 68 for(int i = 1 ; i <= count1 ;i++) //遍歷各個連通塊; 69 { 70 flag1 = 0 , flag2 = 0;//用於判斷同個連通塊是否同時滿足兩個條件; 71 for(int j = 1 ; j <= N ;j++) 72 { 73 if(color[j]==i) //如果是同一個連通塊 74 { 75 if(G[j].v+maxdis>=50||G[j].v-maxdis<=-50||G[j].u+maxdis>=50||G[j].u-maxdis<=-50) 76 { 77 flag1 = 1; 78 }//與矩形相連; 79 80 if((G[j].u)*(G[j].u)+(G[j].v)*(G[j].v)<=(7.5+maxdis)*(7.5+maxdis)) 81 { 82 flag2 = 1; 83 }//與小島圓相連; 84 } 85 } 86 87 if(flag1==1&&flag2==1)//如果同一個連通塊同時滿足兩個條件; 88 { 89 printf("Yes\n"); 90 return 0; 91 } 92 93 } 94 printf("No\n"); 95 return 0; 96 }
總結:這一章感覺學了挺多東西,希望接下來再接再厲,繼續努力;
下一章的目標:好好學習,靈活應用!