数据结构-图-基础知识

news2024/11/19 17:47:31

  • 图的基本概念
    • 图的概念
    • 顶点和边
    • 有向图和无向图
    • 完全图
      • 有向完全图
      • 无向完全图
    • 邻接顶点
    • 顶点的度
    • 路径和路径长度
    • 简单路径和回路
    • 子图
      • 生成树
    • 连通图
    • 强连通图
  • 图的存储结构
    • 邻接矩阵
    • 邻接表
  • 图的遍历
    • BFS
    • DFS

图的基本概念

图的概念

🚀图是由顶点集合及顶点间关系组成的一种数据结构: G = (V,E),其中:顶点集合V = {x|x属于某个数据对象集}是又穷非空集合;
E = {(x,y)|x,y属于V}或者E = {<x,y>|x,y属于V并且path(x,y)}是顶点间关系的有穷集合,也叫做边的集合。

(x,y)表示x与y间的一条双向通路,是没有方向的。<x,y>表示x到y的一条单项路径,是有方向的。

顶点和边

🚀图中的结点就是顶点,第i个顶点记作Vi。两个顶点Vi和Vj相关联称作顶点Vi和顶点Vj之间有一条边,第k条边记作Ek,Ek = (Vi,Vj)或者<Vi,Vj>。

有向图和无向图

🚀在有向图中,顶点对<x,y>是有序的,顶点对<x,y>称为顶点x到顶点y的一条边,<x,y>与<y,x>是两条不同的边。

在这里插入图片描述

🚀在无向图中,顶点对(x,y)是无序的,顶点对(x,y)称为顶点x和顶点y相关联的一条边,这条边是没有方向的,(x,y)与(y,x)是同一条边。

在这里插入图片描述

完全图

有向完全图

🚀在n个顶点的有向图中,存在n*(n - 1)条边,即任意两个顶点之间有且仅有方向相反的边,则称此图为有向完全图。

在这里插入图片描述

无向完全图

🚀在n个顶点的无向图中,存在n*(n - 1) / 2条边,即任意两个顶点之间有且只有一条边,则称此图为无向完全图。

在这里插入图片描述

邻接顶点

🚀在无向图G中,若(u,v)是E(G)的一条边,则称u和v互为邻接顶点,并称边(u,v)依附于顶点u和v。在有向图G中,若<u,v>是E(G)中的一条边,则称顶点u邻接到v,v邻接自u,并称边<u,v>与顶点u和顶点v相关联。

顶点的度

🚀顶点的度是指与它相关联边的条数,记作dev(v)。在有向图中,顶点的度等于顶点的入度与顶点的出度之和,其中顶点v的入度是指以v为终点的有向边的条数,记作indev(v)。顶点v的出度是指以v为起点的有向边条数,记作outdev(v)。

🚀对于无向图顶点的度=顶点的入度=顶点的出度,即dev(u) = indev(u) = outdev(u)。

路径和路径长度

🚀在图G(V,E)中,若从顶点Vi出发有一组边可使其到达顶点Vj,则称顶点Vi到顶点Vj经过的顶点序列为Vi到Vj的路径。

🚀对于不带权值的图,路径长度是指该路径上边的条数。对于带权值的图,路径长度是指该路径上各个边上的权值综合。

简单路径和回路

🚀若路径上各顶点v1,v2,v3,…,vm均不重复,则称这样的路径为简单路
径。若路径上第一个顶点v1和最后一个顶点vm重合,则称这样的路径为回路或环。

在这里插入图片描述

在这里插入图片描述

子图

🚀由一个图的若干个顶点和若干条边组成的新的图称为原图的子图。

在这里插入图片描述

生成树

🚀一个连通图的最小连通子图称作该图的生成树。有n个顶点的连通图的生成树有n个顶点和n-1条边。

连通图

🚀在无向图中,若从顶点v1到顶点v2有路径,则称顶点v1与顶点v2是连通的。如果图中任意一对顶点都是连通的,则称此图为连通图。

强连通图

