美文网首页
dfs与bfs的套路总结

dfs与bfs的套路总结

作者: hekirakuno | 来源:发表于2020-01-09 03:02 被阅读0次

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

    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模板吧。不过其实很容易就看明白了。
    白版纯手打代码,这个格式和空格和制表符好难搞,难受得不行。
    就这样吧,先假装写完啦~~~

    相关文章

      网友评论

          本文标题:dfs与bfs的套路总结

          本文链接:https://www.haomeiwen.com/subject/rtueactx.html