【数据结构与算法】图——邻接表与邻接矩阵

news2024/9/23 7:23:11

文章目录

  • 一、图的基本概念
  • 二、图的存储结构
    • 2.1 邻接矩阵
    • 2.2 邻接表
    • 2.3 邻接矩阵的实现
    • 2.4 邻接表的实现
  • 三、总结

一、图的基本概念

  • 图(Graph)是由顶点的有穷非空集合和顶点之间边的集合组成,通常表示为:G(V,E),其中,G表示一个图,V是顶点的集合,E是边的集合
  • 在图中数据元素,我们则称之为顶点(Vertex)。
  • 图中,任意两个顶点之间都可能有关系,顶点之间的逻辑关系用边来表示,边集可以是空的。

有上面的定义可以得出树是一个特殊的图,与图的区别是没有环连通。
树关注的是节点(顶点)的值,而图关注的是顶点及边的权值。

  • 图按照有无方向分为无向图有向图。无向图由顶点和边构成,有向图由顶点和弧构成。弧有弧尾和弧头之分。
    -

比方说现在想表示社交关系,那么QQ,微信等就是无向图,抖音微博这种就是有向图(你关注的人不一定关注了你)。

  • 图按照边或弧的多少分稀疏图稠密图。如果任意两个顶点之间都存在边叫完全图,有向的叫有向完全图。若无重复的边或顶点到自身的边则叫简单图。
  • 图中顶点之间有邻接点、依附的概念。无向图顶点的边数叫做,有向图顶点分为入度和出度。
  • 图上的边或弧上带权则称为
  • 一个图包含了另一个图的部分顶点和部分边,就叫做子图
    在这里插入图片描述
  • 图中顶点间存在路径,两顶点存在路径则说明是连通的,如果路径最终回到起始点则称为环(回路),当中不重复叫简单路径若无向图任意两顶点都是连通的,则图就是连通图,有向则称强连通图
  • 生成树在无向图中,一个连通图的最小连通子图称作该图的生成树。有 n 个顶点的连通图的生成树有 n 个顶点和 n-1 条边。
    在这里插入图片描述

二、图的存储结构

一个图的信息包括两部分,即图中顶点的信息以及描述顶点之间的关系 ---- 边或者弧的信息。因此无论采用什么方法建立图的存储结构,都要完整、准确地反映这两个面的信息。下面介绍两种常用的图的存储结构。这篇介绍两个常见的结构:邻接矩阵和邻接表。

2.1 邻接矩阵

因为节点与节点之间的关系就是联通与否,即为 0 或者 1,因此邻接矩阵(二维数组)即是:先用一个数组将定点保存,然后采用矩阵来表示节点与节点之间的关系
在这里插入图片描述
可以看出无向图是对称的,而有向图没有对称关系。
如果边是带权值的且两个顶点不相连,我们可以用INT_MAX或者INT_MIN来表示。

邻接矩阵存储图的优点是能够快速知道图中两个顶点是否连通,缺点是顶点很多且边比较少时,比较浪费空间,并且两个节点之间的路径不好求。若要确定图中有多少条边,需要遍历一遍邻接矩阵,空间复杂度为 O(N^2) 。这是用邻接矩阵来存储图的局限性。

所以邻接矩阵适合存稠密图,适合查找两个顶点是否相连

2.2 邻接表

邻接表:使用数组表示顶点的集合,使用链表表示边的关系。

用数组保存顶点,用链表保存连通的顶点。
在这里插入图片描述
邻接表适合存稀疏图,适合查找一个顶点连出去的边

2.3 邻接矩阵的实现

邻接矩阵有以下的模板参数:

template <class V, class W, W MAX = INT_MAX, bool DIR = false>

V - 顶点,W - 权值,MAX - 最大值(默认参数给整形的最大值),DIR - 表示图是否有方向。

template <class V, class W, W MAX = INT_MAX, bool DIR = false>
class Graph
{
public:
private:
	vector<V> _vertexs;// 顶点集合
	unordered_map<V, int> _idxMap;// 顶点映射下标
	vector<vector<W>> _matrix;// 邻接矩阵
};

