NO.91十六届蓝桥杯备战|图论根本-图的存储和遍历|毗邻矩阵|vector|链式前向 ...

打印 上一主题 下一主题

主题 1798|帖子 1798|积分 5398

图的基本概念

图的界说

图G是由顶点集V和边集E组成,记为G = (V, E),其中V(G)表⽰图G中顶点的有限⾮空集;E(G)表⽰图G中顶点之间的关系(边)集合。若                                   V                         =                                   {                                       v                               1                                      ,                                       v                               2                                      ,                            …                            ,                                       v                               n                                      }                                       V = \left\{ v_{1},v_{2},\dots,v_{n} \right\}                  V={v1​,v2​,…,vn​},则⽤                                   ∣                         V                         ∣                              |V|                  ∣V∣表
⽰图G中顶点的个数,也称图G的阶,                                   E                         =                                   (                            u                            ,                            v                            )                            ∣                            u                            ∈                            V                            ,                            v                            ∈                            V                                       E = {(u,v)|u \in V, v \in V}                  E=(u,v)∣u∈V,v∈V,⽤                                   ∣                         E                         ∣                              |E|                  ∣E∣表⽰图G中边的条数。
图是较线性表和树更为复杂的数据结构。


  • 线性表中,除第⼀个和最后⼀个元素外,每个元素只有⼀个唯⼀的前驱和唯⼀的后继结点,元素和元素之间是⼀对⼀的关系;
  • 在树形结构中,数据元素之间有着显着的层次关系,每个元素有唯⼀的双亲,但可能有多个孩⼦,元素和元素之间是⼀对多的关系;
  • ⽽图形结构中,元素和元素之间的关系更加复杂,结点和结点之间的关系是任意的,任意两个结点之间都可能相干,图中元素和元素之间是多对多的关系

有向图和⽆向图

图根据边的类型,可以分为⽆向图和有向图

在图相干的算法中,我们可以将⽆向图中的边看成两条⽅向相反的有向边,从⽽将⽆向图转化为有向图

简单图与多重图

⾃环:⾃⼰指向⾃⼰的⼀条边

重边:图中存在两个或两个以上完全雷同的边

简单图:若图中没有重边和⾃环,为简单图。
多重图:若图中存在重边或⾃环,为多重图。

稠密图和希罕图

有很少条边(如e < nlog2 n )的图称为希罕图,反之称为稠密图

顶点的度

顶点v的度是指与它相干联的边的条数,记作deg(v)。由该顶点发出的边称为顶点的出度,到达该顶点的边称为顶点的⼊度。


  • ⽆向图中,顶点的度即是该顶点的⼊度(indev)和出度(outdev),即deg(v)=indeg(v)=outdeg(v)。
  • 有向图中,顶点的度即是该顶点的⼊度与出度之和,其中顶点v的⼊度indeg(v)是以v为终点的有向边的条数,顶点v的出度outdeg(v)是以v为起始点的有向边的条数,deg(v)=indeg(v)+outdeg(v)

路径

在图G=(V,E)中,若从顶点                                             v                            i                                       v_{i}                  vi​出发,沿⼀些边颠末某些顶点                                             v                                       p                               1                                            ,                                   v                                       p                               2                                            ,                         …                         ,                                   v                                       p                               m                                                 v_{p1},v_{p2},\dots,v_{pm}                  vp1​,vp2​,…,vpm​,到达顶点                                             v                            j                                       v_{j}                  vj​。则称顶点序列                                   (                                   v                            i                                  ,                                   v                                       p                               1                                            ,                                   v                                       p                               2                                            ,                         …                         ,                                   v                                       p                               m                                            ,                                   v                            j                                  )                              (v_{i},v_{p1},v_{p2},\dots,v_{pm},v_{j})                  (vi​,vp1​,vp2​,…,vpm​,vj​)为从顶点                                             v                            i                                       v_{i}                  vi​到顶点                                             v                            j                                       v_{j}                  vj​的路径。
注意:两个顶点间的路径可能不唯⼀

简单路径与回路

