深度优先搜索DFS
所谓深度优先搜索,通俗点理解就是一条路走到头--不撞南墙不回头。
我们先来看一个全排列问题,现在要对1 2 3进行全排列,现在小哼手上拿着1 2 3三张卡片,他要将这三张卡片放入三个盒子里,每放满不就是一种全排列了么?
但是每次到底是先放卡片1还是卡片2,3呢?
小哼想,我按顺序放吧,每次都按照1.2.3的顺序放卡片。于是他走到1号盒子前把卡片1放入,走到2号盒子前把卡片2放入,走到3号盒子前把卡片3放入,走到四号盒子...但小哼的卡片已经放完
啦。这时就产生了一种全排列“1 2 3”啦。
但是还有好多种情况啊,于是产生了一种全排列后小哼要立即返回,他回到3号盒子前,将卡片3取回,看还能不能放其他卡片,显现小哼现在手里没除了卡片3外没有其他卡片了,所以小哼继续
往后退,到了2号盒子前,小哼将卡片2收回,这是小哼手里已经有两张卡片了,于是他将卡片3放入2号盒子,放好后又往后走一步来到三号盒子,将卡片2放入,又来到四号盒子前,当然没有
四号盒子啦,于是又产生了一种全排列“1 3 2”
按照这样模拟后,便会依次生成全排列 “2 1 3” “2 3 1” “3 1 2” “3 2 1”
说了半天,我们看下如何用代码实现吧
先将i号卡片放入第step个盒子中
for(i = 1; i <=n; i++) { a[step] = i; //将卡片i放入第step个盒子里 }
但这里还有个问题,如何一张卡片已经放入其他盒子中了,就不能再放入当前的盒子中。所以,我们需要一个book[]数组来标记手上还有没有这张卡片
for(i = 1; i <= n; i++) { if(book[i] == 0) / /如果手上有这张卡片 { a[step] = i; //将卡片i放入第step个盒子里 book[i] = 1; //此时手上已经没有这张卡了 标记一下 } }
处理完第step个盒子后我们要向后走一步,处理第step+1个盒子,显然处理第step+1个盒子的方法和第step个盒子的一样,那么我们将刚才处理第step个盒子的方法写成一个函数
void dfs(int step) { for(i = 1; i <= n; i++) { if(book[i] == 0) / /如果手上有这张卡片 { a[step] = i; //将卡片i放入第step个盒子里 book[i] = 1; //此时手上已经没有这张卡了 标记一下 } } }
处理第step+1个盒子只有dfs(step+1)就行啦
void dfs(int step) { for(i = 1; i <= n; i++) { if(book[i] == 0) / /如果手上有这张卡片 { a[step] = i; //将卡片i放入第step个盒子里 book[i] = 1; //此时手上已经没有这张卡了 标记一下 } dfs(step+1);//处理下个盒子 book[i] = 0; //这步很重要!!往回走时要将之前的卡片收回! } }
book[i] = 0;是将之前的卡片收回,如果不把刚才放入盒子的卡片收回,那么就无法再进行下一次摆放。
还剩最后一个问题,什么时候输出一个满足条件的序列呢?其实当我们走到第n+1个盒子前时,说明前n个盒子都已经放好卡片了,这时候我们就要return返回了
void dfs(int step) { if (step == n + 1) //判断边界 { for (i = 1; i <= n; i++) cout << a[i] << " "; cout << endl; return;//返回上一个盒子前 } else { for (i = 1; i <= n; i++) { if (book[i] == 0) / /如果手上有这张卡片 { a[step] = i; //将卡片i放入第step个盒子里 book[i] = 1; //此时手上已经没有这张卡了 标记一下 } dfs(step + 1);//处理下个盒子 book[i] = 0; //这步很重要!!往回走时要将之前的卡片收回! } } }
讲到这里想必大家已经对深度优先搜索有一定了解了吧,深搜使用就是递归和回溯的思想。
广度优先搜索BFS
讲完了dfs,我们再来看看什么是广度优先搜索。
所谓广度优先搜索就是一种层层递进的搜索。
我们来看一张图。这是一个迷宫,我们用一个二维数据储存它。
现在小哼站在(1 1)处,它可以向下或向右走,如何才能到达终点呢?已经学会深搜的你因该很快就想到办法了,但这里我们用另一种方法BFS,“一层层”扩展找到终点。扩展时每发现一个点就将这个点放入队列中,知道走到终点位置。和深搜一样,我们也需要一个book数组记录是否走过这个点。
我们每对一个点扩展完毕,这个点就没用了,所以要将这个点出队,对下个点扩展。
理解了这点后代码就很容易实现了^_^。
完整代码
#include<bits/stdc++.h> using namespace std; struct node { int x, y; int s;//步数 int f;//父亲再队列中的编号 }; int main() { queue<node> que; int a[51][51] = { 0 }; int book[51][51] = { 0 }; int next[4][2] = { {0,1}, //向右走 {1,0},//向下走 {0,-1},//向左走 {-1,0}//向上走 }; int n, m, i, j, k, flag,p,q; node start, tnode; //输入迷宫的行列 起始点 cin >> n >> m >> start.x >> start.y; //输入迷宫图 for (i = 1; i <= n; i++) for (j = 1; j <= m; j++) cin >> a[i][j]; cin >> p >> q;//输入终点 book[start.x][start.y] = 1;//将起始点标记为走过 //如果队列不为空 while (!que.empty()) { //枚举四个方向 for (int k = 0; k <= 3; k++) { //计算下一步坐标 tnode.x = que.front().x + next[k][0]; tnode.y = que.front().y + next[k][1]; //判断是否出界 if (tnode.x<1 || tnode.x>n || tnode.y<1 || tnode.y>m) continue; //判断是否是陆地或已经走过 if (mapp[tnode.x][tnode.y] > 0 && book[tnode.x][tnode.y] == 0) { sum++; //每个点只入队一次,标记走过 book[tnode.x][tnode.y] = 1; int ts = que.back().s;//记录父亲的步数 //将该点入队 que.push(tnode); que.back().s = ts+1; //步数是父亲步数加1 } if (tnode.x == p && tnode.y == q) { flag = 1; break; } } if (flag == 1) break; que.pop();//队首出队 } cout << que.back().s << endl; system("pause"); return 0; }
练习
学完DFS和BFS后来练习一下吧。
题目:宝岛探索
小哼通过一种秘密方法得到一张不完整的钓鱼岛航拍图。小哼决定去钓鱼岛探险。
下面的10*10的矩阵就是该航拍图。图中数字表示海拔,0表示海洋,1-9都表示陆地。
小哼的飞机会降落再(6,8),现在需要计算小哼降落岛的面积。此处我们把降落点上
下左右相邻岛视为同一岛屿。
输入
输出
BFS代码如下:
#include<bits/stdc++.h> using namespace std; struct node { int x; int y; }; int main() { queue<node> que; int mapp[51][51]; int book[51][51]; int i, j, n, m,sum; node start, tnode; memset(book, 0, sizeof(book)); memset(mapp, 0, sizeof(mapp)); //输入地图的行列,初始坐标 cin >> n >> m >> start.x >> start.y; //输入地图 for(i=1;i<=n;i++) for (j = 1; j <= m; j++) { cin >> mapp[i][j]; } int next[4][2]= { {0,1}, //向右走 {1,0},//向下走 {0,-1},//向左走 {-1,0}//向上走 }; //向队列插入降落的起始坐标 que.push(start); sum = 1; book[start.x][start.y] = 1; //当队列不为空时循环 while (!que.empty()) { //枚举四个方向 for (int k = 0; k <= 3; k++) { //计算下一步坐标 tnode.x = que.front().x + next[k][0]; tnode.y= que.front().y + next[k][1]; //判断是否出界 if (tnode.x<1 || tnode.x>n || tnode.y<1 || tnode.y>m) continue; //判断是否是陆地或已经走过 if (mapp[tnode.x][tnode.y] > 0 && book[tnode.x][tnode.y] == 0) { sum++; //每个点只入队一次,标记走过 book[tnode.x][tnode.y] = 1; //将该点入队 que.push(tnode); } } //队首出队,下个点继续搜素 que.pop(); } cout << sum << endl; getchar(); getchar(); return 0; }
DFS代码如下:
#include<bits/stdc++.h> using namespace std; struct node { int x; int y; }; int mapp[51][51]; int book[51][51]; int n, m,sum; void dfs(int x,int y) { int next[4][2] = { {0,1}, //向右走 {1,0},//向下走 {0,-1},//向左走 {-1,0}//向上走 }; int tx, ty, k; //枚举四个方向 for (k = 0; k <= 3; k++) { tx = x + next[k][0]; ty = y + next[k][1]; //判断是否出界 if (tx<1 || tx>n || ty<1|| ty>m) continue; //判断是否是陆地或已经走过 if (mapp[tx][ty] > 0 && book[tx][ty] == 0) { sum++; //标记走过 book[tx][ty] = 1; dfs(tx, ty); //执行下一步 } } return; } int main() { int i, j,sx,sy; memset(book, 0, sizeof(book)); memset(mapp, 0, sizeof(mapp)); cin >> n >> m >> sx >> sy; //输入地图 for(i=1;i<=n;i++) for (j = 1; j <= m; j++) { cin >> mapp[i][j]; } book[sx][sy] = 1; sum = 1; dfs(sx, sy); cout << sum << endl; getchar(); getchar(); return 0; }
拓展:如何求有多少个独立岛屿呢?
即求独立子图的个数,这就是Floodfill漫水填充法,又叫种子填充法。Windows下“画图”中的油漆桶工具就是基于这个算法的。
我们将独立岛屿进行染色,所以增加参数color。
代码如下
#include<bits/stdc++.h> using namespace std; struct node { int x; int y; }; int mapp[51][51]; int book[51][51]; int n, m,sum; void dfs(int x,int y,int color) { mapp[x][y] = color;//表示小哼来过这个岛 int next[4][2] = { {0,1}, //向右走 {1,0},//向下走 {0,-1},//向左走 {-1,0}//向上走 }; int tx, ty, k; //枚举四个方向 for (k = 0; k <= 3; k++) { tx = x + next[k][0]; ty = y + next[k][1]; //判断是否出界 if (tx<1 || tx>n || ty<1|| ty>m) continue; //判断是否是陆地或已经走过 if (mapp[tx][ty] > 0 && book[tx][ty] == 0) { sum++; //标记走过 book[tx][ty] = 1; dfs(tx, ty,color); //执行下一步 } } return; } int main() { int i, j,sx,sy,color=0; memset(book, 0, sizeof(book)); memset(mapp, 0, sizeof(mapp)); cin >> n >> m ; //输入地图 for(i=1;i<=n;i++) for (j = 1; j <= m; j++) { cin >> mapp[i][j]; } //对每个大于0(未被染色)的点尝试dfs染色 for (i = 1; i <= n; i++) { for (j = 1; j <= m; j++) { if (mapp[i][j] > 0)//如果没被染色 { color--;//color减一表示一种新的颜色 book[i][j] = 1; //标记走过 dfs(i, j, color); } } } for (i = 1; i <= n; i++) { for (j = 1; j <= m; j++) { cout<<mapp[i][j]<<" "; } cout << endl; } cout <<"共有独立岛屿:"<< -color<<" 个" << endl; getchar(); getchar(); return 0; }