【数据结构】24王道考研笔记——图

六、图

目录

  • 六、图
    • 定义及基本术语
      • 图的定义
      • 有向图以及无向图
      • 简单图以及多重图
      • 顶点-顶点间关系
      • 连通图、强连通图
      • 子图
      • 连通分量
      • 强连通分量
      • 生成树
      • 生成森林
      • 边的权、带权网/图
      • 特殊形态的图
    • 图的存储及基本操作
      • 邻接矩阵
      • 邻接表法
      • 十字链表
      • 邻接多重表
      • 分析对比
      • 图的基本操作
    • 图的遍历
      • 广度优先遍历(BFS)
      • 深度优先遍历(DFS)
    • 图的应用
      • 最小生成树
      • 最短路径BFS
      • 最短路径Dijkstra
      • 最短路径Floyd算法
      • 有向无环图
      • 拓扑排序
      • 关键路径

定义及基本术语

图的定义

image.png

有向图以及无向图

image.png

简单图以及多重图

image.png

image.png

顶点-顶点间关系

image.png

连通图、强连通图

image.png

子图

image.png

(有向图也一样)

连通分量

image.png

强连通分量

image.png

生成树

image.png

生成森林

image.png

边的权、带权网/图

image.png

特殊形态的图

image.png

image.png

image.png

总结:

image.png

图的存储及基本操作

邻接矩阵

#define MaxVertexNum 100//顶点数目的最大值
typedef struct
{char Vex[MaxVertexNum];//顶点表 (可存更复杂的信息)int Edge[MaxVertexNum][MaxVertexNum];//邻接矩阵,边表(可以用bool型或枚举型变量表示边)int vexnum, arcnum;//图的当前顶点数和边数/弧数
}MGraph;

image.png

image.png

存储带权图(网):

image.png

对角线处可以填0或∞

空间复杂度为O(|V|2)只和顶点数相关,和实际的边数无关,适合用于存储稠密图

对于无向图,邻接矩阵是对称矩阵,可以进行对称矩阵的存储压缩,存入一维数组中(只存储上三角区/下三角区)

性质:

image.png

邻接表法

邻接矩阵是用数组实现的顺序存储,空间复杂度高,不适合存储稀疏图

而邻接表法使用顺序+链式存储的方式,表示方式并不唯一(与树的孩子表示法相似)

//邻接表
typedef char VertexType;//顶点的数据类型
//“边/弧”
typedef struct ArcNode
{int adjvex;//边/弧指向哪个节点struct ArcNode* next;//指向下一条弧的指针//InfoType info;//权值
}ArcNode;
//“顶点”
typedef struct VNode
{VertexType data;//顶点信息ArcNode* first;//第一条边/弧
}VNode,AdjList[MaxVertexNum];
//用邻接表存储的图
typedef struct
{AdjList vertices;int vexnum, arcnum;
}ALGraph;

image.png

与邻接矩阵对比:

image.png

十字链表

用邻接矩阵以及邻接表存储有向图时,都有所缺陷:

image.png

使用十字链表存储有向图(不能用于无向图):

image.png

空间复杂度为:O(|V|+|E|)

顺着绿色路线能找到顶点所有的出边

顺着橙色路线能找到顶点所有的入边

邻接多重表

用邻接矩阵以及邻接表存储无向图时,都有所缺陷:

image.png

用邻接多重表存储无向图(不能用于有向图):

image.png

空间复杂度:O(|V|+|E|)

删除边、删除节点等操作很方便

分析对比

image.png

图的基本操作

主要基于图的邻接矩阵以及邻接表

image.png

**Adjacent(G,x,y):**判断图G是否存在边<x, y>或(x, y)。

邻接矩阵:O(1) 邻接表:O(1)~O(|V|)

**Neighbors(G,x):**列出图G中与结点x邻接的边。

邻接矩阵:O(|V|) 邻接表:出边:O(1)~O(|V|) 入边:O(|E|)

