数据结构详细笔记——图

文章目录

  • 图的定义
  • 图的存储
    • 邻接矩阵法
    • 邻接表法
    • 邻接矩阵法与邻接表法的区别
  • 图的基本操作
  • 图的遍历
    • 广度优先遍历(BFS)
    • 深度优先遍历(DFS)
    • 图的遍历和图的连通性


图的定义

图G顶点集V边集E组成,记为G=(V,E),其中V(G)表示图G中顶点的有限非空集;E(G)表示图G中顶点之间的关系(边)集合,用|V|表示图G中顶点的个数,也称图G的阶,用|E|表示图G中边的条数

注意:线性表可以是空表,树可以是空树,但图不可以是空,即V一定是非空集

有向图
若E是有向边(也称)的有限集合时,则图G为有向图。弧是顶点的有序对,记为<v,w>,其中v、w是顶点,v称为弧尾,w称为弧头。<v,w> 不等于 <w,v>

无向图
若E是无向边(简称边)的有限集合时,则图G为无向图。边是顶点的有序对,记为(v,w)(v,w) 等于 (w,v)

简单图
①不存在重复的边
②不存在顶点到自身的边

多重图
图G中某两个结点之间的边数多于一条,又允许顶点通过同一条边和自己关联

顶点的度、入读、出度
对于无向图顶点v的度是指依附于该顶点的边的条数,记为TD(v)
对于有向图
入度是以顶点v为终点的有向边的数目,记为ID(v)
出度是以顶点v为起点的有向边的数目,记为OD(v)
顶点v的度等于其入度和出度之和,即TD(v)=ID(v)+OD(v)

顶点-顶点的关系

- 路径——顶点到顶点之间的一条路径

  • 回路——第一个顶点和最后一个顶点相同的路径称为回路或环
  • 简单路径——在路径序列中,顶点不重复出现的路径称为简单路径
  • 路径长度——路径上,边的数目
  • 点到点的距离——从顶点u出发到顶点v的最短路径若存在,则此路径的长度称为从u到v的距离;若从u到v根本不存在路径,则该距离为无穷(∞)
  • 无向图中,若从顶点v到顶点w有路径存在,则称v和w是连通的
  • 有向图中,若从顶点v到顶点w和从顶点w到顶点v之间都有路径,则称这两个顶点是强连通的

连通图、强连通图
若图G中 任意两个顶点都是连通的,则称图G为 连通图,否则称为非连通图

对于n个顶点的无向图G
若G是连通图,则最少有 n-1 条边
若G是非连通图,则最多可能有在这里插入图片描述条边

若图中 任何一对顶点都是强连通的,则称此图为 强连通图

对于n个顶点的有向图G
若G是强连通图,则最少有n条边(形成回路)

图的局部——子图
设有两个图G(V,E)和G’=(V’,E’),若V’是V的子集,且E’是E的子集,则称G‘是G的 子图

若有满足V(G’)=V(G)—— 顶点相同的子图G’,则称其为G的 生成子图

连通分量
无向图中的 极大连通子图(子图必须连通,且包含尽可能多的顶点和边)称为连通分量

强连通分量
有向图中的 极大强连通子图(子图必须连通,且包含尽可能多的顶点和边)称为强连通分量

生成树
连通图的生成树包含图中全部顶点的一个极小连通子图

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

生成森林
在非连通图中,连通分量的生成树构成了非连通图的生成森林

边的权、带权图/网
边的权——在一个图中,每条边都可以标上具有某种含义的数值,该数值称为该边的权值
带权图/网——边上带有权值的图称为带权图,也称网
带权路径长度——当图是带权图时,一条路径上所有边的权值之和,称为该路径的带权路径长度

几种特殊形态的图
无向完全图——无向图中任意两个顶点之间存在边
有向完全图——有向图中任意两个顶点之间都存在方向相反的两条弧
树——不存在回路,且连通的无向图
有向树——一个顶点的入度为0,其余顶点的入度均为1的有向图

总结