构造函数
我们传进一个数组和一个size_t型数据,数组里面存放顶点,数据表示数组的大小。
在内部我们首先要把每个顶点存储起来,并初始化邻接矩阵,把权值全部初始化成MAX代表不相连。

Graph(const V* a, size_t n)
{
	_vertexs.reserve(n);
	for (size_t i = 0; i < n; i++)
	{
		_vertexs.push_back(a[i]);// 将传入数组的值存储到vector中
		_idxMap[a[i]] = i;// 让数组中的每一个数据映射一个下标
	}

	_matrix.resize(n);
	for (size_t i = 0; i < n; i++)
	{
		_matrix[i].resize(n, MAX);
	}
}

添加边

首先要获取两个顶点的下标,然后还要判断是有向图还是无向图,无向图要添加两次。

// 获取顶点下标
size_t GetIdx(const V& v)
{
	auto it = _idxMap.find(v);
	if (it == _idxMap.end())
	{
		assert(false);
		return -1;
	}
	return it->second;
}

void addEdge(const V& src, const V& dst, const W& w)
{
	size_t si = GetIdx(src);
	size_t di = GetIdx(dst);
	_matrix[si][di] = w;
	if (DIR == false)
	{
		_matrix[di][si] = w;
	}
}

打印观察

void Print()
{
	// 打印矩阵横坐标
	cout << "  ";
	for (size_t i = 0; i < _vertexs.size(); ++i)
	{
		printf("%5d", i);
	}
	cout << endl;

	// 打印矩阵
	for (size_t i = 0; i < _matrix.size(); ++i)
	{
		cout << i << " "; // 打印矩阵纵坐标
		for (size_t j = 0; j < _matrix[i].size(); ++j)
		{
			if (_matrix[i][j] == MAX)
				printf("%5c", '*');
			else
				printf("%5d", _matrix[i][j]);
		}
		cout << endl;
	}
}

整体代码

template <class V, class W, W MAX = INT_MAX, bool DIR = false>
class Graph
{
public:
	Graph(const V* a, size_t n)
	{
		_vertexs.reserve(n);
		for (size_t i = 0; i < n; i++)
		{
			_vertexs.push_back(a[i]);// 将传入数组的值存储到vector中
			_idxMap[a[i]] = i;// 让数组中的每一个数据映射一个下标
		}

		_matrix.resize(n);
		for (size_t i = 0; i < n; i++)
		{
			_matrix[i].resize(n, MAX);
		}
	}

	// 获取顶点下标
	size_t GetIdx(const V& v)
	{
		auto it = _idxMap.find(v);
		if (it == _idxMap.end())
		{
			assert(false);
			return -1;
		}
		return it->second;
	}

	void addEdge(const V& src, const V& dst, const W& w)
	{
		size_t si = GetIdx(src);
		size_t di = GetIdx(dst);
		_matrix[si][di] = w;
		if (DIR == false)
		{
			_matrix[di][si] = w;
		}
	}
	void Print()
	{
		// 打印顶点和下标间的映射关系
		for (size_t i = 0; i < _vertexs.size(); ++i)
		{
			cout << "[" << i << "]" << "->" << _vertexs[i] << endl;
		}
		cout << endl;

		// 打印矩阵横坐标
		cout << "  ";
		for (size_t i = 0; i < _vertexs.size(); ++i)
		{
			printf("%5d", i);
		}
		cout << endl;

		// 打印矩阵
		for (size_t i = 0; i < _matrix.size(); ++i)
		{
			cout << i << " "; // 打印矩阵纵坐标
			for (size_t j = 0; j < _matrix[i].size(); ++j)
			{
				if (_matrix[i][j] == MAX)
					printf("%5c", '*');
				else
					printf("%5d", _matrix[i][j]);
			}
			cout << endl;
		}
	}

private:
	vector<V> _vertexs;// 顶点集合
	unordered_map<V, int> _idxMap;// 顶点映射下标
	vector<vector<W>> _matrix;// 邻接矩阵
};

