图-数据结构

图的介绍

如果你有学过《离散数学》,那么对图的概念一定不陌生,在计算机科学中,一个图就是一些顶点的集合,这些顶点通过一系列连接(结对)。顶点用圆圈表示,边就是这些圆圈之间的连线。注意:顶点也称为节点或交点,边也称为链接。

同时边可以是双向的,也可以是单向的,不一定所有的顶点中间一定要有边,如图:

大家都或多或少听说过最短路径算法,那你知道生活中有哪些场景出现过它的应用吗?没错,就是平常使用的地图导航软件,如图:

在上图中起点、终点、每一个交叉路口都可以看作是一个顶点,而连接它们的绿色通道就是边。

另外可以从图上看出,从一个地方到另一个地方有着多种路径,影响因素有距离、红绿灯、拥堵情况、事故。对于多种因素可以使用边的权重来表示,根据情况的不同给边分配数值。

我们之前学的树和链表其实都是图的特例。

图的表示方法

邻接列表

每一个顶点都会保存着与它相邻的边,并且这个边的起点是这个顶点,即只描述指向外部的边。例如下图中,B应该保存着三条边,C保存一条边,因为B有三条指向外面,而C只有一条指向外面。

而它的邻接列表表示为:

 按照这样的存储方式,想必查找两个顶点之间关系或者权重时会有点费时。

邻接矩阵

邻接矩阵由二维数组表示,行和列都表示顶点,两个顶点所对应的矩阵元素数值表示两个顶点是否相连( 0 表示不相连,非 0 表示相连以及权重值)。

将上面的图用邻接矩阵表式则为(在这里边上无权重,所有1表示的是相连):

这邻接矩阵查找就特别快了,但是往里面添加顶点的成本很高,因为要重新按照新的行和列创建一个新的矩阵,然后将已有的数据复制到新矩阵中去。

使用哪个更好

 操作       邻接列表邻接矩阵
存储空间O(V+E)O(V^2)
添加顶点O(1)O(V^2)
添加边O(1)O(1)
检查相邻性O(1)O(1)

其中V表示图中顶点的个数,E表示边的个数。

结论:大多数情况选择邻接列表。除非图比较密集,即每个顶点都与大部分顶点相邻,并且个数不多,那么就选择邻接矩阵。

图的算法实现

结构体定义

首先要理清楚结构关系,这里的图中有三种结构,分别是边、顶点、邻接链表,以下是它们的定义。

#define MaxSize 1024typedef char DateElem; //顶点中存储的元素
//边
typedef struct _EdgeNode
{int adjvex; //与之相邻的节点的位置int weight; //边的权重struct _EdgeNode* next; //指向下一条与这个顶点相邻的边
}EdgeNode;//顶点
typedef struct _VertexNode
{DateElem date;				//顶点的数据struct _EdgeNode* first;    //指向第一条与之相邻的边
}VertexNode,AdjList;//邻接链表 
typedef struct _AdjListGraph
{AdjList* adjlist; //数组,保存着所有的顶点int vex; //顶点数int edge;//边数
}AdjListGraph;

邻接链表结构体中有着一个数组,数组保存着各顶点,顶点中又保存着与之相邻的边,整体是这样的结构。

图的初始化

其中 visited 数组是为了接下来的遍历操作,防止重复访问节点,因为在初始化里面出现了,那就一起写上。

bool visited[MaxSize]; //节点是否被访问过,被访问过为true
void Init(AdjListGraph& G)
{G.adjlist = new AdjList[MaxSize]; //像哈希表的结构G.edge = 0;G.vex = 0;for (int i = 0; i < MaxSize; i++){visited[i] = false;}
}

图的创建

因为创建一条边,需要知道起始的顶点和结束的顶点的位置,所以使用自己定义的 Location 函数,通过你输入的顶点数据,找到这个顶点在数组中的位置。