**InsertVertex(G,x):**在图G中插入顶点x

邻接矩阵:O(1) 邻接表:O(1)

**DeleteVertex(G,x):**从图G中删除节点x

邻接矩阵:O(|V|) 邻接表:出边:O(1)~O(|V|) 入边:O(|E|)

**AddEdge(G,x,y):**若无向边(x, y)或有向边<x, y>不存在,则向图G中添加该边。

邻接矩阵:O(1) 邻接表:O(1)

**RemoveEdge(G,x,y):**若无向边(x, y)或有向边<x, y>存在,则从图G中删除该边。

邻接矩阵:O(1) 邻接表:O(1)~O(|V|)

**FirstNeighbor(G,x):**求图G中顶点x的第一个邻接点,若有则返回顶点号。若x没有邻接点或图中不存在x,则返回-1。

邻接矩阵:O(1)~O(|V|) 邻接表:出边:O(1) 入边:O(1)~O(|E|)

**NextNeighbor(G,x,y):**假设图G中顶点y是顶点x的一个邻接点,返回除y之外顶点x的下一个邻接点的顶点号,若y是x的最后一个邻接点,则返回-1。

邻接矩阵:O(1)~O(|V|) 邻接表:O(1)

图的遍历

广度优先遍历(BFS)

实现思路:

image.png

#define MAX_VERTEX_NUM 100//顶点数目的最大值bool visited[MAX_VERTEX_NUM];//访问标记数组
void BFSTraverse(Graph G) //对图G进行广度优先遍历
{for (int i = 0; i < G.vexnum; ++i)visited[i] = false;//访问标记数组初始化InitQueue(Q);//初始化辅助队列Qfor (int i = 0; i < G.vexnum; ++i)//从0号顶点开始遍历{if (!visited[i])//对每个连通分量调用一次BFSBFS(G, i);//vi未访问过,从vi开始BFS}
}//广度优先遍历
void BFS(Graph G, int v) //从顶点v出发,广度优先遍历图G
{visit(v);//访问初始顶点vvisited[v] = true;//对v做已访问标记Enqueue(Q, v);//顶点v入队列Qwhile (!isEmpty(Q)){DeQueue(Q, v);//顶点v处队列for (w = FirstNeighbor(G, v); w >= 0; w = NextNeighbor(G, v, w)){//检测v所有邻接点if (!visited[w])//w为v的尚未访问的邻接顶点{visit(w);//访问顶点wvisited[w] = true;//对w做已访问标记EnQueue(Q, w);//顶点w入队列}}}
}

遍历序列是具有可变性的

image.png

对于无向图,调用BFS函数的次数=连通分量数

复杂度分析:

image.png

image.png

广度优先生成树(若是非连通图,则得到广度优先生成森林)

image.png

利用广度优先生成的树,高度是最小的(因为按最短路径)

应用:解决非带权图的单源最短路径问题

//解决非带权图的单源最短路径问题
void BFS_MIN_Distance(Graph G, int u)
{//d[i]表示从u到i结点的最短路径for (int i = 0; i < G.vexnum; ++i){d[i] = 0x3f3f3f3f;//无穷大,初始化路径长度}visited[u] = true;d[u] = 0;EnQueue(Q, u);while (!isEmpty(Q))//BFS算法主过程{DeQueue(Q, u);//队头元素u出队for (w = FirstNeighbor(G, u); w >= 0; w = NextNeighbor(G, u, w)){if (!visited[w])//w为u的尚未访问的邻接顶点{visited[w] = true;//设已访问标记d[w] = d[u] + 1;//路径长度加1EnQueue(Q, w);//顶点w入队}}}
}

深度优先遍历(DFS)

类似于树的先根遍历,使用函数调用栈,递归实现

