【数据结构】图的概念和存储结构



快乐的流畅:个人主页


个人专栏:《C游记》《进击的C++》《Linux迷航》

远方有一堆篝火,在为久候之人燃烧!

文章目录

  • 引言
  • 一、图的概念
  • 二、图的存储结构
    • 2.1 邻接矩阵
      • 2.1.1 成员变量与默认成员函数
      • 2.1.2 GetIndex
      • 2.1.3 AddEdge
      • 2.1.4 Print
    • 2.2 邻接表
      • 2.2.1 结点
      • 2.2.2 成员变量与默认成员函数
      • 2.2.3 GetIndex
      • 2.2.4 AddEdge
      • 2.2.5 Print
  • 总结

引言

数据结构世界——图(Graph)

一、图的概念

图是一种非线性结构,由顶点(vertex)和边(edge)组成。


有向图(directed graph)与无向图(undirected graph):在有向图中,<x,y>和<y,x>是两条不同的有向边;在无向图中,(x,y)和(y,x)指的是同一条边。

完全图(complete graph):在无向图中,任意两点有边相连,则为无向完全图;在有向图中,任意两点有两条方向相反的边相连,则为有向完全图。

度(degree):与顶点关联的边数。在有向图中,度 = 入度 + 出度;在无向图中,度 = 入度 = 出度。

权(weight):边具有的属性。带权图又称为网络(network)。

路径长度:在无权图中,路径长度是指此路径上边的条数;在有权图中,路径长度是指此路径上边的权值之和。

简单路径与回路(cycle):一条路径上各顶点均不重复,则为简单路径;一条路径上首尾顶点重合,则为回路或环。

子图(subgraph):一个图的顶点集和边集都属于另一个图,那么这个图便称为另一个图的子图。


连通图(connected graph)与连通分量(connected component):连通图是一种无向图,要求任意两点都有路径可达。连通分量是非连通图的极大连通子图。

强连通图与强连通分量:强连通图是一种有向图,要求任意两点都有双向路径可达。强连通分量是非强连通图的极大连通子图。

生成树(spanning tree):连通图的极小连通子图称为该图的生成树。

二、图的存储结构

图由顶点和边构成,而顶点用数组存储即可,唯一值得讨论的便是边的存储方式。以下介绍两种最常见的边存储方式。

2.1 邻接矩阵

2.1.1 成员变量与默认成员函数

template<class V, class W, W W_MAX = INT_MAX, bool Direction = false>
class Graph
{
public:Graph(){}Graph(const V* v, int n){_vertexs.reserve(n);for (int i = 0; i < n; ++i){_vertexs.push_back(v[i]);_indexMap[v[i]] = i;}_edges.resize(n, vector<W>(n, W_MAX));}
private:vector<V> _vertexs;map<V, int> _indexMap;vector<vector<W>> _edges;
};

细节:

  1. V代表顶点类型,W代表权值类型,W_MAX代表权值的正无穷,Direction代表图是否有向。
  2. _vertexs存储顶点,_indexMap存储顶点与下标的映射,_edges存储每两个顶点所对应边的权值。

图的创建方式:
1、IO输入——在线oj常用
2、文件写入
3、手动添加边——便于调试

2.1.2 GetIndex

int GetIndex(const V& v)
{auto it = _indexMap.find(v);if (it != _indexMap.end()){return it->second;}else{throw invalid_argument("顶点不存在");return -1;}
}

细节:获取下标额外设计一个函数,防止传入不存在的顶点,增强程序的健壮性。

2.1.3 AddEdge

void _AddEdge(int srci, int dsti, const W& w)
{_edges[srci][dsti] = w;//若为无向图if (!Direction){_edges[dsti][srci] = w;}
}void AddEdge(const V& src, const V& dst, const W& w)
{int srci = GetIndex(src);int dsti = GetIndex(dst);_AddEdge(srci, dsti, w);
}

细节:

  1. 添加边,便是在矩阵对应位置赋上权值,若为无向图则反方向也要添加。
  2. 这里拆出一个子函数,是方便后续直接通过顶点下标进行添加边。

2.1.4 Print

void Print()
{for (int i = 0; i < _vertexs.size(); ++i){cout << "[" << i << "]" << ":" << _vertexs[i] << endl;}cout << endl;for (int i = 0; i < _edges.size(); ++i){for (int j = 0; j < _edges[0].size(); ++j){if (_edges[i][j] == W_MAX){printf("%4c", '*');}else{printf("%4d", _edges[i][j]);}}cout << endl;}
}

细节:为了美观性,将W_MAX表示为*,同时用printf进行对齐控制。

2.2 邻接表

2.2.1 结点

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

细节:

  1. _dsti表示目标点的下标,_w表示到达目标点的边的权值。
  2. 目标点是与当前点直接相连的。

2.2.2 成员变量与默认成员函数

