数据结构—图(上)

文章目录

  • 12.图(上)
    • (1).图的基本概念
      • #1.图的基本定义
      • #2.边的分类
      • #3.数据结构的一些规定
      • #4.子图
      • #5.完全图
      • #6.路径
      • #7.连通性和连通分量
      • #8.度
    • (2).图的存储方式
      • #1.邻接矩阵
      • #2.邻接表
    • (3).图的遍历
      • #1.深度优先搜索(Depth First Search)
        • i.走个迷宫
        • ii.DFS的思想
        • iii.代码实现
      • #2.广度优先搜索(Breadth First Search)
        • i.你爱吃蛋糕吗?
        • ii.BFS的思想
        • iii.代码实现
      • #3.求连通分量
    • 小结

12.图(上)

  图是我们在《数据结构》这门课程中遇到的最后一个类型的结构了,图是一种多对多的结构,从广义的角度上来看,实际上线性表、树这两种结构也能算作图,不过在数据结构当中我们认为图还是一个多对多的结构,那么这样的结构相比于一对一一对多的结构会更加难以表示一点

(1).图的基本概念

#1.图的基本定义

  图其实跟我们平时说的图片还是有一些区别的,一个图 G G G由顶点集 V V V和边集 E E E构成,我们一般记为 G = ( V , E ) G=(V,E) G=(V,E),例如下面的这个图:
p46

#2.边的分类

  边和点共同构成了图,一条边由两个顶点构成,我们在这张图中的边是有方向的,这种边叫做有向边,表示为 < A , B > <A,B> <A,B>;如果边是没有方向的,则称为无向边,表示为 ( A , B ) (A,B) (A,B);只有有向边的图,称为有向图,而只包含无向边的图称为无向图,在数据结构中,我们认为图要么是有向图,要么是无向图

  所以我们就可以读取一下这张图:它的顶点集 V = { V 1 , V 2 , V 3 , V 4 } V=\{V_1, V_2, V_3, V_4\} V={V1,V2,V3,V4},然后边集 E = { < V 1 , V 2 > , < V 1 , V 3 > , < V 4 , V 1 > , < V 3 , V 4 > } E=\{<V_1,V_2>, <V_1,V_3>, <V_4,V_1>,<V_3,V_4>\} E={<V1,V2>,<V1,V3>,<V4,V1>,<V3,V4>}

#3.数据结构的一些规定

  在数据结构中我们规定:顶点不能有指向自己的边,这种边称为自环,同时我们也规定,无向图的两个顶点之间不能有多于一条的边,有向图的两个顶点之间可以有两条方向相反的边,不可以有两条方向相同的边

#4.子图

  首先我们有两个图 G = ( V , E ) , G ′ = ( V ′ , E ′ ) G=(V,E), G'=(V',E') G=(V,E),G=(V,E),它们同为无向图或有向图,且满足 V ′ ⊆ V , E ′ ⊆ E V'\subseteq V, E'\subseteq E VV,EE,则称 G ′ G' G G G G子图,所以子图就是从原图中取了一部分顶点和对应的一部分边形成的新图

#5.完全图

  每对顶点之间都有一条边的无向图,称为完全无向图;而如果每对顶点 i i i j j j之间都有边 < i , j > <i,j> <i,j> < j , i > <j,i> <j,i>的有向图,称为完全有向图

#6.路径

  在无向图 G = ( V , E ) G=(V,E) G=(V,E)中,从顶点v到顶点w之间的路径是一个由顶点组成的顶点序列 ( v 0 , v 1 , . . . , v k ) (v_0, v_1,...,v_k) (v0,v1,...,vk),其中 v 0 = v , v k = w , ( v 1 , v i + 1 ) ∈ E ( G ) ( 0 ≤ j < k ) v_0=v,v_k=w,(v_1,v_{i+1})\in E(G) \ (0\leq j<k) v0=v,vk=w,(v1,vi+1)E(G) (0j<k)

  用 ( v 0 , v 1 , . . . , v k ) (v_0, v_1, ..., v_k) (v0,v1,...,vk)表示这条路径,它的长度为k,如果只有一个顶点,则称v到自身的路径长度为0,若 G G G是有向图,则路径是有方向的, < v 0 , v 1 , . . . , v k > <v_0,v_1,...,v_k> <v0,v1,...,vk>,其中 v 0 = v , v k = w , < v i , v i + 1 ∈ E ( G ) ( 0 ≤ j < k ) v_0=v,v_k=w,<v_i,v_{i+1}\in E(G) \ (0\leq j < k) v0=v,vk=w,<vi,vi+1E(G) (0j<k)