对于n个顶点的无向图G
所有顶点的度之和等于2|E|
若G是连通图,则最少有n-1条边(树),若 |E|>n-1,则一定有回路
若G是非连通图,则最多可能有在这里插入图片描述条边
无向完全图共有在这里插入图片描述条边

对于n个顶点的有向图G
所有顶点的出度之和=入度之和= |E|
所有顶点的度之和等于 2|E|
若G是强连接图,则最少有n条边(形成回路)
有向完全图共有在这里插入图片描述条边

图的存储

邻接矩阵法

#define MaxVertexNum 100           // 顶点数目的最大值
typedef struct{
	char Vex[MaxVertexNum];         // 顶点表
	int Edge[MaxVertexNum][MaxVertexNum];  //邻接矩阵,边表
	int vexnum,arcnum;         // 图的当前顶点数和边数/弧数
}MGraph;

邻接矩阵存储带权图

#define MaxVertexNum 100              // 顶点数量的最大值
#define INFINITY                      // 宏定义常量“无穷”
typedef char VertexType;              // 顶点的数据类型
typedef int EdgeType;                 // 带权图中边上权值的数据类型
typedef struct{
	VertexType Vex[MaxVertexNum];     // 顶点
	EdgeType Edge[MaxVertexNum][MaxVertexNum];    // 边的权
	int vexnum,arcnum;                // 图的当前顶点数和弧数
}MGraph;

空间复杂度
O(|V|²) ——只和顶点数相关,和实际的边数无关
适合于存储稠密图
无向图的邻接矩阵是对称矩阵,可以压缩存储(只存储上三角区/下三角区)

邻接矩阵法的性质
设图G的邻接矩阵为A(矩阵元素为0/1),则Aⁿ的元素Aⁿ[ i ][ j ]等于由顶点 i 到顶点 j 的长度为 n 的路径的数目

举个🌰:
A:
| A | B | C | D |
| A | 0 | 1 | 0 | 0 |
| B | 1 | 0 | 1 | 1 |
| C | 0 | 1 | 0 | 1 |
| D | 0 | 1 | 1 | 0 |

A² [ 1 ] [ 4 ] = a(1,1)a(1,4) + a(1,2)a(2,4) + a(1,3)a(3,4) + a(1,4)a(4,4) = 1
说明 顶点A到顶点D的长度为2的路径有1条

邻接表法

邻接表法——顺序+链式存储

// 边/弧
typedef struct ArcNode{
	int adjvex;       // 边/弧指向哪个结点
	struct ArcNode *next;  // 指向下一条弧的指针
}ArcNode;

// 顶点
typedef struct VNode{
	VertexType data;    // 顶点信息
	ArcNode *first;     // 第一条边/弧
}VNode,AdjList[MaxVertexNum];

// 用邻接表存储的图
typedef struct{
	AdjList vertices;
	int vexnum,arcnum;
}ALGraph;

无向图——边结点的数量是2|E|,整体空间复杂度为 O(|V|+2|E|)
有向图——边结点的数量是|E|,整体空间复杂度为 O(|V|+|E|)

邻接矩阵法与邻接表法的区别

邻接表邻接矩阵
空间复杂度无向图:O(∣V∣+2∣E∣);有向图:O(∣V∣+∣E∣)O(∣V∣²)
适用于存储稀疏图存储稠密图
表示方式不唯一唯一
计算度/出度/入度计算有向图的度、入度不方便,其余很方便必须遍历对应行或列
找相邻的边找有向图的入边不方便,其余很方便必须遍历对应行或列