void TestGraph()
{
	Graph<char, int, INT_MAX, false> g("ABCDE", 5);
	g.addEdge('A', 'B', 1);
	g.addEdge('B', 'D', 4);
	g.addEdge('A', 'D', 2);
	g.addEdge('B', 'C', 9);
	g.addEdge('A', 'C', 8);
	g.addEdge('E', 'A', 5);
	g.addEdge('A', 'E', 3);
	g.addEdge('C', 'D', 6);
	g.Print();
}

在这里插入图片描述

2.4 邻接表的实现

邻接表里面存的是边,所以我们要设计一个边的类。

template <class W>
struct Edge
{
	Edge(int dsti, const W& w)
		: _dsti(dsti)
		, _w(w)
		, _next(nullptr)
	{}
	int _dsti;
	W _w;// 权值
	Edge<W>* _next;
};

当要加入一个边的时候,直接头插即可。
其他的和邻接矩阵同理。

template <class W>
struct Edge
{
	Edge(int dsti, const W& w)
		: _dsti(dsti)
		, _w(w)
		, _next(nullptr)
	{}
	int _dsti;
	W _w;// 权值
	Edge<W>* _next;
};

template <class V, class W, bool DIR = false>
class Graph
{
public:
	Graph(const V* a, size_t n)
	{
		_vertexs.reserve(n);
		for (size_t i = 0; i < n; i++)
		{
			_vertexs.push_back(a[i]);// 将传入数组的值存储到vector中
			_idxMap[a[i]] = i;// 让数组中的每一个数据映射一个下标
		}
		_tables.resize(n, nullptr);
	}

	// 获取顶点下标
	size_t GetIdx(const V& v)
	{
		auto it = _idxMap.find(v);
		if (it == _idxMap.end())
		{
			assert(false);
			return -1;
		}
		return it->second;
	}

	void addEdge(const V& src, const V& dst, const W& w)
	{
		size_t si = GetIdx(src);
		size_t di = GetIdx(dst);
		Edge<W>* eg = new Edge<W>(di, w);
		eg->_next = _tables[si];
		_tables[si] = eg;
		if (DIR == false)
		{
			Edge<W>* eg = new Edge<W>(si, w);
			eg->_next = _tables[di];
			_tables[di] = eg;
		}
	}
	void Print()
	{
		// 打印顶点和下标间的映射关系
		for (size_t i = 0; i < _vertexs.size(); ++i)
		{
			cout << "[" << i << "]" << "->" << _vertexs[i] << endl;
		}
		cout << endl;

		for (size_t i = 0; i < _tables.size(); ++i)
		{
			// 遍历当前链表,并打印链表结点中的相关信息
			cout << _vertexs[i] << "[" << i << "]->";
			Edge<W>* cur = _tables[i];
			while (cur)
			{
				cout << "[" << _vertexs[cur->_dsti] << ":" << cur->_dsti << ":" << cur->_w << "]->";
				cur = cur->_next;
			}
			cout << "nullptr" << endl;
		}
	}

private:
	vector<V> _vertexs;// 顶点集合
	unordered_map<V, int> _idxMap;// 顶点映射下标
	vector<Edge<W>*> _tables;// 邻接表
};

void TestGraph()
{
	Graph<char, int, true> g("ABCDE", 5);
	g.addEdge('A', 'B', 1);
	g.addEdge('B', 'D', 4);
	g.addEdge('A', 'D', 2);
	g.addEdge('B', 'C', 9);
	g.addEdge('A', 'C', 8);
	g.addEdge('E', 'A', 5);
	g.addEdge('A', 'E', 3);
	g.addEdge('C', 'D', 6);
	g.Print();
}

在这里插入图片描述

三、总结

根据邻接表和邻接矩阵的结构特性可知,当图为稀疏图、顶点较多,即图结构比较大时,更适宜选择邻接表作为存储结构。当图为稠密图、顶点较少时,或者不需要记录图中边的权值时,使用邻接矩阵作为存储结构较为合适。
邻接表和邻接矩阵相辅相成,各有优缺点,是互补的。



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

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

相关文章

