步步为营(十五)搜索(一)DFS 深度优先搜索
2015-07-30 16:47
411 查看
前方大坑预警!
先讲讲什么是搜索吧。
有一天你去一个果园摘梨子,果农告诉你,有一棵树上有一个金子做的梨子,找到就是你的,你该怎么找?
地图如下:
S 0 0 0 0
0 0 0 0 0
0 0 0 0 0
0 0 0 0 0
0 0 0 0 G
废话,挨个找呗~ 这就叫做搜索。
在一个区间内找到符合条件的值的过程,就是搜索。(?)
最简单的就是穷举,挨个找。因为都可以走,所以从(1,1)到(n,n)一路走过去,能找到就行。
但是果农告诉你,这个果园有的位置是肥料坑~你不能过去(当然,你非要去坑里游两圈也不拦着你),那么这个果园的地图就成了这个样子。
S 0 1 1 0
0 1 0 0 0
0 0 1 1 0
0 1 0 0 0
0 0 0 0 G
这个时候,果园又来了好几个摘梨子的,果农就说:“金梨子只有一个,你们谁最先找到就归谁!”
那么如何去寻找到一条最短的路程呢?
这时候就该用一些策略了,于是,DFS(深搜)登场了。
首先,为什么叫深度优先搜索?
深度,在这种环境下,可以看做离起始位置的步数。
更学术点的说法,可以看做“单位距离下,离起始状态的长度”
如果你喝水,水杯里刚开始有一升水,你每次喝掉一百毫升,那么你喝掉两百毫升的状态和喝掉三百毫升的状态相比,喝掉三百毫升的这个状态“深度更大”。
如果你在走地图,从(0,0)开始,一次走一步。那么你位于(2,3)的状态和你位于(4,5)的状态相比,(4,5)的这个状态“深度更大”
明白什么叫深度,那么DFS,也就是深度优先搜索的概念就可以明白了。
对于当前状态n,如果找到一个满足条件的下一个状态m,不管接下来还有没有符合条件的状态,都开始对m进行搜索。如果当前状态没有符合条件的下一个状态,就回溯到这个状态的上一个状态。也就是说,如果m没有符合条件的下一个状态点,就继续对n进行搜索。
那么可以用伪代码表示为:
那么对于果园的地图,我们的部分状态是这样的:
S 0 1 1 0
0 1 0 0 0
0 0 1 1 0
0 1 0 0 0
0 0 0 0 G
从S点开始,设S点坐标为(1,1)
(1,1)
->(1,2)
->(1,1)(注意,这里回溯到了(1,1)点)
->(2,1)
->(3,1)
->(3,2)
->(3,1)(回溯到3,1点)
->(4,1)
->(5,1)
……
->(5,5)
但是要注意,DFS由于递归调用,时间复杂度会很高(平均时间(N^3*1/3)),所以要注意优化的技巧。最常用的就是剪枝,对于这道题目来说,对于访问过的点不再进行二次访问,减小递归层数,就是剪枝的一种表现。
至于其他技巧…… 这些还是自己摸索的靠谱。
对于每种找到结果的方案,记录步数,最后便可获得最小的步数。但是,如果要记录获取最小步数的整个过程,DFS就显得力不从心,因为递归调用的问题,DFS并不能保证自己得到的当前解就是最优的解。
所以,DFS适合用来判断某个状态能否达到,或者能够有多少种方案取得最终解,不适合用来做状态记录。
而针对单个最优解和状态记录,更为合适的方法是BFS。
代表题目:N皇后问题
N皇后问题是一个经典的问题,在一个N*N的棋盘上放置N个皇后,每行一个并使其不能互相攻击(同一行、同一列、同一斜线上的皇后都会自动攻击)。
先讲讲什么是搜索吧。
有一天你去一个果园摘梨子,果农告诉你,有一棵树上有一个金子做的梨子,找到就是你的,你该怎么找?
地图如下:
S 0 0 0 0
0 0 0 0 0
0 0 0 0 0
0 0 0 0 0
0 0 0 0 G
废话,挨个找呗~ 这就叫做搜索。
在一个区间内找到符合条件的值的过程,就是搜索。(?)
最简单的就是穷举,挨个找。因为都可以走,所以从(1,1)到(n,n)一路走过去,能找到就行。
但是果农告诉你,这个果园有的位置是肥料坑~你不能过去(当然,你非要去坑里游两圈也不拦着你),那么这个果园的地图就成了这个样子。
S 0 1 1 0
0 1 0 0 0
0 0 1 1 0
0 1 0 0 0
0 0 0 0 G
这个时候,果园又来了好几个摘梨子的,果农就说:“金梨子只有一个,你们谁最先找到就归谁!”
那么如何去寻找到一条最短的路程呢?
这时候就该用一些策略了,于是,DFS(深搜)登场了。
首先,为什么叫深度优先搜索?
深度,在这种环境下,可以看做离起始位置的步数。
更学术点的说法,可以看做“单位距离下,离起始状态的长度”
如果你喝水,水杯里刚开始有一升水,你每次喝掉一百毫升,那么你喝掉两百毫升的状态和喝掉三百毫升的状态相比,喝掉三百毫升的这个状态“深度更大”。
如果你在走地图,从(0,0)开始,一次走一步。那么你位于(2,3)的状态和你位于(4,5)的状态相比,(4,5)的这个状态“深度更大”
明白什么叫深度,那么DFS,也就是深度优先搜索的概念就可以明白了。
对于当前状态n,如果找到一个满足条件的下一个状态m,不管接下来还有没有符合条件的状态,都开始对m进行搜索。如果当前状态没有符合条件的下一个状态,就回溯到这个状态的上一个状态。也就是说,如果m没有符合条件的下一个状态点,就继续对n进行搜索。
那么可以用伪代码表示为:
//对于状态a进行判断 int fun(status a) { if(a是结束条件) { 找到解,进行操作 } for(进行寻找) { if(找到了符合条件的下一个状态b) { //对B进行判断 fun(b); } } 找不到符合条件的状态,退出函数,返回到上一层。 }
那么对于果园的地图,我们的部分状态是这样的:
S 0 1 1 0
0 1 0 0 0
0 0 1 1 0
0 1 0 0 0
0 0 0 0 G
从S点开始,设S点坐标为(1,1)
(1,1)
->(1,2)
->(1,1)(注意,这里回溯到了(1,1)点)
->(2,1)
->(3,1)
->(3,2)
->(3,1)(回溯到3,1点)
->(4,1)
->(5,1)
……
->(5,5)
但是要注意,DFS由于递归调用,时间复杂度会很高(平均时间(N^3*1/3)),所以要注意优化的技巧。最常用的就是剪枝,对于这道题目来说,对于访问过的点不再进行二次访问,减小递归层数,就是剪枝的一种表现。
至于其他技巧…… 这些还是自己摸索的靠谱。
对于每种找到结果的方案,记录步数,最后便可获得最小的步数。但是,如果要记录获取最小步数的整个过程,DFS就显得力不从心,因为递归调用的问题,DFS并不能保证自己得到的当前解就是最优的解。
所以,DFS适合用来判断某个状态能否达到,或者能够有多少种方案取得最终解,不适合用来做状态记录。
而针对单个最优解和状态记录,更为合适的方法是BFS。
代表题目:N皇后问题
N皇后问题是一个经典的问题,在一个N*N的棋盘上放置N个皇后,每行一个并使其不能互相攻击(同一行、同一列、同一斜线上的皇后都会自动攻击)。
#include<stdio.h> #define N 15 int n; //皇后个数 int sum = 0; //可行解个数 int x ; //皇后放置的列数 int place(int k) { int i; for(i=1;i<k;i++) if(abs(k-i)==abs(x[k]-x[i]) || x[k] == x[i]) return 0; return 1; } int queen(int t) { if(t>n && n>0) //当放置的皇后超过n时,可行解个数加1,此时n必须大于0 sum++; else for(int i=1;i<=n;i++) { x[t] = i; //标明第t个皇后放在第i列 if(place(t)) //如果可以放在某一位置,则继续放下一皇后 queen(t+1); } return sum; } int main() { int t; scanf("%d",&n); t = queen(1); if(n == 0) //如果n=0,则可行解个数为0,这种情况一定不要忽略 t = 0; printf("%d",t); return 0; }
相关文章推荐
- 搜狗百度360市值齐跌:搜索引擎们陷入集体焦虑?
- 本人即将筹备败家日志,敬请期待!
- IE:使用搜索助手
- 动易2006序列号破解算法公布
- Ruby实现的矩阵连乘算法
- C#插入法排序算法实例分析
- 超大数据量存储常用数据库分表分库算法总结
- C#数据结构与算法揭秘二
- C#冒泡法排序算法实例分析
- 算法练习之从String.indexOf的模拟实现开始
- C#算法之关于大牛生小牛的问题
- C#实现的算24点游戏算法实例分析
- c语言实现的带通配符匹配算法
- 浅析STL中的常用算法
- 算法之排列算法与组合算法详解
- C++实现一维向量旋转算法
- Ruby实现的合并排序算法
- C#折半插入排序算法实现方法
- 基于C++实现的各种内部排序算法汇总
- C++线性时间的排序算法分析