🚀在有向图中,若在每一对顶点vi和vj之间都存在一条从vi到vj的路径,也存在一条从vj到vi的路径,则称此图是强连通图。

图的存储结构

邻接矩阵

🚀用一个二维数组来表示任意两点间的联通关系,用0或1表示,对于带权值的图来说如果两个点是来联通的那么邻接矩阵中存储的就是权值,不联通通常存的是无穷大。
🚀对于无向图来说,邻接矩阵是关于对角线对称的,有向图的邻接矩阵不一定是对称的。
🚀邻接矩阵的优点是能够迅速判断两个点的联通关系,缺点是当图结构中边的数量比较少时,是比较浪费空间的。所以邻接矩阵适合存储密集图。

namespace matrix {
	template<typename V,typename W,W W_MAX = INT_MAX,bool direction = false>
	class Graph {
		typedef Graph<V, W, W_MAX, direction> self;
	private:
		std::vector<V> _vertex; //存储顶点
		std::unordered_map<V, int> _index_map; //存储顶点和下标的映射关系
		std::vector<std::vector<W>> _matrix; //邻接矩阵
	public:
		Graph(const V* v,size_t n) {
			//顶点集合
			_vertex.reserve(n);
			for (int i = 0; i < n; i++) {
				_vertex.push_back(v[i]);
				_index_map[v[i]] = i;
			}
			//邻接矩阵初始化
			_matrix.resize(n);
			for (int i = 0; i < n; i++) {
				_matrix[i].resize(n,W_MAX);
			}
		}
		Graph() = default; //默认构造
		size_t GetVertexIndex(const V& v) {
			auto it = _index_map.find(v);
			if (_index_map.end() != it) {
				return it->second;
			}
			else {
				throw std::invalid_argument("没有此顶点");
				return -1;
			}
		}
		void _AddEdge(size_t srci, size_t dsti, const W& w) {
			_matrix[srci][dsti] = w;
			if (false == direction) {
				_matrix[dsti][srci] = w;
			}
		}
		void AddEdge(const V& src, const V& dst, const W& w) {
			size_t srci = this->GetVertexIndex(src);
			size_t dsti = this->GetVertexIndex(dst);
			_AddEdge(srci, dsti, w);
		}
		void PrintGraph() {
			//打印顶点与下标的映射关系
			for (int i = 0; i < _vertex.size(); i++) {
				std::cout << "[" << _vertex[i] << "]->" << i << std::endl;
			}
			//打印邻接矩阵
			std::cout << "  ";
			for (int i = 0; i < _vertex.size(); ++i) {
				//std::cout << i << " ";
				printf("%-4d", i);
			}
			std::cout << "\n";
			for (int i = 0; i < _matrix.size(); ++i) {
				std::cout << i << " ";
				for (int j = 0; j < _matrix[i].size(); ++j) {
					if (i == j) {
						//std::cout << 0;
						printf("%-4d", 0);
					}
					else {
						if (_matrix[i][j] == W_MAX) {
							//std::cout << "# ";
							printf("%-4c",'#');
						}
						else {
							//std::cout << _matrix[i][j] << " ";
							printf("%-4d", _matrix[i][j]);
						}
					}
				}
				std::cout << "\n";
			}
		}
	};
}

邻接表

🚀邻接表表示法,用数组表示顶点的集合,使用链表存储边的关系。

在这里插入图片描述

🚀无向图的邻接表,同一条边会出现两次。如果想知道一个顶点的度,只需要知道该顶点Vi边链表集合中的边数目即可。
🚀用邻接表表示法,可以轻松的找到与顶点相连的边,但是不能够立即判断两个点的联通关系。邻接表适合存储稀疏图。

namespace link_table {
	template<typename W>
	struct Edge {
		size_t _dsti;
		W _weight;
		Edge<W>* _next;

