来自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. HDU 2191 悼念汶川地震(多重背包)

    思路: 多重背包转成01背包,怎么转?把一种大米看成一堆单个的物品,每件物品要么装入,要么不装.复杂度比01背包要大.时间复杂度为O(vns)(这里S是所有物品的数量s之和).这个做法太粗糙了,但就是 ...

  2. 制作X509证书

    makecert -r -pe -n "CN=XXX" -b 01/01/2005 -e 01/01/2020 -sky exchange -ss my

  3. C基础的练习集及测试答案(40-50)

    40.(课堂)打印杨辉三角型前10行 #if 0 40.(课堂)打印杨辉三角型前10行 思路分析: 一.打印十行杨辉三角得第十行长度为十,所以建立一个长度为十的数组,作为每行的数据存储 二.按 0-9 ...

  4. linux 命令——9 touch (转)

    linux的touch命令不常用,一般在使用make的时候可能会用到,用来修改文件时间戳,或者新建一个不存在的文件. 1.命令格式: touch [选项]... 文件... 2.命令参数: -a    ...

  5. Python变量状态保持四种方法

    Python状态保持 ​ 全局 global def tester(start): global state state = start def nested(label): global state ...

  6. idea搭建ssm

    第一步:打开intellij idea,创建maven项目 参考:http://blog.csdn.net/w8897282/article/details/71173211  1.创建一个maven ...

  7. NYOJ-596-谁是最好的Coder

    原题链接 谁是最好的Coder 时间限制:1000 ms  |  内存限制:65535 KB 难度:0 描述 计科班有很多Coder,帅帅想知道自己是不是综合实力最强的coder. 帅帅喜欢帅,所以他 ...

  8. 设置和重置ssh key

    查看本地是否有已经生成好的ssh key $ cat ~/.ssh/id_rsa.pub 若有,先删除: $ cd ~ $ rm -rf .ssh 重新生成ssh key ssh-keygen -t ...

  9. Videos

    Videos 时间限制: 1 Sec  内存限制: 128 MB提交: 17  解决: 7[提交] [状态] [讨论版] [命题人:admin] 题目描述 C-bacteria takes charg ...

  10. (转)国内外优秀的Web前端工程师

    1. 国内外优秀的Web前端工程师 寻找Github.微博.知乎等技术社区上比较活跃.影响力大的圈内大神,供大家膜拜! 视野所限,未必全面,欢迎大家推荐.自荐. 排名不分先后,序号只为标记方便. 提供 ...