数据结构 - 图之代码实现

news/2024/12/4 19:46:39/文章来源:https://www.cnblogs.com/hugogoos/p/18524282

书接上回,我们继续来聊聊图的遍历与实现。

01、遍历

在图的基本功能中有个很重要的功能——遍历,遍历顾名思义就是把图上所有的点都访问一遍,具体来说就是从一个连通的图中某一个点出发,沿着边访问所有的点,并且每个点只能访问一遍。

下面我们介绍两种常见的遍历方式:深度优先遍历(DFS)和广度优先遍历(BFS)。

1、深度优先遍历

如果我们把边当作路,深度优先遍历就是路一直往下走,直到没路了再返回走其他路。其实优点像树的先序遍历从根节点沿着子节点一直向下直到叶子节点再调头。

下面我们梳理一下深度优先遍历大致分为以下几个步骤:

(1)从图中任意一个点A出发,并访问点;

(2)找出点A的第一个未被访问的邻接点,并访问该点;

(3)以该点为新的点,重复步骤(2),直至新的邻接点没有未被访问的邻接点;

(4)返回前一个点并依次访问前一个点为未被访问的其他邻接点,并访问该点;

(5)重复步骤(3)和(4),直至所有点都被访问过;

如上图演示了从点A出发进行深度优先遍历过程,其中红色虚线表示前进路线,蓝色虚线表示回退路线。最后输出:A->B->E->F->C->G->D。

2、广度优先遍历

如果说深度优先遍历是找到一条路一直走到底,那么广度优先遍历就是先把所有的路走一步再说。其实优点像树的层次遍历从根节点出发先遍历其子节点然后再遍历其孙子节点直至遍历完所有节点。

下面我们梳理一下广度优先遍历大致分为以下几个步骤:

(1)从图中任意一点A出发,并访问点A;

(2)依次访问点A所有未被访问的邻接点,访问完邻接点后,然后按邻接点顺序把邻接点作为新的出发执行步骤(1);

(3)重复步骤(1)和(2)直至所有点都被访问到。

如上图演示了从点A出发进行广度优先遍历过程,其中红色虚线表示前进路线。最后输出:A->B->C->D->E->F->G。

02、实现(邻接矩阵)

下面我们就以邻接矩阵的存储方式实现一个无向图。

1、定义

根据图的定义,我们需要定义点集合、边集合两个私有变量用于存储核心数据,为了操作访问我们再定义点数量和边数量两个私有变量,代码如下:

//点集合
private T[] _vertexArray { get; set; }
//边集合
private int[,] _edgeArray { get; set; }
//点数量
private int _vertexCount;
//边数量
private int _edgeCount { get; set; }

2、初始化 Init

此方法主要是初始化上面定义的私有变量,同时确定点集合大小,具体代码如下:

//初始化
public MyselfGraphArray<T> Init(int length)
{//初始化指定长度点集合_vertexArray = new T[length];//初始化指定长度边集合_edgeArray = new int[length, length];//初始化点数量_vertexCount = 0;//初始化边数量_edgeCount = 0;return this;
}

3、获取点数量 VertexCount

我们可以通过点数量私有变量快速获取图的点数量,代码如下:

//返回点数量
public int VertexCount
{get{return _vertexCount;}
}

4、获取边数量 EdgeCount

我们可以通过边数量私有变量快速获取图的点数量,代码如下:

//返回边数量
public int EdgeCount
{get{return _edgeCount;}
}

5、获取点索引 GetVertexIndex

该方法是通过点元素获取其索引值,具体代码如下:

//返回指定点元素的索引   
public int GetVertexIndex(T vertex)
{if (vertex == null){return -1;}//根据值查找索引return Array.IndexOf(_vertexArray, vertex);
}

6、获取点元素 GetVertexByIndex

该方法通过点索引获取点元素,具体代码如下:

//返回指定点索引的元素
public T GetVertexByIndex(int index)
{//如果索引非法则报错if (index < 0 || index > _vertexArray.Length - 1){throw new InvalidOperationException("索引错误");}return _vertexArray[index];
}

7、插入点 InsertVertex

插入点元素时,我们需要先通过点元素获取其索引,如果索引已存在或者点集合已经满了则直接返回,否则添加点元素同时更新点数量,具体代码如下:

//插入点
public void InsertVertex(T vertex)
{//获取点索引var index = GetVertexIndex(vertex);//如果索引大于-1说明点已存在,则直接返回if (index > -1){return;}//如果点集合已满,则直接返回if (_vertexCount == _vertexArray.Length){return;}//添加点元素,并且更新点数量_vertexArray[_vertexCount++] = vertex;
}

8、插入边 InsertEdge