若路径上各顶点                                             v                            1                                  ,                                   v                            2                                  ,                         …                         ,                                   v                            m                                       v_{1},v_{2},\dots,v_{m}                  v1​,v2​,…,vm​均不重复,则称这样的路径为简单路径。若路径上第⼀个顶点                                             v                            1                                       v_{1}                  v1​和最后⼀个顶点                                             v                            m                                       v_{m}                  vm​雷同,则称这样的路径为回路或环

路径⻓度和带权路径⻓度

某些图的边具有与它相干的数值,称其为该边的权值。这些权值可以表⽰两个顶点间的距离、泯灭的代价、所需的时间等。⼀般将该种带权图称为⽹络

对于不带权的图,⼀条路径的路径⻓度是指该路径上的边的条数。
对于带权的图,⼀条路径的路径⻓度是指该路径上各个边权值的总和。

⼦图

设图                                   G                         =                                   {                            V                            ,                            E                            }                                       G = \left\{ V, E\right\}                  G={V,E}和图                                             G                            ′                                  =                                   {                                       V                               ′                                      ,                                       E                               ′                                      }                                       G' = \left\{ V',E' \right\}                  G′={V′,E′},若                                             V                            ′                                  ∈                         V                              V'\in V                  V′∈V 且                                             E                            ′                                  ∈                         E                              E'\in E                  E′∈E,则称                                             G                            ′                                       G'                  G′是                                   G                              G                  G的⼦图。如有                                   V                         (                                   G                            ′                                  )                         =                         V                         (                         G                         )                              V(G')=V(G)                  V(G′)=V(G)的⼦图                                             G                            ′                                       G'                  G′,则称                                             G                            ′                                       G'                  G′为                                   G                              G                  G的⽣成⼦图。
相当于就是在原来图的根本上,拿出来⼀些顶点和边,组成⼀个新的图。但是要注意,拿出来的点和边要能构成⼀个图才⾏

G1_1和G1_2为⽆向图G1的⼦图,G1_1为G1的⽣成⼦图。
G2_1和G2_2为有向图G2的⼦图,G2_1为G2的⽣成⼦图。
连通图与连通分量

在⽆向图中,若从顶点                                             v                            1                                       v_{1}                  v1​到顶点                                             v                            2                                       v_{2}                  v2​有路径,则称顶点                                             v                            1                                       v_{1}                  v1​与顶点                                             v                            2                                       v_{2}                  v2​是连通的。如果图G中任意⼀对顶点都是连通的,则图G称为连通图,否则称为⾮连通图。


  • 假设⼀个图有n个顶点,如果边数⼩于n-1,那么此图⼀定是⾮连通图。
  • 极⼤联通⼦图:⽆向图中,拿出⼀个⼦图,这个⼦图包含尽可能多的点和边。
  • 连通分量:⽆向图中的极⼤连通⼦图称为连通分量

⽣成树

连通图的⽣成树是包含图中全部顶点的⼀个极⼩连通⼦图。若图中顶点数为n,则它的⽣成树含有n-1条边。对⽣成树⽽⾔,若砍去⼀条边,则会酿成⾮连通图,若加上⼀条边则会形成⼀个回路

图的存储和遍历

图的存储有两种:毗邻矩阵和毗邻表:


  • 其中,毗邻表的存储⽅式与树的孩⼦表⽰法完全⼀样。因此,⽤vector数组以及链式前向星就能实现。
  • ⽽毗邻矩阵就是⽤⼀个⼆维数组,其中edges[j]存储顶点 i 与顶点 j 之间,边的信息。
    图的遍历分两种:DFS和BFS,和树的遍历⽅式以及实现⽅式完全⼀样。因此,可以仿照树这个数据结构来学习
毗邻矩阵

毗邻矩阵,指⽤⼀个矩阵(即⼆维数组)存储图中边的信息(即各个顶点之间的毗邻关系),存储顶点之间毗邻关系的矩阵称为毗邻矩阵。
对于带权图⽽⾔,若顶点                                             v                            i                                       v_{i}                  vi​和                                             v                            j                                       v_{j}                  vj​之间有边相连,则毗邻矩阵中对应项存放着该边对应的权值,若顶点                                             v                            i                                       v_{i}                  vi​和                                             v                            j                                       v_{j}                  vj​不相连,则⽤                                   ∞                              \infty                  ∞来代表这两个顶点之间不存在边。
对于不带权的图,可以创建⼀个⼆维的bool类型的数组,来标记顶点vi 和vj 之间有边相连

矩阵中元素个数为nxn,即空间复杂度为O(n^2) ,n为顶点个数,和实际边的条数⽆关,适合存储稠密图
  1. #include <iostream>  
  2. #include <cstring>  
  3. using namespace std;  
  4. const int N = 1010;  
  5. int n, m;  
  6. int edges[N][N];  
  7. int main()  
  8. {  
  9.         memset(edges, -1, sizeof edges);  
  10.         cin >> n >> m; // 读⼊结点个数以及边的个数  
  11.         for(int i = 1; i <= m; i++)  
  12.         {  
  13.                 int a, b, c; cin >> a >> b >> c;  
  14.                 // a - b 有⼀条边,权值为 c  
  15.                 edges[a][b] = c;  
  16.                 // 如果是⽆向边,需要反过来再存⼀下  
  17.                 edges[b][a] = c;  
  18.         }
  19.         return 0;  
  20. }
复制代码
vector数组

和树的存储⼀模⼀样,只不外如果存在边权的话,我们的vector数组⾥⾯放⼀个结构体大概是pair即可。
  1. #include <iostream>  
  2. #include <vector>  
  3. using namespace std;  
  4. typedef pair<int, int> PII;  
  5. const int N = 1e5 + 10;  
  6. int n, m;  
  7. vector<PII> edges[N];  
  8. int main()  
  9. {  
  10.         cin >> n >> m; // 读⼊结点个数以及边的个数  
  11.         for(int i = 1; i <= m; i++)  
  12.         {  
  13.                 int a, b, c; cin >> a >> b >> c;  
  14.                 // a 和 b 之间有⼀条边,权值为 c  
  15.                 edges[a].push_back({b, c});  
  16.                 // 如果是⽆向边,需要反过来再存⼀下  
  17.                 edges[b].push_back({a, c});  
  18.         }  
  19.         return 0;  
  20. }
复制代码
链式前向星

和树的存储⼀模⼀样,只不外如果存在边权的话,我们多创建⼀维数组,⽤来存储边的权值即可
  1. #include <iostream>  
  2. using namespace std;  
  3. const int N = 1e5 + 10;  
  4. // 链式前向星  
  5. int h[N], e[N * 2], ne[N * 2], w[N * 2], id;  
  6. int n, m;  
  7. // 其实就是把 b 头插到 a 所在的链表后⾯  
  8. void add(int a, int b, int c)  
  9. {  
  10.         id++;  
  11.         e[id] = b;  
  12.         w[id] = c; // 多存⼀个权值信息  
  13.         ne[id] = h[a];  
  14.         h[a] = id;  
  15. }  
  16. int main()  
  17. {  
  18.         cin >> n >> m; // 读⼊结点个数以及边的个数  
  19.         for(int i = 1; i <= m; i++)  
  20.         {  
  21.                 int a, b, c; cin >> a >> b >> c;  
  22.                 // a 和 b 之间有⼀条边,权值为 c  
  23.                 add(a, b, c); add(b, a, c);  
  24.         }  
  25.         return 0;  
  26. }
复制代码
DFS

和树的遍历⽅式⼀模⼀样,⼀条路⾛到⿊

  • ⽤毗邻矩阵的⽅式存储
  1. #include <iostream>  
  2. #include <cstring>  
  3. #include <queue>  
  4. using namespace std;  
  5. const int N = 1010;  
  6. int n, m;  
  7. int edges[N][N];  
  8. bool st[N]; // 标记哪些点已经访问过  
  9. void dfs(int u)  
  10. {  
  11.         cout << u << endl;  
  12.         st[u] = true;  
  13.         // 遍历所有孩⼦  
  14.         for(int v = 1; v <= n; v++)  
  15.         {  
  16.                 // 如果存在 u->v 的边,并且没有遍历过  
  17.                 if(edges[u][v] != -1 && !st[v])  
  18.                 {  
  19.                         dfs(v);  
  20.                 }  
  21.         }  
  22. }  
  23. int main()  
  24. {  
  25.         memset(edges, -1, sizeof edges);  
  26.         cin >> n >> m; // 读⼊结点个数以及边的个数  
  27.         for(int i = 1; i <= m; i++)  
  28.         {  
  29.                 int a, b, c; cin >> a >> b >> c;  
  30.                 // a - b 有⼀条边,权值为 c  
  31.                 edges[a][b] = c;  
  32.                 // 如果是⽆向边,需要反过来再存⼀下  
  33.                 edges[b][a] = c;  
  34.         }
  35.         return 0;  
  36. }
复制代码

  • ⽤vector数组的⽅式存储
  1. #include <iostream>  
  2. #include <vector>  
  3. #include <queue>  
  4. using namespace std;  
  5. typedef pair<int, int> PII;  
  6. const int N = 1e5 + 10;  
  7. int n, m;  
  8. vector<PII> edges[N];  
  9. bool st[N]; // 标记哪些点已经访问过  
  10. void dfs(int u)  
  11. {  
  12.         cout << u << endl;  
  13.         st[u] = true;  
  14.         // 遍历所有孩⼦  
  15.         for(auto& t : edges[u])  
  16.         {  
  17.                 // u->v 的⼀条边,权值为 w  
  18.                 int v = t.first, w = t.second;  
  19.                 if(!st[v])  
  20.                 {  
  21.                         dfs(v);  
  22.                 }  
  23.         }  
  24. }  
  25. int main()  
  26. {  
  27.         cin >> n >> m; // 读⼊结点个数以及边的个数  
  28.         for(int i = 1; i <= m; i++)
  29.         {  
  30.                 int a, b, c; cin >> a >> b >> c;  
  31.                 // a 和 b 之间有⼀条边,权值为 c  
  32.                 edges[a].push_back({b, c});  
  33.                 // 如果是⽆向边,需要反过来再存⼀下  
  34.                 edges[b].push_back({a, c});  
  35.         }  
  36.         return 0;  
  37. }
复制代码

  • ⽤链式前向星的⽅式存储
  1. #include <iostream>  
  2. #include <queue>  
  3. using namespace std;  
  4. const int N = 1e5 + 10;  
  5. // 链式前向星  
  6. int h[N], e[N * 2], ne[N * 2], w[N * 2], id;  
  7. int n, m;  
  8. // 其实就是把 b 头插到 a 所在的链表后⾯  
  9. void add(int a, int b, int c)  
  10. {  
  11.         id++;  
  12.         e[id] = b;  
  13.         w[id] = c; // 多存⼀个权值信息  
  14.         ne[id] = h[a];  
  15.         h[a] = id;  
  16. }  
  17. bool st[N];  
  18. void dfs(int u)  
  19. {  
  20.         cout << u << endl;  
  21.         st[u] = true;
  22.         // 遍历所有的孩⼦  
  23.         for(int i = h[u]; i; i = ne[i])  
  24.         {  
  25.                 // u->v 的⼀条边  
  26.                 int v = e[i];  
  27.                 if(!st[v])  
  28.                 {  
  29.                         dfs(v);  
  30.                 }  
  31.         }  
  32. }  
  33. int main()  
  34. {  
  35.         cin >> n >> m; // 读⼊结点个数以及边的个数  
  36.         for(int i = 1; i <= m; i++)  
  37.         {  
  38.                 int a, b, c; cin >> a >> b >> c;  
  39.                 // a 和 b 之间有⼀条边,权值为 c  
  40.                 add(a, b, c); add(b, a, c);  
  41.         }  
  42.         return 0;  
  43. }
复制代码
BFS


  • ⽤毗邻矩阵的⽅式存储
  1. #include <iostream>  
  2. #include <cstring>  
  3. #include <queue>  
  4. using namespace std;  
  5. const int N = 1010;  
  6. int n, m;  
  7. int edges[N][N];
  8. bool st[N]; // 标记哪些点已经访问过  
  9. void bfs(int u)  
  10. {  
  11.         queue<int> q;  
  12.         q.push(u);  
  13.         st[u] = true;  
  14.         while(q.size())  
  15.         {  
  16.                 auto a = q.front(); q.pop();  
  17.                 cout << a << endl;  
  18.                 for(int b = 1; b <= n; b++)  
  19.                 {  
  20.                         if(edges[a][b] != -1 && !st[b])  
  21.                         {  
  22.                                 q.push(b);  
  23.                                 st[b] = true;  
  24.                         }  
  25.                 }  
  26.         }  
  27. }  
  28. int main()  
  29. {  
  30.         memset(edges, -1, sizeof edges);  
  31.         cin >> n >> m; // 读⼊结点个数以及边的个数  
  32.         for(int i = 1; i <= m; i++)  
  33.         {  
  34.                 int a, b, c; cin >> a >> b >> c;  
  35.                 // a - b 有⼀条边,权值为 c  
  36.                 edges[a][b] = c;  
  37.                 // 如果是⽆向边,需要反过来再存⼀下  
  38.                 edges[b][a] = c;  
  39.         }  
  40.         return 0;  
  41. }
复制代码

  • ⽤vector数组的⽅式存储
  1. #include <iostream>  
  2. #include <vector>  
  3. #include <queue>  
  4. using namespace std;  
  5. typedef pair<int, int> PII;  
  6. const int N = 1e5 + 10;  
  7. int n, m;  
  8. vector<PII> edges[N];  
  9. bool st[N]; // 标记哪些点已经访问过  
  10. void bfs(int u)  
  11. {  
  12.         queue<int> q;  
  13.         q.push(u);  
  14.         st[u] = true;  
  15.         while(q.size())  
  16.         {  
  17.                 auto a = q.front(); q.pop();  
  18.                 cout << a << endl;  
  19.                 for(auto& t : edges[a])  
  20.                 {  
  21.                         int b = t.first, c = t.second;  
  22.                         if(!st[b])  
  23.                         {  
  24.                                 q.push(b);  
  25.                                 st[b] = true;  
  26.                         }  
  27.                 }  
  28.         }  
  29. }  
  30. int main()  
  31. {  
  32.         cin >> n >> m; // 读⼊结点个数以及边的个数  
  33.         for(int i = 1; i <= m; i++)  
  34.         {  
  35.                 int a, b, c; cin >> a >> b >> c;
  36.                 // a 和 b 之间有⼀条边,权值为 c  
  37.                 edges[a].push_back({b, c});  
  38.                 // 如果是⽆向边,需要反过来再存⼀下  
  39.                 edges[b].push_back({a, c});  
  40.         }  
  41.         return 0;  
  42. }
复制代码

  • ⽤链式前向星的⽅式存储
  1. #include <iostream>  
  2. #include <queue>  
  3. using namespace std;  
  4. const int N = 1e5 + 10;  
  5. // 链式前向星  
  6. int h[N], e[N * 2], ne[N * 2], w[N * 2], id;  
  7. int n, m;  
  8. // 其实就是把 b 头插到 a 所在的链表后⾯  
  9. void add(int a, int b, int c)  
  10. {  
  11.         id++;  
  12.         e[id] = b;  
  13.         w[id] = c; // 多存⼀个权值信息  
  14.         ne[id] = h[a];  
  15.         h[a] = id;  
  16. }  
  17. bool st[N];  
  18. void bfs(int u)  
  19. {  
  20.         queue<int> q;  
  21.         q.push(u);  
  22.         st[u] = true;  
  23.         while(q.size()
  24.         {  
  25.                 auto a = q.front(); q.pop();  
  26.                 cout << a << endl;  
  27.                 for(int i = h[a]; i; i = ne[i])  
  28.                 {  
  29.                         int b = e[i], c = w[i];  
  30.                         if(!st[b])  
  31.                         {  
  32.                                 q.push(b);  
  33.                                 st[b] = true;  
  34.                         }  
  35.                 }  
  36.         }  
  37. }  
  38. int main()  
  39. {  
  40.         cin >> n >> m; // 读⼊结点个数以及边的个数  
  41.         for(int i = 1; i <= m; i++)  
  42.         {  
  43.                 int a, b, c; cin >> a >> b >> c;  
  44.                 // a 和 b 之间有⼀条边,权值为 c  
  45.                 add(a, b, c); add(b, a, c);  
  46.         }  
  47.         return 0;  
  48. }
复制代码
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

刘俊凯

论坛元老
这个人很懒什么都没写!
快速回复 返回顶部 返回列表