		Edge(size_t dsti,const W& weight) :_dsti(dsti),_weight(weight) {}
	};
	template<typename V, typename W,bool direction = false>
	class Graph {
		typedef Edge<W> Edge;
	private:
		std::vector<V> _vertex; //存储顶点
		std::unordered_map<V, int> _index_map; //存储顶点和下标的映射关系
		std::vector<Edge*> _tables; //邻接表
	public:
		Graph(const V* v, size_t n) {
			//顶点集合
			_vertex.reserve(n);
			for (int i = 0; i < n; i++) {
				_vertex.push_back(v[i]);
				_index_map[v[i]] = i;
			}
			//邻接表初始化
			_tables.resize(n, nullptr);
		}
		size_t GetVertexIndex(const V& v) {
			auto it = _index_map.find(v);
			if (_index_map.end() != it) {
				return it->second;
			}
			else {
				throw std::invalid_argument("没有此顶点");
				return -1;
			}
		}
		void AddEdge(const V& src, const V& dst, const W& w) {
			size_t srci = this->GetVertexIndex(src);
			size_t dsti = this->GetVertexIndex(dst);
			Edge* peg = new Edge(dsti,w);
			peg->_next = _tables[srci];
			_tables[srci] = peg;
			if (false == direction) {
				Edge* peg = new Edge(srci,w);
				peg->_next = _tables[dsti];
				_tables[dsti] = peg;
			}
		}
		void PrintGraph() {
			//打印顶点与下标的映射关系
			for (int i = 0; i < _vertex.size(); i++) {
				std::cout << "[" << _vertex[i] << "]->" << i << std::endl;
			}
			//打印邻接表
			for (int i = 0; i < _vertex.size(); ++i) {
				std::cout << "[" << _vertex[i] << ":" << i << "]->";
				Edge* cur = _tables[i];
				while (cur) {
					std::cout << "[" << _vertex[cur->_dsti] << ":" << cur->_dsti << ":" << cur->_weight << "]->";
					cur = cur->_next;
				}
				std::cout << "null\n";
			}
		}
	};
}

图的遍历

BFS

🚀图的广度优先遍历与二叉树的广度优先遍历类似,不同点就是对于二叉树而言,每个结点至多有左右子树与其相连,但是图中的某个结点,其邻接点的个数是不确定的,并且在图的遍历中会使用一个标记数组来标识某个顶点是否被访问过(防止重复遍历,甚至出现死循环的现象)。

在这里插入图片描述

🚀代码实现:

void BFS(const V& src) {
	size_t n = _vertex.size();
	std::vector<bool> visit(n,false);
	std::queue<size_t> q;
	size_t srci = GetVertexIndex(src);
	q.push(srci);
	visit[srci] = true;
	size_t level_size = 1;
	int cnt = 1;
	//一直循环到队列为空,每次取出一个结点并把其临接点带入队列
	while (!q.empty()) {
		std::cout << "第" << cnt++ << "层: ";
		for (int j = 0; j < level_size; ++j) {
			size_t top = q.front();
			q.pop();
			std::cout << "[" << top << ":" << _vertex[top] << "]-";
			for (int i = 0; i < n; i++) {
				if (_matrix[top][i] != W_MAX && visit[i] != true) {
					q.push(i);
					visit[i] = true;
				}
			}
		}
		std::cout << "null\n";
		level_size = q.size();
	}
}

DFS

🚀深度优先遍历的原则就是,一条路走到黑,访问完起始结点的邻接点后,就以邻接点为新的起始结点继续向下访问,知道条件不满足时,再返回到上一层函数栈帧中。同样,深度优先遍历也需要一个标记数组来标记一个顶点是否被访问过。

🚀代码实现:

void _DFS(size_t srci, std::vector<bool>& visit) {
	std::cout << "[" << srci << ":" << _vertex[srci] << "]\n";
	visit[srci] = true;
	for (int i = 0; i < _vertex.size(); ++i) {
		if (_matrix[srci][i] != W_MAX && visit[i] != true) {
			_DFS(i, visit);
		}
	}
}
void DFS(const V& src) {
	size_t n = _vertex.size();
	size_t srci = GetVertexIndex(src);
	std::vector<bool> visit(n, false);
	_DFS(srci, visit);
}

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

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

相关文章

