深搜与广搜
系列:算法
搜索其实挺常见的,但是与搜索引擎并不一样,并不是再庞大的数据中找到匹配的(更类似的是字符串匹配),dfs与bfs更多是指在所有可能的结果中找到可用的结果和对应的路径(后者往往dfs比bfs更加自然,因为dfs常见的实现方式是递归,而bfs基本都是靠队列实现,不过下文我将使用另一种方式指出二者的共通性)
dfs,即深度优先搜索,重点在于深,即一条路走到死,不行再换。这也是为什么常见递归写法的原因,附上简单的伪代码示例:
cpp
| 1 | int dfs(int now, int last) { |
| 2 | if(now满足条件) { |
| 3 | do_some_thing(); |
| 4 | return 1; |
| 5 | } |
| 6 | do_onther_thing(); |
| 7 | for(每种可能选择) { |
| 8 | if(是新的情况) |
| 9 | dfs(选择, now); |
| 10 | } |
| 11 | return 0; |
| 12 | } |
不难看出,dfs只需要记录return 1;时的选择就可以做到记录下找到结果对应的路径对的原因。而且dfs的优势在于完整遍历每一种情况,同时思维更符合人脑(方便调试)
至于bfs,广度优先搜索,则是另一种思维,先把每种路都看一遍,保证视野足够的广,正因如此,不太适合递归一类的方式,常见的是使用队列实现:
cpp
| 1 | int bfs() { |
| 2 | queue q; |
| 3 | q.push(初始位置); |
| 4 | while(!q.empty()) { |
| 5 | do_some_thing(); |
| 6 | int now = q.front(); |
| 7 | q.pop(); |
| 8 | for(每种可能选择) |
| 9 | if(是新的情况) { |
| 10 | q.push(选择); |
| 11 | 记录已被处理 |
| 12 | } |
| 13 | } |
| 14 | } |
bfs的优势主要在于能够找到最短路,比如Dijkstra就是典型的bfs。
但是不妨发散一下思维,递归的本质是让调用栈决定了谁先谁后的问题,所以两者本质上是高度相同的,完全可以尝试:
cpp
| 1 | int dfs_or_bsf(type stack_or_queue) { |
| 2 | stack_or_queue t; |
| 3 | t.push(初始位置); |
| 4 | while(!t.empty()) { |
| 5 | do_some_thing(); |
| 6 | int now = t.top(); |
| 7 | t.pop(); |
| 8 | for(每种可能选择) |
| 9 | if(没被使用过) |
| 10 | t.push(选择); |
| 11 | } |
| 12 | } |
虽然这种行为并没有什么实际意义,但是自认为对于算法的理解是有着加深作用的,至少可以明白,dfs与bfs,两者不只是名字上接近,实际上内核也只有一个queue与stack的区别。未来还会接触不少搜索算法,最后的dfs_or_bsf可以是用不少情况,基本都是type的各种变化。
如果容器是LIFO(栈),就是DFS;如果是FIFO(队列),就是BFS;如果是优先队列,就是带权搜索(如Dijkstra、A*)。