#define MAX_VERTEX_NUM 100//顶点数目的最大值bool visited[MAX_VERTEX_NUM];//访问标记数组void DFSTraverse(Graph G)//深度优先遍历图G
{for (v = 0; v < G.vexnum; ++v)visited[v] = false;//初始化已访问标记数据for (v = 0; v < G.vexnum; ++v)//从v=0开始遍历if (!visited[v])DFS(G, v);
}void DFS(Graph G, int v)//从顶点v触发,深度优先遍历图G
{visit(v);//访问顶点vvisited[v] = true;//设已访问标记for (w = FirstNeighbor(G, v); w >= 0; w = NextNeighbor(G, v, w)){if (!visited[w]){DFS(G, w); // w为v的尚未访问的邻接顶点}}
}

复杂度分析:

image.png

image.png

遍历序列不唯一性:

image.png

深度优先生成树:(非连通生成森林)

image.png

对于无向图进行BFS/DFS遍历,调用BFS/DFS次数=连通分量数,对于连通图,只调用一次

对于有向图进行BFS/DFS遍历,调用BFS/DFS次数要具体问题具体分析,若起始顶点到其他各顶点都有路径,只调用一次,对于强连通图,从任一结点出发都只需调用一次BFS/DFS

图的应用

最小生成树

连通图的生成树是包含图中全部顶点的一个极小连通子图。若图中顶点数为n,则它的生成树含有n-1条边,对生成树而言,若砍去它的一条边,则会变成非连通图,若加上一条边则会形成一个回路。

广度优先生成树的高度是小于等于深度优先生成树的高度的。

最小生成树定义:

image.png

两种求最小生成树的方法:

Kruskal:

image.png

Prim:

image.png

image.png

最短路径BFS

最短路径问题描述:

image.png

利用BFS算法可以求无权图的单源最短路径(无权图可以视作一种特殊的带权图,只是每条边的权值都为1)故各边权值相等时,可以使用BFS算法求解

代码实现:

//解决非带权图的单源最短路径问题
void BFS_MIN_Distance(Graph G, int u)
{//d[i]表示从u到i结点的最短路径for (int i = 0; i < G.vexnum; ++i){d[i] = 0x3f3f3f3f;//无穷大,初始化路径长度path[i]=-1;//记录最短路径从哪个顶点过来}visited[u] = true;d[u] = 0;EnQueue(Q, u);while (!isEmpty(Q))//BFS算法主过程{DeQueue(Q, u);//队头元素u出队for (w = FirstNeighbor(G, u); w >= 0; w = NextNeighbor(G, u, w)){if (!visited[w])//w为u的尚未访问的邻接顶点{visited[w] = true;//设已访问标记d[w] = d[u] + 1;//路径长度加1path[w]=u;//最短路径应从u到wEnQueue(Q, w);//顶点w入队}}}
}

image.png

时间复杂度:邻接矩阵O(|V|2) 邻接表O(|V|+|E|)

最短路径Dijkstra

dist[ ]记录从源点v0到其他各顶点当前的最短路径长度,它的初态为:若从v0到vi有弧,则dist[i]为弧上的权值,否则置于∞

path[ ]表示从源点到顶点i之间的最短路径的前驱结点。在算法结束时,可根据其值追溯得到源点v0到顶点vi的最短路径。

image.png

不适用于有负权值的带权图

用邻接矩阵以及邻接表时间复杂度都为O(|V|2)

最短路径Floyd算法

算法思路:

image.png

image.png

最终实现:

image.png

对于更多结点,若要找到完整路径需要通过path矩阵递归寻找

image.png

Floyd算法可以用于负权图,但不能解决带有“负权回路”的图(有负权值的边组成回路)这种图有可能没有最短路径

不同算法对比:

image.png

有向无环图

若一个有向图中不存在环,则称为有向无环图,简称DAG图

有向无环图是描述含有公共子式的表达式的有效工具

其表示表达式中顶点中不可能出现重复的操作数

步骤:

image.png

表示出来的结果可能不唯一

拓扑排序

AOV网:用DAG图表示一个工程,顶点表示活动,有向边<Vi,Vj>表示活动Vi必须先于活动Vj进行,不能存在环路!

