相较于深度搜索的一条路走到黑,广度搜索如其名是逐层搜索,一次把整一层搜完。我们用一个队列来实现,每次取出队头,对于队头状态的所有分支,把沿着分支所能达到的下一个状态(如果这个状态没被访问过或者可以形成更优的解)插入队尾,直到队列为空。
复习:#150 骑士游历
一个比较典型的棋盘问题,题目先规定了大致的方向,否则的话,应该有8个方向进行遍历;
先规定一个next数组,表示走到下一个点需要的运算;
用book来表示是否已经走过;
当第一次走到终点的时候直接输出;
否则搜索完毕之后输出no solution;
1 #include <iostream> 2 using namespace std; 3 struct queue{ 4 int x; 5 int y; 6 int step; 7 }q[2510]; 8 int next[4][2]={{2,1},{2,-1},{1,2},{1,-2}}; 9 int book[51][51],nx,ny; 10 int main(){ 11 int head=1,tail=1,k,n,m; 12 cin>>n>>m; 13 book[1][1]=1,q[head].x =1;q[head].y=1,q[head].step =0; 14 tail++; 15 book[1][1]=0; 16 k=0; 17 while(head!=tail){ 18 for(int i=0;i<=3;i++){ 19 nx=q[head].x +next[i][0]; 20 ny=q[head].y +next[i][1]; 21 if(nx<1||nx>n||ny<1||ny>m) continue; 22 if(book[nx][ny]==0){ 23 book[nx][ny]=1; 24 q[tail].x =nx; 25 q[tail].y =ny; 26 q[tail].step =q[head].step +1; 27 tail++; 28 } 29 if(nx ==n&&ny ==m){ 30 k=1; 31 break; 32 } 33 } 34 if(k==1) break; 35 head++; 36 } 37 if(k==1) cout<<q[tail-1].step; 38 else cout<<"No solution!"; 39 return 0; 40 }
给一道例题:矩阵距离(ch2501)
描述
给定一个N行M列的01矩阵 A,A[i][j] 与 A[k][l] 之间的曼哈顿距离定义为:
dist(A[i][j],A[k][l])=|i-k|+|j-l|
输出一个N行M列的整数矩阵B,其中:
B[i][j]=min(1≤x≤N,1≤y≤M,A[x][y]=1){dist(A[i][j],A[x][y])}
即求与每个位置曼哈顿距离最近的1
N,M≤1000。
输入格式
第一行两个整数n,m。
接下来一个N行M列的01矩阵,数字之间没有空格。
输出格式
一个N行M列的矩阵B,相邻两个整数之间用一个空格隔开。
题目所求的意思就是 :
离 (i,j) 这个点最近的 “1” 的曼哈顿距离;
然后输出这个矩阵;
我们先来思考一个问题:
给一个地图,全是0、1; 从一个起点出发,1表示不能走,0表示可以走。就可以求得起点到每一个可到达的点 的最小步数。
这样的问题我们形象的称它为倒水 、
于是本题便可以看作是一个有着多个起点(start) 的倒水问题;
在开始搜索前将所有的起点入队,然后用广搜。
当(x,y)这个节点第一次被访问时,便是离它最近的一个起点搜索到了它,这时候用的步数即为b[x][y];
1 #include<iostream> 2 #include<cstdio> 3 #include<cstring> 4 #include<algorithm> 5 #include<queue> 6 using namespace std; 7 const int dx[4] = { -1,1,0,0 }, dy[4] = { 0,0,-1,1 }; 8 char s[1020][1020]; 9 int d[1020][1020], n, m; 10 queue<pair<int, int>> q; 11 int main() 12 { 13 cin >> n >> m; 14 for (int i = 1; i <= n; i++) scanf("%s", s[i] + 1); 15 memset(d, -1, sizeof(d)); 16 for (int i = 1; i <= n; i++) 17 for (int j = 1; j <= m; j++) 18 if (s[i][j] == '1') q.push(make_pair(i, j)), d[i][j] = 0; 19 while (q.size()) { 20 pair<int, int> now = q.front(); 21 q.pop(); 22 for (int k = 0; k < 4; k++) { 23 pair<int, int> next(now.first + dx[k], now.second + dy[k]); 24 if (next.first<1 || next.second<1 || next.first>n || next.second>m) continue; 25 if (d[next.first][next.second] == -1) { 26 d[next.first][next.second] = d[now.first][now.second] + 1; 27 q.push(next); 28 } 29 } 30 } 31 for (int i = 1; i <= n; i++) { 32 for (int j = 1; j <= m; j++) printf("%d ", d[i][j]); 33 puts(""); 34 } 35 }
BFS 的变形
例题:电路维修(ch2601)
这道题我们输入存储数据的时候有一个小技巧。
将每个电路图中的格点当作无向图中的节点。
对于两个节点x,y;
如果他们是在同一个格子的两个角上,就将它连一条边,如果x到y有一根导线,就将x到y的边权设为0
反之 如果是垂直的话,就设为1(这表示需要旋转一次才能够连上)
然后我们只需要求出从左上角到右下角的最短距离;
进行bfs的时候,我们将边权为0的边延伸出来的节点从队头入队;
为1的边延伸出来的节点从队尾入队;
因为如果这样做,我们就能保证每当一个节点第一次出队时,这时的总权值是最优解;
这种bfs变形叫做 双端队列;
1 #include <deque> 2 #include <vector> 3 #include <cstring> 4 #include <iostream> 5 using namespace std; 6 const int N = 506; 7 int r, c, d[N][N]; 8 bool v[N][N]; 9 char s[N][N]; 10 vector<pair<pair<int, int>, int> > p[N][N]; 11 deque<pair<int, int> > q; 12 13 void add(int x1, int y1, int x2, int y2, int z) { 14 p[x1][y1].push_back(make_pair(make_pair(x2, y2), z)); 15 } 16 17 void dlwx() { 18 cin >> r >> c; 19 for (int i = 1; i <= r; i++) cin >> (s[i] + 1); 20 if ((r & 1) != (c & 1)) cout << "NO SOLUTION" << endl; 21 for (int i = 0; i <= r; i++) 22 for (int j = 0; j <= c; j++) 23 p[i][j].clear(); 24 for (int i = 1; i <= r; i++) 25 for (int j = 1; j <= c; j++) 26 if (s[i][j] == '/') { 27 add(i - 1, j - 1, i, j, 1); 28 add(i, j, i - 1, j - 1, 1); 29 add(i, j - 1, i - 1, j, 0); 30 add(i - 1, j, i, j - 1, 0); 31 } else { 32 add(i - 1, j - 1, i, j, 0); 33 add(i, j, i - 1, j - 1, 0); 34 add(i, j - 1, i - 1, j, 1); 35 add(i - 1, j, i, j - 1, 1); 36 } 37 memset(d, 0x3f, sizeof(d)); 38 d[0][0] = 0; 39 memset(v, 0, sizeof(v)); 40 q.clear(); 41 q.push_back(make_pair(0, 0)); 42 while (q.size()) { 43 int x = q.front().first, y = q.front().second; 44 q.pop_front(); 45 v[x][y] = 1; 46 if (x == r && y == c) { 47 cout << d[r][c] << endl; 48 return; 49 } 50 for (unsigned int i = 0; i < p[x][y].size(); i++) { 51 int nx = p[x][y][i].first.first; 52 int ny = p[x][y][i].first.second; 53 int nz = p[x][y][i].second; 54 if (v[nx][ny]) continue; 55 if (nz) { 56 if (d[nx][ny] > d[x][y] + 1) { 57 d[nx][ny] = d[x][y] + 1; 58 q.push_back(make_pair(nx, ny)); 59 } 60 } else { 61 if (d[nx][ny] > d[x][y]) { 62 q.push_front(make_pair(nx, ny)); 63 d[nx][ny] = d[x][y]; 64 } 65 } 66 } 67 } 68 } 69 70 int main() { 71 int t; 72 cin >> t; 73 while (t--) dlwx(); 74 return 0; 75 }
优先队列 bfs
其实双端队列算是优先队列dfs的一个特殊情况,因为权值只有0、1;
对于更加普适性的情况,我们可以用到优先队列;
这时优先队列是一个二叉堆,每次取出当前代价最小的节点进行扩展;
说白了:在最短路中就是堆优化的dijstra算法
双向的bfs
就是有两个节点同时bfs,当一个点是第一次被搜索到既能被一个起点到达,也能被另一个点到达,则当前轮数就是最短的两点会和时间;