int Location(AdjListGraph& G, DateElem c);void Create(AdjListGraph& G)
{cout << "请输入顶点和边的个数:";cin >> G.vex >> G.edge;cout << "请依次输入顶点的数据:";for (int i = 0; i < G.vex; i++){cin >> G.adjlist[i].date;G.adjlist[i].first = NULL;}cout << "请依次输入边的起始和终点:";DateElem v1, v2;int i1 = 0 , i2 = 0;for (int i = 0; i < G.edge; i++){cin >> v1 >> v2;i1 = Location(G, v1);i2 = Location(G, v2);if (i1 != -1 && i2 != -1){EdgeNode* tmp = new EdgeNode;tmp->adjvex = i2;tmp->next = G.adjlist[i1].first;G.adjlist[i1].first = tmp;}}
}//通过顶点保存的数据找到顶点在图中的位置
int Location(AdjListGraph& G, DateElem c)
{for (int i = 0; i < G.vex; i++){if (G.adjlist[i].date == c){return i;}}return -1; //没找到
}

其中插入节点的操作为下图所示:

相当于链表的前插法。

图的遍历

如下图,这也是一个图结构,不过这让我想到树结构,是因为图的遍历方式和树的遍历方式有很多相似之处。

深度遍历

也就是以一个未被访问过的顶点作为起始顶点,沿当前顶点的边去访问未被访问过的顶点。

当没有未被访问过的顶点时,就回退到上一个顶点,访问别的顶点,直到所有的顶点都被访问过。

深度遍历其实迷宫问题中就出现过了,也就是这条路能走就一直走,不撞南墙不回头。

深度遍历和树的前序遍历很类似,假如起始顶点是 A,深度遍历可以是:A D F C B E,也可以是A B E F C D,只是因为输入边时的顺序不同。