拓扑排序:

image.png

image.png

实现代码:

#define MaxVertexNum 100//图中顶点数目的最大值//定义邻接表
typedef char VertexType;//顶点的数据类型
//“边/弧”
typedef struct ArcNode
{int adjvex;//边/弧指向哪个节点struct ArcNode* nextarc;//指向下一条弧的指针//InfoType info;//权值
}ArcNode;
//“顶点”
typedef struct VNode
{VertexType data;//顶点信息ArcNode* firstarc;//第一条边/弧
}VNode, AdjList[MaxVertexNum];
//用邻接表存储的图
typedef struct
{AdjList vertices;int vexnum, arcnum;
}ALGraph;//拓扑排序
bool TopologicalSort(Graph G)
{InitStack(S);//初始化栈,存储入度为0的结点for (int i = 0; i < G.vexnum; i++){if (indegree[i] == 0){Push(S, i);//将所有入度为0的顶点进栈}}int count = 0;//计数,记录当前已经输出的顶点数while (!IsEmpty(S))//栈不空,则存在入度为0的顶点{Pop(S, i);//栈顶元素出栈print(count++) = i;//输出顶点ifor (p = G.vertices[i].firstarc; p; p = p->nextarc){//将所有i指向的顶点的入度减1,并且将入度为0的顶点压入栈Sv = p->adjvex;if (!(--indegree[v]))//若为0{Push(S, v);//入栈}}}if (count < G.vexnum){return false;//排序失败,有向图中有回路}elsereturn true;//拓扑排序成功
}

image.png

时间复杂度:邻接表:O(|V|+|E|) 若采用邻接矩阵 O(|V|2)

逆拓扑排序:

image.png

也可以用DFS算法实现:

//逆拓扑排序
void DFSTraverse(Graph G)//深度优先遍历图G
{for (v = 0; v < G.vexnum; ++v)visited[v] = false;//初始化已访问标记数据for (v = 0; v < G.vexnum; ++v)//从v=0开始遍历if (!visited[v])DFS(G, v);
}void DFS(Graph G, int v)//从顶点v触发,深度优先遍历图G
{visit(v);//访问顶点vvisited[v] = true;//设已访问标记for (w = FirstNeighbor(G, v); w >= 0; w = NextNeighbor(G, v, w)){if (!visited[w]){DFS(G, w); // w为v的尚未访问的邻接顶点}}print(v);//输出顶点
}

关键路径

在带权有向图中,以顶点表示事件,以有向边表示活动,以边上的权值表示完成该活动的开销(如完成活动所需的时间),称之为用边表示活动的网络,简称AOE网。

性质:

image.png

AOE网中仅有一个入度为0的顶点,称为开始顶点(源点),也仅有一个出度为0的顶点,称为结束顶点(汇点)

从源点到汇点的有向路径可能有多条,所有路径中,具有最大路径长度的路径称为关键路径,而把关键路径上的活动称为关键活动

完成整个工作的最短时间就是关键路径的长度,若关键活动 不能按时安成为,则整个工程的完成时间就会延长。

几个概念:

image.png

image.png

image.png

求关键路径的步骤:

image.png

求事件的最早发生时间:

image.png

求事件的最迟发生时间:

image.png

求活动的最早发生时间:

image.png

求活动的最迟发生时间:

image.png

求活动的时间余量:

image.png

最终得出关键路径:

image.png

特性:

若关键活动耗时增加,则整个工程的工期将增长,缩短关键活动的时间,可以缩短整个工程的工期,当缩短到一定程度时,关键活动可能会变成非关键活动。

可能有多条关键路径,只提高一条关键路径上的关键活动速度并不能缩短整个工程的工期,只有加快那些包括在所有关键路径上的关键活动才能达到缩短工期的目的。

思路总结:

image.png

主要参考:王道考研课程
后续会持续更新考研408部分的学习笔记,欢迎关注。
github仓库(含所有相关源码):408数据结构笔记

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

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

