深度优先搜索(DFS)和广度优先搜索(BFS)
深度优先搜索(DFS)
深度优先搜索是一种枚举所有情况并且遍历所有情况的搜索算法。
做过一些题解的小伙伴知道,深度优先搜索可以用栈来实现,但是相较而言,更容易理解又容易实现的方法应该是递归。而递归就需要确定递归边界,用以结束递归。不过在使用递归时,系统会调用一个叫系统栈的东西来存放递归的每一层状态,因此使用递归实现DFS的本质仍然是栈。
如下图:我们需要从p1到达p10,当然现在我们肉眼也可以找出路径,但是当这张图中的节点更多,那便可以通过DFS或者下面即将说的BFS来解决。
首先我们从p1开始遍历,往下遇到第一个分叉,先去到p2,往下又遇到一个分叉,再去到p4,这一条路径已经结束,并没有找到p10,所以i退回到上一个分叉的地方(p2),之后再去到p5,发现也没找到p10,所以退回到上个分叉,但这个分叉的所有支路都访问过,所以再往后退,退回到p1这个分叉。之后再去到p3,遇到一个分叉,先去到p6,,又遇见分叉,先去到p8,再往后便找到了p10,此时便结束了。但如果在上一个分叉没有选择这条支路,则会继续像走(p1->p2->p4)这条支路一样的过程,一路走到黑,不能找到终点,则退回到上一个分叉的地方,这便是深度优先搜索。
那么接下来我们来看一下典型的题目:
一·在n件物品中,每件物品的重量为w[i],价值为c[i],现在需要选出若干件物品放入一个容量为V的背包中,使得在选入背包的物品重量和不超过容量V的前提下,让背包中的物品的价值和最大,求最大价值。(鉴于是典型例题,所以测试数据就比较小)
首先我们分析一下,在这个问题中,我们需要保证在装入背包的物品总重量不能超过背包容量的前提下,找到总价值最大的值。我们对每件物品都有装入和不装入两种选择,而按照上面的讲解,DFS一般利用递归来解决,所以我们需要找到递归边界,而此处的递归边界就是已经选择完所有的物品(不知道每件物品的选择情况)。那么接下来问题就会很容易解决了。
#include <iostream>
using namespace std;
const int N=505;
int n,v,maxvalue=0;//n为物品个数,v为背包容量
int w[N],c[N];//w[]数组用于存放物品重量,c[]数组用于存放物品价值
void DFS(int index,int sumw,int sumc);
//index为当前物品的编号,sumw为当前物品的总重量,sumc为当前物品的总价值
int main()
{
cin>>n>>v;
for(int i=0;i<n;++i)
cin>>w[i];
for(int i=0;i<n;++i)
cin>>c[i];
DFS(0,0,0);//物品编码从0开始,起初背包中重量以及价值都为0
cout<<maxvalue<<endl;
return 0;
}
void DFS(int index,int sumw,int sumc)
{
if(index==n)//当处理完第n个物品截至
{
return ;
}
DFS(index+1,sumw,sumc);//当前物品不放入背包
if(sumw+w[index]<=v)
{
if(sumc+c[index]>maxvalue)
maxvalue=sumc+c[index];
}
DFS(index+1,sumw+w[index],sumc+c[index]);//当前物品放入背包
}
接下来再提供一道较简单的DFS题目:
假设有N个整数,要求选择K个整数相加得到结果为X,如果出现不止一种情况下相加得到X,那么就选择平方和较大的输出其k个整数以及平方和。(可以根据上面的讲解对其做出解答,下面提供一种写法)
#include <iostream>
#include <cmath>
#include <stack>
using namespace std;
const int N=505;
int number[N];//存放n个整数
stack <int>temp,ans;
int n,k,x;
//n为整数个数,k为需要选择整数个数 ,x为选择整数相加的和
int maxsqu=-1;//存放最大平方和
void DFS(int index,int nowk,int sum,int sumsqu);
//index为当前数字编码 ,nowk为当前已选数字个数,sum为已选数组和,sumsqu为已选数字平方和
int main()
{
cin>>n>>k>>x;
for(int i=0;i<n;++i)
cin>>number[i];
DFS(0,0,0,0);
cout<<"平方和最大为"<<maxsqu<<endl;
for(int i=0;i<k;++i)
{
cout<<ans.top()<<" ";
ans.pop();
}
return 0;
}
void DFS(int index,int nowk,int sum,int sumsqu)
{
if(nowk==k&&sum==x)
{
if(sumsqu>maxsqu)
{
maxsqu=sumsqu;
ans=temp;
}
return ;
}
if(index==n||nowk>k||sum>x)//当处理完数字未得到结果
return ;
int j=0;//j用来作为temp数组下标
temp.push(number[index]);
DFS(index+1,nowk+1,sum+number[index],sumsqu+number[index]*number[index]);//选择该数字
temp.pop();
DFS(index+1,nowk,sum,sumsqu);//不选择该数字
}
广度优先搜索(BFS)
不同于深度优先搜索的是广度优先搜索是一种理想情况下做出的,而深度优先搜索在现实中可以实现。它是一种当遇见分叉是对所有能直接到达的节点进行访问的算法。
同样是这一张图片,那么我们来分析一下BFS的走法。
首先我们从p1进入,这是起点,其次我们发现他有两个直接节点p2和p3,那我们便依次访问p2和p3,这是第一层。在之后我们发现p2有两个直接节点p4和p5,p3有两个直接节点,p6和p7,接下来我们便依次去访问p4,p5,p6,p7,这里是第二层。在之后p4,p5,p7已经没有后续节点了,所以去访问p6的直接节点p8和p9,这里是第三层。同理p9没有了后继节点,所以去访问p8的直接后继节点。
而这一算法可以用于解决迷宫这一类问题,(前文提到的DFS也可以用于解决,但相较而言,BFS应该比DFS的消耗更小在一般情况下),在一届蓝桥杯中便有这种题目,下面就提供迷宫的BFS解法。
迷宫(15分)
题目描述
下图给出了一个迷宫的平面图,其中标记为 1 的为障碍,标记为 0 的为可
以通行的地方。
010000
000100
001001
110000
迷宫的入口为左上角,出口为右下角,在迷宫中,只能从一个位置走到这
个它的上、下、左、右四个方向之一。
对于上面的迷宫,从入口开始,可以按DRRURRDDDR 的顺序通过迷宫,
一共 10 步。其中 D、U、L、R 分别表示向下、向上、向左、向右走。
对于下面这个更复杂的迷宫(30 行 50 列) ,请找出一种通过迷宫的方式,
其使用的步数最少,在步数最少的前提下,请找出字典序最小的一个作为答案。
请注意在字典序中D<L<R<U。(如果你把以下文字复制到文本文件中,请务
必检查复制的内容是否与文档中的一致。在试题目录下有一个文件 maze.txt,
内容与下面的文本相同)
01010101001011001001010110010110100100001000101010
00001000100000101010010000100000001001100110100101
01111011010010001000001101001011100011000000010000
01000000001010100011010000101000001010101011001011
00011111000000101000010010100010100000101100000000
11001000110101000010101100011010011010101011110111
00011011010101001001001010000001000101001110000000
10100000101000100110101010111110011000010000111010
00111000001010100001100010000001000101001100001001
11000110100001110010001001010101010101010001101000
00010000100100000101001010101110100010101010000101
11100100101001001000010000010101010100100100010100
00000010000000101011001111010001100000101010100011
10101010011100001000011000010110011110110100001000
10101010100001101010100101000010100000111011101001
10000000101100010000101100101101001011100000000100
10101001000000010100100001000100000100011110101001
00101001010101101001010100011010101101110000110101
11001010000100001100000010100101000001000111000010
00001000110000110101101000000100101001001000011101
10100101000101000000001110110010110101101010100001
00101000010000110101010000100010001001000100010101
10100001000110010001000010101001010101011111010010
00000100101000000110010100101001000001000000000010
11010000001001110111001001000011101001011011101000
00000110100010001000100000001000011101000000110011
10101000101000100010001111100010101001010000001000
10000010100101001010110000000100101010001011101000
00111100001000010000000110111000000001000000001011
10000001100111010111010001000110111010101101111000
题目分析
题目代码
#include <iostream>
#include <cstring>
#include <string>
#include <queue>
using namespace std;
char mp[30][50]; //迷宫
bool vis[30][50]; //标记该点是否走过
int dir[4][2] = { {1,0},{0,-1},{0,1},{-1,0} }; //方向数组按照下,左,右,上的顺序走
char dirc[4] = { 'D','L','R','U' };//对应上方的四个方向
int n, m; //迷宫的行列
struct node {
int x; //横坐标
int y; //纵坐标
int step; //步数
string str; //路径
node(int xx, int yy, int ss, string s) //用于结构体初始化的构造函数
{
x = xx;
y = yy;
step = ss;//用于记录步数
str = s;//用于记录走出迷宫的路径
}
};
queue<node> q; //创建队列
bool check(int x, int y) { //判断是否越界以及是否是墙以及是否访问过了
if (x < 0 || x >= n || y < 0 || y >= m || vis[x][y] || mp[x][y] == '1') {
return false;
}
return true;
}
void bfs(int x, int y) {
q.push(node(x, y, 0, ""));
//走过一个点,便把这个点压入队列,并且在标记数组中将这个点标记
vis[x][y] = true;
while (!q.empty()) {
node now = q.front();
if (now.x == n - 1 && now.y == m - 1) { //到达终点了
cout << now.str << endl;
cout << now.step << endl;
break;
}
q.pop();
for (int i = 0; i < 4; i++) {
int nx = now.x + dir[i][0];
int ny = now.y + dir[i][1];
if (check(nx, ny)) {
q.push(node(nx, ny, now.step + 1, now.str + dirc[i]));
vis[nx][ny] = true;
}
}
}
}
int main()
{
cin >> n >> m;
for (int i = 0; i < n; ++i)
{
for (int j = 0; j < m; ++j)
{
cin >> mp[i][j];
}
}
bfs(0, 0);
return 0;
}