NO.91十六届蓝桥杯备战|图论基础-图的存储和遍历|邻接矩阵|vector|链式前向星(C++)

图的基本概念

图的定义

图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)uV,vV,⽤ ∣ E ∣ |E| E表⽰图G中边的条数。
图是较线性表和树更为复杂的数据结构。

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

图根据边的类型,可以分为⽆向图和有向图
![[Pasted image 20250411211513.png]]

在图相关的算法中,我们可以将⽆向图中的边看成两条⽅向相反的有向边,从⽽将⽆向图转化为有向图
![[Pasted image 20250411211622.png]]

简单图与多重图

⾃环:⾃⼰指向⾃⼰的⼀条边
![[Pasted image 20250411211640.png]]

重边:图中存在两个或两个以上完全相同的边
![[Pasted image 20250411211811.png]]

简单图:若图中没有重边和⾃环,为简单图。
多重图:若图中存在重边或⾃环,为多重图。
![[Pasted image 20250411211832.png]]

稠密图和稀疏图

有很少条边(如e < nlog2 n )的图称为稀疏图,反之称为稠密图
![[Pasted image 20250411211857.png]]

顶点的度

顶点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)
    ![[Pasted image 20250411212011.png]]
路径

在图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的路径。
注意:两个顶点间的路径可能不唯⼀
![[Pasted image 20250411212248.png]]

简单路径与回路

若路径上各顶点 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相同,则称这样的路径为回路或环
![[Pasted image 20250411212403.png]]

路径⻓度和带权路径⻓度

某些图的边具有与它相关的数值,称其为该边的权值。这些权值可以表⽰两个顶点间的距离、花费的代价、所需的时间等。⼀般将该种带权图称为⽹络
![[Pasted image 20250411212509.png]]

对于不带权的图,⼀条路径的路径⻓度是指该路径上的边的条数。
对于带权的图,⼀条路径的路径⻓度是指该路径上各个边权值的总和。
![[Pasted image 20250411212521.png]]

⼦图

设图 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 VV E ′ ∈ E E'\in E EE,则称 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的⽣成⼦图。
相当于就是在原来图的基础上,拿出来⼀些顶点和边,组成⼀个新的图。但是要注意,拿出来的点和边要能构成⼀个图才⾏
![[Pasted image 20250411212748.png]]

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,那么此图⼀定是⾮连通图。
  • 极⼤联通⼦图:⽆向图中,拿出⼀个⼦图,这个⼦图包含尽可能多的点和边。
  • 连通分量:⽆向图中的极⼤连通⼦图称为连通分量
    ![[Pasted image 20250411212932.png]]
⽣成树

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