相关文章

vue学习笔记(一)

1.编辑器选择 是用vscode 和 webstrom 个人感觉 vscode的插件比较多&#xff0c;对vue3的支持比较好 webstorm的自动保存比较好 各有优劣吧 我学习的这个项目目前采用vscode 2.vue2 还是 vue3 框架学通了都是通用的&#xff0c;这个时间点来学肯定是学vue3 只是顾虑到团…

JavaScript XHR、Fetch

1 前端数据请求方式 2 Http协议的解析 3 XHR的基本用法 4 XHR的进阶和封装 5 Fetch的使用详解 6 前端文件上传流程 早期的页面都是后端做好&#xff0c;浏览器直接拿到页面展示的&#xff0c;用到的是jsp、asp、php等等的语言。 这个叫做服务器端渲染SSR。 这里后端向前端…

金融数据库的战场,太平洋保险和OceanBase打了场胜仗

点击关注 文丨刘雨琦 “数据库的国产替代&#xff0c;必须经过严格的考虑&#xff0c;保证不会出错&#xff0c;所以大多数企业的领导层选择按兵不动或者简单扩容。因为不换就不会错&#xff0c;选了很久如果选错&#xff0c;还可能会出现重大事故。” 某银行数据库技术人员…

Go语言之函数补充defer语句,递归函数,章节练习

defer语句是go语言提供的一种用于注册延迟调用的机制&#xff0c;是go语言中一种很有用的特性。 defer语句注册了一个函数调用&#xff0c;这个调用会延迟到defer语句所在的函数执行完毕后执行&#xff0c;所谓执行完毕是指该函数执行了return语句、函数体已执行完最后一条语句…

netty组件详解-上

