LeetCode刷题 - 广度优先搜索与深度优先搜索小结

一.广度优先搜索

一般用来:

1.遍历树结构(level order)

2.遍历图结构(BFS,Topological)

3.遍历二维数组

模板

// 计算从起点start到终点target的最近距离
int BFS(Node start,Node target){
  Queue<Node> q; // 核心数据结构
  Set<Node> visited; // 避免走回头路

  q.offer(start); // 将起点加入队列
  visited.add(start);
  int step = 0; // 记录扩散的步数

  while(q not empty){
    int sz = q.size();
  /* 将当前队列中的所有节点向四周扩散 */
    for(int i = 0;i < sz;i++){
      Node cur = q.poll();
      /* 划重点:这里判断是否到达终点 */
      if(cur is target)
        return step;
      /* 将 cur 的相邻节点加入队列 */
      for(Node x: cur.adj())
        if(x not in visited){
          q.offer(x);
          visited.add(x);
        }
     }
     /* 划重点:更新步数在这里 */
     step++;
  }
} 

优点

对于解决最短或最少问题特别有效,而且寻找深度小,但缺点是内存消耗费量大(需要开大量的数组单元用来存储状态,取决于树本身的形状,扁平还是长条)

数据结构上的运用

BFS选取状态用队列的形式,先进先出

总结

bfs最擅长解决哪一类问题?bfs与dfs的效率哪个更好?

首先,bfs最擅长解决哪一类问题?bfs和dfs的效率哪个更好?

1.比如求最少步数的解,最少交换次数的解,因为bfs搜索过程中遇到的解一定是离根最近的,所以遇到一个解,一定就是最优解,此时搜索算法可以终止。这个时候不适合使用dfs,因为dfs搜索到的解不一定是离根最近的,只有全局搜索完毕,才能从所有解中找出离根最近的解

2.dfs是空间效率高,dfs不需要保存搜索过程中的状态(在heap中纵向),而bfs在搜索过程中需要保存搜索过的状态(横向level),而且一般情况需要一个队列来记录。详细来说的话取决于树(图)的形状,扁的还是长条形

3.dfs适合搜索全部的解,因为要搜索全部的解,那么bfs搜索过程中,遇到离根最近的解,并没有什么用,也必须遍历完整棵搜索树,dfs搜索也会搜索全部,但是相比dfs不用记录过多信息(Java heap帮你保存了,除非overflow需要自己外部开一个空间做stack),所以搜索全部解的问题,dfs显然更加合适,一般情况下,dfs也需要高效的剪枝操作

4.可以双向bfs优化搜索的速度,进阶还会有 A * search

二.深度优先搜索

深度优先遍历的主要思想是:首先以一个未被访问过的顶点作为起始顶点,沿当前顶点的边走到未访问过的顶点;当没有未访问过的顶点时,则回到上一个顶点,继续试探访问别的顶点,直到所有的顶点都被访问。

沿着某条路径遍历直到末端,然后回溯,再沿着另一条进行同样的遍历,直到所有顶点都被访问过为止。

模板

result = []
def backtrack(路径,选择列表):
  if 满足结束条件:
    result.add(路径)
    return
  
  for 选择 in 选择列表:
    做选择
    backtrack(路径,选择列表)
    撤销选择

优点

扁平型tree内存开销较小

能处理子节点较多或树层次过深的情况(相比BFS)

一般用于解决连通性问题(是否有解)

对于解决遍历和求所有问题有效,对于问题搜索深度小的时候处理速度迅速

缺点

只能寻找有解但无法最快找到最优解(寻找最优解同样要遍历所有路径)

数据结构上的运用

DFS用递归的形式,用到了栈结构,先进后出

复杂度

DFS的复杂度与BFS的复杂度大体一致,不同之处在于遍历的方式与对于问题的解决出发点不同,DFS适合目标明确,而BFS适合大范围的寻找

一般使用场景

1.模板dfs,mask举例dfs

2.外部空间dfs(用stack写成iterative way,for example tree traversal)

3.dfs + memo(DP剪枝)

4.使用在模拟流程,寻找所有情况全排列解

剪枝优化

剪枝的题目一般可以使用二分法去做,也相当于增加了限制条件

一些题目也可以使用状态压缩DP来解决,比如人数少的时候,12个人的状态都压缩在一个integer里面,dfs + memo

一般简直优化入手点

1.优化搜索顺序

在一些题目中,可以通过对子问题分支进行分析,先解决相对简单的子问题从而使尚未解决的子问题得到简化,通过对搜索顺序的优化可以实现这一点

2.排除冗余信息

对限制条件进行分析,不要额外添加没有意义的搜索规则

3.可行性剪枝

对于显然不包含目标状态的搜索方向及时停止搜索,转而向可能包含目标状态的分支进行搜索

4.最优化剪枝

每次搜索完成后更新当前得到的最优状态/最优解,在每次搜索开始前判断当前解是否已经比上次得出的状态/解更劣?如果是则停止本次搜索,转向其他搜索分支

5.记忆化搜索

这是DP部分内容

DFS的优化

1.sort倒序,task先做大的这样可以累积时间先达到终止条件

2.global的result,如果我们是求最小值,当过程中结果已经大于res的时候我们就直接停止

3.跳过重复的元素,类似permutation里面

4.改变搜索思路,遍历task,或者遍历worker可以大幅提升速度

int res;
public int minSessions(int[] tasks,int sessionTime) {
  Arrays.sort(tasks);                      //1.剪枝,从大到小
  dfs(tasks.length - 1,0);
  return res;
}

private void dfs(int taskID,int sessionCount) {
  if (sessionCount > res) return;          //2.剪枝,和global比较
  if (taskID < 0) {                        //ID到头,统计最小
    res = Math.min(res,sessionCount);
  }
  for (int i = 0; i < sessionCount; i++) { //3.常规backtracking,尝试较小的部分
    if (i > 0; i < sessionCount; i++) {    //4.一样的只取一个
    sessions[i] += tasks[taskID];
    dfs(taskID - 1, sessionCount);
    sessions[i] -= tasks[taskID];
  }
}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值