实用算法实现-第13篇 搜索(盲目搜索)

在人工智能中,盲目搜索是相对于启发式搜索来说的。

13.1 广度优先搜索

《算法导论》中,广度优先(Breadth-First)搜索树的伪代码如下:

BFS(G,s)

1 for each vertex u ∈ V[G] - {s}

2 docolor[u] ← WHITE

3 d[u] ← ∞

4 ∏[u] ← NIL

5 color[s] ← GRAY

6 d[s] ← 0

7 ∏[s] ← NIL

8 Q ← Ф

9ENQUEUE(Q, s)

10while Q ≠ Ф

11 do u ← head[Q]

12 for each v ∈ Adj[u]

13 doif color[v] = WHITE

14 thencolor[v] ← GRAY

15 d[v] ← d[u] + 1

16 ∏[v] ← u

17 ENQUEUE(Q,v)

18 color[u] ← BLACK

13.1.1实例

PKU JudgeOnline, 3669, Meteor Shower.

13.1.2问题描述

初始位置在原点,所站的地方隔一段时间之后就要被砸,被砸的时候和以后该地方都不能待,要逃到一个安全的地方。问最少需要多少步才能到一个安全的地方。

PKU JudgeOnline, 3414, Pots是对状态空间的BFS搜索。

13.1.3输入

4

00 2

21 2

11 2

03 5

13.1.4输出

5

13.1.5程序

#include <iostream.h> #include <stdio.h> #include <string.h> int map[1002][1002]; bool visited[1002][1002]; int depth[1002][1002]; int queue[150100][2]; int top; int adj[5][2] = {{0, 0}, {0, -1}, {0, 1}, {-1, 0}, {1, 0}}; #define enqueue(i, j) queue[top][0] = i;queue[top][1] = j;top++; #define MAX 0x7F7F7F7F inline void dequeue(int *i, int *j) { top--; *i = queue[top][0]; *j = queue[top][1]; } int main() { //freopen("meteor.12.in","r", stdin); //freopen("out.out","w", stdout); int M; int i; int j; int tempX; int tempY; inttempTime; int X; int Y; int min; scanf("%d",&M); memset(map, 0x7F, sizeof(map)); for(i = 0;i < M; i++){ scanf("%d%d%d",&tempX, &tempY, &tempTime); for(j =0; j < 5; j++){ X = tempX + adj[j][0]; Y = tempY + adj[j][1]; if((X>= 0&&Y >= 0)&& (map[X][Y] > tempTime)){ map[X][Y] = tempTime; } } } memset(visited, 0, sizeof(visited)); depth[0][0] = 0; top = 0; enqueue(0, 0); visited[0][0] = true; min = MAX; for(i=0;i<top;i++) { //操是队列不是堆栈错在这里了 tempX = queue[i][0]; tempY = queue[i][1]; for(j =1; j < 5; j++){ X = tempX + adj[j][0]; Y = tempY + adj[j][1]; tempTime = depth[tempX][tempY] +1; if((X>= 0&&Y >= 0)&& (map[X][Y] >tempTime)&& (visited[X][Y] == false)){ enqueue(X, Y); visited[X][Y] = true; depth[X][Y] = tempTime; if(map[X][Y]== MAX){ if(min> depth[X][Y]){ min = depth[X][Y]; } gotofind; } } } } find: if(min ==MAX){ printf("-1\n"); }else{ printf("%d\n",min); } /* for(i = 0; i < 2; i++) { for(j = 0; j< 2 ; j++) { cout<< depth[X][Y]; } }*/ ; return 1; }

13.2 代价一致搜索

当所有路径耗散相等的时候,广度优先搜索是最优的,因为它总是先扩展深度最浅的结点。更一般地,如果路径耗散是结点深度的非递减函数,广度优先搜索也是最优的。

而代价一致(UniformCost)搜索扩展的是路径消耗最低的结点。而如果搜有单独耗散都相等的话,这种算法就和广度优先搜索算法是一样的。

代价一致搜索对一条路径的步数并不关心,而关心所经历步骤总的消耗。因此,在扩展到一个具有能返回同一状态的零耗散行动的结点时就会陷入无限循环。

在实现上,广度优先搜索只需要使用先进先出队列,而代价一致搜索则需要使用优先级队列。

PKU JudgeOnline, 2312, Battle City是典型的代价一致搜索算法的实例。具体分析参看最小堆部分。

13.3 深度优先搜索

算法导论中,深度优先(Depth-First)搜索树的伪代码如下:

DFS(G)

1 for each vertex u ← V[G]

2 docolor[u] ← WHITE

3 [u] ← NIL

4 time ← 0

5 for each vertex u ∈ V[G]

6 do if color[u] = WHITE