图的基本操作

  • 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(|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(|V|)
    邻接表 ————删出边:O(1)~O(|V|);删入边:O(|E|)

  • AddEdge(G,x,y):若无向边(x,y)或有向边<x,y>不存在,则向图G中添加该边
    邻接矩阵————O(1)
    邻接表 ————O(1)

  • FirstNeighbor(G,x):求图G中顶点x的第一个邻接点,若有则返回顶点号,若没有邻接点或图中不存在x,则返回-1
    无向图:
    邻接矩阵————O(1)~O(|V|)
    邻接表 ————O(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)

  • Get_edge_value(G,x,y):获取图G中边(x,y)或<x,y>对应的权值

  • Set_edge_value(G,x,y):设置图G中边(x,y)或<x,y>对应的权值

图的遍历

广度优先遍历(BFS)

图的广度优先遍历和树的层次遍历很相似
代码实现:

bool visited[MAX_VERTEX_NUM];   // 访问标记数组

// 广度优先遍历
void BFS(Graph G, int v){    // 从顶点v出发,广度优先遍历图G
	visit(v);                // 访问初始顶点v
	visited[v] = true;       // 对v做已访问标记
	EnQueue(Q,v);            // 顶点v入队列Q
	while(!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);           // 访问顶点w
				visited[w]=true;    // 对w做已访问标记
				EnQueue(Q,w);       // 顶点w入队列
			}
		}
	}
}
	

如果是非连通图,则无法遍历完所有的结点,以上的代码就出现了Bug
所以先对所有的顶点遍历,还需要加上以下代码

void BfsTraverse(Graph G){
	for(i=0;i<G.vexnum;++i){
		visited[i]=false;  // 访问标记数组
	}
	InitQueue(Q);          // 初始化辅助队列Q
	for(i=0;i<G.vexnum;++i){   // 从0号顶点开始遍历
		if(!visited[i])    // 对每一个连通分量调用依次BFS
			BFS(G,i);      // vi未访问过,从vi开始BFS
	}
}

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

注意:
同一个图的邻接矩阵表示方式唯一,因此广度优先遍历序列唯一
同一个图的邻接表表示方式不唯一,因此广度优先遍历序列不唯一

复杂度分析
对于邻接矩阵存储的图:O(|v|²)
对于邻接表存储的图:O(|V|+|E|)

广度优先生成树
由广度优先遍历过程确定,将遍历过程中没有经过的边去掉

由于邻接表的表示方式不唯一,因此基于邻接表的广度优先生成树也不唯一

遍历非连通图可以得到广度优先生成森林

深度优先遍历(DFS)

图的深度优先遍历和树的先根遍历很相似

bool visited[MAX_VERTEX_NUM];   // 访问标记数组
void DFSTraverse(Graph G){
	for(v=0;v<G.vexnum;++v)
		visited[v]=false;
	for(v=0;v<G.vexnum;++v)
		if(!visited[v])
			DFS(G,v)
}


void DFS(Graph G,int v){
	visit(v);
	visited[v]=true;
	for(w=FirstNeighbor(G,v);w>=0;w=NextNeighbor(G,v,w)){
		if(!visited[w]){
			DFS(G,w);
		}
	}
}

结论:对于无向图,调用D FS函数的次数=连通分量数

复杂度分析
空间复杂度:O(|v|)
对于邻接矩阵存储的图:O(|v|²)
对于邻接表存储的图:O(|V|+|E|)

深度优先遍历序列

注意:
同一个图的邻接矩阵表示方式唯一,因此深度优先遍历序列唯一
同一个图的邻接表表示方式不唯一,因此深度优先遍历序列不唯一

深度优先生成树
由深度优先遍历过程确定,将遍历过程中没有经过的边去掉

由于邻接表的表示方式不唯一,因此基于邻接表的深度优先生成树也不唯一

遍历非连通图可以得到深度优先生成森林

图的遍历和图的连通性

无向图:
DFS/BFS函数调用次数=连通分量数
有向图:
1、若从起始顶点到其他顶点都有路径,则只需要调用1次DFS/BFS函数
2、对于强连通图,从任一顶点出发都只需要调用1次DFS/BFS函数

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

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

相关文章

首发|PS 2024 正式版来袭,内置AI神经滤镜,支持Win/Mac

前言 Photoshop 2024应用程序发布了生成式AI绘图&#xff0c;这是世界上第一个创意和设计工作流程的软件&#xff0c;为用户提供了一种神奇的新工作方式。生成式AI绘图由Adobe Firefly提供支持&#xff0c;Adobe的创意生成AI模型系列。 正文简介 Photoshop 2024正式版 支持…

