3.图 Graph (BFS DFS 二部图 最小生成树 拓扑序 连通性)含例题

图 Graph

一、图的连通性

1. 宽度优先搜索 BFS

基本思路:先选择一个点作为起始点,可以将这个起始点作为第0层,然后从这个点开始搜索这个点的邻接点,将这些邻接点作为第1层,然后再对这些邻接点进行搜索,将搜索到的点作为第2层,以此类推,知道搜索完所有的点。

采用BFS,可以很自然的在图上产生一颗搜索树。

BFS树的性质:非树边要不连接在同一层,要不连接在相邻层

proof:假设BFS进行到边DFS(x),在选择边(x,y)时,如果y已经被访问了,即y先被访问,那么考虑y被访问的时刻,为什么没有选择x呢?因为x也已经被访问了,x要么跟y同一层同时被访问,要么x被另一个和y同层结点访问。所以x和y要么被同时访问了(在同一层),要么其中一个被另一个下一次访问(在相邻层)

2. 深度优先搜索 DFS

基本思路:尽可能深的前进直到必须的时候才后退。

这个方法最容易用递归的方法实现,具体实现的算法如下:


DFS(u)

​ 将u标记为“已探索”

​ For 从u出发的任意一条边(u,v)

​ if v没有被标记为“已探索”

​ 递归调用DFS(v)


当然也可以使用栈(stack)实现这个方法。

同样的,深度搜索也可以产生一个树,我们称之为深度搜索树,即每次探索到新的节点就标记为当前节点的子节点即可

DFS树的性质:非树边连接的两个点,一个必定是另一个的祖先

proof:首先由DFS的算法可知,在DFS(x)被激活和这整个递归结束之前所以被访问的节点都应该是x的后代。

假设(x,y)是一条非树边,且DFS(x)先开启,那么在(x,y)被访问的时刻,y没有被加入到树中是因为y已经被访问过了,但是在DFS(x)首次开始时,因为是DFS(x)先开启,所以y此时没有被访问,因此y是在DFSx)被激活和这整个递归结束之前所以被访问的节点。所以y是x的后代。

二、用栈或者优先队列实现上述遍历

1.图的表示

图的表示主要有以下两种:

邻接矩阵:以每个点作为横纵坐标绘制的矩阵,优点是能用O(1)的时间知道给定边是否出现,缺点是会浪费n2的空间,并且当你需要检查某个点所连接的边的时候,你需要O(n)的时间

邻接表:运用邻接表只需要O(n+m)的空间,并且可以很短时间找到某个点所连接的所有边

2.用队列和栈实现

BFS通常用队列实现,DFS通常用栈实现。时间复杂度都是O(m+n)

三、二部图:DFS的一个应用

基本思路:我们只需要对这个图运用一次bfs,因为在运用bfs时,实际上会对搜索过的图分成很多层(就像树一样),此时我们只需对相邻的层使用不同的两种颜色即可。然后我们再去搜索每一个edge,观察是否有一条边有两个相同的颜色即可知是否是一个二部图。

在实现这个算法之前我们来看一个断言:

只要这个图不是一个二部图,则其必然包含一个奇圈

由于这个层是由bfs产生,所以一个点不能跨过自己的父层或者子层去和更高或者更低的层面的点相连。

在这个前提下,第一种情况,不存在某同一层的两个点相连,那么正好可以以相邻层着色的方式画出二部图。

第二种情况,若存在同一层两个点相连的情况,则不是二部图,此时这两个点到他们的最低共同父节点的距离相同,假设为m,且亮点之间的距离是1,则这个圈的长度是2m+1。即一个奇圈。

四、有向图的连通性

判断一个图是否是强连通的基本方式是:任取有向图G的某结点S,从S开始进行深度优先搜索,若可以遍历G的所有结点,则将G的所有边反向,再次从S开始进行深度优先搜索,如果再次能够遍历G的所有结点,则G是强连通图,两次搜索有一次无法遍历所有结点,则G不是强连通图。此外,上述搜索可以换成广度优先搜索等其他方案。

五、有向无圈图(DAG)和拓扑排序

定理:如果G有一个拓扑排序,那么G是一个DAG,这句话反过来也是成立的。

什么是拓扑排序:对一个DAG而言,如果存在边<u,v>那么在这个排序中,则u必须出现在v之前。

知道了什么是拓扑排序,其实这个证明就是显而易见的了,毕竟一个圈是无法出现拓扑排序的。

现在我们考虑这个定理的逆命题,是否每一个DAG中都存在一个拓扑排序呢?

在每一个DAG中都必定存在一个没有入边的点

这个也很好证明,假设每个点都至少存在一个入边,我们从某个点s开始往回走(顺着某个入边走),那么在走了n+1次,我们必将重复访问某个点,则就构成了一个环,这与DAG的定义相悖。

好,现在我们来试着证明这个逆命题。

假设某个有n+1个点的图G,v是其中一个没有入边的点,G是一个DAG,那么很G-v也必然是一个DAG,因为删除一个点是不可能产生一个不存的环的。那么这句话就完成了归纳法证明里n->n+1的过程——我们只需要把G-v的拓扑序列排在v的后面即可。

那么我们的算法也就完成了:不停的寻找一个没有入边的点即可。