7 then DFS-VISIT(u)

13.3.1实例

PKU JudgeOnline, 2386,Lake Counting.

13.3.2问题描述

一块地上有一些水洼,计算一共有多少水洼。

13.3.3输入

1012

W........WW.

.WWW.....WWW

....WW...WW.

.........WW.

.........W..

..W......W..

.W.W.....WW.

W.W.W.....W.

.W.W......W.

..W.......W.

13.3.4输出

3

13.3.5分析

其实这个题目也可以用BFS来实现了。

13.3.6程序

#include <iostream.h> #include <string.h> #define VISITED '*' #define UNVISITED 'W' #define BOUND '.' #define UP -1 #define DOWN 1 #define LEFT -1 #define RIGHT 1 #define NOP 0 int move[8][2]={{UP, NOP}, {UP, RIGHT}, {NOP, RIGHT}, {DOWN, RIGHT}, {DOWN,NOP}, {DOWN, LEFT}, {NOP, LEFT}, {UP, LEFT}}; int num; int row; int column; char grid[102][102]; void DFS_Visit(int a,int b) { grid[a][b] = VISITED; //num++; //always click onan X int i; inttempRow; inttempColumn; char temp; for(i = 0;i < 8; i++){ tempRow = a + move[i][0]; tempColumn = b + move[i][1]; temp = grid[tempRow][tempColumn]; if(temp== UNVISITED){ DFS_Visit(tempRow, tempColumn); }else if((temp == BOUND)&& (move[i][0] == 0||move[i][1] == 0)){ } } return; } int main() { int i; int j; cin >> row >> column; if(row == 0&& column == 0){ return1; } memset(grid, BOUND, sizeof(grid)); for(i = 1;i <= row; i++){ for(j =1; j <= column; j++){ cin >> grid[i][j]; } } num = 0; for(i = 1;i <= row; i++){ for(j =1; j <= column; j++){ if(grid[i][j]== UNVISITED){ num++; DFS_Visit(i, j); } } } cout <<num << endl; //---------------------------------------- /* for(i = 0; i <=row + 1; i++){ for(j = 0; j<= column + 1; j++){ cout<<grid[i][j] << " "; } cout<<endl; }*/ //---------------------------------------- }
13.4 深度有限搜索

使用深度优先搜索的一个危险在于,如果所期待的解处在一个有限的深度上,而树的某个子树深度却无穷大,那么使用它深度优先搜索就很有可能陷入无限的搜索当中,而不能找到这个本来应该可以找到的解。而这种情况又是很可能发生的。

故此深度有限搜索(Depth-Limited),就引入对搜索深度的限制,将所有深度达到某限度的结点都看做是没有子结点,即可避免陷入无限的搜索当中。

13.5 迭代加深的深度优先搜索

《人工智能,一种现代方法》和《人工智能,复杂问题求解的结构和策略》介绍了迭代加深(Iterative Deepening)的深度优先搜索的思想。

迭代加深的深度优先搜索是对于深度优先搜索和广度优先搜索的一个很好的折衷,这种平衡是通过对深度优先搜索使用一个界限。

因为折衷算法一层层地搜索空间,所以它保证所发现的目标路径是最短的。这一点和广度优先搜索是一致。

又因为它实质上是一种深度优先搜索,所以它不需要广度优先搜索指数级别那么大的空间要求,这一点上是和深度优先搜索是类似的。

RichardE. Korf似乎对搜索算法贡献很大。对于迭代加深的深度搜索的效率,他指出:因为树上某一给定层的结点数量随着深度呈指数增长,所以几乎所有的时间都花在最深层上,因为即使以算数递增的速度多次产生较浅层也无所谓。

而实际上,有趣的是:尽管看起来迭代加深的深度优先方法在时间上比深度优先搜索和广度优先搜索效率低,但是实际上它的复杂度与两个方法处于同一个数量级,即O(B^n)。其中B为结点的平均孩子数,n为目标结点所在的层次。

13.5.1实例

PKU JudgeOnline, 3009, Curling 2.0.

13.5.2问题描述


如上图,一个游戏是在冰面上滑动石块,有如下规则:

1. 如果石块紧靠着一堵墙,那么石块不能朝该方向滑动。否则石块可以朝着该方向滑动。

2. 如果滑动的过程中,石块遇到一堵墙,石块停在墙的前面,并把墙夷为平地。否则一直向前滑。

3. 如果石块滑出区域,游戏失败。

4. 如果石块到达目的地,游戏成功。

5. 如果十步之内没有到达目的地,游戏失败。

问题是找出从出发点到目的地所需要的步数。

13.5.3输入

21

32

66

10 0 2 1 0

11 0 0 0 0

00 0 0 0 3

00 0 0 0 0