//深度遍历一个节点
void DFS(AdjListGraph& G, int v)
{if (visited[v] == true){return;}cout << G.adjlist[v].date << " ";visited[v] = true;EdgeNode* temp = G.adjlist[v].first;int cur = 0;while (temp != NULL){cur = temp->adjvex; //当前节点的位置if (visited[cur] == false){DFS(G, cur);}temp = temp->next;}}
//深度遍历全部节点
void DFS_All(AdjListGraph& G)
{for (int i = 0; i < MaxSize; i++){visited[i] = false;}for (int i = 0; i < G.vex; i++){if (visited[i] == false){DFS(G, i);}}
}

广度遍历

首先以一个未被访问过的顶点作为起始顶点,访问其相邻的所有顶点。

然后对每个相邻的顶点,再访问它们相邻的未被访问过的顶点,直至所有顶点都被访问过,遍历结束。

广度遍历有点像树的层序遍历,例如 A 为起始顶点,那么广度遍历的结果可能是:A D C B F E 或者是:A B C D E F 。

//广度优先遍历 需要包含头文件 <queue> 
void BFS(AdjListGraph& G, int v)
{queue<int> q;q.push(v);int cur = -1;while (!q.empty()){cur = q.front(); //取队头元素q.pop(); //出队if (visited[cur] == false){cout << G.adjlist[cur].date << " ";visited[cur] = true;}//将与 cur 顶点相邻的顶点都入队EdgeNode* tmp = G.adjlist[cur].first; while (tmp != NULL){q.push(tmp->adjvex);tmp = tmp->next;}}
}
void BFS_All(AdjListGraph& G)
{for (int i = 0; i < MaxSize; i++) //初始化{visited[i] = false;}for (int i = 0; i < G.vex; i++){if (visited[i] == false){BFS(G, i);}}
}

 

用上图的图作为演示,得到运行结果。

其实从它们的叫法中也能看出一些来。

全部代码

#include <iostream>
#include <queue>
using namespace std;#define MaxSize 1024
typedef char DateElem;
//边
typedef struct _EdgeNode
{int adjvex; //与之相邻的节点int weight; //边的权重struct _EdgeNode* next; //指向下一条与这个顶点相邻的边
}EdgeNode;//顶点
typedef struct _VertexNode
{DateElem date;				//顶点的数据struct _EdgeNode* first;//指向第一条与之相邻的边
}VertexNode,AdjList;//邻接链表 
typedef struct _AdjListGraph
{AdjList* adjlist; //数组,保存着所有的顶点int vex; //顶点数int edge;//边数
}AdjListGraph;bool visited[MaxSize]; //节点是否被访问过,被访问过为true
//图的初始化
void Init(AdjListGraph& G)
{G.adjlist = new AdjList[MaxSize]; //像哈希表G.edge = 0;G.vex = 0;for (int i = 0; i < MaxSize; i++){visited[i] = false;}
}int Location(AdjListGraph& G, DateElem c);
//图的创建
void Create(AdjListGraph& G)
{cout << "请输入顶点和边的个数:"<<endl;cin >> G.vex >> G.edge;cout << "请输入顶点:" << endl;for (int i = 0; i < G.vex; i++){cin >> G.adjlist[i].date;G.adjlist[i].first = NULL;}cout << "请输入边:" << endl;DateElem v1, v2;int i1 = 0 , i2 = 0;for (int i = 0; i < G.edge; i++){cin >> v1 >> v2;i1 = Location(G, v1);i2 = Location(G, v2);if (i1 != -1 && i2 != -1){EdgeNode* tmp = new EdgeNode;tmp->adjvex = i2;tmp->next = G.adjlist[i1].first;G.adjlist[i1].first = tmp;}}
}//通过顶点保存的数据找到顶点在图中的位置
int Location(AdjListGraph& G, DateElem c)
{for (int i = 0; i < G.vex; i++){if (G.adjlist[i].date == c){return i;}}return -1; //没找到
}//图的遍历,深度和广度//深度遍历一个节点
void DFS(AdjListGraph& G, int v)
{if (visited[v] == true){return;}cout << G.adjlist[v].date << " ";visited[v] = true;EdgeNode* temp = G.adjlist[v].first;int cur = 0;while (temp != NULL){cur = temp->adjvex; //当前节点的位置if (visited[cur] == false){DFS(G, cur);}temp = temp->next;}}
//深度遍历全部节点
void DFS_All(AdjListGraph& G)
{for (int i = 0; i < MaxSize; i++){visited[i] = false;}for (int i = 0; i < G.vex; i++){if (visited[i] == false){DFS(G, i);}}
}//广度优先遍历 包含头文件 <queue> 
void BFS(AdjListGraph& G, int v)
{queue<int> q;q.push(v);int cur = -1;while (!q.empty()){cur = q.front(); //取队头元素q.pop();if (visited[cur] == false){cout << G.adjlist[cur].date << " ";visited[cur] = true;}EdgeNode* tmp = G.adjlist[cur].first; while (tmp != NULL){q.push(tmp->adjvex);tmp = tmp->next;}}
}
void BFS_All(AdjListGraph& G)
{for (int i = 0; i < MaxSize; i++){visited[i] = false;}for (int i = 0; i < G.vex; i++){if (visited[i] == false){BFS(G, i);}}
}int main(void)
{AdjListGraph G;//图的初始化Init(G);//图的创建Create(G);//图的遍历:深度遍历cout << "深度遍历:";DFS_All(G);cout << endl;//图的遍历:广度遍历cout << "广度遍历:";BFS_All(G);return 0;
} 

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

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

相关文章

使用 PyTorch FSDP 微调 Llama 2 70B

通过本文&#xff0c;你将了解如何使用 PyTorch FSDP 及相关最佳实践微调 Llama 2 70B。在此过程中&#xff0c;我们主要会用到 Hugging Face Transformers、Accelerate 和 TRL 库。我们还将展示如何在 SLURM 中使用 Accelerate。 完全分片数据并行 (Fully Sharded Data Paral…

Win11在Virtualbox上安装ubuntu操作系统

注&#xff1a;原创笔记&#xff0c;以下图片水印为本人 相关工具展示 第四个&#xff1a;ubuntu的vmdk文件&#xff0c;用于配置虚拟机 第5/6个&#xff1a;virtualbox安装包 一、安装 VirtualBox 由于win11版本问题&#xff0c;如果装不了 virtualBox需要在官网安装最新版…

【USRP】LFTX / LFRX

LFTX/LFRX 设备概述 LFTX 子板利用两个高速运算放大器来允许 0-30 MHz 的传输。该板仅接受实模式信号。LFTX 非常适合 HF 频段的应用&#xff0c;或使用外部前端来上变频和放大中间信号的应用。LFTX 的输出可以独立处理&#xff0c;也可以作为单个 I/Q 对进行处理。 主要特征…

C语言使用malloc函数模拟开辟二维数组(带分析)

系列文章目录 &#x1f31f; 个人主页&#xff1a;古德猫宁- &#x1f308; 信念如阳光&#xff0c;照亮前行的每一步 文章目录 系列文章目录&#x1f308; *信念如阳光&#xff0c;照亮前行的每一步* 当我们使用malloc函数来模拟创建一个二维数组时&#xff0c;我们需要理解…

【23真题】做不完啦!

马上就要考研了&#xff0c;虽然掐着手指头&#xff0c;也没办法把我手里的真题全部做完&#xff0c;发完。但是我依然会更新到考研前一天&#xff0c;绝不断更&#xff0c;就和我每日一题一样&#xff0c;从开始到现在&#xff0c;无论发生任何事情&#xff0c;我一天没有断更…

城市数字化管理、智慧城市、数字孪生城市间的关系和演变

基于《基于数字孪生的智慧城市》和《2023版数字孪生世界白皮书》&#xff0c;我们可以全面了解从数字城市管理到智慧城市&#xff0c;再到数字孪生城市的关系和发展历程。 以下是这一顺序和继承关系的要点总结&#xff1a; 城市数字化管理 这是城市地区向智慧城市演进的初始…

Android---Kotlin 学习002

声明变量 在 Kotlin 中定义一个变量&#xff0c;通过关键字 var 开始。然后是变量名&#xff0c;在“:”后紧跟变量类型。 示例1&#xff1a;声明一个 int 类型的变量 var num:Int 1 示例2&#xff1a;声明一个 String 类型的变量 var str:String "Hello world&quo…

Rocket MQ 架构介绍

文章目录 为什么选择Rocket MQ基本概念优点缺点架构图编程模型发送者发送消息固定步骤消费者消费消息固定步骤 为什么选择Rocket MQ Rocket MQ是阿帕奇顶级的开源项目&#xff0c;由阿里开发并开源。它的研发背景是Active MQ与Kafka不能很好的解决当时的业务场景。官网上是这么…

【OPNEGIS】Geoserver原地升级jetty,解决Apache HTTP/2拒绝服务漏洞 (CVE-2023-44487)

Geoserver是我们常用的地图服务器&#xff0c;在开源系统中的应用比较广泛。在实际环境中&#xff0c;我们可能会选用官方的二进制安装包进行部署&#xff0c;这样只要服务器上有java环境就可以运行&#xff0c;方便在现场进行部署。 1.问题来源 这次由于甲方一月一次的漏洞扫…

openGauss学习笔记-153 openGauss 数据库运维-备份与恢复-物理备份与恢复之gs_probackup

文章目录 openGauss学习笔记-153 openGauss 数据库运维-备份与恢复-物理备份与恢复之gs_probackup153.1 背景信息153.2 前提条件153.3 限制说明153.4 命令说明153.5 参数说明153.6 备份流程153.7 故障处理 openGauss学习笔记-153 openGauss 数据库运维-备份与恢复-物理备份与恢…

Connection refused: no further information

解决目录 一、报错信息二、解决方法 一、报错信息 二、解决方法 1、报错原因是开启了代理&#xff0c;像AS是绝对不能开代理的。 2、设置为No proxy&#xff0c;然后Apply再选择OK&#xff0c;重新同步。 要远离消耗你的人和事&#xff0c;不要花费任何情绪或者精力在他们身…

盘点六款颇具潜力的伪原创AI工具

写作作为信息传递的主要媒介&#xff0c;在庞大的信息海洋中&#xff0c;为了在激烈的竞争中脱颖而出&#xff0c;伪原创AI工具成为越来越多写手的神秘利器。在本文中&#xff0c;我们将深入盘点六款颇具潜力的伪原创AI工具&#xff0c;为你揭开它们神秘的面纱。 1. 文心一言 …