【数据结构】图的存储(邻接矩阵与邻接表)

article/2025/6/18 2:51:19

图的存储结构

因为图中既有节点,又有边(节点与节点之间的关系),因此,在图的存储中,只需要保存:节点和边关系即可。

节点保存比较简单,只需要一段连续空间即可,那边关系该怎么保存呢?

1. 邻接矩阵

第一种存储结构---->邻接矩阵

邻接矩阵如何保存图的顶点和边呢?

因为节点与节点之间的关系就是连通与否,即为0或者1,因此邻接矩阵(二维数组)即是:

先用一个数组将顶点保存,然后采用矩阵来表示节点与节点之间的关系(边)

比如:

值为1就表示对应的这两个顶点是连通的,为0就表示两个顶点不连通

那其实观察上面的图我们可以发现:

无向图的邻接矩阵是对称的,第i行(列)元素之和,就是顶点i的度(边没有权值,只存0/1的情况下,元素和就是度)

有向图的邻接矩阵则不一定是对称的,第i行(列)元素之和就是顶点i 的出(入)度(边没有权值,只存0/1的情况下)

另外呢:

1. 如果边带有权值,并且两个节点之间是连通的,上图中的边的关系(1/0)就用权值代替,如果两个顶点不通,可以使用无穷大代替(后面我们实现的时候就要增加一个表示无穷大的模板参数)

2. 用邻接矩阵存储图的优点是能够快速知道两个顶点是否连通,取到权值

3. 缺陷是如果顶点比较多,边比较少时,矩阵中存储了大量的0成为系数矩阵,比较浪费空间;所以邻接矩阵比较适合存储稠密图(边比较多的图),不适合存储稀疏图(边比较少的图) 而且要求两个节点之间的路径不好求; 还有求一个顶点相连的顶点有哪些也不好求(O(N),这个用后面的邻接表结构存储的话就很好求)。

 在写邻接矩阵存储的代码时遇到了一个值得学习的bug,下面的bug怎么修改

template<class V, class W, W MAX_W = INT_MAX, bool Direction>
//                          参数3有默认值 ↗      ↗ 参数4无默认值

参数 3 (MAX_W) 有默认值,但参数 4 (Direction) 没有。这违反了 C++ 标准。

所以我们把bool Direction移到 这个  W MAX_W = INT_MAX前面就好了,或者添加在后面添加一下默认参数。

接下来我实现一下,下图中这个例子: 

namespace Adjacency_matrix
{// V接受顶点(vertex)的类型,W接受权值的类型,Direction(false无向图,true有向图)template<class V, class W, bool Direction, W MAX_W = INT_MAX>// 有权值的情况下不连通的边权值存INT_MAXclass Graph{public:// "0123" 4 Graph(const V* ver, size_t n){// 存储顶点与下标建立映射_vertexs.reserve(n);for (int i = 0; i < n; i++){_vertexs.push_back(ver[i]);_indexMap[ver[i]] = i;}//给邻接矩阵开空间_matrix.resize(n); // 外向量n大小for (auto& e : _matrix){e.resize(n, MAX_W); // 内向量n大小}}size_t GetVertexIndex(const V& v){auto it = _indexMap.find(v);if (it != _indexMap.end()){return it->second;}else{cout << "顶点不存在" << endl;return -1;}}void AddEdge(const V& src, const V& dst, const W& w) // src起始顶点,dst终止顶点,w权值{size_t srci = GetVertexIndex(src);size_t dsti = GetVertexIndex(dst);// 添加一条边为A->B,B->A_matrix[srci][dsti] = w;if (Direction == false) // 无向图{_matrix[dsti][srci] = w;}}void DellEdge(const V& src, const V& dst){size_t srci = GetVertexIndex(src);size_t dsti = GetVertexIndex(dst);// 删除一条边为A->B,B->A;_matrix[srci][dsti] = 0;if (Direction == false) // 无向图{_matrix[dsti][srci] = 0;}}void Print(){// 打印顶点和下标映射关系for (size_t i = 0; i < _vertexs.size(); ++i){cout << _vertexs[i] << "--" << i << "  ";}cout << endl << 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_W)cout << _matrix[i][j] << " ";elsecout << "#" << " ";}cout << endl;}cout << endl << endl;}private:vector<V> _vertexs; // 顶点集合vector<vector<W>> _matrix;//存储边的矩阵map<V, int> _indexMap; //顶点和下标建立映射};void test(){Graph<char, int, false, INT_MAX> g("ABCD", 4);g.AddEdge('A', 'B', 1);g.AddEdge('B', 'C', 2);g.AddEdge('C', 'D', 3);g.AddEdge('D', 'A', 4);g.DellEdge('D', 'A');g.Print();}
}