【服务器数据恢复】多块磁盘离线导致RAID5崩溃的数据恢复案例

服务器数据恢复环境&故障&#xff1a; 某品牌StorageWorks存储设备&#xff0c;8块磁盘组建一组raid5磁盘阵列。存储中2块磁盘掉线导致阵列崩溃&#xff0c;经过检查发现掉线的2块磁盘均存在物理故障。 服务器数据恢复过程&#xff1a; 1、硬件工程师对掉线的两块磁盘进行…

性能测试-压力测试如何快速上手?8年资深测试总结整理,永不背锅...

目录&#xff1a;导读 前言一、Python编程入门到精通二、接口自动化项目实战三、Web自动化项目实战四、App自动化项目实战五、一线大厂简历六、测试开发DevOps体系七、常用自动化测试工具八、JMeter性能测试九、总结&#xff08;尾部小惊喜&#xff09; 前言 一般我们在刚介入…

mulesoft MCIA 破釜沉舟备考 2023.05.04.30(易错题)

mulesoft MCIA 破釜沉舟备考 2023.05.04.30(易错题) 1. According to MuleSoft, which major benefit does a Center for Enablement (C4E) provide for an enterprise and its lines of business?2. An organization is choosing between API-led connectivity and other i…

ASEMI代理ADM3251EARWZ-REEL原装ADI车规级ADM3251EARWZ-REEL

编辑&#xff1a;ll ASEMI代理ADM3251EARWZ-REEL原装ADI车规级ADM3251EARWZ-REEL 型号&#xff1a;ADM3251EARWZ-REEL 品牌&#xff1a;ADI/亚德诺 封装&#xff1a;SOIC-20-300mil 批号&#xff1a;2023 引脚数量&#xff1a;20 工作温度&#xff1a;-40C~85C 安装类型…

vim常用命令总结

vim常用命令总结 &#xff08;转) 在命令状态下对当前行用 &#xff08;连按两次&#xff09;, 或对多行用n&#xff08;n是自然数&#xff09;表示自动缩进从当前行起的下面n行。你可以试试把代码缩进任意打乱再用n排版&#xff0c;相当于一般IDE里的code format。使用ggG可对…

如何在Windows AD域中驻留ACL后门

前言 当拿下域控权限时&#xff0c;为了维持权限&#xff0c;常常需要驻留一些后门&#xff0c;从而达到长期控制的目的。Windows AD域后门五花八门&#xff0c;除了常规的的添加隐藏用户、启动项、计划任务、抓取登录时的密码&#xff0c;还有一些基于ACL的后门。 ACL介绍 …

X3派 部署pytorch yolov5 demo

一、配置环境 我的pytorch之前已配置了&#xff0c;参照链接&#xff1a; 安装anconda配置pytorch 查看环境conda env list 激活环境 conda activate yolov5_py3.10 安装onnx&#xff1a;pip install -i https://pypi.tuna.tsinghua.edu.cn/simple onnx 安装yolov5需要的包…

小程序中使用CANVAS实现手写签名并写入模板图片中

实测&#xff0c;开发者工具中滚动条位置会影响书写&#xff0c;显示会有些问题&#xff0c;手机上测试正常 index.js const App getApp();Page({/*** 页面的初始数据*/data: {curScrollTop : 0},/*** 生命周期函数--监听页面加载*/onLoad(options) {},/*** 生命周期函数--监…

【软考高项笔记】第2章 信息技术发展2.1 信息技术及其发展

2.1 信息技术及其发展 获取信息、处理信息、传输信息、使用信息硬技术&#xff08;物化技术&#xff09;传感器&#xff0c;服务器&#xff0c;手机&#xff0c;软技术&#xff08;非物化&#xff09;数据分析&#xff0c;规划决策2.1.1 计算机软硬件 硬件 物理装置 &#xff…

知识管理在企业中的重要性

随着经济全球化和信息化的快速发展&#xff0c;企业面临着越来越多的竞争和挑战。如何把握市场动态、满足客户需求、提高产品质量和效率等&#xff0c;成为了企业发展中亟待解决的问题。而知识管理作为一种新兴的管理方式&#xff0c;逐渐引起了企业们的重视。本文将从以下几个…

