数据结构—图(上)

文章目录

  • 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->v
    g[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的极大连通子图(即连通分量)**的所有顶点

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

小结

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

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

基于SSM的图书商城(有报告)。Javaee项目。ssm项目。

演示视频&#xff1a; 基于SSM的图书商城&#xff08;有报告&#xff09;。Javaee项目。ssm项目。 项目介绍&#xff1a; 采用M&#xff08;model&#xff09;V&#xff08;view&#xff09;C&#xff08;controller&#xff09;三层体系结构&#xff0c;通过Spring SpringMv…

word2019保存后的图片变模糊了怎么办?Word 2019 默认保存后压缩变模糊的问题,解决方案

Word 2019 默认保存后压缩变模糊的问题&#xff0c;解决方案 1&#xff0c;新建word 文件&#xff0c;插入一张原始图片&#xff0c;1080*1920&#xff0c;如下图&#xff1a; 2&#xff0c;保存时&#xff0c;word 2019默认选项&#xff0c;导致word 保存后&#xff0c;图片…

MySQL-DQL

DQL是数据查询语言&#xff0c;用来查询数据库中表中的数据。 DQL语句编写顺序和执行顺序&#xff1a; 编写顺序&#xff1a;由上至下 执行顺序&#xff1a; 基本查询 1. 查询多个字段&#xff1a;SELECT 字段1,字段2,字段3... FROM 表名; 查询所有字段&#xff1a; SELECT*FR…

【C程序设计】C数组

C 语言支持数组数据结构&#xff0c;它可以存储一个固定大小的相同类型元素的顺序集合。数组是用来存储一系列数据&#xff0c;但它往往被认为是一系列相同类型的变量。 数组的声明并不是声明一个个单独的变量&#xff0c;比如 runoob0、runoob1、...、runoob99&#xff0c;而…

数据结构(JS实现)

目录 链表链表的特点链表中的常见操作单链表append(data)尾部追加新节点toString()输出链表的节点数据插入节点insert(position,data)get(position)获取链表指定位置节点的数据indexOf(data)查找对应数据节点的位置update(position, newData)更新指定位置节点数据removeAt(posi…

在 wsl 中运用 kubeconfig 实现自由管理 kubernetes 集群

本文来自我的博客地址 文章目录 k8s 集群配置理解 kubeconfig思路整理:在 wsl 上安装 kubectl配置自动补全 拷贝 kubeconfig登到 k8s 集群的 master 节点, 把 kubeconfig 拷贝到 wsl测试 kubectl k8s 集群配置 IPHost配置11.0.1.150master1 (keepalivedhaproxy)2C 4G 30G11.0.…

git本地创建分支并推送到远程关联起来

git本地创建分支并推送到远程关联起来 git本地基于当前分支创建个新的分支&#xff0c;然后推送到远程&#xff0c;并把本地新创建的分支和远程分支关联 在当前分支下&#xff0c;新建分支 git checkout -b test推送到远程仓库 git push origin test将本地分支和远程分支关联…

2015年电赛控制类—STM32风力摆控制系统资料+源程序

目录 一、项目背景 二、主要研究内容 三、总体思路与研究方案 四、主要研究结果 五、程序 六、图片 一、项目背景 风力摆控制系统是一种利用风力控制物体做简谐运动的系统&#xff0c;风力的利用和控制技术在我国的发展尚未完善&#xff0c;国内正处于起步阶段。风力摆的…

免费的GPT4来了,你还不知道吗?

程序员的公众号&#xff1a;源1024&#xff0c;获取更多资料&#xff0c;无加密无套路&#xff01; 最近整理了一波电子书籍资料&#xff0c;包含《Effective Java中文版 第2版》《深入JAVA虚拟机》&#xff0c;《重构改善既有代码设计》&#xff0c;《MySQL高性能-第3版》&…

SSM实现的校园门户平台网站系统----计算机毕业设计

项目介绍 本系统为前后台项目&#xff0c;后台为管理员登录&#xff0c;前台为社团、学生、教师角色登录&#xff1b; 管理员角色包含以下功能&#xff1a; 管理员登录,角色管理,权限管理,社团管理,教师管理,学生管理,公告管理,新闻管理,校园风采管理,求职招聘管理,校历管理…

RabbitMQ快速入门(详细)

RabbitMQ 消息中间件/消息队列 1、消息中间件 1、简介 **消息中间件也可以称消息队列&#xff0c;是指用高效可靠的消息传递机制进行与平台无关的数据交流&#xff0c;并基于数据通信来进行分布式系统的集成。**通过提供消息传递和消息队列模型&#xff0c;可以在分布式环境…

Linux——系统安全及应用

一、基本安全措施 1、系统账号清理 常见的非登录用户账号包括bin、daemon、 adm、lp、mail等。为了确保系统安全&#xff0c;这些用户账号的登录Shell通常是/ sbin/nologin&#xff0c;表示禁止终端登录&#xff0c;应确保不被人为改动。 //将非登陆用户的Shell设为/sbin/nolo…

文献综述 AI 应用对比 — Elicit, GPTs 与 Perplexity

&#xff08;注&#xff1a;本文为小报童精选文章&#xff0c;已订阅小报童或加入知识星球「玉树芝兰」用户请勿重复付费&#xff09; 通过我的这些尝试&#xff0c;你无需再自己去摸索&#xff0c;可以直接根据我展示的结果选择合适的工具&#xff0c;更有效地进行文献回顾。 …

抖店怎么上架商品?

我是电商珠珠 没有货源的商家&#xff0c;上架商品其实很简单&#xff0c;只需要找到工具去采集链接上架即可。但在上架商品的时候也不能完全照搬&#xff0c;需要涉及到主图、详情页、标题和价格的优化&#xff0c;这些都会影响到商品的点击率&#xff0c;所以上架商品的过程…

3元一平方公里的在线卫星影像

我们为大家分享了免费下载卫星影像的方法。 但让人遗憾的是&#xff0c;该影像的最高分辨率只有10米&#xff0c;需要更高清且比较新的卫星影像&#xff0c;看来还是得付费购买才比较靠谱。 自助选择区县范围 商业卫星影像主要面向企事业单位&#xff0c;一般来讲都比较贵&a…

每周一算法:倍增法求区间最大最小值(RMQ)

RMQ RMQ 是英文 Range Maximum/Minimum Query 的缩写&#xff0c;表示区间最大&#xff08;最小&#xff09;值。使用倍增思想解决 RMQ 问题的方法是 ST 表&#xff08;Sparse Table&#xff0c; 稀疏表 &#xff09;。ST 表是用于解决 可重复贡献问题 的数据结构。 可重复贡献…

PHP语言B/S架构医院(安全)不良事件上报系统源码

医院安全&#xff08;不良&#xff09;事件上报系统采用无责的、自愿的填报不良事件方式&#xff0c;有效地减轻医护人员的思想压力&#xff0c;实现以事件为主要对象&#xff0c;可以自动、及时、实际地反应医院的安全、不良、近失事件的情况&#xff0c;更好地掌握不良事件的…

Linux学习记录——삼십오 传输层UDP协议

文章目录 1、端口号2、UDP协议 信息加上应用层报头后&#xff0c;下一步发送到传输层 1、端口号 端口号标识了一个主机上进行通信的唯一一个应用程序。 在TCP/IP协议中&#xff0c;通过源IP&#xff0c;源端口号&#xff0c;目的IP&#xff0c;目的端口号&#xff0c;协议号来…

静态网页设计——红旗汽车官网(HTML+CSS+JavaScript)

前言 声明&#xff1a;该文章只是做技术分享&#xff0c;若侵权请联系我删除。&#xff01;&#xff01; 感谢大佬的视频&#xff1a; https://www.bilibili.com/video/BV1gK411x7Bg/?vd_source5f425e0074a7f92921f53ab87712357b 使用技术&#xff1a;HTMLCSSJS&#xff08;…

Maven(mvn)的学习下载和配置

文章目录 Maven&#xff08;mvn&#xff09;1.Maven 是什么&#xff1f;2.Maven做什么&#xff1f;2.1传统方式对项目的管理2.2Maven对jar包的管理 3.Maven怎么学3.1Maven如何创建项目3.2Maven的下载与配置3.3Maven的项目结构3.4Maven依赖的引入3.5Maven依赖的剔除3.6Maven依赖…