算法的复杂度:每次挑选一个没有入边的点需要花费O(n)的时间,总共要寻找n次,故时间复杂度是O(n2

当然这个复杂度可以通过一种方式降低到O(m+n)下面来介绍一下这个方法:

即我们每次都把已经选择加入拓扑序列的点从图中删除,这里我们需要维护两个东西:

  • 每个点的入度(因为我们在删除点,所以这个入度是动态变化的)
  • 没有入度的点(即可以加入拓扑序列的点)

六、例题

1.给定一个图,找可能的拓扑序

可以先确定起始点(没有入边的点),再确定终点(没有出度的点),然后根据图再去确定中间的点。

2.移动机器人

假设你有两个机器人在图上行走,在每个时间点你可以让某个机器人行走一个单位长度,但是不能让两个机器人靠的过近(distance<r),假设两个机器人的起点分别是(a,b)他们要移动到(c,d),那么请你确定是否存在这样一个调度,能满足不会发生靠的过近的要求呢?

解题思路:当看到这种判断是否成立的题目时,我们很容易的可以想到就是图的连通性判断。

我们只需要构造一个新的图H,这个图包含了所有在原来的图中,两个机器人可能存在的位置(u,v),如果通过一步行走能使得

(u,v)变成(u,v’)或者是(u’,v)那么则将(u,v)和这个点连接起来。

下一步我们要从图H中删除那些,距离过近的点,这个的实现我们可以在原图G中使用bfs,从而把距离小于r的点对都删除。

最后一步,我们只需要判断这个图是否是连通的即可。

时间复杂度分析

假设原图G有n个点,m条边。

  1. 第一步先要分析图H的规模(这点是个好习惯),H最多有n2个点对,在这n2个点对中,每个G中的点都出现了n次,因为原图G中所有点的度数之和为2m,所以H中边的规模应该是O(mn)
  2. 接下来分析一下删除距离小于r的点所需要的时间,对每个点我都进行了一次BFS,所需要的时间是O(m+n),总共需要O(mn+n2)的时间。
  3. 最后分析对H进行连通性判断所需要的时间,也可以简单的使用BFS或者DFS,时间是(n2+mn)
  4. 把这些时间相加就是最后的时间复杂度

3. 判断图中是否有圈

请设计一个算法,用于判断一个无向图中是否有圈,如果有你应该输出他。算法的复杂度应该是O(m+n)

解题思路

  • 对这个图进行BFS算法(假设这个图是连通的,如果不是,进行多次BFS即可),的到一颗DFS数T
  • 检查所有的边,如果存在一条边属于G而不属于T则这个图有圈
  • 找到这条边连接的点u,v然后找到他们的最小共同父节点,连起来就能找到我们要的圈

4. 二部图问题

一个研究团队找到了n个标本,他们认为这些标本应该可以粗略的分为两个类别A,B,但是他们难以区分这些标本到底属于哪个类别,因此,他们只能对标本进行两两比较,给出“same”和“different”的结果,他们总共给出了m个结果(有一些标本对他们难以区分),此时他们怀疑他们的判断是否有误,请你设计一个时间复杂图为O(m+n)的算法,判断他们的判断是否有问题。

解题思路:观察到时间复杂度是O(m+n)很容易想到应用图的知识,又因为分为两个类别,可以得知这是一个二部图,我们可以考虑使用BFS的方法解题。

那么拿到题目的第一步就是给构建这样的图,我们将n个标本当作n个点,将m个判断作为边(标记上same和dif)

首先这个图不一定是连通的,我们需要对每个连通分支都进行这样的算法,首先对某个连通分支,找到某个节点s作为起始节点,并给其标上red颜色,从这个节点开始做BFS,如果连接的边是same标记,则给这个连接的点标记上相同的颜色(red),如果是diff边,则标记上不同的颜色(blue)。

算法的第一步就结束了,然后检查每条边,若边是same边,而边连接的两个点颜色不同,则说明判断有问题;若边是diff边,而边连接的两个点的颜色相同,则判断也有问题;如果所有的边都没有问题,则判断没有问题。

算法复杂度:BFS的复杂度是O(m+n),检查每条边的复杂度是O(m)所以总复杂度是O(m+n)

5. 深度搜索和广度搜索树问题

如果T是跟在u的一颗BFS树,也是一颗DFS树,那么T就是图G。

proof:如果T是BFS树,则非树边必定连接同层或者邻层,如果T是DFS树,那么非树边必定连接不同层,由此非树边必定连接邻层,且他们是祖孙关系,但是这样非树边就成了树边,与假设相悖。

6.无向图下的最短路径问题

计算一个图G中两点v-m之间最短路径的条数。

首先要注意到一个事实,perform一个BFS树,则两个节点之前的最短距离就是两个节点之间的层数。

那么我们只需要以v为起点,perform一条BFS树,然后找到m点,假设在j层。然后开始往回推,先在j-1层找到能到达m点的点,再依次往回推,最后总计即可。

因为做了一次BFS所以复杂度是O(m+n)

7.巧妙的构建DAG

假如有n个人,你知道一些事实,每个事实有以下两种情况:

  • 对于某个人i和j,i死在j出生之前
  • 对于某个人i和j,i和j的生命有一部分交集

对于这些事实不一定准确,你要设计一个算法判断这个事实有没有内部矛盾。

对于这样的问题,必须想出一种方法来准确的构建一个图使得能够描绘出上述两种情况。

显然,以人做为vertex并不能做到上述这一点,有效的方式是对于每个人假设一个出生日期Bi和一个死亡日期Di。

首先让每个人的Bi指向Di(代表Bi发生在Di之前),然后如果i死在j出生之前,让Di指向Bj即可,若i和j的生命有交集,且i先死亡,那么就让Bi指向Dj,Bj指向Di。

然后判断这个图是否存在一个环即可(拓扑序列)

  • 3
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值