template<class V, class W, bool Direction = false>
class Graph
{typedef EdgeNode<W> Node;
public:Graph(const V* v, int n){_vertexs.reserve(n);for (int i = 0; i < n; ++i){_vertexs.push_back(v[i]);_indexMap[v[i]] = i;}_edges.resize(n, nullptr);}
private:vector<V> _vertexs;map<V, int> _indexMap;vector<Node*> _edges;
};

细节:

  1. V代表顶点类型,W代表权值类型,Direction代表图是否有向。
  2. _vertexs存储顶点,_indexMap存储顶点与下标的映射,_edges存储每个顶点所连的边的信息。

2.2.3 GetIndex

int GetIndex(const V& v)
{auto it = _indexMap.find(v);if (it != _indexMap.end()){return it->second;}else{throw invalid_argument("顶点不存在");return -1;}
}

细节:获取下标额外设计一个函数,防止传入不存在的顶点,增强程序的健壮性。

2.2.4 AddEdge

void AddEdge(const V& src, const V& dst, const W& w)
{int srci = GetIndex(src);int dsti = GetIndex(dst);Node* node1 = new Node(dsti, w);node1->_next = _edges[srci];_edges[srci] = node1;//若为无向图if (!Direction){Node* node2 = new Node(srci, w);node2->_next = _edges[dsti];_edges[dsti] = node2;}
}

细节:添加边,便是在矩阵对应位置赋上权值,若为无向图则反方向也要添加。

2.2.5 Print

void Print()
{for (int i = 0; i < _vertexs.size(); ++i){cout << "[" << i << "]" << ":" << _vertexs[i] << endl;}cout << endl;for (int i = 0; i < _edges.size(); ++i){cout << "[" << i << "]" << "->";Node* cur = _edges[i];while (cur){cout << cur->_dsti << "->";cur = cur->_next;}cout << "nullptr" << endl;}
}

总结

邻接矩阵:适合处理稠密图,空间换时间

  • 查询边关系非常快速
  • 但空间效率低

邻接表:适合处理稀疏图,空间使用高效

  • 插入删除操作高效
  • 但查询性能相对较慢

真诚点赞,手有余香

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

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

相关文章

【设计模式-外观】

这里写自定义目录标题 定义UML图角色作用代码使用场景 定义 为子系统中一组相关接口提供一致界面&#xff0c;定义一个高级接口&#xff0c;使得子系统更加容易使用。 UML图 角色作用 外观&#xff08;Facade&#xff09;角色&#xff1a;这是外观模式的核心&#xff0c;它知…

pdf去水印怎么去掉免费?6个pdf去除水印的方法快码住,超级好用!

pdf去水印怎么去掉免费&#xff1f;您是否有一些带有水印的pdf文档&#xff0c;让您感觉到头疼&#xff1f;您又是否希望能够去除这些水印&#xff0c;或者想用其他水印来替换现有的水印&#xff1f;如果是这样的话&#xff0c;我非常推荐您继续阅读本篇文章。本文将为您提供一…

openeuler 22.03 lts sp4 使用 kubeadm 部署 k8s-v1.28.2 高可用集群

文章目录 [toc]废话篇这篇文章什么时候写的为什么是 openeuler为什么是 22.03 lts sp4高可用架构题外话 干活篇环境介绍系统初始化相关关闭防火墙关闭 selinux关闭 swap开启内核模块开启模块自动加载服务 sysctl 内核参数调整清空 iptables 规则安装各种依赖和工具修改 .bashrc…

Qt --- 信号和信号槽

前言 Linux信号Signal&#xff0c;系统内部的通知机制&#xff0c;进程间通信方式。 信号源&#xff1a;谁发的信号。 信号的类型&#xff1a;哪种类别的信号。 信号的处理方式&#xff1a;注册信号处理函数&#xff0c;在信号被触发的时候自动调用执行。 Qt中的信号和Lin…

JUC学习笔记(三)

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 八、共享模型之工具--JUC8.1 AQS 原理1. 概述2 实现不可重入锁自定义同步器自定义锁 3.心得起源目标设计1) state 设计2&#xff09;阻塞恢复设计3&#xff09;队列…

计算机毕业设计选题推荐-共享图书管理系统-小程序/App

✨作者主页&#xff1a;IT研究室✨ 个人简介&#xff1a;曾从事计算机专业培训教学&#xff0c;擅长Java、Python、微信小程序、Golang、安卓Android等项目实战。接项目定制开发、代码讲解、答辩教学、文档编写、降重等。 ☑文末获取源码☑ 精彩专栏推荐⬇⬇⬇ Java项目 Python…

【Linux】查看操作系统开机时初始化的驱动模块列表的一个方法

这个方法是摸索出来的&#xff0c;也不一定对&#xff1a; 1、驱动层module_init(module_init_function)作为模块初始化&#xff0c;并且提供模块内部初始化的函数名&#xff1b; 2、找到所有驱动目录drivers下所有module_init(module_init_function)&#xff0c;在内核6.9.0…

你的绩效是不是常年都是B

原创不易&#xff0c;求赞&#xff0c;求关注&#xff0c;&#x1f64f;&#x1f64f;&#x1f64f;&#x1f64f;&#x1f64f;&#x1f64f;&#x1f64f;&#x1f64f; 目录 原创不易&#xff0c;求赞&#xff0c;求关注&#xff0c;&#x1f64f;&#x1f64f;&#x1f64…

