陈越《数据结构》第六讲 图(上)
6.1 什么是圖
6.1.1 定義
- 一組頂點 :通常用 V (Vertex) 表示頂點集合;
- 一組邊:通常用 E (Edge) 表示邊的集合;
— 邊是頂點對:(v,w)∈E,其中 v,w∈V ;
— 又向邊 <v,w> <script type="math/tex" id="MathJax-Element-7"> </script>表示從 v 指向w的邊(單行線);
— 不考慮重邊與回路。
6.1.2 抽象數據類型
6.1.3 常見術語
無向圖 :無向圖中頂點之間的邊無方向性,邊 (w,v) 同 (v,w) ;
有向圖 :有向圖中頂點之間的邊有方向性,邊 <w,v> <script type="math/tex" id="MathJax-Element-14"> </script>不同 <v,w> <script type="math/tex" id="MathJax-Element-15"> </script>;
簡單圖 :圖中出現重邊,則稱之為非簡單圖;
鄰接點 :如果 (v,w) 是無向圖中任意一條邊,那么稱 v 和w互為“鄰接點”;如果 <v,w> <script type="math/tex" id="MathJax-Element-21"> </script>是有向圖中任意一條邊,那么稱 v 鄰接到終點w,也稱 w 鄰接自終點v。
有向完全圖 :一個有向圖中,如果任意兩頂點之間都有方向互為相反的兩條弧相連接,則稱“有向完全圖”;一個含有 n 個頂點的有向完全圖中,共有n(n?1)/2條邊。
稠密圖、稀疏圖 :一個圖的”稠密度“定義為平均頂點度 2|E|/|V| ;稠密圖可定量地定義為 “平均頂點度與頂點數量|V|成正比的圖” 。
權、網絡 :邊上附加一個數值信息我們稱之為權;邊上帶權的圖稱為網圖或“網絡”。
6.1.4 怎么在程序中表示一個圖
1.鄰接矩陣
優勢:
- 直觀、簡單、好理解;
- 方便檢查任意一對頂點間是否存在邊;
- 方便找任一頂點的所有“鄰接點”(有邊直接相連的頂點);
- 方便計算任一頂點的“ 度 ”(從該點發出的邊數為“出度”,指向該點的邊數為“入度”);
– 無向圖:對應行(或列)非0元素的個數;
– 有向圖:對應行非0 元素的個數是“出度”;對應列非0元素的個數是“入度”。
劣勢:
- 浪費空間(特別是稀疏圖);
- 浪費時間 (例如:統計稀疏圖中一共有多少條邊)。
2. 數組
用一個長度為N(N+1)/2 的1 維數組A存儲; {G00,G10,G11,……,G(n?1)0,…,G(n?1)(n?1)} ,則 Gij 在A 中對應的下標是:
i?(i+1)/2+j
對于 網絡 ,只要把 G[i][j] 的值定義為邊 <vi,vj> <script type="math/tex" id="MathJax-Element-43"> </script>的權重即可。
3. 鄰接表
- 方便找任一頂點的所有“鄰接點”;
- 節約 稀疏圖 的空間;
– 需要 N 個頭指針 + 2E個結點(每個結點至少 2 個域)。 - 方便計算任一頂點的“度”?
– 對無向圖:是的;
– 對有向圖:只能計算“出度”;需要構造“逆鄰接表”(存指向自己的邊)來方便計算“入度”。 - 方便檢查任意一對頂點間是否存在邊?( No )
用一維數組G[ ]存儲有4個頂點的無向圖如下:
G[]=0,1,0,1,1,0,0,0,1,0 則頂點2和頂點0之間是有邊的。(對或錯?)
答:對,記得從0開始計數,運用數組的公式 i?(i+1)/2+j 。
用鄰接表表示有 N 個頂點、E條邊的圖,則遍歷圖中所有邊的時間復雜度為: O(N+E) 。
6.2 圖的遍歷
6.2.1 深度優先搜索(Depth First Search,DFS)
類似于樹的先序遍歷
若有 N 個頂點、E條邊, 時間復雜度 是:
- 用鄰接表存儲圖,有 O(N+E) ;
- 用鄰接矩陣存儲圖,有 O(N2) ;
6.2.2 廣度優先搜索 (Breadth First Search, BFS)
類似于樹的層序遍歷
若有 N 個頂點、E條邊, 時間復雜度 是:
- 用鄰接表存儲圖,有 O(N+E) ;
- 用鄰接矩陣存儲圖,有 O(N2) 。
6.2.3 DFS和BFS的優點和缺點
BFS :一種基于隊列這種數據結構的搜索方式,它的特點是由每一個狀態可以擴展出許多狀態,然后再以此擴展,直到找到目標狀態或者隊列中頭尾指針相遇,即隊列中所有狀態都已處遍歷完畢。
- 優點:對于解決最短或最少問題特別有效,而且尋找深度小;
- 缺點:內存耗費量大,需要開辟大量的數組單元用來存儲狀態。
DFS :基于遞歸的搜索方式,它的特點是由一個狀態擴展到另外一個狀態,然后不停地擴展,直到找到目標或者無法繼續到另一個狀態。
- 優點:占內存少,對于解決連通性性問題比較有效,能找到最優解(一定條件下),但能很快找到接近解;
- 缺點:可能不必遍歷所有分枝(也就是速度快),在深度很大的情況下效率不高。
6.2.4 圖不連通怎么辦?
- 極大頂點數:再加1個頂點就不連通了;
- 極大邊數:包含子圖中所有頂點相連的所有邊。
打印每個連通圖:
6.3
/* queue 模板類的定義在<queue>頭文件中。 與stack 模板類很相似,queue 模板類也需要兩個模板參數,一個是元素類型,一個容器類 型,元素類型是必要的,容器類型是可選的,默認為deque 類型。 定義queue 對象的示例代碼如下: queue<int> q1; queue<double> q2;queue 的基本操作有: 入隊,如例:q.push(x); 將x 接到隊列的末端。 出隊,如例:q.pop(); 彈出隊列的第一個元素,注意,并不會返回被彈出元素的值。 訪問隊首元素,如例:q.front(),即最早被壓入隊列的元素。 訪問隊尾元素,如例:q.back(),即最后被壓入隊列的元素。 判斷隊列空,如例:q.empty(),當隊列空時,返回true。 */ #include<iostream> #include<string.h> #include "queue" using namespace std; int M[10][10]; //存儲圖的矩陣; bool visited[10]; //看圖中的每個節點是否訪問過; int result[10]; //存放結果的矩陣; int vertex,ridge; //頂點和邊 int k; //計每一個鄰接表存儲的結果 void DFS(int x) { /*深度搜索*/int i;result[k++] = x;visited[x] = true;for(i = 0;i < vertex; i++){if(M[x][i] == 1 && !visited[i])/*是否有邊;是否訪問過*/DFS(i);} }void BFS(int x) {int i;queue<int> q;q.push(x);visited[x] = 1;result[k++] = x;while (!q.empty()) {int l = q.front();q.pop(); for ( i = 0; i < vertex; i++) {if (M[l][i] == 1 && !visited[i]) {/*是否有邊;是否訪問過*/visited[i] = 1;result[k++] = i;q.push(i);}}} }int main() {int i,j,m,n;cin>>vertex>>ridge;/*初始化*/memset(visited,0, sizeof(visited)); //visited數列全部為0for(i = 0;i < vertex;i++){for(j = 0; j < ridge; j++){M[i][j] = 0;}}while(ridge--){cin>>m>>n;M[m][n] = 1;M[n][m] = 1;}/*開始進行深度搜索*/for(i = 0;i < vertex;i++ ){k = 0;if(!visited[i]){DFS(i);cout<<"{ ";for(j = 0;j < k;j++)cout<<result[j]<<" ";cout<<"}"<<endl;}}/*開始進行廣度搜索*/memset(visited, 0, sizeof(visited));for ( i = 0; i < vertex; i++){k = 0;if (!visited[i]) {BFS(i);cout << "{ ";for ( j = 0; j < k; j++)cout << result[j] << " ";cout << "}" << endl;}}//system("pause");return 0; }
6.4
#include <stdio.h> #include <string.h> #include <iostream> #include <algorithm> #include <vector> #include <queue> #include <set> #include <map> #include <string> #include <math.h> #include <stdlib.h> #include <time.h> /*邦德*/ using namespace std; const int inf=1<<30; struct node {double x,y; } a[100+5]; int n,vis[100+5],b[100+5],ans[100+5],cnt; double d,ansfirst,nowfirst; int dis(node d1,node d2) {if(d*d<(d1.x-d2.x)*(d1.x-d2.x)+(d1.y-d2.y)*(d1.y-d2.y)) return 0;return 1; } int first(int d1) {if(sqrt(a[d1].x*a[d1].x+a[d1].y*a[d1].y)>d+7.5) return 0;else return 1; }double first1(int d1) {return sqrt(a[d1].x*a[d1].x+a[d1].y*a[d1].y)-7.5; } int safe(node d1) {if(d1.x>=50-d) return 1;if(d1.y>=50-d) return 1;if(d1.x<=-50+d) return 1;if(d1.y<=-50+d) return 1;return 0; } void dfs(int d1,int now) {int i;if(safe(a[d1])){//printf("%d %.2f\n",now,nowfirst);if(now<cnt){for(i=0; i<now; i++)ans[i]=b[i];cnt=now;ansfirst=nowfirst;}else if(now==cnt&&ansfirst>nowfirst){for(i=0; i<now; i++)ans[i]=b[i];cnt=now;ansfirst=nowfirst;}return ;}else{for(i=1; i<=n; i++){if(!vis[i]&&dis(a[d1],a[i])){vis[i]=1;b[now]=i;dfs(i,now+1);vis[i]=0;}}}return; } int main() {int i;while(~scanf("%d%lf",&n,&d)){a[0].x=a[0].y=0;for(i=1; i<=n; i++){scanf("%lf%lf",&a[i].x,&a[i].y);}if(d+7.5>=50){printf("1\n");return 0;}ansfirst=(double)inf;cnt=inf;memset(ans,0,sizeof(ans));for(i=1; i<=n; i++){memset(vis,0,sizeof(vis));if(!vis[i]&&first(i)){nowfirst=first1(i);if(safe(a[i])){if(ansfirst>nowfirst){ans[0]=i;cnt=1;ansfirst=nowfirst;}}vis[i]=1;memset(b,0,sizeof(b));b[0]=i;dfs(i,1);}}if(cnt==inf) printf("0\n");else{printf("%d\n",cnt+1);for(i=0; i<cnt; i++){printf("%.0f %.0f\n",a[ans[i]].x,a[ans[i]].y);}}}return 0; }
6.5
/* 題意: 找到一個圖中每個節點通過最多5條邊 能找到的所有節點 然后輸出百分比思路:廣搜 記錄層數為6以內的所有節點本題的關鍵在于 如何記錄節點當前的層數 1. 引入2個變量 last tail 分別指向 當前層數的最后一個元素 和 下一層的最后一個元素 2. 若當前出隊的元素與last相等 則說明即將進入下一層 將last更新為tail 更新tail 重復~~知道level = 6 或者隊列空 */ /*6度空間*/ #include "iostream" #include "stdio.h" #include "queue" using namespace std; bool map[10001][10001] = {false}; int n, m; int Count; void bfs(int x) {bool visited[10001] = { false };queue<int>q;q.push(x);visited[x] = true;int level = 0; /* 記錄層數 */int last = x; /* 記錄當前層數的最后一個元素 */int tail; /* 指向下一層最后一個元素 */while (!q.empty()) {x = q.front();q.pop();for (int i = 1; i <= n; i++) {if (!visited[i] && map[x][i] == 1) {q.push(i); /* 進隊 */Count++;visited[i] = true;tail = i;}}if (last == x) {level++;last = tail;}if (level == 6)break;} } int main() {cin >> n >> m;for (int i = 0; i < m; i++) { int k, l;cin >> k >> l;map[k][l] = 1;map[l][k] = 1;}for (int i = 1; i <=n; i++) { /* 對于所有節點 做bfs() */Count = 1;bfs(i);cout << i << ": ";float answer = (float)Count / n * 100;printf("%.2f%%\n", answer);}return 0; }
總結
以上是生活随笔為你收集整理的陈越《数据结构》第六讲 图(上)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 移动架构 (一) 详解架构设计中UML图
- 下一篇: c++语言最小公倍数怎么求,如何在C++