【数据结构与算法笔记04】对图搜索策略的一些思考(包括DFS和BFS)

这里的“图搜索策略”应该怎么理解呢?

首先,是“图搜索”,所谓图无非就是由节点和边组成的,那么图搜索也就是将这个图中所有的节点和边都访问一遍。

其次是“策略”:

==> 如果就直接给你一个图,要怎么样才能将所有的节点和边都访问一遍呢?

这里可以考虑一个非常非常大并且结构复杂的图,那么当拿到这个图的时候信息庞杂无比,你不知道里面有多少个节点,有多少条边,不知道节点和边之间是怎样错综复杂的关系,不知道有多少连通子图......

对这样的图进行搜索会让人一下子摸不着头绪,这时候肯定会想到,得有一个入手点才行!这是一个很通用的解决问题的策略,当整体过于复杂的时候不如试着从一个很小的局部开始一点点梳理脉络。

这个入手点一般是某个节点。

==> 有了一个入手点之后,要怎样继续去搜索其他的节点和边呢?

这时候会考虑,从我已有的知识来说,我目前只知道一个搜索的起点(记为source),以及关于这个起点的一些信息:与source相连的边的集合,与source相邻的节点集合。不过这两个信息其实是重合的,如果我现在从source出发去访问一条与source相连的边,其实也就是访问了这条边另一头的节点(source的一个邻居)。

当我走了这一条边之后,到达source的这个邻居节点之后,又会得到与这个邻居节点相邻的边以及它的邻居......

也就是说,在每一个时间截面(或者说选择节点)上,我都有很多选择——我有一个可以访问但还没有访问的节点的集合,此时我会从这个节点集合中选出一个节点,想前走一步,每探索一步,我对这个图的了解就更深一步(如果说有一个已经访问过的节点的集合,那么这个集合中节点的个数就可以理解为深入的程度),但这样的深入并非永无止境,这个图总会遍历完(总会到达这样一个选择节点:此时没有任何可以继续访问的节点了),所以这个探索会像一个先膨胀再收缩的过程(或者说像一个回力镖?),总会回到已经访问过的节点上来,这时候就不必再继续下去了,再继续下去对 “遍历更多的节点和边” 这一目标没有任何价值。

于是我得到了一个初步的搜索思路

我有一个可以访问但还没有访问的节点的集合(记为Package),初始时这个集合中只有我一个节点。

每访问一个节点,都有可能会向这个Package中装入一些新的节点(当前访问的节点的邻居中,还没有访问过的那些节点),当然也可能不会。

我每次从这个Package中取一个节点,然后更新一下Package的内容,然后再取一个节点......就这样一直重复直到Package为空,这时就已经将从source所在的那个连通子图全部遍历完了。

==> 每次取出一个节点?

对于刚刚得出的搜索策略,还有一个值得思考的地方:每次取一个节点?每次取哪个节点?

如何决定每一次取出哪一个节点这个问题就是所谓的“策略”。

当确定了“策略”之后,还需要考虑Package用什么数据结构来存储,要选择一个最适合于搜索策略的数据结构。

画外音:不能狭隘地认为一说到图搜索或者遍历图,不是DFS就是BFS,虽然确实这两种是最最常用而且能高效地解决很多问题的搜索策略。但是我也可以简单地每次就随机取一个节点进行访问,或者说每次先把剩余未访问邻居最多的节点拿出来访问,或者说每次把距离source最近的节点拿出来访问等等。我这里只是随便举一些例子,我想说的是,图搜索的策略有千千万万种,我们不能局限自己的思路,当拿到一个问题时,应该想着,怎么样为这个问题服务,而不是直接就开始想“这题用DFS能不能做?不能就用BFS”。

 

深度优先搜素(DFS)& 广度优先搜索(BFS)

跟着上面的思路来考虑DFS和BFS的搜索策略:

DFS:每次从Package中选出距离source最远的节点进行访问。

BFS:每次从Package中选出距离source最近的节点进行访问。

如果在某一个选择节点有多个距离source最近/远的节点,选哪一个都可以,具体选哪个现在暂时无法决定,这个问题先留着之后再考虑。

 

在前面的“初步搜索思路”中有一个很重要的细节,回顾一下我们是如何更新Package的?

每访问一个节点,都有可能会向这个Package中装入一些新的节点(当前访问的节点的邻居中,还没有访问过的那些节点)...

如果在访问某个节点A时对Package进行了更新:

DFS:A离source的距离(记为k)大于等于当前Package中的所有节点

                     ==> A的邻居中,还没有被访问过的那些节点离source的距离(k+1)一定大于当前Package中的所有节点

      ==> 下一次访问的节点一定是这次刚刚加入Package的节点中的某一个

      ==> 典型的“后入先出” 

      ==> Package用栈来存

      ==> 每次从栈顶取出一个节点,对其进行访问,并将它的 邻居中还没有被访问过的节点 压栈

BFS: 假设当前Package中距离source最近的节点构成一个集合S_k,假设它们离source为k

                      ==> 每次从这个集合中选出一个节点,并将该节点的邻居中还没有被访问过的那些节点(离source的距离为k+1)放入Package中

      ==> 在访问S_k时加入到Package中的节点构成集合 S_k+1,以此类推

      ==> 这就意味着节点是按照他们和source的距离加入Package的,也是按照他们和source的距离离开Package的

      ==> 加入的顺序 = 离开的顺序

      ==> 典型的“先入先出”

      ==> Package用队列来存

      ==> 每次从队首取出一个节点,对其进行访问,并将它的 邻居中还没有被访问过的节点 放入队尾

 

再回头看刚才遗留的那个问题:如果在某一个选择节点有多个距离source最近/远的节点时,应该选哪一个?

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/zzxddd.html