第四章(下) 广度优先算法 Breadth First Search BFS 深度优先算法的兄弟,包含另外一种搜索思维 也叫宽度优先算法
概述 与深度优先算法不同,广度优先算法注重于”对所有情况的分析搜索” 如果是深度优先算法是刨根问底地分析每种情况, 广度优先就是在在层层扩展 中找到题解
例 还是之前的问题,我们想在n*m的迷宫中找到起点到终点的最短路径
分析 我们的核心思想是分析扩展时每发现一个点,就将这个点加入到队列中 ,直到到达终点 另外,为防止一个点被多次走到,我们还要一个数组来记录一个点是否被走到
队列与搜索路径 我们决定使用一个队列来模拟搜索过程,我们需要: 1.两个参数x,y代表点坐标 2.一个参数s代表我们走过的步数3.某一点之前的父 ,用于计算路径 (本题不需要计算路径,就省去这部分)
实现如下:
1 2 3 4 5 struct note { int x ; int y ; int s ; };
初始化 我们设定相关数据完成队列,并且对各个条件进行初始化
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 struct node que [2501] ; int head,tail ;int a[51 ][51 ] = {0 } ; int book[51 ][51 ] = {0 } ; head = 1 ; tail = 1 ; que[tail].x = 1 ; que[tail].y = 1 ; que[tail].s = 0 ; tail++; book[1 ][1 ] = 1 ;
开始搜索 我们以(1,1) -> (1,2)为例解释怎么进行点的判断 1.尝试走到(1,2)
1 2 tx = que[head].x + 0 ; ty = que[head].y + 1 ;
2.判断 主要是两点,一是是否越界,二是是否为障碍,三是是否已经走过
1 2 3 4 5 6 7 if (tx<1 || tx>n || ty<1 || ty >m){ continue ; } if (a[tx][ty] == 0 && book[tx][ty] == 0 ){ }
入队 1 2 3 4 5 6 7 8 9 10 11 if (a[tx][ty] == 0 && book[tx][ty] == 0 ){ book[tx][ty] = 1 ; que[tail].x = tx ; que[tail].y = ty ; que[tail].s = que[head].s + 1 ; tail++ ; }
更新 我们再用相同的做法判断(2,1),至此我们已经搜索了(1,1)周围的所有点 现在我们需要更新(1,1),这个点已经没用了,我们将它出队
之后,我们新的出发点就变成了(1,2),对它进行搜索,然后再更新,再搜索…直到到达终点
另外,这里的按四个方向搜索的办法和深度是一样的
1 int next[4 ][2 ] = { {0 ,1 },{1 ,0 },{0 ,-1 },{-1 ,0 }};
完整代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 #include <stdio.h> struct note { int x ; int y ; int s ; int f ; }; int main () { struct note que [2501] ; int a[51 ][51 ] = {0 } , book[51 ][51 ] = {0 }; int next[4 ][2 ] = { {0 ,1 }, {1 ,0 }, {0 ,-1 }, {-1 ,0 } }; int head,tail ; int i,j,k,n,m,startx,starty,p,q,tx,ty,flag ; scanf ("%d %d" ,&n,&m) ; for (i=1 ;i<=m;i++) for (j=1 ;j<=m;j++) scanf ("%d" ,&a[i][j]); scanf ("%d %d %d %d" ,&startx,,&starty,&p,&q); head = 1 ; tali = 1 ; que[tail].x = startx ; que[tail].y = statty ; que[tail].f = 0 ; que[tail].s = 0 ; tail++; book[startx][starty] = 1 ; flag = 0 ; while (head < tail){ for (k=0 ;k<=3 ;k++){ tx = que[head].x + next[k][0 ] ; ty = que[head].y + next[k][1 ] ; if (tx<1 || tx>n || ty<1 || ty>m) continue ; if (a[tx][ty] == 0 && book[tx][ty] == 0 ){ book[tx][ty] =1 ; que[tail].x = tx ; que[tail].y = ty ; que[tail].f = head ; que[tail].s = que[head].s +1 ; tail ++ ; } if (tx == p && ty == q){ flag = 1 ; break ; } } if (flag ==1 ) break ; head++ ; } printf ("%d" ,que[tail-1 ].s); getchar();getchar(); return 0 ; }
实战 例子 我们有一个海岛,海岛有一个主岛和附属岛屿,总地图大小为10*10 我们使用0代表海洋,1-9代表路段,我们由一个点(比如(6,8)开始)算出所在岛的面积 我们想计算所在岛屿的面积
解决 我们直接利用广度搜索来进行这这件事,我们直接上了
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 #include <stdio.h> struct node { int x ; int y ; }; int main () { struct node que [2501] ; int head , tail ; int a[51 ][51 ] ; int book[51 ][51 ] = {0 }; int i,j,k,sum,max = 0 ,mx,my,n,m,startx,starty,tx,ty ; int next[4 ][2 ] = { {0 ,1 }, {1 ,0 }, {0 ,-1 }, {-1 ,0 } }; scanf ("%d %d %d %d" ,&n,&m,&startx,&starty); for (i=1 ;i<=n:i++) for (j=1 ;j<=n;j++) scanf ("%d" ,&a[i][j]); head = 1 ; tail = 1 ; que[tail].x = startx ; que[tail].y = starty ; tail++; book[startx][starty] = 1 ; sum = 1 ; while (head<tail){ for (k=0 ;k<=3 ;k++){ tx = que[head].x + next[k][0 ]; ty = que[head].y + next[k][1 ]; if (tx<1 || tx>n || ty<1 || ty>m) continue ; if (a[tx][ty] > 0 && book[tx][ty] == 0 ){ sum++; book[tx][ty] = 1 ; que[tail].x = tx ; que[tail].y = ty ; tail++; } } head++; } printf ("%d \n" ,sum); getchar();getchar(); return 0 ; } ``` ## 水管连接 ### 题干 我们有一张N*M大小的地图 我们想要把水管从 **(1 ,1 )的铺设起点** 铺到 **(N,M)的铺设终点** 其中我们有两种水管,弯管和直管,他们都可以自由地90 °旋转 其中弯管可以连接上下左右任意两个相邻位置,直管则连接相对位置 计算连接方式,如果可以连接则输出路径,否则输出impossible ### 思路 其实本质上还是我们的深度/宽度搜索,额外添加了下面两个条件: 1. 需要考虑进出水口的方向 2. 需要给出路径 #### 摆放情况 我们首先讨论摆放情况,有下面六种情况,我们也用1 -6 的编号表示它们: 1 :上到右2 :下到右3 :左到下4 :右到上5 :左到右6 :上到下#### 状态分析 水管连接和之前的搜索比起来,最重要的一点就是条件 那么我们先结合DFS搜索,把对应的代码写出来: ```C void dfs (int x , int y , int front) { if (x<1 || x>n || y<1 || y>m){ return ; } if (book[x][y] ==1 ){ return ; } book[x][y] = 1 ; if ( a[x][y] >=5 && a[x][y]<=6 ){ if (front == 1 ){ dfs(x,y+1 ,1 ) ; } if (front == 2 ){ dfs(x+1 ,y,2 ) ; } if (front == 3 ){ dfs(x,y-1 ,3 ) ; } if (front == 4 ){ dfs(x-1 ,y,4 ) ; } } book[x][y] ; return ; }
对于弯管的处理,其原理是一样的,这里我们就不赘述了 另外,当我们到达(n,m+1)这个点时,就表明产生了完整方案——可以返回了
1 2 3 4 5 if (x==n && y==m+1 ){ flag = 1 ; return ; }
完整代码(无路径返回) 我们一步步来,先得到不返回路径,只产生方案的搜索:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 #include <stdio.h> int a[51 ][51 ] ; int book[51 ][51 ] ,n,m,flag = 0 ; void dfs (int x , int y , int front) { if (x==n && y==m+1 ){ flag = 1 ; return ; } if (x<1 || x>n || y<1 || y>m){ return ; } if (book[x][y] ==1 ){ return ; } book[x][y] = 1 ; if ( a[x][y] >=5 && a[x][y]<=6 >){ if (front == 1 ){ dfs(x,y+1 ,1 ) ; } if (front == 2 ){ dfs(x+1 ,y,2 ) ; } if (front == 3 ){ dfs(x,y-1 ,3 ) ; } if (front == 4 ){ dfs(x-1 ,y,4 ) ; } } if ( a[x][y]>=1 && a[x][y]<=4 ){ if (front == 1 ){ dfs(x+1 ,y,2 ) ; dfs(x-1 ,y,4 ) ; } if (front == 2 ){ dfs(x,y+1 ,1 ) ; dfs(x,y-1 ,3 ) ; } if (front == 3 ){ dfs(x-1 ,y,4 ) ; dfs(x+1 ,y,2 ) ; } if (front == 4 ){ dfs(x,y+1 ,1 ) ; dfs(x,y-1 ,3 ) ; } } book[x][y] ; return ; } int main () { int i,j,num = 0 ; scanf ("%d %d" ,&n,&m); for (i=1 ;i<=n:i++) for (j=1 ;j<=m;j++) scanf ("%d" ,&a[i][j]); dfs(1 ,1 ,1 ); if (flag==0 ) printf ("impossible \n" ); else printf ("找到了方案 \n" ) ; } ``` 啊,“找到方案”,对,我们只是**找到了方案** 想要让这个做法真正有意义,我们就应该**输出相应的路径** ### 增加路径的返回 这一步比想象中简单————毕竟我们已经一步步找到了结果了 我们只要在代码中加入一个**栈**,就可以利用它输出路径了 ```C struct note { int x ; int y ; } s[100 ];
完整代码(含路径的返回) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 #include <stdio.h> int a[51 ][51 ] ; int book[51 ][51 ] ,n,m,flag = 0 ;struct note { int x ; int y ; } s[100 ]; void dfs (int x , int y , int front) { if (x==n && y==m+1 ){ flag = 1 ; return ; } if (x<1 || x>n || y<1 || y>m){ return ; } if (book[x][y] ==1 ){ return ; } book[x][y] = 1 ; top++ ; s[top].x = x; s[top].y = y ; if ( a[x][y] >=5 && a[x][y]<=6 ){ if (front == 1 ){ dfs(x,y+1 ,1 ) ; } if (front == 2 ){ dfs(x+1 ,y,2 ) ; } if (front == 3 ){ dfs(x,y-1 ,3 ) ; } if (front == 4 ){ dfs(x-1 ,y,4 ) ; } } if ( a[x][y]>=1 && a[x][y]<=4 ){ if (front == 1 ){ dfs(x+1 ,y,2 ) ; dfs(x-1 ,y,4 ) ; } if (front == 2 ){ dfs(x,y+1 ,1 ) ; dfs(x,y-1 ,3 ) ; } if (front == 3 ){ dfs(x-1 ,y,4 ) ; dfs(x+1 ,y,2 ) ; } if (front == 4 ){ dfs(x,y+1 ,1 ) ; dfs(x,y-1 ,3 ) ; } } book[x][y] = 0 ; top -- ; return ; } int main () { int i,j,num = 0 ; scanf ("%d %d" ,&n,&m); for (i=1 ;i<=n:i++) for (j=1 ;j<=m;j++) scanf ("%d" ,&a[i][j]); dfs(1 ,1 ,1 ); if (flag==0 ) printf ("impossible \n" ); else printf ("找到了方案 \n" ) ; }