【Linux从入门到放弃】环境变量

&#x1f9d1;‍&#x1f4bb;作者&#xff1a; 情话0.0 &#x1f4dd;专栏&#xff1a;《Linux从入门到放弃》 &#x1f466;个人简介&#xff1a;一名双非编程菜鸟&#xff0c;在这里分享自己的编程学习笔记&#xff0c;欢迎大家的指正与点赞&#xff0c;谢谢&#xff01; 文…

torch.stack

看网上看多没讲的不是很明白&#xff0c;我来试试空间上的理解 # 假设是时间步T1的输出 T1 torch.tensor([[1, 2, 3],[4, 5, 6],[7, 8, 9]]) # 假设是时间步T2的输出 T2 torch.tensor([[10, 20, 30],[40, 50, 60],[70, 80, 90]])输出&#xff1a; print(torch.stack((T1,T2…

Spring Boot中配置文件生效位置

1. 配置文件位置 首先小伙伴们要明白&#xff0c;Spring Boot 默认加载的配置文件是 application.properties 或者 application.yaml&#xff0c;properties优先级高于yaml。默认的加载位置一共有五个&#xff0c;五个位置可以分为两类&#xff1a; 从 classpath 下加载&…

设计模式-行为型模式-责任链模式

一、什么是责任链模式 责任链模式是一种设计模式。在责任链模式里&#xff0c;很多对象由每一个对象对其下家的引用而连接起来形成一条链。请求在这个链上传递&#xff0c;直到链上的某一个对象决定处理此请求。发出这个请求的客户端并不知道链上的哪一个对象最终处理这个请求&…

【数据分享】2023年我国省市县三级的专精特新“小巨人”企业数量(Excel/Shp格式)

企业是经济活动的参与主体。一个城市的企业数量决定了这个城市的经济发展水平&#xff01;比如一个城市的金融企业较多&#xff0c;那这个城市的金融产业肯定比较发达&#xff1b;一个城市的制造业企业较多&#xff0c;那这个城市的制造业肯定比较发达。 之前我们给大家分享了…

【数据结构】10道经典面试题目带你玩转链表

&#x1f984;个人主页:修修修也 &#x1f38f;所属专栏:数据结构 ⚙️操作环境:Visual Studio 2022 目录 一.移除链表元素 二.反转链表 三.链表的中间结点 四.链表中倒数第K个结点 五.合并两个有序链表 六.链表分割 七.链表的回文结构 八.相交链表 九.环形链表 一.移…

国内优质企业网盘推荐:满足您的文件存储与共享需求

企业网盘是主要用于企业工作过程中给的文件存储、共享以及协作。很多用户在挑选文件协作工具时更偏爱国内的工具&#xff0c;原因是使用上可能更贴合国人的使用习惯&#xff01; 那么现在国内做的比较好的企业网盘有什么&#xff1f; Zoho Workdrive企业网盘&#xff0c;ZOHO…

.NET 8 正式 GA 遥遥领先

.NET 8 一正式 已正式 GA。 微软称 .NET 8 提供了数以千计的性能、稳定性和安全性改进&#xff0c;以及平台和工具增强功能&#xff0c;有助于提高开发者的工作效率和创新速度。 比如 .NET 8 为 Android 和 WASM 引入了全新的 AOT 模式、改进 System.Text.Json&#xff0c;以…

谈谈如何写作(二)

序言 没有什么比一套好理论更有用了。——库尔特勒温 谈谈如何写作系列今天进入第二篇&#xff0c;第一篇请速戳&#xff1a;谈谈如何写作&#xff08;一&#xff09; 今天&#xff0c;博主从如何写报告讲起。 Q&#xff1a;如何写报告 如何写报告呢&#xff1f; 当每位盆友接到…

MAXScript实现简单的碰撞检测教程

