Lecture 17-1

绪论

上一章节我们介绍了图的基本概念和存储方式,后面的章节对图论中简单的操作和算法依次进行介绍,首先要介绍的即是图的遍历,以及遍历的衍生应用。


Graph Traversal

图的遍历和树的遍历一样就是将所有的结点全部检查一遍,同样分为可以用递归和栈实现的基于深度的遍历和用队列实现的基于广度的遍历。

在这里插入图片描述
区别在于,树状结构中结点到结点的路径都是唯一的,也就是说在搜索的过程中不会出现重复搜索的情况,而图中两个节点之间的路径很有可能不唯一,为了在不同分支避免重复搜索的情况,需要额外给每个结点定义一个哈希表或基于布尔型的数组来判重。

在这里插入图片描述

Breadth-first traversal

宽度优先遍历的实现方式和在树状结构进行的宽度优先遍历的实现方式是一样的:用队列作为容器,将正在遍历的结点出队,然后在队列中进队正在遍历的结点的邻居之中可进行扩展也就是未遍历过的结点,对进队的结点进行标记,直到所有结点遍历完全。

如果一个结点与所有的节点相邻,那么队列中此时会存放近乎所有的结点,所以队列大小的复杂度为O(V)。

比起树的宽度优先遍历多了进队判重和标记的部分。
在这里插入图片描述

基于队列的实现:

在这里插入图片描述

Depth-first traversal

图上深度优先遍历的实现方式也和树上深度优先遍历的实现基本相同,就是多了标记和判重的部分。

在这里插入图片描述

基于栈的实现:

在这里插入图片描述

有关图遍历不愿多说,因为对于图的遍历,具体代码的细节要比算法思想难处理的多。


Applications

图遍历在应用时往往做搜索的功能,基于深度优先遍历的搜索是按照一条一条分支进行搜索,基于宽度优先遍历的搜索则是按照优先级或者说深度一层一层的进行搜索,两个搜索的依据不同,因此特性不同,在使用之前要先考虑清楚当前使用的搜索方式的特性是什么。

但无论特性是什么,两种搜索的对象都是在同一个连通分量。

Connectedness

就是判断两个结点是否连通:如果两个结点连通,那么我们以其中的一个结点作为搜索的起点在它所在的连通分量进行搜索一定能够搜索到另外一个结点。

采用的搜索方式是宽度优先的,课件上没有说理由,我个人觉得宽度优先和深度优先都可以,并且在复杂度上其实没有太多的差距。

在这里插入图片描述

Connected Components

Connected Components是基于判断两个结点是否连通更深层的应用,之前提到无论宽度优先遍历还是深度优先遍历,搜索的范围都在搜索起始结点所在的连通分量,于是我们可以根据这个性质将一个非连通图分割成若干个连通分量。

为了效率,我们用并查集来处理结点之间的关系。

在这里插入图片描述
使用宽度优先遍历或者深度优先遍历,将一个连通分量实质上转化成并查集对应的树,将所有在本轮搜索过的结点的前驱节点都改成本轮搜索的起始点即可。

在这里插入图片描述

进行多次的遍历将所有结点遍历完全。

在这里插入图片描述

但这个时候存在一个问题,当我们一轮遍历结束后,我们该如何找到下一轮遍历的起始点?

课本上的方法是直接遍历一遍所有的结点,看哪个结点是没有遍历过的,但这么做的复杂度为O(n),在很多情况下显然是不可接受的。

课件上介绍了一种处理未遍历过的结点的方式能让复杂度降到O(1):

准备两个数组,第一个数组放所有未遍历过的结点的信息,第二个数组以结点信息作为索引存放结点在第一个数组的下标。

在这里插入图片描述

当我们需要访问某个节点时,从第二个数组找到该节点在第一个数组中的下标,然后删去该结点表示已经访问过,将第一个数组的最后一个结点放到结点被删去后空余的位置。

第二个数组的下标也要修改。

在这里插入图片描述

当某一轮遍历完之后,不再通过第二个数组寻找下标,而是直接找第一个数组最后一个元素,以最后一个元素作为下一轮遍历的起始点。

在这里插入图片描述

第二个数组让每次查询结点的复杂度降低到O(1),由于我们每次删去一个访问过的结点都是用当前表中最后一个结点进行填充的,所以当我们需要寻找下一轮的起始点时,此时第一个数组的存在值的空间一定集中于数组前面的部分,最后一个元素一定存在,且删去不会破坏结构。

Unweighted path length

未加权路径的长度,简直是屁话,如果真的没有权哪里来的长度?与其说是不加权,不如说是每条路径的权都相等。

这个应用的基础就是宽度优先遍历的定义:宽度优先遍历是按照优先级一层一层的进行遍历,宽度优先遍历过程中当前一层遍历的就是同一优先级。

显然是以A作为起始点,实际操作起来还需要很多细节要处理。
在这里插入图片描述
事实上将BFS的对象看作所有路径权都为1的加权图,这里很显然就是结点A到其他结点的代价,很显然的想到,我们给每条边带上不同的权值,再将队列改成优先队列,就是weighted path length,也就是我们常说的UCS(权值优先搜索)。

这个应用也是BFS最常见的最容易扩展应用之一(寻路)。

Identifying bipartite graphs

Bipartite Graphs是二部图的意思,那什么叫做二部图呢?就是说可以对点集进行一个划分,划分成两个子集,图中的任意一条边的两个顶点都不存在于同一个子集中,那么称这个图为二部图。

处理二分图的最大匹配或许比较难,但是判断二分图还是非常简单的。

首先可以确定的是,二部图一定是一个连通图,那么我们任取一个点进行遍历,是一定能够遍历完整个图的所有结点的。

另外起始点作为图中的点,如果图是二部图,那么起始点一定属于两个子集之一,那么它的邻居一定属于另一个集合。

我们只需要任选一个结点开始遍历,将它置于任意一个集合,也就是标记成其中一个颜色。

在这里插入图片描述

所有的邻居都放入另一个子集(这么说就知道是用宽度优先遍历,不过用深度优先遍历也是完全可以的)。

在这里插入图片描述
持续下去,每次将遍历的结点的为放入子集的邻居,放入到与自己不同的子集中。

在这里插入图片描述
如果节点能够全部遍历完全,且不会出现在遍历一个节点时,它的邻居已经放入子集并和它属于同一个子集的情况,那么说明这个图为二分图。


总结

BFS和DFS说实话真的是老生常谈但是每次学习和实现都会出现各种各样状况的问题,甚至对于很多情况,大家还要思考是否真的需要判重,在回溯时是否取消这些之前的标记,这些都是非常值得思考的问题。

比起课本上的应用,课件上介绍了一些其他的应用,都是很常见和很实用的应用,但是在应用之前一定要想清楚自己使用的搜索方法偏向于的功能是什么,特性是什么,这对于你使用搜索算法非常的重要。

反正我学的很烂hhhh

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值