O(根号n/ln(根号n))时间复杂度内求n的所有因子

O&#xff08;&#xff09;复杂度内求n的所有因子&#xff0c;在2e9数量级比O&#xff08;&#xff09;快10倍左右 先用范围内的质数除n&#xff0c;求出n的分解质因数形式&#xff0c;然后爆搜求出n的所有因子&#xff0c; n范围内的质数大约有个&#xff0c;所以是这个时间…

Spring Framework 黑马程序员-学习笔记

5.spring-核心概念 IoC &#xff1a;控制反转 使用对象时&#xff08;如在service类中调用Dao层的对象&#xff0c;以便使用Dao类中的方法&#xff09;&#xff0c;本来是依靠new一个Dao层的对象来实现&#xff0c;而实现了Ioc思想的Spring为了解耦&#xff0c;将此过程改为&…

Play Beyond:Sui让优秀的游戏变得更好

自问世以来&#xff0c;视频游戏就紧随着文化产业发展。从Pong和Space Invaders的时代到Animal Crossing和Among Us&#xff0c;伟大的游戏总有能力吸引玩家&#xff0c;并推动娱乐产业发展。根据Grand View Research的数据&#xff0c;全球视频游戏市场在2022年估计为2170.6亿…

fastadmin插件 shopro 商城支付配置

1、 2、 注意上图中有添加支付方式链接&#xff0c;可以点击添加&#xff0c;这里添加后立即生效

zkVM设计性能分析

1. 引言 本文主要参考&#xff1a; 2023年9月ZKSummit10 Wei Dai 1k(x) & Terry Chung 1k(x)分享视频 ZK10: Analysis of zkVM Designs - Wei Dai & Terry Chung 当前有各种zkVM&#xff0c;其设计思想各有不同&#xff0c;且各有取舍&#xff0c;本文重点对现有各z…