10 0 0 0 1

01 1 1 1 1

61

11 2 1 1 3

61

10 2 1 1 3

121

20 1 1 1 1 1 1 1 1 1 3

131

20 1 1 1 1 1 1 1 1 1 1 3

0 0

13.5.4输出

1

4

-1

4

10

-1

13.5.5分析

最基本的迭代加深的深度优先搜索就可以解决这个题目,而且非常自然。据说DFS配合剪枝也可以解决。

13.5.6程序

#include <stdio.h> #include <string.h> #define maxNum 22 #define VISITED '*' #define UNVISITED 'W' #define BOUND '.' #define UP -1 #define DOWN 1 #define LEFT -1 #define RIGHT 1 #define NOP 0 int move[4][2]={{UP, NOP}, {NOP, RIGHT}, {DOWN, NOP}, {NOP, LEFT}}; int num; int row; int column; int grid[maxNum][maxNum]; int depth; int maxDepth; int StartX; int StartY; int IDDFS_Visit(int a, int b) { int i; inttempRow; int tempColumn; intstoneRow; intstoneColumn; char temp; depth++; int find; find = 0; if(depth> maxDepth){ gotoend; } for(i = 0;i < 4; i++){ tempRow = a + move[i][0]; tempColumn = b + move[i][1]; if((tempRow< 1 || tempColumn < 1) ||(tempRow > row || tempColumn >column) ||(grid[tempRow][tempColumn] == 1)) {//紧接着的是边界或者砖头 continue; } while((tempRow>= 1 && tempColumn >= 1) && (tempRow <= row&& tempColumn <= column)){ temp = grid[tempRow][tempColumn]; if(temp== 3){ find = 1; gotoend; }elseif(temp == 1){ break; } stoneRow = tempRow; stoneColumn = tempColumn; tempRow = tempRow + move[i][0]; tempColumn = tempColumn +move[i][1]; } if(tempRow< 1 || tempColumn < 1 || tempRow > row || tempColumn >column) {//一直滑出了边界 continue; } grid[tempRow][tempColumn] = 0; find = IDDFS_Visit(stoneRow,stoneColumn); grid[tempRow][tempColumn] = 1; if(find== 1) { gotoend; } } end: depth--; returnfind; } int main() { int i; int j; while(scanf("%d%d", &column, &row)) { if(row== 0 && column == 0) { break; } memset(grid, 0, sizeof(grid)); for(i =1; i <= row; i++){ for(j= 1; j <= column; j++){ scanf("%d", &grid[i][j]); if(grid[i][j]== 2){ StartY = i; StartX = j; } } } for(maxDepth= 1; maxDepth <= 10; maxDepth++) { depth = 0; if(IDDFS_Visit(StartY,StartX) == 1) { break; } } if(maxDepth> 10){ printf("-1\n"); }else{ printf("%d\n",maxDepth); } } }
13.6 双向搜索

双向搜索(Bidirectional)是同时从源结点和目标结点对状态进行搜索。要使用双向搜索首先要同时知道源结点和目标结点的状态,其次要保证从两个状态开始的两个方向的搜索是可行的。

13.7 总结

《人工智能,一种现代方法》对于几种搜索策略的介绍相当深入、细致。讨论了广度优先搜索、代价一致搜索、深度优先搜索、深度有限(Depth-Limited)搜索、迭代深入深度优先搜索、双向搜索(Bidirectional)等无信息的搜索策略。

对于搜索策略,可以从以下几个标准进行评价:

1. 是否完备?即:当问题有解时,这个算法能够找到一个解。

2. 是否最优?即:这个搜索策略是否找到最优解。

3. 时间复杂度。即:找到一个解需要花费多长时间。

4. 空间复杂度。即:执行搜索过程需要多少内存。

该文还总结了下表:

13.8 实例

13.8.1广度优先搜索算法实例

PKU JudgeOnline, 3170, Knights of Ni.

PKU JudgeOnline, 3669, Meteor Shower.

PKU JudgeOnline, 3126, Prime Path.

PKU JudgeOnline, 2251, Dungeon Master.

PKU JudgeOnline, 3414, Pots.

13.8.2代价一致搜索实例

PKU JudgeOnline, 2312, Battle City

13.8.3深度优先搜索算法实例

PKU JudgeOnline, 1562, Oil Deposits.

PKU JudgeOnline, 2386, Lake Counting.

PKU JudgeOnline, 1111, Image Perimeters.

PKU JudgeOnline, 3620, Avoid The Lakes.

13.8.4迭代加深的深度优先搜索实例

PKU JudgeOnline, 3009, Curling 2.0.

本文章欢迎转载,请保留原始博客链接http://blog.csdn.net/fsdev/article
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值