http连接github远程仓库密码问题解决办法

目录 一、问题&#xff1a;使用http连接失败 二、解决办法&#xff1a;使用个人访问令牌。 1、生成访问令牌&#xff1a; 步骤 1: 登录 GitHub 步骤 2: 进入设置页面 步骤 3: 生成新的访问令牌 步骤 4: 配置访问令牌 步骤 5: 复制令牌 2. 使用访问令牌 一、问题&#…

图像滤波---各项异性扩散滤波使用笔记及代码

图像滤波---各项异性扩散滤波使用笔记及代码 一、文章内容介绍二、各项异性扩散滤波和各项同性滤波1、各项同性滤波2、各项异性扩散滤波3、各项异性和各项同性的对比 三、各项异性扩散滤波的原理介绍四、各项异性扩散滤波公式五、公式中的参数使用说明1、扩散速率 λ \lambda λ…

kubernetes中pause容器的作用与源码详解

概述 摘要&#xff1a;上一篇文章我们介绍了kubernetes是如何通过pause容器来构建一个pod。本文我们对pause容器做一个总结&#xff0c;并再此基础上次深入浅出&#xff0c;从pause容器的源码详细了解pause容器的实现原理。 正文 pause容器是什么 在 Kubernetes 中&#xff…

STM32(十五):I2C通信

I2C通信 I2C&#xff08;Inter IC Bus&#xff09;是由Philips公司开发的一种通用数据总线&#xff0c;实现单片机读写外部模块寄存器的功能。 两根通信线&#xff1a;SCL&#xff08;Serial Clock&#xff09;、SDA&#xff08;Serial Data&#xff09; 同步&#xff0c;半双工…

css百分比布局中height:100%不起作用

百分比布局时&#xff0c;我们有时候会遇到给高度 height 设置百分比后无效的情况&#xff0c;而宽度设置百分比却是正常的。 当为一个元素的高度设定为百分比高度时&#xff0c;是相对于父元素的高度来计算的。当没有给父元素设置高度&#xff08;height&#xff09;时或设置…

Celery的使用

Celery 一、Celery概述1. 特点:2. celery组成3. 安装与使用4. 邮箱配置二、Celery的使用实操——发送邮件1. 安装2. 配置一、Celery概述 1. 特点: 2. celery组成 配置任务队列Broker,采用redis保存要执行的任务队列 Client:任务的发出者 Worker:任务的处理者 3. 安装与使用…

『功能项目』第三职业弓弩的平A【58】

我们打开上一篇57第二职业法师的平A的项目&#xff0c; 本章要做的事情是实现第三职业弓弩的平A伤害 首先修改脚本&#xff1a;MagicBall.cs 将脚本挂载在Sphere预制体身上 注意组件设置 运行项目 本章做了第三职业弓弩的平A伤害及显示伤害UI 接下来文章的内容&#xff1a; …

如何升级用 Helm 安装的极狐GitLab Runner?

本分分享如何对 Helm 安装的 Runner 进行升级。整个过程分为三步&#xff1a;1、确定 Runner 最新版本或者想要升级的版本是否存在&#xff1b;2、用 Helm upgrade 命令进行升级&#xff1b;3、升级确认。 极狐GitLab 为 GitLab 的中国发行版&#xff0c;中文版本对中国用户更…

Mac笔记本上查看/user/目录下的文件的几种方法

在Mac笔记本上查看/user/下的文件&#xff0c;可以通过多种方法实现。以下是一些常见的方法&#xff1a; 一、使用Finder 打开Finder&#xff1a;点击Dock栏中的Finder图标&#xff0c;或者使用快捷键Command F。 导航到用户目录&#xff1a; 在Finder的菜单栏中&#xff0…

编译运行 webAssembly(wasm)

环境准备&#xff1a; lunix下docker 参考https://hub.docker.com/r/emscripten/emsdk 拉编译环境 docker pull emscripten/emsdk 编译 随便找个目录&#xff0c;敲下面命令&#xff0c;编译一个webAssembly 程序 # create helloworld.cpp cat << EOF > hellowo…

【nginx】搭配okhttp 配置反向代理

nginx的默认是一个反向代理。 nginx会默认把输入的请求,转向其他的服务器执行。 这些转向的服务器与客户端发起的服务器不是同一个。 客户端只认识nginx,不知道ngiix转向何方。 正向代理修改okhttp的proxy,实际上很多代理都是正向的。 反向代理修改请求路径到nginx。 感觉还…

在线IP代理检测:保护您的网络安全

在互联网飞速发展的今天&#xff0c;越来越多的人开始意识到网络安全和隐私保护的重要性。在线IP代理检测工具作为一种有效的网络安全手段&#xff0c;能够帮助用户识别和检测IP代理的使用情况&#xff0c;从而更好地保护个人隐私和数据安全。本文将详细介绍在线IP代理检测的相…