插入边时可以同时指定边的权值。我们首先需要把两个点元素转换为点索引,同时验证索引,验证不通过则直接返回。否则开始添加边,因为无向图的特性,所以需要添加两点索引相反的边。同时更新边数量,具体代码如下:

//插入边
public void InsertEdge(T vertex1, T vertex2, int weight)
{//根据点元素获取点索引var vertexIndex1 = GetVertexIndex(vertex1);//如果索引等于-1说明点不存在,则直接返回if (vertexIndex1 == -1){return;}//根据点元素获取点索引var vertexIndex2 = GetVertexIndex(vertex2);//如果索引等于-1说明点不存在,则直接返回if (vertexIndex2 == -1){return;}//更新两点关系,即边信息_edgeArray[vertexIndex1, vertexIndex2] = weight;//用于无向图,对于有向图则删除此句子_edgeArray[vertexIndex2, vertexIndex1] = weight;//更新边数量_edgeCount++;
}

9、获取边权值 GetWeight

该方法可以获取边的权值,权值可以根据需要在插入边方法中设置,需要对输入的点进行验证,如果点不存在则报错,具体代码如下:

//返回两点之间边的权值
public int GetWeight(T vertex1, T vertex2)
{//根据点元素获取点索引var vertexIndex1 = GetVertexIndex(vertex1);//如果索引等于-1说明点不存在if (vertexIndex1 == -1){//如果未找到点则报错throw new KeyNotFoundException($"点不存在");}//根据点元素获取点索引var vertexIndex2 = GetVertexIndex(vertex2);//如果索引等于-1说明点不存在if (vertexIndex2 == -1){//如果未找到点则报错throw new KeyNotFoundException($"点不存在");}return _edgeArray[vertexIndex1, vertexIndex2];
}

10、深度优先遍历 DFS

深度优先遍历正常有两种实现方法,一种是使用递归调用,一种是使用栈结构实现,下面我们使用递归的方式来实现。

因为我们需要保证每个点只会被访问一次,因此需要定义一个数组用来记录元素已经被访问过。我们这里是以无向图为例,因为无向图的对称性,索引我们选用一维数组即可满足记录被访问元素,而如果是有向图我们则需要使用二维数组记录被访问元素。

具体代码如下:

//深度优先遍历
public void DFS(T startVertex)
{//根据点元素获取点索引var startVertexIndex = GetVertexIndex(startVertex);//如果索引等于-1说明点不存在if (startVertexIndex == -1){//如果未找到点则报错throw new KeyNotFoundException($"点不存在");}//定义已访问标记数组//因为无向图对称特性因此一维数组即可//如果是有向图则需要定义二维数组var visited = new bool[_vertexCount];DFSUtil(startVertexIndex, visited);Console.WriteLine();
}
//深度优先遍历
private void DFSUtil(int index, bool[] visited)
{//标记当前元素已访问过visited[index] = true;//打印点Console.Write(_vertexArray[index] + " ");//遍历查找与当前元素相邻的元素for (var i = 0; i < _vertexCount; i++){//如果是相邻的元素,并且元素未被访问过if (_edgeArray[index, i] == 1 && !visited[i]){//则递归调用自身方法DFSUtil(i, visited);}}
}

11、广度优先遍历 BFS

广度优先遍历可以借助队列来实现。首先把起始点添加入队列,然后把点出队列,同时把该点的所有邻接点添加入队列,循环往复,一直到把所有元素处理完为止。

//广度优先遍历
public void BFS(T startVertex)
{//根据点元素获取点索引var startVertexIndex = GetVertexIndex(startVertex);//如果索引等于-1说明点不存在if (startVertexIndex == -1){//如果未找到点则报错throw new KeyNotFoundException($"点不存在");}//定义已访问标记数组//因为无向图对称特性因此一维数组即可//如果是有向图则需要定义二维数组var visited = new bool[_vertexCount];//使用队列实现广度优先遍历var queue = new Queue<int>();//将起点入队queue.Enqueue(startVertexIndex);//标记起点为已访问visited[startVertexIndex] = true;//遍历队列while (queue.Count > 0){//出队点var vertexIndex = queue.Dequeue();//打印点Console.Write(_vertexArray[vertexIndex] + " ");//遍历查找与当前元素相邻的元素for (var i = 0; i < _vertexCount; i++){//如果是相邻的元素,并且元素未被访问过if (_edgeArray[vertexIndex, i] == 1 && !visited[i]){//则将相邻元素索引入队queue.Enqueue(i);//并标记为已访问visited[i] = true;}}}Console.WriteLine();
}

:测试方法代码以及示例源码都已经上传至代码库,有兴趣的可以看看。https://gitee.com/hugogoos/Planner

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

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

相关文章

CF1554E You

