dfs与bfs的套路总结

2020-01-09  本文已影响0人  hekirakuno

为了我亲爱的鸽鸽!
致敬!

dfs和bfs,顾名思义就是,深度优先搜索和广度优先搜索的简称。那么什么是深度优先什么是广度优先?
举个简单的例子吧。
比如说电话的9键中。
2--a,b,c
3--d,e,f
4--g,h,i
……
有着这样的对应关系。
那么当我依次按下,2,3,4的时候,一共会有多少种组合呢?
首先我们用深度优先搜索的思路来讲。
深度优先,就是每次都会遍历得到一个结果,并将其放入结果集合中。每次都要走到无路可走,才知道回头。
最直观的反映,让我们来看结果集的样子。它会是这样增加的:
[{a,d,g}]-->[{a,d,g},{a,d,h}]-->[{a,d,g},{a,d,h},{a,d,i}]-->[{a,d,g},{a,d,h},{a,d,i},{a,e,g}]……
先找到一个结果,然后通过回溯查找,每次都在集合中增加一个可达结果。

为了迅速找到不同,接下来我们用广度优先搜索的思路来讲。
广度优先,逐层遍历,当把第一层所有情况都记录下以后,再进行第二层的遍历。
最直观的反映在结果集的样子。它会是这样增加的:
[{a},{b},{c}]-->
[{a,d},{a,e],{a,f}]-->[{a,d},{a,e],{a,f},{b,d},{b,e},{b,f}]-->[{a,d},{a,e],{a,f},{b,d},{b,e},{b,f},{c,d},{c,e},{c,f}]-->
[{a,d,g},{a,d,h},{a,d,i}……]
每次都在之前已有记录的基础上,遍历当前行拼接之后的所有情况。


一般来说,这种找到【组合】类型,获得【所有结果】,列出【所有情况】的题目,就可以考虑使用这两种解法。【贪心,动归等不要杠精,我只是说暴搜解而已】
那么,他们分别更加适用的场景是什么呢?
我们观察和思考了算法之后,很容易想到,当结果集庞大,每个最终结果的深入层次比较浅时,或者只需求最终结果时,选择bfs更快更方便;当每个最终结果都需要寻觅非常深入,或者存在回溯查找时,选择dfs会更好。
毕竟,广度优先和深度优先的名字摆在这里。自然是在得意领域更加肆意了。

好的,我们看下它们的必要条件:可重复利用上一次的结果 || 需要当前结果的状态 || 需要记录当前结果||需要记录所有已知情况

需要之前的状态和结果记录啊。也就是说可以选择用递归的方式。

而且,其实dfs和bfs的代码真的很有套路。基本上你确定了用哪一个之后,就可以套用类似模板的写法了。干倒一般性难度的题目不成问题。

回归本质,我们用这道题简化版来举个例子:
如图,二维数组,从每个子数组中取出一个值,找到所有排列组合。
String arr =
{{a,b,c},
{d,e,f},
{g,h,i}}

//dfs模式样板
public class solution{
    public List<List<String>> getResult(String[][] arr){
        //第一行,定义结果集合
        List<List<String>> result = new ArrayList<List<String>>();
        //第二行,定义每次单独产生的结果
        List<String> temp = new ArrayList<String>();
        //第三行,传入第一次dfs的条件(空的结果数组,空的tmp,开始深度0,目标数组),通过dfs拿到最终的结果。
        dfs(result,temp,0);
        //第四行,返回结果
        return result;
    }
    //dfs的逻辑
    public void dfs(List<List<String>> result,List<String> temp,int depth){
        //递归出口。返回条件-->找到一个有效结果(或者说,一条道走到死,走到无路可走,最深处)
        if(arr.length() == depth){
            //将当前结果添加到结果集合中
            result.add(new ArrayList<String>(temp));
            //如果查找到一条结果,则回溯到上个状态
            return;
        }else{
            //dfs相当于用depth这个参数做了外层的,也就是深度的控制,让你每次都更加向内走
            for(int i = 0;i<arr[depth].length;i++){
              //添加当前深度的当前字符
              temp.add(arr[depth][i]);
              //带着当前的中间态结果temp去下一层,直到找到最深一层。然后记录结果。return当前调用。
              dfs(result,temp,depth+1);
             //举例来说:第一次搜索后得到的结果是[a,d,g],此时深度为目标深度,因此,记录结果到result中,然后return。
            //此时回到**dfs(result,temp,depth+1);**这一句。此时depth为1,也就是倒数第二层。temp中有[a,d,g]三个对象,result中有一条记录[[a,d,g]]。然后,我们删掉最后的对象g,让temp为[a,d]的状态,来到当前深度的第二个字符面前。
              temp.remove(temp.size()-1);
             //然后temp会变成[a,d,h],然后再进入最后一层。然后再记录到result中,然后再回到倒数第二层。以此类推。
        }
    }
}

所以当最后dfs遍历完成后,result数组中就保存了所有结果。
有没有稍微明白一点点了呢?
所有的dfs基本都是这样的套路。

//结果函数
solution(){
    //最终结果集
    new resultSets;
    //单个结果
    new resultOne;
    //初始调用dfs
    dfs(resultSets,resultOne,0);
    return result;
}
dfs(T<M> resultSets,M resultOne, int depth){
    //递归出口
    if(depth == 最深){
         //这里每一次的单个结果都需要new一次
         resultSets.add(new resultOne);
         //回溯
         return;
    }else{
          //当前深度的数据数据获取
          for(int i = 0;i<arr[depth],length;i++){
                //记录当前深度的当前下标的数据
                resultOne.add(arr[depth][i]);
                //寻找下一层
                dfs(resultSets,resultOne,depth+1);
                //清除下一层的对象数据
                resultOne.remove(resultOne.size()-1);
          }
    }
}

好的,接下来是bfs,同样的题目。
我们来看bfs

class Solution {
    public List<List<String>> getResult(String[][] arr){
        //第一行,定义结果集合
        List<List<String>> result = new ArrayList<List<String>>();
        //第三行,使用广度优先搜索的方式
        result = bfs(result,0);
        //第四行,返回结果
        return result;
    }
    public List<List<String>> bfs(List<List<String>> result,int depth){
        //递归出口
        if(depth==arr.length){
            return;
        }else{
          List<List<String>> tempResult = new ArrayList<List<String>>();
          //不是第一行了,循环遍历,补充结果集
          if(result.size()>0){
              for(List<String> temp:result){
                  for(int i = 0;i<arr[depth].length;i++){
                      tempResult.add(temp.add(arr[depth][i]));
                  }
              }
          }else{
              //第一次遍历,初始化结果集,添加第一层的值
               for(int i = 0;i<arr[depth].length;i++){
                   tempResult.add(new ArrayList(arr[depth][i]));
               }
           }
          bfs(tempResult,depth+1);
    }
}

好困啊···明天再整理bfs模板吧。不过其实很容易就看明白了。
白版纯手打代码,这个格式和空格和制表符好难搞,难受得不行。
就这样吧,先假装写完啦~~~

上一篇下一篇

猜你喜欢

热点阅读