深度优先搜索
深度优先搜索是一种枚举所有完整路径以遍历所有情况的搜索方法
背包问题
每个物品都有选或者不选两个选项,这就相当于是“岔道口”,而当一个背包中总重量大于了V就代表走入了死胡同,需要回到上一个“岔道”
#include<cstdio>
const int maxn = 30;
int n,V,maxValue = 0; //物品件数n,背包容量V,最大价值maxValue
int w[maxn], c[maxn]; //w[i]每个物品重量,c[i]每个物品的价值
//DFS, index为当前处理的物品的编号
//sumW和sumC分别为当前总重量和当前总价值
void DFS(int index,int sumW, int sumC)
{
if(index==n){
//已经完成对n件物品的选择
if(sumW <= V && sumC > maxValue)
{
maxValue = sumC;
}
return;
}
DFS(index+1,sumW,sumC); //不选这个物品
DFS(index+1,sumW+w[index],sumC+c[index]); //选这个物品
}
int main(){
scanf("%d%d",&n,&V);
for(int i=0;i<n;i++)
{
scanf("%d",&w[i]);
}
for(int i=0;i<n;i++)
{
scanf("%d",&c[i]);
}
DFS(0,0,0);
printf("%d\n",maxValue);
return 0;
}
上述代码的时间复杂度是 O(2n), 上述代码总是在n个物品全部决定选还是不选后才去更新maxValue,但是可能会有在某个物品被选后,处理的数量还没有到n时,选中的重量就超过V了的情况,所以可以对代码进行下改进
index表示已经处理过的节点的个数,最开始是0表示没有节点被处理过
#include<cstdio>
const int maxn = 30;
int n,V,maxValue = 0; //物品件数n,背包容量V,最大价值maxValue
int w[maxn], c[maxn]; //w[i]每个物品重量,c[i]每个物品的价值
//DFS, index为当前处理的物品的编号
//sumW和sumC分别为当前总重量和当前总价值
void DFS(int index,int sumW, int sumC)
{
if(index==n) return;
DFS(index+1,sumW,sumC); //不选择index+1的后的总重量和总价值
if(sumW+w[index+1]<=V)
{
if(sumC+c[index+1]>maxValue) maxValue = sumC+c[index+1];
DFS(index+1,sumW+w[index+1],sumC+c[index+1]);
}
}
上述这种通过题目条件的限制来减少计算量的方法叫做剪枝
那么在选择出结果后,怎么保存我们得到结果的方案那?
#include<cstdio>
#include<cstdlib>
#include<vector>
using namespace std;
//用一个临时的数组先放着,当价值确实更大的时候,更新给结果数组
const int maxn = 30;
int n,V,maxValue = 0; //物品件数n,背包容量V,最大价值maxValue
int w[maxn], c[maxn]; //w[i]每个物品重量,c[i]每个物品的价值
vector<int> temp,result; //临时数组与结果数组
//DFS, index为当前处理的物品的编号
//sumW和sumC分别为当前总重量和当前总价值
void DFS(int index,int sumW, int sumC)
{
if(index==n) return;
DFS(index+1,sumW,sumC); //没选index+1号这个节点
if(sumW+w[index+1]<=V) //选择了这个节点
{
temp.push_back(index+1);
if(sumC+c[index+1]>maxValue){
//如果是更好的方案,那么就更新
maxValue = sumC+c[index+1];
result = temp;
}
DFS(index+1,sumW+w[index+1],sumC+c[index+1]);//在这条分支走结束之前是不会,
//进行下一条语句temp.pop_back的也就是这条分支走完会回溯到最新的节点没有被选择的情况下
temp.pop_back(); //为了不影响没有选择这个节点的那个分支,记得pop出来,因为temp是个全局变量
}
}
int main()
{
scanf("%d%d",&n,&V);
for(int i=0;i<n;i++){
scanf("%d",&w[i]);
}
for(int i=0;i<n;i++){
scanf("%d",&c[i]);
}
DFS(0,0,0);
printf("%d\n",maxValue);
for(int i=0;i<result.size();i++){
printf("%d ",result[i]);
}
return 0;
}
序列枚举问题
枚举从N个整数中选择几个数使得他们和为X,并且尽量使得平方和最大
#include<cstdio>
#include<cstdlib>
#include<vector>
using namespace std;
const int maxn = 100;
int n,k,x,maxSumSqu=-1,A[maxn];
vector<int> temp,ans;
//当前处理index号整数,当前已选整数个数为nowK
//当前已选整数之和为sum,当前已选整数平方和为sumSqu
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号数
temp.push_back(A[index]);
DFS(index+1,nowK + 1, sum+A[index],sumSqu+A[index]*A[index]);
temp.pop_back();
//不选index号数
DFS(index+1,nowK,sum,sumSqu);
}
宽度优先搜索
矩阵中"块"的个数
(上图右上角那个只有一个1的部分也算是一个块,这样算总共就有4个块)
基本思想: 枚举每一个位置的元素,如果是0那么跳过,如果是1,搜索其相邻的元素看是否都是1
为了防止走回头路,一般可以设置一个bool型的数组in_queue来判断每个位置是否已经在BFS中入队过了
#include<cstdio>
#include<queue>
using namespace std;
const int maxn = 100;
struct node{
int x,y;
}Node;
int n,m;
int matrix[maxn][maxn];
bool in_queue[maxn][maxn] = {
false};
int X[4] = {
0,0,1,-1};
int Y[4] = {
1,-1,0,0};
//判断(x,y)是否越界或者是否已经被访问过啦
bool judge(int x,int y)
{
if(x>=n || x<0 || y>=m || y<0) return false;
if(matrix[x][y]==0 || in_queue[x][y] == true) return false; //当前位置为0,或者(x,y)已经入队
return true;
}
void BFS(int x,int y)
{
queue<node> Q;
Node.x = x,Node.y = y;
Q.push(Node);
in_queue[x][y] = true;
while(!Q.empty())
{
node 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))
{
Node.x = newX, Node.y = newY;
Q.push(Node);
in_queue[newX][newY] = true;
}
}
}
}
int main()
{
scanf("%d%d",&n,&m);
for(int x=0;x<n;x++)
{
for(int y=0;y<m;y++)
{
scanf("%d",&matrix[x][y]);
}
}
int ans = 0 ;//存放块数
for(int x=0;x<n;x++)
{
for(int y=0;y<m;y++)
{
//如果元素为1并且还没有入队
if(matrix[x][y]==1 && in_queue[x][y]==false)
{
ans++;
BFS(x,y);
}
}
}
printf("%d\n",ans);
return 0;
}
求在迷宫中最小步数
由于BFS是通过层次的顺序来遍历的,可以从起点S开始记录遍历的层数,那么在到达终点T时的层数就是需要求解的从S到T的最小步数
分叉口的节点看做是同一层
#include<cstdio>
#include<cstring>
#include<queue>
using namespace std;
const int maxn = 100;
struct node{
int x,y;
int step; //从S到该位置的最小步数,也就是层数
}S,T,Node; //起点,终点,临时结点
int n,m;
char maze[maxn][maxn];
bool inq[maxn][maxn] = {
false};
int X[4] = {
0,0,1,-1};
int Y[4] = {
1,-1,0,0};
bool test(int x,int y)
{
if(x<0 || x>=n || y<0 || y>=m) return false;
if(maze[x][y]=='*') return false;
if(inq[x][y]==true) return false;
return true;
}
int BFS()
{
queue<node> q;
q.push(S);
while(!q.empty())
{
node top = q.front();
q.pop();
if(top.x==T.x && top.y==T.y) return top.step;
for(int i=0;i<4;i++)
{
int newX = top.x+X[i];
int newY = top.y+Y[i];
if(test(newX,newY))
{
Node.x = newX,Node.y = newY;
Node.step = top.step + 1;
q.push(Node);
inq[Node.x][Node.y]=true;
}
}
}
return -1;//无法到达终点
}
int main()
{
scanf("%d%d",&n,&m);
for(int i=0;i<n;i++)
{
getchar();
for(int j=0;j<m;j++)
{
maze[i][j] = getchar();
}
maze[i][m+1] = '\0';
}
scanf("%d%d%d%d",&S.x,&S.y,&T.x,&T.y);
S.step = 0;
printf("%d\n",BFS());
return 0;
}
需要注意的是,使用STL的queue当使元素入队时,仅仅是创造了一个元素的副本入队,如果修改队列中的元素值不会改变原先元素的值