来自https://github.com/soulmachine/leetcode

广度优先搜索

输入数据:没有什么特征,不像dfs需要有递归的性质。如果是树/图,概率更大。

状态转换图:数或者DAG图(有向无环图)

求解目标:求最短

思考的步骤:

,是求路径长度,还是路径本身(动作序列)

  a,如果是求路径长度,则状态里面要存路径长度(或双端队列+一个全局变量)

  b,如果是求路径本身或动作序列

    i,要用一颗树存储宽搜过程的路径

    ii,是否能够预算状态个数的上限?

      能够预估状态总数,则开辟一个大数组,用树的双亲表示法;

      如果不能预估状态总数,则要使用一颗通用的树,这也是第4步的需要不充分条件。

,如何表示状态?即一个状态需要存储哪些必要的数据,才能够完整提供如何扩展到下一步状态的所有信息。一般记录当前位置或整体局面。

,如何扩展状态?这一步和第2步有关,状态里记录的数据不同,扩展方法就不同。

  对于固定不变的数据结构(一般题目直接将给出,作为输入数据),如二叉树、图。扩展方法简单,直接往下一层走。

  对于隐式图,要先在第一步里想清楚状态所带的数据,想清楚了这一点,就可以直到如何扩展了。

,如何判断重复?

  如果状态转换图是一棵树,则永远不会出现回路,不需要判重。

  如果状态转换图是一个图(这时候是一个图上的BFS),则需要判断重复。

    a,如果是求最短路径长度或一条路径,则只需要让“点”(就是状态)不重复出现,即可保证不出现回路

    b,如果是求所有路径,注意此刻,状态转换图是DAG,即允许两个父节点指向同一个字节点。具体实现时,每个节点要“延迟”加入到已访问集合visited,

     要等一层全部访问完后,再加入到visited集合。

    c,具体实现?

      i,状态是否存在完美哈希方案?即将状态一一映射到整数,互相之间不会冲突。

      ii,如果不存在,则需要使用通用的哈希表(自己实现,或使用STL,例如unordered_set)来判重;

        自己实现的哈希表,如果能够预估状态个数的上限,则可以开两个数组,head和next表示哈希表(下面有例子)。

      iii,如果存在,则可以开一个大布尔数组,来判重,且此刻可以精确计算出状态总数,而不仅仅是预估上限。

,目标状态是否已知?

  如果题目已经给出了目标状态,可以带来很大便利,这时候可以从起始状态出发,正向广搜,

  也可以从目标状态出发,逆向广搜,

  也可以同时出发,双向广搜。

-----

代码模板?

