算法笔记第八章——搜索

深度优先搜索

深度优先搜索是一种枚举所有完整路径以遍历所有情况的搜索方法

背包问题

在这里插入图片描述
每个物品都有选或者不选两个选项,这就相当于是“岔道口”,而当一个背包中总重量大于了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当使元素入队时,仅仅是创造了一个元素的副本入队,如果修改队列中的元素值不会改变原先元素的值

猜你喜欢

转载自blog.csdn.net/weixin_44972129/article/details/110310545