netty服务端示例: private void doStart() throws InterruptedException {System.out.println("netty服务已启动");// 线程组EventLoopGroup group new NioEventLoopGroup();try {// 创建服务器端引导类ServerBootstrap server new ServerBootstrap();// 初始化服…

苹果APP安装包ipa如何安装在手机上

苹果APP安装包ipa如何安装在手机上 苹果APP的安装比安卓复杂且困难&#xff0c;很多人不知道如何将ipa文件安装到手机上。以下是几种苹果APP安装在iOS设备的方式&#xff0c;供大家参考。 一、上架App Store 这是最正规的方式。虽然审核过程复杂、时间较长&#xff0c;且审核…

数据可视化组件有什么用?

数据可视化组件在数据分析中扮演着至关重要&角色。 通过图表、图形和交互式界面&#xff0c;数据可视化组件帮助将复杂的数据转化为易于理解的视觉展示。这种形式的数据呈现有助于发现模式、趋势和异常&#xff0c;并能够快速有效地传达数据的含义和洞察。 下面简单举两个…

使用Visual Studio打造强大的程序,从添加第三方库开始

使用Visual Studio打造强大的程序&#xff0c;从添加第三方库开始 博主简介一、引言二、理解第三方库三、下载和安装第三方库四、示例代码和演示五、总结 博主简介 &#x1f4a1;一个热爱分享高性能服务器后台开发知识的博主&#xff0c;目标是通过理论与代码实践的结合&#x…

【状态估计】基于FOMIAUKF、分数阶模块、模型估计、多新息系数的电池SOC估计研究(Matlab代码实现)

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…

SpringBoot项目中MVC使用--【JSB系列之010】

SpringBoot系列文章目录 SpringBoot知识范围-学习步骤【JSB系列之000】 文章目录 SpringBoot系列文章目录Http协议是马冬梅Cookie机制Session机制Token MVC模型本章的专注内容UserController代码 ThymeleafLets GO!总结作业配套资源题外话 Http协议是马冬梅 HTTP简介 1. HTTP…

润和软件与华秋达成生态共创合作,共同推动物联网硬件创新

7月11日&#xff0c;在2023慕尼黑上海电子展现场&#xff0c;江苏润开鸿数字科技有限公司(以下简称“润开鸿”)与深圳华秋电子有限公司(以下简称“华秋”)签署了生态共创战略合作协议&#xff0c;共同推动物联网硬件生态繁荣发展。当前双方主要基于润开鸿的硬件产品及解决方案开…

完整的电商平台后端API开发总结

对于开发一个Web项目来说&#xff0c;无论是电商还是其他品类的项目&#xff0c;注册与登录模块都是必不可少的&#xff1b;注册登录功能也是我们在日常生活中最长接触的&#xff0c;对于这个业务场景的需求与逻辑大概是没有什么需要详细介绍的&#xff0c;市面上常见的邮箱注册…

混合背包(01+完全+多重背包大杂烩)

因为我们知道求解多重背包时&#xff0c;是将其进行二进制优化为01背包问题&#xff0c;那么我们就将01背包和多重背包看成一种情况&#xff0c;然后只要处理&#xff0c;完全背包和01背包问题即可&#xff08;详细看下方代码&#xff09; #include<bits/stdc.h> using n…

【ArcGIS Pro二次开发】(47):要素类追加至空库(批量)

本工具主要是针对国空数据入库而做的。 如果你手头已经整理了一部分要素类数据&#xff0c;但是数据格式&#xff0c;字段值可能并没有完全按照规范设置好&#xff0c;需要将这些数据按规范批量和库&#xff0c;就可以尝试用这个工具。 准备数据&#xff1a;标准空库、你已做…

Python、Selenium实现问卷星自动填写(内含适配个人问卷的方法)

&#x1f9d1;‍&#x1f4bb;作者名称&#xff1a;DaenCode &#x1f3a4;作者简介&#xff1a;啥技术都喜欢捣鼓捣鼓&#xff0c;喜欢分享技术、经验、生活。 &#x1f60e;人生感悟&#xff1a;尝尽人生百味&#xff0c;方知世间冷暖。 &#x1f4d6;所属专栏&#xff1a;Py…

SpringMVC的数据响应-直接回写json字符串

一般我们操作对象&#xff0c;将对象转变为json 这时导入json 转换工具的包 包1 包2-json数据绑定 包3 返回的就是json字符串你直接返回就行了 返回一个json格式的字符串 直接回写就加这个res.... 内部字符串要进行相应的转意 能够看到json字符串 能不能你封装对象&#xff0c…

【蓝图】p28-p29按键+鼠标点击实现开关门

p28-p29按键鼠标点击实现开关门 p28&#xff0c;创建门的蓝图类创建一个Actor注意&#xff08;当门的中心点不在边角上时&#xff09; 蓝图三个旋转区别按E键开关门使鼠标点击也可以开门可能遇到的bug问题 p28&#xff0c;创建门的蓝图类 actor和组件的区别、门的轴心点修改 …

【Ajax】笔记-取消请求

在进行AJAX(Asynchronous JavaScript and XML) 请求时&#xff0c;有时候我们需要取消正在进行的请求。取消请求可以帮助我们提高用户体验&#xff0c;病减少不必要的网络流量和服务器负载。 取消请求的方法 在AJAX请求中&#xff0c;我们可以使用以下方法来取消正在进行的请求…

golang 日志库logrus和lumberjack 日志切割库实践

package mainimport (log "github.com/Sirupsen/logrus""gopkg.in/natefinch/lumberjack.v2" )func main() {logger : &lumberjack.Logger{// 日志输出文件路径Filename: "/var/log/myapp/foo.log",// 日志文件最大 size, 单位是 MBMaxSiz…

数字 IC 设计职位经典笔/面试题(二)

共100道经典笔试、面试题目&#xff08;文末可全领&#xff09; FPGA 中可以综合实现为 RAM/ROM/CAM 的三种资源及其注意事项&#xff1f; 三种资源&#xff1a;BLOCK RAM&#xff0c;触发器&#xff08;FF&#xff09;&#xff0c;查找表&#xff08;LUT&#xff09;&#xf…