图的存储和遍历

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

  • 其中,邻接表的存储⽅式与树的孩⼦表⽰法完全⼀样。因此,⽤vector数组以及链式前向星就能实现。
  • ⽽邻接矩阵就是⽤⼀个⼆维数组,其中edges[i][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 之间有边相连
![[Pasted image 20250411214010.png]]

矩阵中元素个数为nxn,即空间复杂度为O(n^2) ,n为顶点个数,和实际边的条数⽆关,适合存储稠密图

#include <iostream>  
#include <cstring>  
using namespace std;  
const int N = 1010;  
int n, m;  
int edges[N][N];  
int main()  
{  memset(edges, -1, sizeof edges);  cin >> n >> m; // 读⼊结点个数以及边的个数  for(int i = 1; i <= m; i++)  {  int a, b, c; cin >> a >> b >> c;  // a - b 有⼀条边,权值为 c  edges[a][b] = c;  // 如果是⽆向边,需要反过来再存⼀下  edges[b][a] = c;  }return 0;  
}
vector数组

和树的存储⼀模⼀样,只不过如果存在边权的话,我们的vector数组⾥⾯放⼀个结构体或者是pair即可。

#include <iostream>  
#include <vector>  using namespace std;  
typedef pair<int, int> PII;  
const int N = 1e5 + 10;  
int n, m;  
vector<PII> edges[N];  int main()  
{  cin >> n >> m; // 读⼊结点个数以及边的个数  for(int i = 1; i <= m; i++)  {  int a, b, c; cin >> a >> b >> c;  // a 和 b 之间有⼀条边,权值为 c  edges[a].push_back({b, c});  // 如果是⽆向边,需要反过来再存⼀下  edges[b].push_back({a, c});  }  return 0;  
}
链式前向星

和树的存储⼀模⼀样,只不过如果存在边权的话,我们多创建⼀维数组,⽤来存储边的权值即可

#include <iostream>  
using namespace std;  
const int N = 1e5 + 10;  
// 链式前向星  
int h[N], e[N * 2], ne[N * 2], w[N * 2], id;  
int n, m;  
// 其实就是把 b 头插到 a 所在的链表后⾯  
void add(int a, int b, int c)  
{  id++;  e[id] = b;  w[id] = c; // 多存⼀个权值信息  ne[id] = h[a];  h[a] = id;  
}  
int main()  
{  cin >> n >> m; // 读⼊结点个数以及边的个数  for(int i = 1; i <= m; i++)  {  int a, b, c; cin >> a >> b >> c;  // a 和 b 之间有⼀条边,权值为 c  add(a, b, c); add(b, a, c);  }  return 0;  
}
DFS

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

  1. ⽤邻接矩阵的⽅式存储
#include <iostream>  
#include <cstring>  
#include <queue>  
using namespace std;  
const int N = 1010;  
int n, m;  
int edges[N][N];  
bool st[N]; // 标记哪些点已经访问过  
void dfs(int u)  
{  cout << u << endl;  st[u] = true;  // 遍历所有孩⼦  for(int v = 1; v <= n; v++)  {  // 如果存在 u->v 的边,并且没有遍历过  if(edges[u][v] != -1 && !st[v])  {  dfs(v);  }  }  
}  int main()  
{  memset(edges, -1, sizeof edges);  cin >> n >> m; // 读⼊结点个数以及边的个数  for(int i = 1; i <= m; i++)  {  int a, b, c; cin >> a >> b >> c;  // a - b 有⼀条边,权值为 c  edges[a][b] = c;  // 如果是⽆向边,需要反过来再存⼀下  edges[b][a] = c;  }return 0;  
}
  1. ⽤vector数组的⽅式存储
#include <iostream>  
#include <vector>  
#include <queue>  
using namespace std;  
typedef pair<int, int> PII;  
const int N = 1e5 + 10;  
int n, m;  
vector<PII> edges[N];  
bool st[N]; // 标记哪些点已经访问过  
void dfs(int u)  
{  cout << u << endl;  st[u] = true;  // 遍历所有孩⼦  for(auto& t : edges[u])  {  // u->v 的⼀条边,权值为 w  int v = t.first, w = t.second;  if(!st[v])  {  dfs(v);  }  }  
}  int main()  
{  cin >> n >> m; // 读⼊结点个数以及边的个数  for(int i = 1; i <= m; i++){  int a, b, c; cin >> a >> b >> c;  // a 和 b 之间有⼀条边,权值为 c  edges[a].push_back({b, c});  // 如果是⽆向边,需要反过来再存⼀下  edges[b].push_back({a, c});  }  return 0;  
}
  1. ⽤链式前向星的⽅式存储
#include <iostream>  
#include <queue>  
using namespace std;  
const int N = 1e5 + 10;  
// 链式前向星  
int h[N], e[N * 2], ne[N * 2], w[N * 2], id;  
int n, m;  
// 其实就是把 b 头插到 a 所在的链表后⾯  
void add(int a, int b, int c)  
{  id++;  e[id] = b;  w[id] = c; // 多存⼀个权值信息  ne[id] = h[a];  h[a] = id;  
}  bool st[N];  void dfs(int u)  
{  cout << u << endl;  st[u] = true;// 遍历所有的孩⼦  for(int i = h[u]; i; i = ne[i])  {  // u->v 的⼀条边  int v = e[i];  if(!st[v])  {  dfs(v);  }  }  
}  
int main()  
{  cin >> n >> m; // 读⼊结点个数以及边的个数  for(int i = 1; i <= m; i++)  {  int a, b, c; cin >> a >> b >> c;  // a 和 b 之间有⼀条边,权值为 c  add(a, b, c); add(b, a, c);  }  return 0;  
}
BFS
  1. ⽤邻接矩阵的⽅式存储
#include <iostream>  
#include <cstring>  
#include <queue>  
using namespace std;  
const int N = 1010;  
int n, m;  
int edges[N][N];
bool st[N]; // 标记哪些点已经访问过  
void bfs(int u)  
{  queue<int> q;  q.push(u);  st[u] = true;  while(q.size())  {  auto a = q.front(); q.pop();  cout << a << endl;  for(int b = 1; b <= n; b++)  {  if(edges[a][b] != -1 && !st[b])  {  q.push(b);  st[b] = true;  }  }  }  
}  int main()  
{  memset(edges, -1, sizeof edges);  cin >> n >> m; // 读⼊结点个数以及边的个数  for(int i = 1; i <= m; i++)  {  int a, b, c; cin >> a >> b >> c;  // a - b 有⼀条边,权值为 c  edges[a][b] = c;  // 如果是⽆向边,需要反过来再存⼀下  edges[b][a] = c;  }  return 0;  
}
  1. ⽤vector数组的⽅式存储
#include <iostream>  
#include <vector>  
#include <queue>  
using namespace std;  
typedef pair<int, int> PII;  
const int N = 1e5 + 10;  
int n, m;  
vector<PII> edges[N];  
bool st[N]; // 标记哪些点已经访问过  
void bfs(int u)  
{  queue<int> q;  q.push(u);  st[u] = true;  while(q.size())  {  auto a = q.front(); q.pop();  cout << a << endl;  for(auto& t : edges[a])  {  int b = t.first, c = t.second;  if(!st[b])  {  q.push(b);  st[b] = true;  }  }  }  
}  int main()  
{  cin >> n >> m; // 读⼊结点个数以及边的个数  for(int i = 1; i <= m; i++)  {  int a, b, c; cin >> a >> b >> c;// a 和 b 之间有⼀条边,权值为 c  edges[a].push_back({b, c});  // 如果是⽆向边,需要反过来再存⼀下  edges[b].push_back({a, c});  }  return 0;  
}
  1. ⽤链式前向星的⽅式存储
#include <iostream>  
#include <queue>  
using namespace std;  
const int N = 1e5 + 10;  
// 链式前向星  
int h[N], e[N * 2], ne[N * 2], w[N * 2], id;  
int n, m;  
// 其实就是把 b 头插到 a 所在的链表后⾯  
void add(int a, int b, int c)  
{  id++;  e[id] = b;  w[id] = c; // 多存⼀个权值信息  ne[id] = h[a];  h[a] = id;  
}  bool st[N];  void bfs(int u)  
{  queue<int> q;  q.push(u);  st[u] = true;  while(q.size(){  auto a = q.front(); q.pop();  cout << a << endl;  for(int i = h[a]; i; i = ne[i])  {  int b = e[i], c = w[i];  if(!st[b])  {  q.push(b);  st[b] = true;  }  }  }  
}  
int main()  
{  cin >> n >> m; // 读⼊结点个数以及边的个数  for(int i = 1; i <= m; i++)  {  int a, b, c; cin >> a >> b >> c;  // a 和 b 之间有⼀条边,权值为 c  add(a, b, c); add(b, a, c);  }  return 0;  
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/diannao/79295.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【项目日记(一)】-仿mudou库one thread oneloop式并发服务器实现

1、模型框架 客户端处理思想&#xff1a;事件驱动模式 事件驱动处理模式&#xff1a;谁触发了我就去处理谁。 &#xff08; 如何知道触发了&#xff09;技术支撑点&#xff1a;I/O的多路复用 &#xff08;多路转接技术&#xff09; 1、单Reactor单线程&#xff1a;在单个线程…

Go语言实现OAuth 2.0认证服务器

文章目录 1. 项目概述1.1 OAuth2 流程 2. OAuth 2.0 Storage接口解析2.1 基础方法2.2 客户端管理相关方法2.3 授权码相关方法2.4 访问令牌相关方法2.5 刷新令牌相关方法 2.6 方法调用时序2.7 关键注意点3. MySQL存储实现原理3.1 数据库设计3.2 核心实现 4. OAuth 2.0授权码流程…

结合 Python 与 MySQL 构建你的 GenBI Agent_基于 MCP Server

写在前面 商业智能(BI)正在经历一场由大型语言模型(LLM)驱动的深刻变革。传统的 BI 工具通常需要用户学习复杂的界面或查询语言,而生成式商业智能 (Generative BI, GenBI) 则旨在让用户通过自然语言与数据交互,提出问题,并获得由 AI 生成的数据洞察、可视化建议甚至完整…

Linux中常用命令

目录 1. linux目录结构 2. linux基本命令操作 2.1 目录操作命令 2.2 文件操作命令 2.3 查看登录用户命名 2.4 文件内容查看命令 2.5 系统管理类命令 3. bash通配符 4. 压缩与解压缩命令 4.1 压缩和解压缩 4.2 测试网络连通性命令 ping 4.3 vi编辑器 4.4 管道操作(…

C++ 与 MySQL 数据库优化实战:破解性能瓶颈,提升应用效率

&#x1f9d1; 博主简介&#xff1a;CSDN博客专家、CSDN平台优质创作者&#xff0c;高级开发工程师&#xff0c;数学专业&#xff0c;10年以上C/C, C#, Java等多种编程语言开发经验&#xff0c;拥有高级工程师证书&#xff1b;擅长C/C、C#等开发语言&#xff0c;熟悉Java常用开…

tcp特点+TCP的状态转换图+time_wait详解

tcp特点TCP的状态转换图time wait详解 目录 一、tcp特点解释 1.1 面向连接 1.1.1 连接建立——三次握手 1.1.2 连接释放——四次挥手 1.2 可靠的 1.2.1 应答确认 1.2.2 超时重传 1.2.3 乱序重排 1.2.4 去重 1.2.5 滑动窗口进行流量控制 1.3 流失服务&#xff08;字节…

探秘 Ruby 与 JavaScript:动态语言的多面风采

1 语法特性对比&#xff1a;简洁与灵活 1.1 Ruby 的语法优雅 Ruby 的语法设计旨在让代码读起来像自然语言一样流畅。它拥有简洁而富有表现力的语法结构&#xff0c;例如代码块、符号等。 以下是一个使用 Ruby 进行数组操作的简单示例&#xff1a; # 定义一个数组 numbers [1…

点评项目回顾

表结构 基于Session实现登录流程 发送验证码&#xff1a; 用户在提交手机号后&#xff0c;会校验手机号是否合法&#xff0c;如果不合法&#xff0c;则要求用户重新输入手机号 如果手机号合法&#xff0c;后台此时生成对应的验证码&#xff0c;同时将验证码进行保存&#xf…

OpenShift介绍,跟 Kubernetes ,Docker关系

1. OpenShift 简介 OpenShift是一个开源项目,基于主流的容器技术Docker及容器编排引擎Kubernetes构建。可以基于OpenShift构建属于自己的容器云平台。OpenShift的开源社区版本叫OpenShift Origin,现在叫OKD。 OpenShift 项目主页:https://www.okd.io/。OpenShift GitHub仓库…

Ubuntu服务器性能调优指南:从基础工具到系统稳定性提升

一、性能监控工具的三维应用 1.1 监控矩阵构建 通过组合工具搭建立体监控体系&#xff1a; # 实时进程监控 htop --sort-keyPERCENT_CPU# 存储性能采集 iostat -dx 2# 内存分析组合拳 vmstat -SM 1 | awk NR>2 {print "Active:"$5"MB Swpd:"$3"…

计算机视觉——基于MediaPipe实现人体姿态估计与不良动作检测

概述 正确的身体姿势是个人整体健康的关键。然而&#xff0c;保持正确的身体姿势可能会很困难&#xff0c;因为我们常常会忘记。本博客文章将逐步指导您构建一个解决方案。最近&#xff0c;我们使用 MediaPipe POSE 进行身体姿势检测&#xff0c;效果非常好&#xff01; 一、…

LSTM结合LightGBM高纬时序预测

1. LSTM 时间序列预测 LSTM 是 RNN&#xff08;Recurrent Neural Network&#xff09;的一种变体&#xff0c;它解决了普通 RNN 训练时的梯度消失和梯度爆炸问题&#xff0c;适用于长期依赖的时间序列建模。 LSTM 结构 LSTM 由 输入门&#xff08;Input Gate&#xff09;、遗…

六、adb通过Wifi连接

背景 收集是荣耀X40,数据线原装全新的&#xff0c;USB连上之后&#xff0c;老是断&#xff0c;电脑一直叮咚叮咚的响个不停&#xff0c;试试WIFI 连接是否稳定&#xff0c;需要手机和电脑用相同的WIFI. 连接 1.通过 USB 连接手机和电脑(打开USB调试等这些都略过) adb device…

如何理解前端开发中的“换皮“

"换皮"在前端开发中是一个常见的术语&#xff0c;通常指的是在不改变网站或应用核心功能和结构的情况下&#xff0c;只改变其外观和视觉表现。以下是关于前端"换皮"的详细理解&#xff1a; 基本概念 定义&#xff1a;换皮(Skinning)是指保持应用程序功能不…

从 Vue 到 React:深入理解 useState 的异步更新

目录 从 Vue 到 React&#xff1a;深入理解 useState 的异步更新与函数式写法1. Vue 的响应式回顾&#xff1a;每次赋值立即生效2. React 的状态更新是异步且批量的原因解析 3. 函数式更新&#xff1a;唯一的正确写法4. 对比 Vue vs React 状态更新5. React useState 的核心源码…

使用Redis实现分布式限流

一、限流场景与算法选择 1.1 为什么需要分布式限流 在高并发系统中&#xff0c;API接口的突发流量可能导致服务雪崩。传统的单机限流方案在分布式环境下存在局限&#xff0c;需要借助Redis等中间件实现集群级流量控制。 1.2 令牌桶算法优势 允许突发流量&#xff1a;稳定速…

快速搭建WordPress网站的主题

WP快主题(wpkuai.com )是一款由知名WordPress专业团队打造的专业化WordPress主题&#xff0c;旨在让用户使用该wordpress主题快速搭建网站。 WP快主题专注于快速搭建WordPress网站的主题解决方案。其主题设计注重简洁性与高效性&#xff0c;旨在帮助用户快速完成网站的搭建和部…

STM32江科大----------PID算法

声明&#xff1a;本人跟随b站江科大学习&#xff0c;本文章是观看完视频后的一些个人总结和经验分享&#xff0c;也同时为了方便日后的复习&#xff0c;如果有错误请各位大佬指出&#xff0c;如果对你有帮助可以点个赞小小鼓励一下&#xff0c;本文章建议配合原视频使用❤️ 如…

将JSON格式的SQL查询转换为完整SQL语句的实战解析

一、背景与需求 在现代数据处理中,JSON格式因其灵活性和可读性,常被用于定义SQL查询的结构。然而,直接编写JSON格式的SQL指令后,如何将其转换为可执行的SQL语句是开发者常遇到的挑战。本文将通过一个Python函数和多个实际案例,解析如何将JSON结构转换为完整的SQL语句,并…

java CountDownLatch用法简介

CountDownLatch倒计数锁存器 CountDownLatch&#xff1a;用于协同控制一个或多个线程等待在其他线程中执行的一组操作完成&#xff0c;然后再继续执行 CountDownLatch用法 构造方法&#xff1a;CountDownLatch(int count)&#xff0c;count指定等待的条件数&#xff08;任务…