广搜需要一个队列,用于一层一层扩展,一个hashset,用于判重,一棵树(只求长度时不需要)用于存储整棵树。

  对于队列,可以用queue,也可以把vector当作队列使用。当求长度时,有两种做法:

    1,只用一个队列,但在状态结构体state_t里面增加一个整数字段level,表示当前所在的层次,当碰到目标状态时,直接输出level即可。

    这个方案可以很容易编程A*搜索,把queue替换为priority_queue即可。

    2,用两个队列,current,next,分别表示当前层次和下一层,另设一个全局整数level,表示层数(即路径长度),当碰到目标状态,输出level即可。

    这个方案,状态里可以村路径长度,只需全局设置一个整数level,比较节省内存;

  对于hashset,

    如果有完美哈希方案,用布尔数组(bool visited[STATE_MAX]或vector<bool> visited(STATE_MAX,false)来表示;

    如果没有完美哈希方案,需要用STL里的set或unordered_set。

  对于树,

    如果用STL,可以用unordered_map<state_t,state_t> father表示一棵树,代码很简洁。

    如果能够预估状态总数的上限(设为STATE_MAX),可以使用数组state_t nodes[STATE_MAX],即树的双亲表示法来表示树,效率更高,但是代码更高。

代码在这里

===============================================================

深度优先搜索

适用场景:

输入数据,如果是递归数据结构,如单链表,二叉树,集合,则百分百可以用深搜;如果是非递归数据结构,如一维数组,二维数组,字符串,图则概率小一些。但是也有的。

状态转换图,树或者图

求解目标:必须走到最深处(例如对于树,必须要走到叶子节点)才能得到一个解,适合是恩搜。

思考步骤:

1,深搜常见的三个问题,求可行解的总数,求一个可行解,求所有可行解。

  a,如果是路径条数,则不需要存储路径

  b,如果哦是路径本身,则要用一个数组path存储路径。

    跟宽搜不同,宽搜虽然也是一条路径,但是需要存储扩展过程中的所有路径,在没找到答案之前所有路径都不能放弃。

    而深搜,在搜索过程中始终只有一条路径,因此用一个数组就可以了。

2,只要求一个解,还是求所有解。

  只求一个解,找到一个解就返回。

  求所有解,找到一个后,还要继续遍历。

  广搜一般只要求一个解,(广搜也会求所有解,这时需要扩展到所有叶子节点,相当于在内存中存储整个状态转换图,非常占内存,因此广搜不适合求这类问题)。

3,如果表示状态?

  即一个状态需要存储哪些必要的数据,才能够完整提供如何扩展到下一步状态的所有信息。跟广搜不同,深搜的惯用写法,不是把数据记录在状态struct里,而是

  添加函数参数(有时为了节省递归堆栈,用全局变量),struct里的字段与函数参数一一对应。

4,如何扩展状态?

  这一步跟上一步相关。状态里记录的数据不同,扩展方法就不同,对于固定不变的数据结构(一般题目直接给出,作为输入数据),二叉树、图,扩展方法很简单,直接往下一步走就行了。对于隐式图,要先在第1步中想清楚状态所带的数据,才能直到扩展。

5,终止条件?

  是指到了不能扩展的末端节点。对于树,是叶子节点。对于图或隐式图,是出度为0的节点。

6,收敛条件?

  是指找到一个合法解的时刻。

  如果正向深搜(父节点处理完了,才进行递归,即父状态不依赖子状态,递归语句在最后,尾递归),则是指是否达到目标状态;

  如果是逆向搜索,(处理父状态时需要先知道子状态的结果,此时递归语句不在最后),则是指是否到达初始状态。

  很多时候,终止状态和收敛条件是合二为一的,很多人不会区分这两种条件。仔细区分这两种条件,是有必要的。

  为了判断是否到了收敛条件,要在函数接口里用一个参数记录当前的位置(或距离目标还有多远)。

  如果是求一个解,直接返回这个解;如果是求所有解,要在这里搜集,即把第一步中表示路径的数组path[]复制到解集合里。

7,关于判重

  a,是否需要判重?

    如果状态转换图是一颗树,则不需要判重,因为在遍历的过程中不会出现重复;

    如果状态状态图是一个DAG,则需要判重。这一点和BFS不一样,BFS的状态转换图总是DAG,必须判重。

  b,怎么判重,跟广搜一样。同时,DAG说明存在重叠子问题,此时可以用缓存加速。见第8步(下一步)。

8,如何加速?

  a,剪枝。深搜一定要好好考虑怎么剪枝,成本小收益大,加几行代码,就能大大加速。

      这里没有通用的方法,只能具体问题,具体分析,要充分观察,充分利用各种信息来剪枝,在中间节点提前返回。

  b,缓存。

    i,前提条件:状态转换图是一个DAG。DAG=>存在重叠子问题=>子问题的解会被重复利用,用缓存自然会由加速效果。

     如果依赖关系是树状的(例如树,单链表等),没必要加缓存,因为子问题只会一层层往下,用一次就再也不会用到,加了缓存也没什么效果。

    ii,具体实现:可以使用数组或hashmap。

      维度简单的,用数组;

      维度复杂的,用hashmap,c++有map,c++11以后有unordered_map,比map快。

代码模板

/**
*@brief dfs模板
*@param[in] input 输入数据指针
*@param[out] path 当前路径,也是中间结果
*@param[out] result 存放最终结果
*@param[inout] cur or gap 标记当前位置或距离目标的距离
*@return 1,路径长度,2路径本身
*/
void dfs(type &input,type &path,type &result,int cur or gap){
if(data is valid) return;//终止条件
if(curr==input.size()){// 收敛条件,正向
///if(gap==0){}///逆向
result.push_back(path);
} if(可以剪枝) return; for(...){///执行所有的可能的扩展
执行动作,修改path
dfs(input,step+,or gap--,result);
恢复path
}
}

---------

深搜和回溯法?

深搜:维基百科

回溯法:维基百科

回溯法=深搜+剪枝,一般在用深搜时,或多或少会用到剪枝,因此深搜和回溯法没有什么不一样的。

深搜与递归recursion?

深搜,是逻辑意义上的算法;递归是物理意义上的实现,递归和迭代iteration相对应。

深搜,可以用递归实现,也可以用栈实现。而递归,一般总是用来实现深搜,可以说,递归一定是深搜,但是深搜不一定递归。

递归由两种加速策略,1剪枝,对中间结果判断,提前返回。2缓存,缓存中间结果,防止重复计算,用空间换时间。

其实,递归+缓存,就是memorization(翻译为备忘录法),就是top-down with cache(自顶向下+缓存),它是Donald Michie在1968年创造的术语,表示

  一种优化技术,在top-down形式的程序中,使用来避免重复计算,可以加速。

memorization不一定用递归,就像深搜不一定用递归一样,可以在迭代iterative中使用memorization。递归也不一定用memorization,可以使用它来加速,但也不是必须的。

  只有在使用了缓存时,它才是memorizaiton。

DFS和BFS遍历的问题的更多相关文章

  1. 邻接矩阵实现图的存储,DFS,BFS遍历

    图的遍历一般由两者方式:深度优先搜索(DFS),广度优先搜索(BFS),深度优先就是先访问完最深层次的数据元素,而BFS其实就是层次遍历,每一层每一层的遍历. 1.深度优先搜索(DFS) 我一贯习惯有 ...

  2. 图的DFS与BFS遍历

    一.图的基本概念 1.邻接点:对于无向图无v1 与v2之间有一条弧,则称v1与v2互为邻接点:对于有向图而言<v1,v2>代表有一条从v1到v2的弧,则称v2为v1的邻接点. 2.度:就是 ...

  3. 列出连通集(DFS及BFS遍历图) -- 数据结构

    题目: 7-1 列出连通集 (30 分) 给定一个有N个顶点和E条边的无向图,请用DFS和BFS分别列出其所有的连通集.假设顶点从0到N−1编号.进行搜索时,假设我们总是从编号最小的顶点出发,按编号递 ...

  4. 判断图连通的三种方法——dfs,bfs,并查集

    Description 如果无向图G每对顶点v和w都有从v到w的路径,那么称无向图G是连通的.现在给定一张无向图,判断它是否是连通的. Input 第一行有2个整数n和m(0 < n,m < ...

  5. 数据结构(三十二)图的遍历(DFS、BFS)

    图的遍历和树的遍历类似.图的遍历是指从图中的某个顶点出发,对图中的所有顶点访问且仅访问一次的过程.通常有两种遍历次序方案:深度优先遍历和广度优先遍历. 一.深度优先遍历 深度优先遍历(Depth_Fi ...

  6. PAT Advanced 1106 Lowest Price in Supply Chain (25) [DFS,BFS,树的遍历]

    题目 A supply chain is a network of retailers(零售商), distributors(经销商), and suppliers(供应商)– everyone in ...

  7. Clone Graph leetcode java(DFS and BFS 基础)

    题目: Clone an undirected graph. Each node in the graph contains a label and a list of its neighbors. ...

  8. 用邻接表实现DFS和BFS

    #include <stdio.h> #include <stdlib.h> #define MAXVERTEX 10 typedef char VertexType; //顶 ...

  9. DFS - leetcode [深度优先遍历]

    最短路径=>BFS    所有路径=>DFS 126. Word Ladder II BFS+DFS: BFS找出下一个有效的word进队 并记录step 更新两个变量:unordered ...

随机推荐

  1. IE的Trident引擎下实现C++和Javascript相互调用

    我们知道实现C++和Javascript通讯有下表5种接口: 引擎 编写语言 API接口 C.C++与JavaScript交互(变量.函数.类) vc2005编译静态库的大小 示例EXE的大小 执行. ...

  2. Javascript 向量

    向量 既有大小又有方向的量叫做向量(亦称矢量),与标量相对,用JS实现代码如下,直接搬miloyip的了 Vector2 = function(x, y) { this.x = x; this.y = ...

  3. EOS签名R值过大导致报错"is_canonical( c ): signature is not canonical"

    简要 EOS中规定签名的R和S必须同时小于N/2才是合法的签名. 详细 EOS签名交易相对BTC和ETH来说,对签名的要求更加严格了. BTC中bip62规定了((Low S values in si ...

  4. ssh: connect to host localhost port 22: Connection refused

    1.hadoop安装好之后,执行ssh localhost无法执行, 提示“ssh: connect to host localhost port 22: Connection refused”. 2 ...

  5. AngularJS 数字

    AngularJS数字就像JavaScript数字 AngularJS实例: <!DOCTYPE html><html><head><meta http-eq ...

  6. 梁勇 java教材 编程练习题 第二章 2.6 键盘 读取一个在0 到 9999 之间的整数,并将该整数的各位数字相加。

    import java.util.Scanner; /** * 需求:从键盘输入任意0~9999之间的整数,输出这个整数各位数字之和. * 思路:方法一,使用扫描器Scanner类,扫描控制台输入流 ...

  7. iOS进阶面试题

    1. 风格纠错题 修改完的代码: 修改方法有很多种,现给出一种做示例: // .h文件 // http://weibo.com/luohanchenyilong/ // https://github. ...

  8. es6之Object扩展及内部属性的总结

    对象扩展: 1.Object.is(A,B) :比较两个值是否相等,取代===运算:只要值相等代表相等:其中NAN和NAN相等:+0和-0不相等: 2.Object.assign(target,sou ...

  9. Linux网络编程之"获取网络天气信息"

    需求分析: 1.需要Linux c 网络编程基础, 2.需要了解 http 协议 3.需要天气信息相关api(可以从阿里云上购买,很便宜的!) 4.需要cJSON解析库(因为获取到的天气信息一般是用c ...

  10. pycahrm git配置笔记

    1. 在file - setting - plugins 中查看是否有github插件, 此处是用于处理插件位置