DFS和BFS

DFS:深度优先搜索

尽量往深度搜索,直到遇到了叶节点就回溯到上一节点,然后看看这个节点还可不可以往深度搜索,不可以继续回溯,递归地重复这个规程。

通过递归来进行往下搜索,所用空间较小,不具备最短性,关键词:“回溯”“剪枝”

  1. 回溯:每一次当前节点深度搜索结束后回溯,要将各种参量置零,恢复现场
  2. 递归:每一次深度搜索都是一个递归的过程
  3. 剪枝:在当前节点判断一下是否已经不满足题目条件,满足才继续深搜,不满足直接回溯

应用举例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;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值