go-zero

目录 引入开发派系标准库/自研派系——不要让框架束缚开发web框架派系——gingrpc大一统框架 go-zerogo-zero快速实现一个微服务user serviceorder api server启动 goctl安装生成的api网关目录生成的pb目录api语法syntaximport语法块infotypeservice注释 命令大全 引入 该图片来…

图论专题(各类算法和蓝桥杯真题例题)

1.图论入门 1.1存边方式 1.1.1 数组存边 1.1.2 临接矩阵存边 1.1.3 临接表存边 1.2 图的遍历和连通性 通过DFS和BFS遍历每一个图 对于非连通图&#xff0c;循环对每一个点dfs操作 也可以通过并查集来判断连通性 1.2.1全球变暖例题 import sys sys.setrecursionlimit(6000…

【目标检测】Haar-like特征检测简介

文章目录 一、背景模板匹配&#xff08;template matching&#xff09;关键点检测角点检测 二、harris特征提取原理Harris Detector 的具体流程&#xff1a;harris特征的可复用性旋转尺度 scale亮度 illuminationview point 三、Viola Jones检测原理Harr-like特征提取积分图训练…

QT教程demo之串口助手代码设计实现

关注WeChat Official Account 南山府嵌入式获取更多精彩 我创建了一个群关注V号后加入。因为这里不允许添加二维码 代码&#xff1a;QT_Pr 1-QT开发串口助手需要的基本文件 在QT6开发串口助手时&#xff0c;通常需要以下头文件&#xff1a; #include <QSerialPort> #i…

TCP协议策略

TCP可靠性 基于序号的确认应答(ACK)机制 TCP保证可靠性最核心的机制就是基于序号的确认应答机制。 TCP并不是百分之百可靠的&#xff0c;但是只要一条消息有应答&#xff0c;那么我们就可以确定该消息100%被对方收到了&#xff0c;这就是确认应答的意义。 可靠性不仅仅是保…

MOSS模型量化版部署过程

文章目录 项目背景配置环境与准备部署推理命令行部署报错1报错2&#xff1a; 网页版部署 项目背景 2023年4月21日&#xff0c;复旦大学自然语言处理实验室正式开放MOSS模型&#xff0c;是国内首个插件增强的开源对话大语言模型。MOSS 相关代码、数据、模型参数已在 GitHub 和 …

【键入网址到网页显示】

HTTP 对 URL 进行解析之后&#xff0c;浏览器确定了 Web 服务器和文件名&#xff0c;接下来就是根据这些信息来生成 HTTP 请求消息了。 http://www.server.com/dir1/file1.html http:访问数据的协议 www.server.com:服务器 dir1:目录名 file1.html:文件名生产 HTTP 请求信息…

SpringCloud-10_Alibaba Nacos

SpringCloud系列 SpringCloud-9、SleuthZipkin SpringCloud-8、Gateway网关服务 SpringCloud-7_OpenFeign服务调用 SpringCloud-6_Ribbon负载均衡 SpringCloud-5_模块集群化 文章目录 SpringCloud系列Nacos基础Nacos是什么&#xff1f;Nacos下载&运行 创建Nacos服务提供者…

JavaScript:哈希表

文章目录 哈希表242. 有效的字母异位词思路补充&#xff1a;JavaScript String charCodeAt() 方法代码详细分析 349. 两个数组的交集代码分析补充&#xff1a;JavaScript Set 对象思考一下哈希是什么&#xff1f;什么时候使用&#xff1f;补充&#xff1a;js 数组 map() 基本用…

github使用workflow工作流git push后自动打包部署github pages

workflows介绍 根目录新建.github/workflows/docs.yml .github/workflows/ 目录是用于存放 GitHub Actions 工作流程文件的目录&#xff0c;该目录的文件名必须以 .yml 或 .yaml 为后缀名&#xff0c;否则 GitHub 将无法识别该文件为工作流程文件。这些工作流程文件可用于自动…