结果展示: 

 

 2. 邻接表 

使用数组存储顶点的集合,使用链表存储顶点的关系(边)。

比如

无向图邻接表存储

一个顶点与哪些顶点相连,相连的顶点就存到这个顶点对应的链表中,当然如果带权的话也要存上对应边的权值。 (每个顶点都有一个对应的链表,多条链表用一个指针数组就可以维护起来)

注意:无向图中同一条边在邻接表中出现了两次。如果想知道顶点vi的度,只需要知道顶点vi 对应链表集合中结点的数目即可

有向图邻接表存储:

那通过上面的了解其实我们可以得出,对于邻接表的存储方式 

1. 适合存储稀疏图(边比较少的图),因为邻接表的话有多少边链表里面就存几个对应的顶点,不需要额外的空间;而上面邻接矩阵不论边多边少都要开一个N*N的矩阵(二维数组),边少的时候那就大部分位置都存的是0

2. 方便查找从一个顶点连接出去的边有哪些,因为它对应的边链表里面存的就是与这个顶点相连的顶点

3. 但是不方便确定两个顶点是否相连和获取权值(要遍历其中一个顶点的边链表查找O(N))

代码实现方式: 

namespace link_table
{template<class W>struct Edge{size_t _dsti; // 边的终止顶点下标W _w;	   // 权值Edge<W>* _next;Edge(const size_t& dsti, const W& w):_dsti(dsti),_w(w),_next(nullptr){}};// V接受顶点(vertex)的类型,W接受权值的类型,Direction(false无向图,true有向图)template<class V, class W, bool Direction = false>class Graph{typedef Edge<W> Edge;public:// "0123"  4Graph(const V* ver, size_t n){//存储顶点并与下标建立映射_vertexs.reserve(n);for (size_t i = 0; i < n; i++){_vertexs.push_back(ver[i]);_indexMap[ver[i]] = i;}// 给邻接表开空间_tables.resize(n, nullptr);}~Graph(){for (size_t i = 0; i < _tables.size(); i++){if (_tables[i]){Edge* cur = _tables[i];Edge* next = cur;while (cur){next = cur->_next;delete cur;cur = next;}}}}size_t GetVertexIndex(const V& v){auto it = _indexMap.find(v);if (it != _indexMap.end()){return it->second;}else{cout << "顶点不存在" << endl;return -1;}}void AddEdge(const V& src, const V& dst, const W& w){size_t srci = GetVertexIndex(src);size_t dsti = GetVertexIndex(dst);// src -> dst  A -> BEdge* eg = new Edge(dsti, w);eg->_next = _tables[srci];_tables[srci] = eg;// 如果是无向图,再添加反向边dst->srcif (Direction == false){Edge* eg = new Edge(srci, w);eg->_next = _tables[dsti];_tables[dsti] = eg;}}void DelEdge(const V& src, const V& dst){del(src, dst);// 如果是无向图,再添加反向边dst->srcif (Direction == false){del(dst, src);}}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* cur = _tables[i];while (cur){cout << "[" << _vertexs[cur->_dsti] << ":" << cur->_dsti << ":" << cur->_w << "]->";cur = cur->_next;}cout << "nullptr" << endl;}}private:void del(const V& src, const V& dst){size_t srci = GetVertexIndex(src);size_t dsti = GetVertexIndex(dst);// A -> B -> C 删除A->B// 1.先判断这个边在不在,如果在就删除,不在就返回Edge* cur = _tables[srci];Edge* per = cur;while (cur){// 如果找到了if (cur->_dsti == dsti){// 如果在头if (per == cur){_tables[srci] = per->_next;delete cur;break;}else{// 如果在尾和中间per->_next = cur->_next;delete cur;break;}}// 如果没找到per = cur;cur = cur->_next;}}private:vector<V> _vertexs;		// 顶点集合map<V, size_t> _indexMap;	// 顶点和下标建立映射vector<Edge*> _tables;	// 邻接表};void Test2(){string a[] = {"张三","李四","王五","赵六"};Graph<string, size_t> g1(a, 4);g1.AddEdge("张三", "李四", 100);g1.AddEdge("张三", "王五", 100);g1.AddEdge("张三", "赵六", 100);g1.AddEdge("王五", "赵六", 100);g1.Print();}
}

结果展示: 

 


http://www.hkcw.cn/article/hPxXaVMSVD.shtml

相关文章

C++修炼:unordered_map和unordered_set的使用和封装

Hello大家好&#xff01;很高兴我们又见面啦&#xff01;给生活添点passion&#xff0c;开始今天的编程之路&#xff01; 我的博客&#xff1a;<但凡. 我的专栏&#xff1a;《编程之路》、《数据结构与算法之美》、《题海拾贝》、《C修炼之路》 欢迎点赞&#xff0c;关注&am…

Centos环境下安装/重装MySQL完整教程

目录 一、卸载残留的MySQL环境&#xff1a; 二、安装MySQL&#xff1a; 1、下载MySQL官方的yum源&#xff1a; 2、更新系统yum源&#xff1a; 3、确保系统中有了对应的MySQL安装包&#xff1a; 4、安装MySQL服务&#xff1a; 5、密钥问题安装失败解决方法&#xff1a; …

【机器学习基础】机器学习入门核心算法:决策树(Decision Tree)

机器学习入门核心算法&#xff1a;决策树&#xff08;Decision Tree&#xff09; 一、算法逻辑1.1 基本概念1.2 算法流程 二、算法原理与数学推导2.1 特征选择指标信息熵&#xff08;ID3算法&#xff09;信息增益&#xff08;Information Gain&#xff09;信息增益率&#xff0…

基于晶体塑性有限元(CPFEM)的钛合金圆棒拉伸过程模拟

作者&#xff1a;辞殇 关键词&#xff1a;CPFEM&#xff1b;钛合金&#xff1b;单轴拉伸&#xff1b;织构极图&#xff1b;孪晶 晶体塑性有限元是一种结合了晶体塑性理论和有限元方法的数值模拟技术‌。这种方法考虑了晶体材料的各向异性、滑移系统的开动和相互作用、以及变形…

开源是什么?我们为什么要开源?

本片为故事类文章推荐听音频哦 软件自由运动的背景 梦开始的地方 20世纪70年代&#xff0c;软件行业处于早期发展阶段&#xff0c;软件通常与硬件捆绑销售&#xff0c;用户对软件的使用、修改和分发权利非常有限。随着计算机技术的发展和互联网的普及&#xff0c;越来越多的开…

帕金森带来的生活困境

当这种健康状况出现&#xff0c;行动不再自如成为最明显的改变。日常行走时&#xff0c;步伐会逐渐变小、变慢&#xff0c;甚至会出现 “小碎步” 往前冲&#xff0c;难以停下&#xff0c;简单的起身、转身都可能变得艰难。手部也会不受控制地颤抖&#xff0c;拿水杯、系纽扣这…

第3期:PCB设计教程:自动布线与导出制版文件详解

第3期&#xff1a;PCB设计教程&#xff1a;自动布线与导出制版文件详解 一、前言 本篇教程主要聚焦于PCB设计中的自动布线功能及文件导出步骤。通过本教程&#xff0c;您将学习如何&#xff1a; 使用自动布线工具高效完成线路连接处理自动布线失败的情况进行DRC检查确保设计…

NACOS 动态配置

1.引入Nacos 配置中心依赖 <!-- nacso 配置中心--><dependency><groupId>com.alibaba.cloud</groupId><artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId></dependency> 2.在application.properties 配置…

【清晰教程】查看和修改Git配置情况

目录 查看安装版本 查看特定配置 查看全局配置 查看本地仓库配置 设置或修改配置 查看安装版本 打开命令行工具&#xff0c;通过version命令检查Git版本号。 git --version 如果显示出 Git 的版本号&#xff0c;说明 Git 已经成功安装。 查看特定配置 如果想要查看特定…

C语言 — 动态内存管理

目录 1.malloc和free函数1.1 malloc函数1.2 free函数1.3 malloc函数的使用 2.calloc函数2.1 calloc函数2.2 calloc函数的使用 3.realloc函数3.1 realloc函数3.2 realloc函数的使用 4.动态内存管理笔试题4.1 笔试题&#xff08;1&#xff09;4.2 笔试题&#xff08;2&#xff09…

动态规划算法

简称 DP&#xff0c;是一种求解多阶段决策过程最优化问题的方法。在动态规划中&#xff0c;通过把原问题分解为相对简单的子问题&#xff0c;先求解子问题&#xff0c;再由子问题的解而得到原问题的解。 一、概念 动态规划最早由理查德 贝尔曼于 1957 年在其著作「动态规划&…

Qt -使用OpenCV得到SDF

博客主页&#xff1a;【夜泉_ly】 本文专栏&#xff1a;【暂无】 欢迎点赞&#x1f44d;收藏⭐关注❤️ 目录 cv::MatdistanceTransform获得SDF 本文的目标&#xff0c; 是简单学习并使用OpenCV的相关函数&#xff0c; 并获得QImage的SDF(Signed Distance Field 有向距离场) 至…

【小米拥抱AI】小米开源 MiMo-7B-RL-0530

更新日志 [2025.05.30] 在强化学习训练过程中&#xff0c;通过持续扩大训练窗口尺寸&#xff08;从32K提升至48K&#xff09;&#xff0c;MiMo-7B-RL-0530模型在AIME24基准测试上的表现持续提升&#xff0c;最终超越DeepSeek R1模型的性能水平。 BenchmarkMiMo-7B-RLMiMo-7B-…

俄布良斯克州桥梁坍塌致列车脱轨事故造成3死28伤

△图片来源:莫斯科交通检察院总台记者当地时间6月1日获悉,据俄罗斯紧急情况部初步统计,布良斯克州桥梁坍塌致火车脱轨事故共造成31人伤亡,其中3人不幸遇难,28人已送往医疗机构救治。此前据俄罗斯BAZA网站报道,事件造成4人死亡,至少44人受伤。俄紧急情况部称,救援人员正…

JDK17 与JDK8 共同存在一个电脑上

官网下载JDK17 官网链接 &#xff1a;https://www.oracle.com/java/technologies/downloads/#java17-windows 下载这个 安装 环境变量设置 因为之前设置过JDK 8这里为了使 两者共存&#xff0c;采用设置变量方式来实现具体操作如下 1、进入高级系统环境设置 1.1先建一个关…

打开NRODIC SDK编译不过怎么处理,keil与segger studio

打开NRODIC SDK编译不过怎么处理,以下是keil处理. 1,如图,不要安装安装也不会过 2. 不要安装点击否 3.点击确定后进来这个样子 4.这里选择这个勾,OK后就不会再有后面的pack_license 5.去掉勾后这里要选择自己SDK对应的pack版本,我的是8.27.0 6.OK后弹出个界面也要反复选择…

每日八股文5.31

每日八股-5.31 Go1.切片是值传递还是引用传递&#xff1f;2.切片的深拷贝与浅拷贝3.切片的底层实现4.切片的扩容机制5.Map是线程安全的吗&#xff1f;6.哪些类型可以作为map的key&#xff1f;7.Map删除一个key内存是否会释放&#xff1f;8.Map为什么是无序的&#xff1f;9.如何…

智能重塑连接:AI原生互联网的范式革命与未来十年

引言:互联网的下一幕——智能涌现与体验重塑 2024年初,OpenAI发布的文生视频模型Sora,以其惊人的逼真度和对物理世界的理解能力,再次将人工智能的魔力推向了全球聚光灯下。这不仅仅是一个技术演示,更像是一个强烈的信号:我们正加速驶向一个由AI深度重塑的未来。回望互联…

【深度学习相关安装及配环境】Anaconda搭建虚拟环境并安装CUDA、cuDVV和对应版本的Pytorch,并在jupyter notebook上部署

目录 1. 查看自己电脑的cuda版本2.安装cuda关于环境变量的配置测试一下&#xff0c;安装完成 3.安装cuDVV环境变量的配置测试一下&#xff0c;安装完成 4.创建虚拟环境先安装镜像源下载3.11版本py 5.在虚拟环境下&#xff0c;下载pytorch6.验证是否安装成功7.在jupyter noteboo…

2. 手写数字预测 gui版

2. 手写数字预测 gui版 背景1.界面绘制2.处理图片3. 加载模型4. 预测5.结果6.一点小问题 背景 做了手写数字预测的模型&#xff0c;但是老是跑模型太无聊了&#xff0c;就配合pyqt做了一个可视化界面出来玩一下 源代码可以去这里https://github.com/Leezed525/pytorch_toy拿 …