一 DFS深度优先遍历
有n件物品,每件物品重量为w[i],价值位c[i]。现在需要选出若干件物品放入一个容量为V的背包中,使得在选入背包中的物品容量和不超过容量V的前提下,让背包中物品的价格之和最大,求最大价值。
由于对每件物品都有选与不选两种选择,这就是岔道口。一旦选择的物品重量之和超过V,就会到达死胡同,需要返回就近的岔道口。
显然每次都要对物品进行选择,因此DFS函数的参数必须记录当前处理的物品编号index,还需要记录在处理当前物品之前,已选物品的总重量sumW和总价值sumC
void DFS(int index,int sumW,int sumC)
如果选择不放入index号物品,那么sumW和sumC就不变,接下来处理index+1号物品,选择的分支如下
void DFS(index+1,sumW,sumC)
如果选择放入index号物品,那么sumW和sumC将分别加w[index]和c[index],接下来处理index+1号物品,选择的分支如下
void DFS(index+1,sumW+w[index],sumC+c[index])
一旦index涨到了n,说明已经把n件物品处理完毕(物品的编号位为0到n-1),此时记录的就是所选物品的总重量和总价值。如果sumW不超过V而且sumC大于一个全局的记录最大总价值的变量maxValue,说明当前的选择方案可以得到最大的价值,于是用sumC代替maxValue
#include <iostream>
#include <cstdio>
using namespace std;
const int maxn=10;
int n,V,maxValue=0; //n件物品,背包容量为V,最大价值maxValue
int w[maxn],c[maxn]; //分别为每件物品的重量和价值
void DFS(int index,int sumW,int sumC)
{
if(index==n) //进入死胡同,已经处理完所有物品
{
if(sumW<=V&&sumC>maxValue)
{
maxValue=sumC;
}
return;
}
//岔道口
DFS(index+1,sumW,sumC); //不选index件物品
DFS(index+1,sumW+w[index],sumC+c[index]); //选择第index件物品
}
int main()
{
scanf("%d%d",&n,&V);
for(int i=0;i<n;i++)
{
scanf("%d",&w[i]); //每件物品重量
scanf("%d",&c[i]); //每件物品的价值
}
DFS(0,0,0);
printf("%d\n",maxValue);
return 0;
}
由于每件物品都有2中选择,因此代码时间复杂度为O(2n)(2的n次幂)。但其实仔细分析就会发现,在进入岔道口之前可以先来判断下是否满足背包容量不超过V这个特点,如果已经不满足,就不必进入岔道口了。
void DFS(int index,int sumW,int sumC)
{
if(index==n) //进入死胡同,已经处理完所有物品
{
return;
}
//岔道口
DFS(index+1,sumW,sumC); //不选index件物品
//只有在选择加入第indecx个物品时才判断是否超出容量
if(sumW+w[index]<=V)
{
if(sumC+c[index]>maxValue)
{
maxValue=sumC+c[index]; //更新最大价值
}
DFS(index+1,sumW+w[index],sumC+c[index]); //选择第index件物品
}
}
记录选择的物品编号
#include <iostream>
#include <cstdio>
#include <vector>
using namespace std;
const int maxn=10;
int n,V,maxValue=0; //n件物品,背包容量为V,最大价值maxValue
int w[maxn],c[maxn]; //分别为每件物品的重量和价值
vector<int> temp,ans;
void DFS(int index,int sumW,int sumC)
{
if(index==n) //进入死胡同,已经处理完所有物品
{
return;
}
//岔道口
//只有在选择加入第indecx个物品时才判断是否超出容量
if(sumW+w[index]<=V)
{
if(sumC+c[index]>maxValue)
{
maxValue=sumC+c[index]; //更新最大价值
ans=temp;
}
temp.push_back(index);
DFS(index+1,sumW+w[index],sumC+c[index]); //选择第index件物品
temp.pop_back();
DFS(index+1,sumW,sumC); //不选index件物品
}
}
int main()
{
scanf("%d%d",&n,&V);
for(int i=0;i<n;i++)
{
scanf("%d",&w[i]); //每件物品重量
scanf("%d",&c[i]); //每件物品的价值
}
DFS(0,0,0);
printf("%d\n",maxValue);
for(vector<int>::iterator it=ans.begin();it!=ans.end();it++)
{
cout<<"您选了第"<<*it<<"件物品"<<endl;
}
return 0;
}
扩展
枚举从N个整数中选择K个数的所有方案。
给定N个整数,从中选择K个数(每个数可以被选择多次),使得这K个数之和恰好等于一个给定整数X;若果有多种方案,选择他们中元素平方和最大的一个。
在求解中,我们需要记录当前处理的整数编号index,由于需要K个数,需要一个nowK来记录当前已经选择数的个数,还需要参数sum和sumSqu分别记录当前已选整数之和与平方和。
void DFS(int index,int nowK,int sum,int sumSqu)
由于每个数可以被选择多次,因此当选择index号之后,不应该直接进入index+1号数的处理。知道某个时刻决定不再选择index号时,会进入另一条分支。
#include <iostream>
#include <cstdio>
#include <vector>
using namespace std;
const int maxn=30;
//序列A中n个数选K个数使得和为x,最大平方和为maxSumSqu
int n,k,x,maxSumSqu=-1,A[maxn];
//temp存放临时方案,ans存放最后方案
vector<int> temp,ans;
//当前处理index号整数,当前已选个数为nowK
void DFS(int index,int nowK,int sum,int sumSqu)
{
if(nowK==k&&sum==x)
{
if(sumSqu>maxSumSqu)
{
maxSumSqu=sumSqu;
ans=temp;
}
return;
}
if(index==n||nowK>k||sum>x)
return;
//选index号数,index号可以多选几次
temp.push_back(A[index]);
DFS(index,nowK+1,sum+A[index],sumSqu+A[index]*A[index]);
//不选index号数
temp.pop_back();
DFS(index+1,nowK,sum,sumSqu);
}
int main()
{
scanf("%d%d%d",&n,&k,&x);
for(int i=0;i<n;i++)
scanf("%d",&A[i]);
DFS(0,0,0,0);
for(vector<int>::iterator it=ans.begin();it!=ans.end();it++)
printf("%d ",*it);
return 0;
}
二 广度优先遍历(BFS)
DFS是以深度作为第一关键词,即当碰到岔道口时总是先选择其中的一条岔路前进,而不管其他岔路,直到碰到死胡同才返回岔路口并选择其他岔路。
而广度优先搜索BFS是以广度为第一关键词,当碰到岔道口时,总是先依次访问从岔道口能直接到达的所有节点,然后再按照这些节点被访问的次序去依次访问他们可以直接到达的所有节点,依次类推,直到所有节点都被访问为止。
广度优先搜索(BFS)一般由队列实现,且总是按照层序的顺序进行遍历,基本写法如下:
void BFS(int s)
{
queue<int> q;
q.push(s);
while(!q.empty())
{
取出队首元素;
访问队首元素;
将队首元素出队;
将top的下一层节点中未曾入队的节点全部入队,并设置为已入队;
}
}
题目一
给出一个m*n矩阵,矩阵中元素为0或1。称位置(x,y)与其上下左右四个位置(x,y+1),(x,y-1),(x+1,y),(x-1,y)是相邻的。如果矩阵中有若干个1是相邻的(不必22相邻),那么称这些1构成了一个块,求矩阵中块的个数。
0 1 1 1 0 0 1
0 0 1 0 0 0 0
0 0 0 0 1 0 0
0 0 0 1 1 1 0
1 1 1 0 1 0 0
1 1 1 1 0 0 0
这个6*7的矩阵,有4个块。
可以枚举每一个位置的元素,如果为0,则跳过;如果为1,则使用BFS查询与该位置相邻的4个元素(前提是不出界),判断他们是否为1,(如果某个相邻的位置为1,则同样需要查询与该位置相邻的4个位置,直到整个1块都被访问完)。为了防止重复访问,可以设置一个布尔数组来记录每个位置是否再BFS中已入过队。(而不是是否已经访问过)(因为访问的速度很慢,每次只访问一个,有可能存在这种情况:有个节点在队列中但还未访问时由于其他节点可以到达它而将这个节点再次入队,导致重复入队,增加计算量。)
对于当前位置(x,y)来说,与其相邻的四个位置,可以用2个增量数组来表示四个方向。
//下上右左
int X[]={0,0,1,-1};
int Y[]={1,-1,0,0};
BFS解法
#include <cstdio>
#include <queue>
using namespace std;
const int maxn=100;
int rows,cols; //行列数
int X[]={0,0,-1,1}; //增量矩阵
int Y[]={1,-1,0,0};
bool inq[maxn][maxn]={false}; //记录是否已经入队
int matrix[maxn][maxn]; //矩阵,存放01数
typedef struct node
{
int x;
int y;
}snode;
snode temp,top;
bool judge(int x,int y) //判断x,y是否需要访问
{
if(x>=cols||x<0||y>=rows||y<0)
return false;
if(inq[x][y]==true||matrix[x][y]==0)
return false;
return true;
}
//BFS访问位置(x,y)所在的块,将该块中所有的1都设置为已入队
void BFS(int x,int y)
{
queue<snode> Q;
temp.x=x,temp.y=y;
Q.push(temp);
inq[x][y]=true;
while(!Q.empty())
{
top=Q.front();
Q.pop();
for(int i=0;i<4;i++)
{
int newx=top.x+X[i];
int newy=top.y+Y[i];
if(judge(newx,newy))
{
temp.x=newx;
temp.y=newy;
Q.push(temp);
inq[newx][newy]=true;
}
}
}
}
int main()
{
printf("请输入矩阵的行数和列数\n");
scanf("%d%d",&rows,&cols);
printf("请输入01矩阵\n");
for(int i=0;i<rows;i++)
{
for(int j=0;j<cols;j++)
{
scanf("%d",&matrix[i][j]);
}
}
int number_of_index=0;
for(int i=0;i<rows;i++)
{
for(int j=0;j<cols;j++)
{
if(matrix[i][j]==1&&inq[i][j]==false)
{
number_of_index++;
BFS(i,j);
}
}
}
printf("%d\n",number_of_index);
return 0;
}
DFS解法
#include <cstdio>
using namespace std;
const int maxn=10;
bool idx[maxn][maxn]={0}; //存放每个1的层数
int maze[maxn][maxn];
int rows,cols;
int X[4]={0,0,1,-1};
int Y[4]={1,-1,0,0};
int index=0;
bool checkedge(int x,int y) //检查边界
{
if(x>=cols||x<0||y>=rows||y<0)
return false;
return true;
}
void dfs(int x,int y,int id) //id是层数
{
if(idx[x][y]>0||maze[x][y]!=1)
return;
idx[x][y]=id;
for(int i=0;i<4;i++)
{
if(checkedge(x+X[i],y+Y[i]))
dfs(x+X[i],y+Y[i],id);
}
}
int main()
{
scanf("%d%d",&rows,&cols);
for(int i=0;i<rows;i++)
{
getchar();
for(int j=0;j<cols;j++)
scanf("%d",&maze[i][j]);
}
for(int i=0;i<rows;i++)
{
for(int j=0;j<cols;j++)
{
printf("%d",maze[i][j]);
}
printf("\n");
}
for(int i=0;i<rows;i++)
{
for(int j=0;j<cols;j++)
{
if(idx[i][j]==0&&maze[i][j]==1)
dfs(i,j,++index);
}
}
printf("%d\n",index);
return 0;
}
注意选好死胡同
题目二 走出迷宫的最少步数
给出一个n*m的迷宫,其中*代表不可通过的墙壁,而“.”代表平地,S表示起点,T表示终点。移动过程中,如果当前位置是(x,y)(下标从0开始),且每次只可以前往上下左右四个位置的平地,求从起点S到达终点T的最少步数。
. . . . .
. * . * .
. * S * .
. * * * .
. . . T *
S的坐标为(2,2),T的坐标为(4,3)。
BFS解法
本题中,由于求的是最小步数,而BFS是通过层次的顺序来遍历的,因此可以从起点S开始计数遍历的层数,在到达终点T的时候的层数就是最小步数。
#include <cstdio>
#include <queue>
using namespace std;
const int maxn=10;
typedef struct node
{
int x;
int y;
int step;
}snode;
snode s,e,temp,top;
char matrix[maxn][maxn]; //魔宫矩阵
bool inq[maxn][maxn]={false}; //判断是否已经入队
int X[]={-1,1,0,0}; //增量矩阵
int Y[]={0,0,-1,1};
int rows,cols; //矩阵行列数
bool judge(int x,int y)
{
if(x>=cols||x<0||y>=rows||y<0)
return false;
if(matrix[x][y]=='*'||inq[x][y]==true)
return false;
return true;
}
int BFS()
{
queue<snode> Q;
Q.push(s);
while(!Q.empty())
{
top=Q.front();
Q.pop();
if(top.x==e.x&&top.y==e.y)
{
return top.step;
}
for(int i=0;i<4;i++)
{
int newx=top.x+X[i];
int newy=top.y+Y[i];
if(judge(newx,newy))
{
temp.x=newx;
temp.y=newy;
temp.step=top.step+1;
Q.push(temp);
inq[newx][newy]=true;
}
}
}
return -1;
}
int main()
{
printf("请输入行列数:\n");
scanf("%d%d",&rows,&cols);
for(int i=0;i<rows;i++)
{
getchar();
for(int j=0;j<cols;j++)
{
scanf("%c",&matrix[i][j]);
}
}
for(int i=0;i<rows;i++)
{
for(int j=0;j<cols;j++)
{
printf("%c",matrix[i][j]);
}
printf("\n");
}
printf("请输入起始点和终点的坐标:\n");
scanf("%d%d%d%d",&s.x,&s.y,&e.x,&e.y);
s.step=0;
printf("%d\n",BFS());
return 0;
}
DFS解法
#include <cstdio>
using namespace std;
const int maxn=10;
char matrix[maxn][maxn]; //迷宫矩阵
bool visited[maxn][maxn]={false}; //记录是否访问过
int rows,cols; //矩阵行列数
int s_x,s_y,e_x,e_y; //起始点和结束点坐标
int MIN=999999;
int X[]={-1,1,0,0};
int Y[]={0,0,-1,1};
bool judge(int x,int y)
{
if(x>=cols||x<0||y>=rows||y<0)
return false;
if(matrix[x][y]=='*'||visited[x][y]==true)
return false;
return true;
}
void dfs(int x,int y,int step)
{
if(x==e_x&&y==e_y)
{
if(step<MIN)
MIN=step;
return;
}
for(int i=0;i<4;i++)
{
int newx=x+X[i];
int newy=y+Y[i];
if(judge(newx,newy))
{
visited[newx][newy]=true; //选
dfs(newx,newy,step+1);
visited[newx][newy]=false; //不选
}
}
return;
}
int main()
{
printf("请输入矩阵的行列数:\n");
scanf("%d%d",&rows,&cols);
for(int i=0;i<rows;i++)
{
getchar();
for(int j=0;j<cols;j++)
{
scanf("%c",&matrix[i][j]);
}
}
for(int i=0;i<rows;i++)
{
for(int j=0;j<cols;j++)
{
printf("%c",matrix[i][j]);
}
printf("\n");
}
printf("请输入起始点和终点的坐标:\n");
scanf("%d%d%d%d",&s_x,&s_y,&e_x,&e_y);
dfs(s_x,s_y,0);
printf("%d\n",MIN);
return 0;
}