DFS:深度优先搜索
尽量往深度搜索,直到遇到了叶节点就回溯到上一节点,然后看看这个节点还可不可以往深度搜索,不可以继续回溯,递归地重复这个规程。
通过递归来进行往下搜索,所用空间较小,不具备最短性,关键词:“回溯”“剪枝”
- 回溯:每一次当前节点深度搜索结束后回溯,要将各种参量置零,恢复现场
- 递归:每一次深度搜索都是一个递归的过程
- 剪枝:在当前节点判断一下是否已经不满足题目条件,满足才继续深搜,不满足直接回溯
应用举例1:给定一个整数n,将1~n排成一排,按照字典序将所有排序方法输出
#include <stdio.h>
#include <string.h>
int n;
int path[500]; //用来存储往下深度搜索的路径,也就是本题的排列顺序
int st[500]; //用来判断数字1,2,3,4……n,是否已经被用过,比如如果1被用过,那么st[1]=1
void dfs(int u)
{
if (u == n) //当u==n的时候,说明已经深度搜索到了叶节点,停止搜索并打印答案
{
for (int i = 0; i < n; i++)
printf("%d ", path[i]);
printf("\n");
return ;
}
else
{
for (int i = 1; i <= n; i++) //从1这个节点开始往下深搜,一直
if (!st[i]) //如果当前这个数字没有用过
{
path[u] = i; //就把这个数字存放到path当前的空位里
st[i] = 1; //然后把这个数字置为已使用
dfs(u + 1); //然后进一步深度搜索path的下一个空位应该存放什么
st[i] = 0; //走到这里是深度搜索已经结束,开始回溯去恢复原样,重置数字为0
}
}
}
int main()
{
scanf("%d", &n);
dfs(0); //dfs(0)说明是从根节点开始寻找
return 0;
}
应用举例2:n皇后问题,将n个皇后放在n*n的国际象棋棋盘上,使每一个皇后不能互相攻击到,即不能处在同一条横线,同一条竖线,同一条斜线上,输出所有的皇后摆法
思路1:棋盘上一个格子一个格子地往下遍历,每一个格子都判断一下当前这个格子要不要放皇后,直到深度搜索逐个遍历到了最后一行放上了最后一个皇后,再将该种摆法打印;
#include<stdio.h>
#include<string.h>
int n;
char g[200][200]; //用来存储棋盘以及皇后的摆放位置
int row[200],col[200],dg[200],udg[200];//row判断同行有无皇后以及col同列 ug正对角 udg反对角
void dfs(int x,int y,int s) //x代表棋盘行数,y代表棋盘列数,s代表皇后的个数
{
if(y==n) y=0,x++; //如果搜索到了该行的最后一个格子,换行
if(x==n) //如果搜索到了最后一行
{
if(s==n) //如果此时已经放上了最后一个皇后就打印
{
for(int i=0;i<n;i++) puts(g[i]);
puts("");
}
return; //返回进行下次深搜
}
dfs(x,y+1,s); //如果当前格子不放皇后,就搜索到下一个格子
if(!row[x]&&!col[y]&&!dg[x+y]&&!udg[x-y+n]) //如果横竖斜上没有皇后,说明该格子可以放皇后
{
g[x][y]='Q';
row[x]=col[y]=dg[x+y]=udg[x-y+n]=1; //将该点的横竖斜都标为有皇后
dfs(x,y+1,s+1); //在该点横竖斜上都有皇后的情况下进入下格格子深搜
row[x]=col[y]=dg[x+y]=udg[x-y+n]=0; //走到这一步说明已经全部深搜完,回溯恢复现场
g[x][y]='.'; //回溯的时候将棋盘也恢复现场
}
}
int main()
{
scanf("%d",&n);
for(int i=0;i<n;i++)
for(int j=0;j<n;j++)
g[i][j]='.';
dfs(0,0,0); //代表从第0行第0列,0个皇后的情况开始深搜
return 0;
}
思路2:将问题抽象化为全排列问题,每一行就是一种排列方式,类似于应用1找到数字的全排列方式,只不过多了当前横竖斜不能有皇后的限制
#include <stdio.h>
#include <string.h>
int n;
char g[200][200];
int col[200], dg[200], udg[200];
void dfs(int u)
{
if (u == n)
{
for (int i = 0; i < n; i++) puts(g[i]);
puts("");
return;
}
for (int i = 0; i < n; i++)
{
if (!col[i] && !dg[u + i] && !udg[n - u + i])
{
g[u][i] = 'Q';
col[i] = dg[u + i] = udg[n - u + i] = 1;
dfs(u + 1);
col[i] = dg[u + i] = udg[n - u + i] = 0;
g[u][i] = '.';
}
}
}
int main() {
scanf("%d", &n);
getchar();
for (int i = 0; i < n; i++)
for (int j = 0; j < n; j++)
g[i][j] = '.';
dfs(0);
return 0;
}
dfs:宽度优先搜索
每次搜索一层,直到搜索完本层的所有节点才会去搜索下一层
通过遍历队列来进行往下搜索,所用空间较大,具有最短性,应用场景如最小次数,最短路径问题
应用举例1:走迷宫,给定一个n*m的二维整数数组,用来表示一个迷宫,数组中只包含0和1,0表示可以走,1表示不可走,最初有一个人在左上角(1,1)处,每次可以向上下左右任意一个方向移动一个位置,问该人从左上角移动到右下角(n,m)处,至少需要移动多少步?
#include <stdio.h>
#include <string.h>
int n, m;
int g[300][300]; //存储迷宫
int d[300][300]; //存储迷宫里的各个格子都是第几层宽搜到的
typedef struct {
int x;
int y;
} student;
student q[500], t; //队列用来存储各个需要进行宽搜的点,按照先入先出的顺序进行逐层宽搜
int bfs() {
int hh = 0, tt = 0;
q[0].x = 0, q[0].y = 0; //从左上角的点开始,所以一开始是(0,0)
memset(d, -1, sizeof d); //将d[ ][ ]里全部定义为-1,代表该格子还没有被宽搜到
d[0][0] = 0; //(0,0)点处是第0层
int dx[4] = {-1, 0, 1, 0}, dy[4] = {0, 1, 0, -1};
//一个小技巧,利用dx和dy可以避免用四个if来判断上下左右四个宽搜的方向,只需要用一个for循环
while (hh <= tt) {
t.x = q[hh].x, t.y = q[hh].y; //队头出队,代表从这个点开始要宽搜了!
hh++;
for (int i = 0; i < 4; i++) {
int x = t.x + dx[i], y = t.y + dy[i];
//(x,y)即代表这个点上下左右四个方向的点
if (x >= 0 && x < n && y >= 0 && y < m && g[x][y] == 0 && d[x][y] == -1)
//如果没有超出这个迷宫的范围,而是通路,且下个点没有走过
{
d[x][y] = d[t.x][t.y] + 1;
//宽搜到下一个点,这个点是上一个点的下一层搜到的,所以+1
tt++;
q[tt].x = x, q[tt].y = y;
//将该点入队,作为之后还要继续往下宽搜的点
}
}
}
return d[n - 1][m - 1];
}
int main() {
scanf("%d %d", &n, &m);
getchar();
for (int i = 0; i < n; i++)
for (int j = 0; j < n; j++)
scanf("%d", &g[i][j]);
printf("%d", bfs());
return 0;
}