[C语言】(指针解决)输入数组,最大的与第一个元素交换,最小的与最后一个元素交换,输出数组

代码 下面是使用指针解决的代码示例&#xff1a; #include <stdio.h>void swap(int *a, int *b) {int temp *a;*a *b;*b temp; }int main() {int arr[100], n, max_index 0, min_index 0;printf("Enter the size of the array: ");scanf("%d"…

Maven(项目构建管理工具)

为什么要使用Maven&#xff1f; 传统项目管理状态分析&#xff1a; ①jar包不统一&#xff0c;jar包不兼容&#xff1b; ②工程升级维护过程操作繁琐&#xff1b; ........... Maven(JAVA书写)&#xff1a;管理jar包以及jar之间的依赖关系&#xff0c;完成项目编译&#xff0c;…

【算法】排序——归并排序和计数排序

主页点击直达&#xff1a;个人主页 我的小仓库&#xff1a;代码仓库 C语言偷着笑&#xff1a;C语言专栏 数据结构挨打小记&#xff1a;初阶数据结构专栏 Linux被操作记&#xff1a;Linux专栏 LeetCode刷题掉发记&#xff1a;LeetCode刷题 算法头疼记&#xff1a;算法专栏…

回归预测|GWO-BPNN-Adaboost算法原理及其实现(Matlab)

在上一篇文章中介绍了BPNN-Adaboost算法的原理及其实现&#xff0c;Adaboost算法可以将多个BPNN作为弱分类器进行训练&#xff0c;使其相互补充&#xff0c;集成为具有较强鲁棒性的强分类器。但由于BPNN对于初始权值和阈值的选取具有随机性&#xff0c;这将导致模型精度的不定性…

【易语言】m3u8下载器源码

前阵子接了个下载视频的小单子&#xff0c;部分视频是m3u8链接的&#xff0c;临时弄了个批量下载器&#xff0c;如图&#xff1a; 这东西网上虽然很多&#xff0c;但还是喜欢自己折腾一下&#xff0c;就直接开源了。代码好不好&#xff0c;只看能不能跑。 原理就是调用ffmpeg&a…

Polygon Mide状态模型:解决状态膨胀,而不牺牲隐私和去中心化

1. 引言 前序博客有&#xff1a; Polygon Miden&#xff1a;扩展以太坊功能集的ZK-optimized rollupPolygon Miden zkRollup中的UTXO账户混合状态模型Polygon Miden交易模型&#xff1a;Actor模式 ZKP &#xff1e; 并行 隐私 在Polygon Miden交易模型&#xff1a;Actor模…

国庆day5

客户端 #include "widget.h" #include "ui_widget.h"Widget::Widget(QWidget *parent): QWidget(parent), ui(new Ui::Widget) {ui->setupUi(this);socket new QTcpSocket(this);//此时&#xff0c;已经向服务器发送连接请求了&#xff0c;如果成功连…

安全防御—密码学

1. 什么是APT&#xff1f; APT&#xff08;Advanced Persistent Threat&#xff09;是指高级持续性威胁&#xff0c;本质是针对性攻击。 利用先进的攻击手段对特定目标进行长期持续性网络攻击的攻击形式&#xff0c;APT攻击的原理相对于其他攻击形式更为高级和先进&#xff0c;…

一文教你搞懂Redis集群

一、Redis主从 1.1、搭建主从架构 单节点的Redis的并发能力是有上限的&#xff0c;要进一步的提高Redis的并发能力&#xff0c;据需要大家主从集群&#xff0c;实现读写分离。 共包含三个实例&#xff0c;由于资源有限&#xff0c;所以在一台虚拟机上&#xff0c;开启多个red…

第八章 排序 一、排序的基本概念

目录 一、定义 二、排序算法的评价指标 1、算法的稳定性 2、时间复杂度和空间复杂度 三、排序算法的分类 &#xff08;1&#xff09;内部排序 &#xff08;2&#xff09;外部排序 一、定义 排序是将一组数据按照一定的规则或条件进行重新排列的过程&#xff0c;使得数据…

代码随想录第35天 | ● 01背包问题,你该了解这些! ● 01背包问题—— 滚动数组 ● 416. 分割等和子集

01背包 题目 有n件物品和一个最多能背重量为w 的背包。第i件物品的重量是weight[i]&#xff0c;得到的价值是value[i] 。每件物品只能用一次&#xff0c;求解将哪些物品装入背包里物品价值总和最大。 代码 function testWeightBagProblem (weight, value, size) {// 定义 d…

【已解决】spring-boot项目使用maven打包时出现BOOT-INF文件夹的问题

jar中多了这个BOOT-INF文件夹的原因&#xff0c;主要是因为我们在maven的pom文件中加入了spring-boot-maven-plugin这个插件&#xff0c;如下所示&#xff1a; 只需要将加个configuration标签&#xff0c;并在里面嵌套加入一个skip子标签&#xff0c;并将skip的值设为true&…

实现文档AI搜索,提高问题解决效率

在当今的数字时代&#xff0c;以AI为动力的文档搜索变得越来越重要。随着在线提供信息的指数增长&#xff0c;传统的搜索方法通常效率低下且耗时。实施文档AI搜索可以显著提高搜索相关文档的效率和有效性。 | 在网站中实施文档AI搜索的好处很多 首先&#xff0c;它通过提供无缝…

联想M7216NWA打印一体机墨粉清零方法

在设备就绪状态下&#xff0c; 按“功能”键&#xff0c;进入设置菜单&#xff0c;按上下键进行选择&#xff0c;屏幕出现“设备信息”项时按"确认"键&#xff0c; 再按上下键选择&#xff0c;当屏幕出现“重置硒鼓”后长按“确认”键不松手&#xff0c;指导屏幕出现…

RPC 框架之Thrift入门(一)

&#x1f4cb; 个人简介 &#x1f496; 作者简介&#xff1a;大家好&#xff0c;我是阿牛&#xff0c;全栈领域优质创作者。&#x1f61c;&#x1f4dd; 个人主页&#xff1a;馆主阿牛&#x1f525;&#x1f389; 支持我&#xff1a;点赞&#x1f44d;收藏⭐️留言&#x1f4d…