题面题解 注意a[u]是点u位置的a,不是每选一个点然后把非标记个数丢进vector里( 每选择一个点,相当于把相邻的非标记的边标为外向,最后一个点u的外向边个数就是a[u] 又观察发现每种边定向方案都可以构造(拓扑),所以一共有2^(n-1)种方案 设f[k]表示gcd=k,g[k]表示k|gcd,…

汽车虚拟仿真软件有哪些?行业软件大盘点!

汽车虚拟仿真可以大大提高汽车的研发效率和质量,降低成本和风险,增强汽车的竞争力和创新能力。本文将带领大家了解汽车虚拟仿真软件有哪些、汽车虚拟仿真实际应用以及汽车云交互实时渲染平台三个要点。汽车虚拟仿真是指利用计算机技术,根据汽车的设计、制造、测试、运行等各…

《机器学习》 学习记录 - 第四章

第4章 决策树 4.1 基本流程 决策树(decision tree)是一类常见的机器学习方法,也叫“判定树”。顾名思义,决策树是基于树的结构进行决策的。 一般的,一棵决策树包含一个根结点、若干个内部结点和若干个叶结点:叶结点对应于决策结果,其他每个结点则对应于一个属性测试; 每…

Fullcalendar

月光光关注作者注册登录 使用Fullcalendar管理时间计划调度安排月光光2020-01-05 阅读 4 分钟 Fullcalendar可以很好的管理日程安排事件,可以管理时间和任务调度,比如日常值班岗位安排、举办活动会议议程安排、项目行动安排、车间劳动岗位排班等等。今天我们来了解一下使用Fu…

哈希函数与数据完整性 (^=◕ᴥ◕=^)

哈希函数与数据完整性:保护猫咪世界的小鱼干 (^=◕ᴥ◕=^) 在数字世界中,我们总是希望确保传输和存储的数据没有被篡改,就像猫咪们想保护它们珍贵的小鱼干不被“偷吃”一样。为此,哈希函数(Hash Functions)成为了一个强大而可靠的工具。哈希函数能生成独特的数据“指纹”…

学期 2024-2025-1 学号 20241403 《计算机基础与程序设计》第六周学习总结

学期(如2024-2025-1) 学号(如:20241403) 《计算机基础与程序设计》第六周学习总结 作业信息这个作业属于哪个课程 <班级的链接>(如2024-2025-1-计算机基础与程序设计)这个作业要求在哪里 <作业要求的链接>(如2024-2025-1计算机基础与程序设计第一周作业)这个…

2024 强网杯逆向 Writeups

最心有余而力不足的一集,做完 vm 颈椎病犯了,第二天根本打。最后,加上学弟学妹打的,最后剩一个 Android 逆向没 AK,要是没有颈椎病这一说肯定 AK 了。感觉快退役了... mips 编译一个 qemu-6.2.0 mips-linux-user bindiff 一下恢复符号,怀疑修改了 ELF loader 或者 syscal…

坐标系-投影

墨卡托投影墨卡托投影(Mercator Projection),是正轴等角圆柱投影。由荷兰地图学家墨卡托(G.Mercator)于1569年创立。假设地球被围在一中空的圆柱里,其基准纬线与圆柱相切(赤道)接触,然后再假想地球中心有一盏灯,把球面上的图形投影到圆柱体上,再把圆柱体展开,这就是一…

蚂蚁KAG框架核心功能研读

首篇KAG框架解读,看蚂蚁KAG框架如何通过知识图谱和向量检索结合,增强大模型在专业领域知识服务中的准确性和逻辑推理能力,解决现有RAG技术栈的挑战。作者介绍:薛明:拥有近10年在医疗和零售领域应用机器学习和人工智能的经验。曾就职于通用电气、复星医药等企业。长期专注于…

Burpsuite下载安装超详细教程,社区版永久有效,专业版汉化激活到2099年,不看会后悔系列,亲测好用!

声明:该公众号大部分文章来自作者日常学习笔记,也有部分文章是经过作者授权和其他公众号白名单转载. 未经授权,严禁转载,如需转,联系开白, 请勿利用文章内的相关技术从事非法测试,如因此产生的一切不良后果与文章作者和本公众号无关.现在只对常读和星标的公众号才展示大图推送…

非对称加密:猫咪的双钥匙保护 (^• ω •^)

非对称加密:猫咪的双钥匙保护 (^• ω •^) 在之前的博客中,我们讨论了对称加密算法。这种算法使用单一密钥对数据进行加密和解密,但这也带来了一些问题,例如密钥分发和共享的安全风险。为了解决这些问题,非对称加密(Asymmetric Encryption)应运而生。 非对称加密采用…

2024.10.28(商品品牌的增删改查)

按着ALt按左键整体编辑查询功能