NTU 课程: MAS714(3) DFS & BFS(搜索算法)

NTU课程:MAS714 (3)Graph Algorithms_UQI-LIUWJ的博客-CSDN博客中,我们讲了图中点遍历的问题,其中,我们讲到SmartExplore:

正如之前分析的那样,它的时间复杂度是O(m+n)【n是顶点数,m是边数】

那么,我们应该用什么数据结构来实现这个呢?

1 队列与栈

1.1 队列

        先进先出 FIFO (First In First Out) 

        一般用于BFS(广度优先遍历)(Breath First Search)

1.1.1 队列的操作

        enqueue(Q,x)——将x加入Q的末尾

        dequque(Q)——移除队列Q的第一个元素

1.1.2 队列的实现

  1.         链表,同时记录链表的头节点和尾节点

        插入节点:tail.next=new; tail=tail.next

        删除节点: head=head.next

      2.        队列,记录头节点和尾节点 

1.2 栈

        后进先出 LIFO(Last In First Out)  

        一般用于DFS(深度优先遍历)(Depth First Search)

1.2.1 栈的操作

        push(S,x):将元素x放至stack的栈顶

        pop(S):将stack栈顶的元素弹出栈

        peek(S):查看stack栈顶的元素(但不弹出)

1.2.2 栈的实现

        只需要维护一个栈顶指针即可

2 广度优先搜索

其本质上还是SmartExplore,只是用了特定的数据结构来表示之。所以时间复杂度仍然是SmartExplore的O(m+n)

 2.1 广度优先实例说明

 

以此图为例:

一开始从1节点开始,队列中推入1(此时队列【1】) ,将1标记为visited(绿色节点)

左边就是目前索引到的数,右边是BFS对应的搜索树T(后同)

        然后弹出1节点,将1节点的unvisited邻居2和3插入队列(此时队列【2,3】),将2和3标记为visited

         然后弹出2节点,将2节点的unvisited邻居4和5插入队列(此时队列【3,4,5】),将4和5标记为visited 

         然后弹出节点3,将节点3的unvisited邻居7和8插入队列(此时队列【4,5,7,8】),将7和8标记为visited

然后弹出4,因为4的邻居都是visited了,所以没有需要入队列的(此时队列【5,7,8】)

然后弹出5,将5的unvisited邻居6入队列,并设置为visited(此时队列【6,7,8】)

 以此类推,最终有:

 2.2 带距离的广度优先

和前面的广度优先一样,只不过在其基础上多了一步设置和初始点之间的distance这一操作

时间复杂度依旧是O(m+n)

2.2.1 dist的性质

 

2.3 一致代价

当所有搜索步骤的花费相同时,宽度优先是最优算法,因为它永远只展开最浅的节点

当各搜索步骤的花费不一致时,我们能可以将BFS拓展到“一致代价问题”,我们将展开最浅一层的节点改成展开当前路径花费g(n)最小的节点

此时我们就不是使用BFS的数据结构 队列了,我们这里使用优先队列,存储开节点集(开节点指的是还没有被展开,但是即将要被展开的节点)

        但是注意一点,目标测试(判断是否找到了target),是在展开某一个子节点的时候进行,而不是生成这个节点的时候——>第一次遇到这个节点的时候,未必是最优的情况

        第二点需要注意的是,当发现一个子节点已经在优先队列中了,但是此次到这各子节点的路径花费更少——>可以用更短的花费代替子节点在优先队列中存储的原有花费。

2.3.1 伪代码 

2.4 BFS总结

  • 从u开始,“一圈一圈”向外计算
  • 使用邻接列表的话,时间复杂度为O(m+n)
  • 可以到达所有和u有路径的点
  • 可以用来计算在无权重图中和u的最短距离

3 DFS

DFS算法和BFS算法的时间复杂度一致,那为什么要DFS呢?因为空间复杂性。广搜算法是一圈一圈向外搜索的,所以理论上最差需要存储所有的节点;而DFS只需要存储从根节点到叶节点的一条路径,加上路径上每个节点未展开的节点即可

 3.1 手动维护栈的DFS

3.2 使用递归的DFS 

3.2.1 RAM,编译器与递归

        在RAM和硬件中,理论上是不支持递归的。那么递归是怎么实现的呢?

        在编译器编译的时候,如果需要递归,那么编译器自己会维护一个栈,来将递归的状态保存起来(不用人为去维护栈)

        比如我们先调用DFS(X),然后DFS(X)中会调用DFS(Y):

        那么我们在进行DFS(X)的过程中遇到了DFS(Y),那么我们就先把DFS(X)的状态(变量,寄存器状态等)先保存起来,放到栈里面去,然后执行DFS(Y)。当DFS(Y)执行完毕之后,将DFS(X)的状态弹出,恢复到DFS(X)未完成的那时候,然后继续DFS(X)的剩余语句。

3.3 DFS执行过程举例

假如有这样一个有向图

 

我们从1开始,那么1 先进栈,并将1设置为visited

然后看栈顶元素1,有没有unvisited的邻居,发现有(2),那么将2进栈,并将2设置为visited(此时栈的情况为【1,2】

         然后看栈顶元素2,看他有没有unvisited的邻居,发现有,是5,于是将5弹入栈中,并将5设置为visited(此时的栈情况为【1,2,5】)

然后看栈顶元素5,看看他有没有unvisited的邻居,发现有,是4,于是把4弹入栈中,并将4设置为visited(此时栈的情况为【1,2,5,4】)

然后看栈顶元素4,看看他有没有unvisited的元素,发现没有,那么将4pop出来

 然后看栈顶元素5,看看他有没有unvisited的元素,发现没有,那么将5pop出来

以此类推,直到栈为空为止。

此时我们从1出发可以到达的点就都遍历好了。

3.4 DAG

相当于把无向图中树的概念推广到有向图中

3.4.1 DAG的判别

当一个图G的DFS树没有back edge的时候(3.3例子中的红边),那么此时G是一个GAF

3.5 DFS的总结

  • 时间复杂度也是O(m+n) (因为也相当于是用另一种数据结构来实现SmartExplore
  • 帮助理解有向图结构

4 迭代加深算法 iterative deepening search

        把宽度优先和深度优先相结合,以便发现最好的搜索深度。这种方法一次设置搜索深度,从0,1,2,一直到找到解的深度

5 双向搜索


同时运行两个搜索程序,一个是从初始状态向后搜索,一个是从目标状态向前搜索

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

UQI-LIUWJ

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值