2019.4.22更新
前言:本文用我自己能懂的方法进行叙述,不保证正确,本文涉及到的搜索均指bfs和dfs
搜索的基本介绍
假设一个问题可以用搜索算法来解决,那么这个问题一定可以被描述成这样:
有一个“类”,给出它的各个状态的约束条件(成为正解的条件),求满足题目要求的一个状态。
这个类的状态集(满足约束条件)也一定可以依据某种顺序被枚举出来,通过这种顺序枚举这些状态的过程叫搜索。
如果将每一个独立状态看做 一个图中的 一个节点,现在将这些节点
按照搜索过程中被访问到的顺序(每个状态被枚举到的顺序)排成一列,
这一个“列表”一定可以由一颗树的某种遍历顺序来表示(假设每一个节点只会被访问一次)。
广度优先搜索
bfs经常被用于求转移状态问题中的最小代价解
从宏观上来看,解决状态转移问题时,
广度优先搜索给我的第一感觉是 "一层一层的" ,看着很舒服,但是看了运用bfs思想做的各种例题,发现
我想的那种多线程并进 的 运行模式 在我现有的知识下很难实现,这应该很多人都发现过,但是广度优先搜索依然被称为“一层一层遍历”,
这一定是有原因的。
可以看一下bfs的搜索树。
在转移状态问题中,
对于每个状态节点的“下一层”,即此状态的转移状态,
在这个bfs搜索树中可以定义成它的儿子节点,所以对于“同时”中的“时”,
我的理解是它在这一个搜索树中的深度,“同层”同样。
对于同一深度的节点,到达它们的代价是到达它们在bfs搜索树中的father节点的代价加一个单位代价。
综上,对于 bfs为什么总是可以在状态转移问题中
得到最小代价解 这个问题,可以这么解释:
最优解一定在这个bfs算法的搜索树上,也就是说“最优解”(最小代价)的定义可以
被描述成这样:
“此节点的深度”
为什么呢?
由于广搜的特性,从源点开始的每一“层”节点都会在宏观上的一次搜索中同时被访问到,
那么如果目标节点不在本层,目标节点一定在更大的某层;
如果目标节点在当前层,那么在之前各层的访问中,一定没有访问此目标节点(因为每层的访问总是访问当前层的所有节点),意即此时
目标节点是第一次被访问,访问目标节点的最小代价也就被发现了
又因为bfs算法的搜索树就是按照层次建立的,所以最优解可以被定义成“目标节点在bfs搜索树中的深度”。
上一道例题,可以让我们更好地理解bfs。
3、最少转弯问题(TURN)
【问题描述】
给出一张地图,这张地图被分为n×m(n,m<=100)个方块,任何一个方块不是平地就是高山。平地可以通过,高山则不能。现在你处在地图的(x1,y1)这块平地,问:你至少需要拐几个弯才能到达目的地(x2,y2)?你只能沿着水平和垂直方向的平地上行进,拐弯次数就等于行进方向的改变(从水平到垂直或从垂直到水平)的次数。例如:如图,最少的拐弯次数为5。
【输入格式】
第1行:n m
第2至n+1行:整个地图地形描述(0:空地;1:高山),
如(图)第2行地形描述为:1 0 0 0 0 1 0
第3行地形描述为:0 0 1 0 1 0 0
……
第n+2行:x1 y1 x2 y2 (分别为起点、终点坐标)
代码如下
1 #include<cstdio> 2 #include<iostream> 3 #include<queue> 4 using namespace std; 5 struct node{ 6 int x,y; 7 }; 8 int sr,sc,er,ec,n,m,ans[120][120];//ans记录到某地点的最小转弯次数(因为我懒),sr为“start r”,er为“end r”,c类比。 9 bool vis[120][120];//标记不能走的地方 10 int dr[4] = {1,-1,0,0}; 11 int dc[4] = {0,0,1,-1}; 12 queue<node>q; 13 int main() { 14 cin>>n>>m; 15 for(int i = 1; i <= n; ++i) 16 for(int j = 1; j <= m; ++j) cin>>vis[i][j];//障碍物不能走 17 cin>>sr>>sc>>er>>ec; 18 ans[sr][sc] = -1; 19 vis[sr][sc] = 1; 20 q.push((node){sr,sc}); 21 22 while(!q.empty()) { 23 int r = q.front().x; 24 int c = q.front().y; 25 q.pop(); 26 for(int i = 0; i < 4; ++i) { 27 int r1 = r, c1 = c; 28 while(r1>=1 && r1<=n && c1>=1 && c1<=m && !vis[r1+dr[i]][c1+dc[i]]) {//将此状态节点在搜索树中所有儿子节点都找出来 29 r1 += dr[i], c1 += dc[i]; 30 vis[r1][c1] = 1;//走过的点不能走,因为 走 走过的点相当于浪费转弯次数 31 ans[r1][c1] = ans[r][c] + 1;//体现了搜索树深度的度量单位 32 if(r1 == er && c1 == ec) { 33 cout<<ans[r1][c1]; 34 return 0; 35 } 36 q.push((node){r1,c1}); 37 } 38 } 39 }//没有处理不能到达的情况,因为我懒 40 return 0; 41 }
可以看到,本题只是将bfs搜索树的深度定义为转弯次数而不是行走步数而已。
鸽子真可爱>w<