在本教程中&#xff0c;我们将创建一个使轮子在地形上跟随的脚本。此脚本将没有任何UI。并且仅适用于特定对象。 因此&#xff0c;第一步是创建一个新的脚本。打开侦听器窗口&#xff0c;然后在文件菜单下选择“新建脚本…”。 我们首先需要创建与场景中的对象相对应的3个变量…

实战提升(六)

前言&#xff1a;Practice makes perfect&#xff01;今天实战Leetcode链表分割还有回文结构。今天的题全都来自于牛客网。 实战一&#xff1a; 思路&#xff1a;我们一这个链表为例&#xff0c;小于5的链表尾插到第一个链表&#xff0c;大于5的链表尾插到第二个链表&#xf…

【开源】基于Vue.js的开放实验室管理系统的设计和实现

项目编号&#xff1a; S 013 &#xff0c;文末获取源码。 \color{red}{项目编号&#xff1a;S013&#xff0c;文末获取源码。} 项目编号&#xff1a;S013&#xff0c;文末获取源码。 目录 一、摘要1.1 项目介绍1.2 项目录屏 二、研究内容2.1 实验室类型模块2.2 实验室模块2.3 实…

vim指令

> 作者简介&#xff1a;დ旧言~&#xff0c;目前大二&#xff0c;现在学习Java&#xff0c;c&#xff0c;c&#xff0c;Python等 > 座右铭&#xff1a;松树千年终是朽&#xff0c;槿花一日自为荣。 > 目标&#xff1a;熟练掌握vim&#xff0c;并且能用vim敲出简单的代…

【计算机组成原理】知识点巩固 - 存储器概述

目录 1、存储器分类 1.1、按存储介质分类 1.2、按存取方式分类 1.3、按信息的可改写性分类 1.4、按信息的可保存性分类 1.5、按功能和存取速度分类 2、存储器技术指标 2.1、存储容量 2.2、存取速度 3、存储系统层次结构 4、主存的基本结构 5、主存中数据的存放 5…

麒麟 ZYJ 服务器软件适配 参考示例

一、zyj 环境简介 1. ZYJ 概述 国产化 SMZYJ 是由国家 BM 主管部门鉴定并批准生产使用的国内自主开发的 整机 JM 国标设备&#xff0c;设备采用了自主设备基础硬件&#xff08;飞腾、国科微等&#xff09;、安全硬 件自主固件&#xff08;昆仑等&#xff09;自主 SM 专用操作…

7、传统CV之高斯滤波

这一节在上一节均值滤波的基础上,再进阶一下,了解一下什么是高斯滤波。 首先,如上一节所说,均值滤波是利用一个窗口在图片上滑动,每次都计算窗口内能看到的像素的平均值,然后将平均值作为滤波的输出,从而可以起到平滑图像、去噪点的作用。 有没有发现,此时并没有特别…

栈的实现及OJ练习(c语言)

目录 前言 栈 栈的实现&#xff08;数组栈&#xff09; 初始化栈 入栈 出栈 获取栈顶元素 获取栈中有效元素个数 检测栈是否为空 销毁栈 最终代码&#xff1a; 选择练习 栈的OJ题 前言 我们在之前已经学习了顺序表和链表的概念&#xff0c;它们有这样的优缺点&a…

4.2 Windows驱动开发:内核中进程线程与模块

内核进程线程和模块是操作系统内核中非常重要的概念。它们是操作系统的核心部分&#xff0c;用于管理系统资源和处理系统请求。在驱动安全开发中&#xff0c;理解内核进程线程和模块的概念对于编写安全的内核驱动程序至关重要。 内核进程是在操作系统内核中运行的程序。每个进…

键鼠自动化2.0展示

软件介绍&#xff1a;桌面键鼠自动化工具 Qtc 编写&#xff1a; 本软件采用Qt C编写&#xff0c;旨在提供高效、跨平台的桌面键鼠自动化解决方案。Qt C框架的选择确保了软件的稳定性、可靠性&#xff0c;并通过其图形用户界面实现了用户友好的操作体验。 鼠标移动与点击&#…