#7.连通性和连通分量

  • 如果无向图G中每对顶点v和w都有从v到w的路径,那么称无向图 G G G是连通的,无向图 G G G中的极大连通子图为G的连通分量

  • 如果有向图 G G G中每对顶点v和w都有从v到w的路径,则称有向图 G G G强连通

  • 如果有向图 G G G中每对顶点v和w,有一个由不同顶点组成的顶点序列 < v 0 , v 1 , . . . , v k > <v_0,v_1,...,v_k> <v0,v1,...,vk>,其中 v 0 = v , v k = w v_0=v,v_k=w v0=v,vk=w,且 < v i , v i + 1 > ∈ E <v_i,v_{i+1}>\in E <vi,vi+1>∈E < v i + 1 , v i > ∈ E ( 0 ≤ i < k ) <v_{i+1},v_i>\in E\ (0\leq i<k) <vi+1,vi>∈E (0i<k),那么称有向图 G G G是弱连通的

  • 强连通的有向图一定是弱连通的,有向图 G G G极大强连通子图为图 G G G强连通分量,有向图 G G G极大弱连通子图为图 G G G弱连通分量

  • 如果图 G G G中有一条路径 ( v 0 , v 1 , . . . , v k ) (v_0, v_1,...,v_k) (v0,v1,...,vk),且 v 0 = v k v_0=v_k v0=vk,那么称这条路径为回路(或环)

#8.度

  在 无向图 G G G 中,如果 v ∈ V ( G ) v\in V(G) vV(G),那么v邻接的顶点个数称为顶点v的度
  在 有向图 G G G 中,如果 v ∈ V ( G ) v\in V(G) vV(G),那么邻接到v的顶点个数为顶点v的入度,邻接于v的顶点个数称为顶点v的出度

(2).图的存储方式

  图这一部分的基本概念还是相当多的,接下来我们就要看看怎么在计算机中表示和存储一个图了,我们一般用的是邻接矩阵和邻接表两种形式

#1.邻接矩阵

  对于一个具有 n n n个顶点的无向图,定义矩阵 A n × n A_{n\times n} An×n为:
A ( i , j ) = { 1 , ( i , j ) ∈ E ( G ) , 0 , ( i , j ) ∉ E ( G ) A(i,j) = \begin{cases} 1, (i,j)\in E(G),\\ 0, (i,j)\notin E(G) \end{cases} A(i,j)={1,(i,j)E(G),0,(i,j)/E(G)
  所以可以很容易地知道,顶点i的度为:
∑ j = 1 n A ( i , j ) \sum_{j=1}^nA(i,j) j=1nA(i,j)
  此时的 A ( i , j ) A(i,j) A(i,j)仅代表两个顶点是否连通,如果是带权边,则 A ( i , j ) A(i,j) A(i,j)存储的值是两个顶点之间的边的权重值,此时 A A A定义为:
A ( i , j ) = { w i j , i ≠ j , ( i , j ) ∈ E ( G ) , 0 , i = j , ∞ A(i,j)=\begin{cases} w_{ij}, i\neq j, (i,j)\in E(G),\\ 0, i = j, \\ \infty \end{cases} A(i,j)= wij,i=j,(i,j)E(G),0,i=j,
  毕竟在C++里没有一个真正的 ∞ \infty ,所以我们一般会在程序的最前面定义一个inf用于后续的赋值操作:

constexpr int inf = 0x3f3f3f3f;

  那么对于 n n n个顶点的有向图,有:
A ( i , j ) = { 1 , < i , j > ∈ E ( G ) , 0 , < i , j > ∉ E ( G ) A(i, j) = \begin{cases} 1, <i, j>\in E(G),\\ 0, <i, j>\notin E(G) \end{cases} A(i,j)={1,<i,j>∈E(G),0,<i,j>/E(G)
  所以此时也可以比较轻松地求出顶点i的入度和出度分别为:
i d = ∑ i = 1 n A ( i , j ) , o d = ∑ j = 1 n A ( i , j ) id = \sum_{i=1}^nA(i,j), od = \sum_{j=1}^nA(i,j) id=i=1nA(i,j),od=j=1nA(i,j)
  其中id为入度,od为出度,分别是第j列的和以及第i行的和

  所以邻接矩阵的空间复杂度为 O ( n 2 ) O(n^2) O(n2),毕竟需要存储一个 n × n n\times n n×n的矩阵嘛,邻接矩阵存储图的方式比较方便,对我们来说比较直观,但是假设图中的边数非常少(称为稀疏图),这时候就会有非常多的空间浪费,所以我们还有另一种存储方法—邻接表

#2.邻接表

  邻接表的想法很简单:既然图是由点和边构成的,那么我们对应每个点,把由它为起点的所有边全都存储起来,这样一来,我们就可以有效地减少没有边的部分的空间浪费,当然,如果我们的图足够稠密,邻接表的存法就不如邻接矩阵了,我们看看邻接表的一般定义:

#include <vector>
constexpr int MAXN = 5e3 + 20; // 最大结点数量
struct edge
{int end; // 终点int w;   // 边权
};vector<edge> g1[MAXN]; // 带权图
vector<int> g2[MAXN];  // 无权图

  带权图和无权图最主要的区别就在边权,所以存储带权图的时候我们需要在边上附带上边权的属性,那么无权图当然是没有必要的了

  到这儿你应该也能看出来为什么邻接表更多用于存储稀疏图了,因为当图足够稠密的时候,邻接表就会退化成为邻接矩阵

  因为我们是基于C++的数据结构博客,所以直接用vector实现会很方便,如果你使用C语言,我们可能还需要用到链表,这里大概介绍一下利用链表实现的邻接表:

constexpr int MANX = 5e3 + 20;
struct node
{int end;int w;node* next;
};// 此处忽略若干关于链表的操作定义
using list = node*;list g[MAXN]; // 邻接链表

  无向图和有向图都可以用邻接表来存储,不过如果是无向图,我们对于一条边需要存储两次,例如:

// n为顶点数,m为边数
for (int i = 1; i <= m; i++) {int u, v, w;cin >> u >> v >> w; // 无向边g[u].push_back({v, w}); // u->vg[v].push_back({u, w}); // v->u
}

  因为是无向边,两边实际上都要连起来,否则如果只有一边就会导致两个顶点中单向连通,在后续会出现非常多问题

(3).图的遍历

  就像线性表的遍历、树的遍历,我们存储了一个图之后,首先也要能够把整张图遍历一遍,至少要把所有节点扫一遍吧,那么这就是个技术活了,线性表和树的遍历都可以保证不会走重复的路,但是图的结构太复杂,让我们来走都不一定能保证不重不漏,所以就需要一些方法,DFS和BFS就是两种常见的方法

#1.深度优先搜索(Depth First Search)

i.走个迷宫

  走迷宫的时候有一种原则叫做右手法则,当然不是电磁学的那个,而是说,我们在走迷宫的时候始终沿着右手的方向走,最后总是能够走到出口,当然,因为遍历的那个顶点不是人,它其实不存在左右的的概念,不过我们可以借鉴一下这个过程

ii.DFS的思想

  比如说,我们走到了某个顶点,就向可以走的所有顶点做一个试探,直到走到了死路,我们就认为这次试探结束,然后开始回溯,回溯到上一个有选项的地方,选择另一条路继续进行尝试,你发现,这个东西,实在是有点熟悉啊:我们在树的前序/中序/后序遍历好像好像都是这个思路做的,我们平时在玩有多分支但是可以回溯的游戏的时候,也会在打完了一个结局之后回溯到上一个分支点,选择其他的选项,所以其实,这就是深度优先搜索,我们每次找到一个终点,然后回溯到上一个分叉点选择其它的选项

  不过我们现在考虑是图,有的顶点有可能会被重复访问,所以这时候我们需要一个对应的数组记忆哪一些顶点是已经被访问过的了,如果已经访问过,就不再重新访问

iii.代码实现

  所以我们可以给出一个非常简单的基于邻接表的DFS代码,DFS更多还是一种思想:

#include <iostream>
#include <vector>
using namespace std;
constexpr int MAXN = 5e3 + 20;vector<int> g[MAXN];
bool visited[MAXN]{ 0 };void dfs(int u)
{visit[u] = true; // u为起点cout << u << " ";for (const auto& v : g[u]) {if (!visited[v]) dfs(v);}
}

  对于邻接表,DFS的时间复杂度为 O ( ∣ V ∣ + ∣ E ∣ ) O(|V|+|E|) O(V+E),而对于邻接矩阵,其时间复杂度为 O ( ∣ V ∣ 2 ) O(|V|^2) O(V2),当然 ∣ E ∣ |E| E最大可以到 ∣ V ∣ 2 |V|^2 V2级别,所以对于稠密图,二者的时间复杂度几乎是一致的

#2.广度优先搜索(Breadth First Search)

i.你爱吃蛋糕吗?

  广度优先搜索就是另一个思路了,我们可以用蛋糕来举个例子,某个国家的F小姐很喜欢吃蛋糕,今天她获得了10个品种的小蛋糕各一份,她应该怎么品尝10种蛋糕呢?当然是想吃什么吃什么,还是要按照一定的顺序来,她当然可以每次吃掉一块蛋糕,然后去吃下一块(类似DFS),也可以每次从头开始,10块蛋糕依次吃一小口,然后这么一直循环,直到把所有蛋糕全都吃完

ii.BFS的思想

  所以这就是广度优先搜索,从起点开始,我们首先找到它连接的顶点(记为I),然后从I中的所有顶点中找到与它们连接的顶点,这么一直循环下去直到最后遍历完所有的点,广度优先搜索实际上就是按照层次完成图的遍历访问

  哦呀,这不就是树的层次遍历吗!说起这个来你应该就很熟悉了,没错,树的层次遍历实际上也就是一种BFS

iii.代码实现

  BFS同样也只是一种思想,这里给出一个基本的基于邻接表实现的BFS:

#include <iostream>
#include <vector>
using namespace std;
constexpr int MAXN = 5e3 + 20;vector<int> g[MAXN];
bool visited[MAXN]{ 0 };void bfs(int u)
{queue<int> q;cout << u << " "; // 从u开始访问visited[u] = true;q.push(u);while (!q.empty()) {int t{ q.front() };q.pop();for (const auto& v : g[t]) {if (!visited[v]) {cout << v << " ";visited[v] = true;q.push(v);}}}
}

  这样就好了,我们从起点开始,每次把它邻接的未访问顶点加入队列中,然后一直循环这个过程,直到所有的顶点都已经被访问过一次,BFS就结束了

  同理,基于邻接表的广度优先搜索时间复杂度仍然是 O ( ∣ V ∣ + ∣ E ∣ ) O(|V|+|E|) O(V+E),而邻接矩阵为 O ( ∣ V ∣ 2 ) O(|V|^2) O(V2)

#3.求连通分量

  如果一个无向图是连通图,那么无论从哪个顶点开始遍历(BFS/DFS均可),都可以访问所有顶点;但如果不连通,那么从任何顶点 v v v出发通过遍历**只能访问到 v v v的极大连通子图(即连通分量)**的所有顶点

  因此要求出所有的连通分量,可以对所有顶点进行检验,如果已经被访问,则该顶点已经出现在某个连通分量中,如果没有被访问,则从这个顶点开始遍历,就可以得到另一个连通分量

小结

  图这一篇我还是分成了两个部分,上半部分主要是介绍一些基本的图的概念,而下半部分则主要会集中于几个图的算法,最小生成树、最短路径和拓扑排序三个关键的问题

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

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

相关文章

LeetCode-无重复字符的最长子串(3)

题目描述&#xff1a; 给定一个字符串 s &#xff0c;请你找出其中不含有重复字符的 最长子串 的长度。 代码&#xff1a; class Solution {public int lengthOfLongestSubstring(String s) {Set<Character> occnew HashSet<Character>();int lens.length();int…

Yolov5/8的小程序部署前后端实现

Yolov5/8的小程序部署前后端实现 导语本机配置硬件环境配置 前端实现后端实现总结参考文献 导语 毕设的题目与Yolo系列的图像识别相关&#xff0c;通过搜查了很多资料和实践最后完成&#xff0c;看到某些平台上居然卖300&#xff0c;觉得很离谱&#xff0c;所以决定把代码开源…

Springcloud alibab和dubbo有什么区别?

Spring Cloud Alibaba 和 Dubbo 都是为了简化企业级应用开发而生的框架&#xff0c;尤其是在分布式系统和微服务架构的背景下。 虽然他们在某些功能上有重叠&#xff0c;但各有侧重点和使用场景。 微服务架构图 首先介绍一下 Spring Cloud Alibaba&#xff1a; Spring Cloud …

Fiddler抓取https原理?

首先fiddler截获客户端浏览器发送给服务器的https请求&#xff0c; 此时还未建立握手。 第一步&#xff0c; fiddler向服务器发送请求进行握手&#xff0c; 获取到服务器的CA证书&#xff0c; 用根证书公钥进行解密&#xff0c; 验证服务器数据签名&#xff0c; 获取到服务器C…

数据结构学习 jz34 二叉树中和为某一值的路径

关键词&#xff1a;回溯 二叉树 前序遍历 路径记录 因为我没有仔细接触过二叉树的遍历过程&#xff0c;所以我是懵懵懂懂按照dfs的方法写的。没想到写对了&#xff0c;看了解答发现这叫做二叉树的前序遍历。用时29min。 这让我明白了前序遍历和dfs原来是有相同之处的。&#…

Local server not started, start with 报错python -m weditor

一、python -m weditor 如图报错 Local server not started, start with 报错 二、解决方案 右上角选择新的无痕窗口下&#xff0c;然后打开 http://localhost:17310/ 即可

PCIe 6.0生态业内进展分析总结-2

3.PCIe 6.0协议分析仪 (1)Keysight Keysight是德科技在2023年6月份对外宣布&#xff0c;第一款支持PCIe 6.0协议验证调试工具。 Keysight PCIe 6.0架构解决方案具备以下特点&#xff1a; 分析PCIe 6.0技术设计的数据链路/事务层 支持所有PCIe技术速率——从2.5 GT/s至64 GT/…

实验笔记之——基于COLMAP的Instant-NGP与3D Gaussian Splatting的对比

之前博客进行了COLMAP在服务器下的测试 实验笔记之——Linux实现COLMAP-CSDN博客文章浏览阅读794次&#xff0c;点赞24次&#xff0c;收藏6次。学习笔记之——NeRF SLAM&#xff08;基于神经辐射场的SLAM&#xff09;-CSDN博客NeRF 所做的任务是 Novel View Synthesis&#xf…

《PySpark大数据分析实战》-25.数据可视化图表Matplotlib介绍

&#x1f4cb; 博主简介 &#x1f496; 作者简介&#xff1a;大家好&#xff0c;我是wux_labs。&#x1f61c; 热衷于各种主流技术&#xff0c;热爱数据科学、机器学习、云计算、人工智能。 通过了TiDB数据库专员&#xff08;PCTA&#xff09;、TiDB数据库专家&#xff08;PCTP…

2024-01-04 用llama.cpp部署本地llama2-7b大模型

点击 <C 语言编程核心突破> 快速C语言入门 用llama.cpp部署本地llama2-7b大模型 前言一、下载llama.cpp以及llama2-7B模型文件二、具体调用总结 使用协议: License to use Creative Commons Zero - CC0 该图片个人及商用免费&#xff0c;无需显示归属&#xff0c;但如果…

阿里通义千问「全民舞王」,一张照片就能跳《科目三》,刷爆朋友圈

这两天看朋友圈、网上都在发这种跳舞的视频。只要上传一张全身照&#xff0c;就可以生成各种跳舞的视频。 比如前段时间火爆海底捞的《科目三》&#xff0c;还有《DJ慢摇》、《鬼步舞》、《兔子舞》、甚至还有咱《秧歌舞》。 先来一睹为快&#xff01; 阿里通义千问「全民舞王…

虚拟机(克隆)导入/导出镜像(OVAOVF)

一.了解虚拟化和 UEFI 虚拟化是一种技术&#xff0c;通过在物理硬件上创建虚拟的计算环境&#xff0c;使得多个操作系统和应用程序可以在同一台计算机上同时运行。虚拟机是在这个虚拟化环境中运行的实例&#xff0c;它们需要被赋予操作系统和固件等系统软件来进行运行。UEFI&a…

Python+selenium+chromedriver实现爬虫示例代码

下载好所需程序 1.Selenium简介 Selenium是一个用于Web应用程序测试的工具&#xff0c;直接运行在浏览器中&#xff0c;就像真正的用户在操作一样。 2.Selenium安装 方法一&#xff1a;在Windows命令行&#xff08;cmd&#xff09;输入pip install selenium即可自动安装&am…

炫酷的倒计时引导页

文章目录 文件分布介绍效果预览代码css样式Locationplayer.css js样式player.js 文件分布介绍 效果预览 代码 css样式 Location html {height: 100%;}body {font-family: "Helvetica Neue", "Luxi Sans", "DejaVu Sans", Tahoma, "Hirag…

c语言-函数指针

目录 前言一、函数指针1.1 函数指针定义1.2 函数指针调用函数1.3 函数指针代码分析 总结 前言 本篇文章介绍c语言中的函数指针以及函数指针的应用。 一、函数指针 函数指针&#xff1a;指向函数的指针。 函数在编译时分配地址。 &函数名 和 函数名代表的意义相同&#xf…

外包干了3个多月,技术退步明显。。。。。

先说一下自己的情况&#xff0c;本科生生&#xff0c;19年通过校招进入广州某软件公司&#xff0c;干了接近4年的功能测试&#xff0c;今年年初&#xff0c;感觉自己不能够在这样下去了&#xff0c;长时间呆在一个舒适的环境会让一个人堕落!而我已经在一个企业干了四年的功能测…

SpringIOC之support模块DefaultMessageSourceResolvable

博主介绍&#xff1a;✌全网粉丝5W&#xff0c;全栈开发工程师&#xff0c;从事多年软件开发&#xff0c;在大厂呆过。持有软件中级、六级等证书。可提供微服务项目搭建与毕业项目实战&#xff0c;博主也曾写过优秀论文&#xff0c;查重率极低&#xff0c;在这方面有丰富的经验…

Profinet转Modbus网关助你畅享智能工业

Modbus转Profinet网关&#xff08;XD-MDPN100/200&#xff09;具有广泛的应用价值。无论是汽车制造、机械加工还是能源管理&#xff0c;都可以通过使用该网关&#xff0c;实现设备之间的高效通信。其次&#xff0c;Modbus转Profinet网关&#xff08;XD-MDPN100/200&#xff09;…

异步http接口调用库:httpx

谈到http接口调用&#xff0c;Requests大家并不陌生&#xff0c;例如&#xff0c;robotframework-requests、HttpRunner等HTTP接口测试库/框架都是基于它开发。这里将介绍另一款http接口测试框架:httpx。 它的API和Requests高度一致。 github: GitHub - encode/httpx: A next…

智慧校园的“边缘智能“: 打造未来教育的桥梁

在科技飞速发展的时代&#xff0c;智能已经渗透到我们生活的各个角落。而当智能遇上教育&#xff0c;会激发出怎样的火花呢&#xff1f;今天&#xff0c;我们就来聊聊这个热门话题——智慧校园和边缘智能网关的结合&#xff0c;它们是如何共同塑造未来教育的新形态。 什么是边…