搜索专题

本文深入解析深度优先搜索(DFS)与广度优先搜索(BFS)算法,涵盖背包问题、图遍历、迷宫寻路等典型应用案例,提供C++实现代码。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

DFS(深度优先遍历)

最基本

1、深度优先搜索是一种枚举所有完整路径以遍历所有情况的搜索方法,使用递归可以很好的实现深度优先搜索,使用递归来实现DFS的本质其实是栈。

问题:有n件物品,每件物品的重量为w[i],价值为c[i],现在需要选出若干件物品放入一个容器为V的背包中,使得在选入背包的物品重量和不超过容量V的前提下,让背包中物品的价值之和最大,求最大价值(1<=n<=20)

分析:如果选择不放入index号物品,那么sumM,sumC就不变,接下来处理index+1好物品,即前往DFS(index+1,sumM,sumC)这条分支,
如果选择放入index号物品,那么sumM+w[index],sumC+c[index],即前往DFS(index+1,sumM++w[index],sumC+c[index];

/*问题:有n件物品,每件物品的重量为w[i],价值为c[i],现在需要选出若干件物品放入一个容器为V的背包中,
使得在选入背包的物品重量和不超过容量V的前提下,让背包中物品的价值之和最大,求最大价值(1<=n<=20)*/
/*如果选择不放入index号物品,那么sumM,sumC就不变,接下来处理index+1好物品,即前往DFS(index+1,sumM,sumC)这条分支,
如果选择放入index号物品,那么sumM+w[index],sumC+c[index],即前往DFS(index+1,sumM++w[index],sumC+c[index])*/

#include <iostream>
#include<cstdio>
using namespace std;
const int maxn=30;
int n,v,maxvalue=0;//物品件数,背包容量v,最大价值maxvalue
int w[maxn],c[maxn];//w[i]为每件物品的重量,c[i]为每件物品的价值
//DFS,index为当前处理的物品编号、
//sumW,和sumC分别为当前总重量和当前总价值
void DFS(int index,int sumM,int sumC)
{
    if(index==n)
    {
        return;//已经完成对n件物品的选择
    }
    DFS(index+1,sumM,sumC);//不选第index件物品
    if(sumM+w[index]<=v)//只有加入第index件物品后未超过容量V,才能继续
    {
        if(sumC+c[index]>maxvalue)
        {
            maxvalue=sumC+c[index];
        }
        DFS(index+1,sumM+w[index],sumC+c[index]);//选第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);//初始时为第0件物品,当前总重量和总价值为0
    cout<<maxvalue<<endl;

    return 0;
}

问题:给定N个整数(可能有负数),从中选取k个数,使得这k个数之和恰好等于一个给定的整数x,如果有多种方案,选择它们中元素平方和最大的一个。数据保证这样的方案唯一。例如,从4个整数{2,3,3,4}中选择两个数,使它们的和为6,显然有两种方案{2,4}与{3,3},其中平方和最大的方案为{2,4}

/*问题:给定N个整数(可能有负数),从中选取k个数,使得这k个数之和恰好等于一个给定的整数x,如果有多种方案,
选择它们中元素平方和最大的一个。数据保证这样的方案唯一。例如,从4个整数{2,3,3,4}中选择两个数,使它们的和为6,
显然有两种方案{2,4}与{3,3},其中平方和最大的方案为{2,4}*/
#include <iostream>
#include<cstdio>
#include<vector>
using namespace std;
const int maxn=20;
int n,k,x,maxsumsqu=-1,A[maxn];//序列A中n个数选取k个数使得和为x,最大平方和为maxsumsqu
vector<int> temp,ans;//temp存放临时方案,ans存放平方和最大的方案
//当前处理index号整数,当前已选整数个数为nowK
//当前已选整数之和为sum,当前已选整数平方和为sumsqu;
void DFS(int index,int nowK,int sum,int sumsqu)
{
    if(nowK==k&&sum==x)//找到k个数的和为x
    {
        if(sumsqu>maxsumsqu)
        {
            maxsumsqu=sumsqu;
            ans=temp;//更新最优方案
        }
        return ;
    }
    //已经处理完n个数,或者超过k个数,或者和超过x,返回
    if(index==n||nowK>k||sum>x)
        return ;
/*当试图进入“选index号数”这条分支时,就把A[index]加入temp中,当这条分支结束时,就把它从temp中去除,使它不会影响“不选index号数”这个分支*/
    //选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);
}
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);
   while(!ans.empty())
   {
      cout<<ans.front()<<" ";
      ans.erase(ans.begin());//这里打印的是头,所以删除的也应该是头
   }
    return 0;
}

 

1、用dfs遍历图,每次都是沿着路径到不能再前进时才退回到最近的岔路口。

2、在这之前 ,需要先定义maxv为最大顶点数,INF为一个很大的数字

const int maxv=1000;//最大顶点数
const int INF=1000000000;//设INF是一个很大的数

①邻接矩阵版

int n,G[maxv][maxv];//n为顶点数,maxv为最大顶点数
bool vis[maxv]={false};//如果顶点i已被访问,则vis[i]==true,初值为false
void dfs(int u,int depth)//u为当前访问的编号,depth为深度
{
	vis[u]=true;
	for(int v=0;v<u;v++)//对每个顶点v
	{
		if(vis[v]==false&&G[u][v]!=INF)//如果v未被访问,且u可到达v
		{
			dfs(v,depth+1);//访问v,深度加1
		}
	}
}
void dfstrave()
{
	for(int u=0;u<n;u++)//对每个顶点u
	{
		if(vis[u]==false)
		{
			dfs(u,1);//访问u和所在的连通块,1表示初始为第一层
		}
	}
}

②邻接表版

vector<int> Adj[maxv];//图G的邻接表
int n;//n为顶点数,maxv为最大顶点数
bool vis[maxv]={false};//如果顶点i已被访问,则vis[i]==true,初值为false
void dfs(int u,int depth)//u为当前访问的编号,depth为深度
{
	vis[u]=true;
	for(int i=0;i<Adj[u].size;i++)//对从u出发可到达的所有顶点
	{
		int v=Adj[u][i];
		if(vis[v]==false)//如果v未被访问
		{
			dfs(v,depth+1);//访问v,深度加1
		}
	}
}
void dfstrave()
{
	for(int u=0;u<n;u++)//对每个顶点u
	{
		if(vis[u]==false)
		{
			dfs(u,1);//访问u和所在的连通块,1表示初始为第一层
		}
	}
}

BFS(广度优先搜索)

最基本

1、广度优先搜索:当碰到岔路口时,总是先依次访问从该岔路口能直接到达的所有结点,然后再按这些节点被访问的顺序去一次访问它们能直接到达的所有结点,以此类推,直到所有结点都被访问为止。

BFS中设置的inq数组的含义是判断结点是否已经入过队,而不是结点是否已经被访问

总结:广度优先搜索一般由队列实现,且总是按层次的顺序进行遍历,其基本写法如下

void BFS(int s)
{
	queue<int> q;
	q.push(s);
	while(!q.empty())
	{
		取出队首元素top;
		访问队首元素top;
		将队首元素出队;
		将top的下一层结点中未曾入队的结点全部入队,并设为已经入队;
	}
}

问题:给出一个m*n的矩阵,矩阵中的元素为0或1,称位置(x,y)与其上下左右的位置为相邻。如果矩阵中有若干个1是相邻的,那么称这些1构成了一个“块”,求给定的矩阵中“块”的个数


/*问题:给出一个m*n的矩阵,矩阵中的元素为0或1,称位置(x,y)与其上下左右的位置为相邻。如果矩阵中有若干个1是相邻的,
那么称这些1构成了一个“块”,求给定的矩阵中“块”的个数*/
#include <iostream>
#include<queue>
#include<cstdio>
using namespace std;
const int maxn=100;
bool inq[maxn][maxn]={false};
int juzhen[maxn][maxn];
int X[]={0,0,-1,1};
int Y[]={1,-1,0,0};
int n,m;//n*m矩阵
int ans=0;
struct node
{
    int x;
    int y;
}Node;//Node只是一个结点
bool check(int x,int y)
{
    if(juzhen[x][y]==0||inq[x][y]==true)
    {
        return false;
    }
    if(x>=n||x<0||y>=m||y<0)//这是二维数组,自行理解
    {
        return false;
    }
     return true;
}
void BFS(int x,int y)
{
   queue<node> Q;
   Node.x=x;
   Node.y=y;
   int newX,newY;
   Q.push(Node);
   inq[x][y]=true;//这步我忘
   while(!Q.empty())
   {

      node s=Q.front();//取出队首元素,这步我不会定义
       Q.pop();//队首元素出队
       for(int i=0;i<4;i++)
       {
           newX=s.x+X[i];
           newY=s.y+Y[i];
           while(check(newX,newY))
           {
               Node.x=newX;
               Node.y=newY;
               Q.push(Node);
               inq[newX][newY]=true;
           }
       }
   }

}
int main()
{
    scanf("%d%d",&n,&m);
    for(int i=0;i<n;i++)
    {
        for(int j=0;j<m;j++)
        {
            scanf("%d",&juzhen[i][j]);
        }
    }
    for(int i=0;i<n;i++)
    {
        for(int j=0;j<m;j++)
        {
            if(juzhen[i][j]==1&&inq[i][j]==false)
            {
                ans++;
                BFS(i,j);
            }
        }
    }
    cout << ans << endl;
    return 0;
}

问题: 给定一个n*m大小的迷宫,其中*代表不可通过的墙壁,而“.”代表平地,S表示起点,T代表终点,移动过程中,如果当前位置为(x,y)(下标从0开始),且每次只能前往上下左右(x,y+1),(x,y-1),(x-1,y),(x+1,y)四个位置的平地,求从起点S到达终点T的最小步数

/*问题: 给定一个n*m大小的迷宫,其中*代表不可通过的墙壁,而“.”代表平地,S表示起点,T代表终点,移动过程中,
如果当前位置为(x,y)(下标从0开始),且每次只能前往上下左右(x,y+1),(x,y-1),(x-1,y),(x+1,y)四个位置的平地,求从起点S到达终点T的最小步数*/

#include <iostream>
#include<cstdio>
#include<queue>
using namespace std;
const int maxn=100;
int X[]={0,0,-1,1};
int Y[]={1,-1,0,0};
int n,m;
int step=0;
struct node
{
    int x;
    int y;
    int step;
}S,T,Node;//S为起点,T为终点,Node为临时结点
char mig[maxn][maxn];
bool inq[maxn][maxn]={false};
bool check(int x,int y)
{
    if(x>=n||x<0||y>=m||y<0)
    {
        return false;
    }
    if(mig[x][y]=='*'||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(check(newX,newY))
            {
                Node.x=newX;
                Node.y=newY;
                Node.step=top.step+1;
                Q.push(Node);
                inq[newX][newY]=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++)
        {
            mig[i][j]=getchar();
        }
        mig[i][m+1]='\0';
    }
    scanf("%d%d%d%d",&S.x,&S.y,&T.x,&T.y);
    S.step=0;//初始化起点的层数为0,即S到S的最小步数为0


    cout <<BFS()<< endl;
    return 0;
}

 

1、和树的遍历一样,使用BFS遍历图需要使用一个队列,通过队列反复取出队首顶点,将该顶点可到达的未曾加入过队列(而不是 )的顶点全部入队,直到队列为空时遍历结束。

2、BFS的具体实现

①邻接矩阵版

int n,G[maxv][maxv];//n为顶点数,maxv为最大顶点数
bool inq[maxv]={false};//若顶点i曾入过队列,则inq[i]=true,初值为false
void bfs(int u)//遍历u所在的连通块
{
	queue<int> q;//定义队列q
	q.push(u);//将初始点u入队
	inq[u]=true;//设置u已被加入过队列
	while(!q.empty())
	{
		int u=q.front();//取出队首元素
		q.pop();//将队首元素出队
		for(int v=0;v<n;v++)
		{
			//如果u的邻接点v未曾加入过队列
			if(inq[v]==false&&G[u][v]!=INF)
			{
				q.push(v);//将v入队
				inq[v]=true;//标记v为已被加入过队列
			}
		}
	}
}
void bfstrave()//遍历图G
{
	for(int u=0;u<n;u++)//枚举所有点
	{
		if(inq[u]==false)
		{
			bfs(q);
		}
	}
}

②邻接表版

vector<int> Adj[maxv];//图G,Adj[u]存放从顶点u出发可以到达的所有顶点
int n;//n为顶点数,maxv为最大顶点数
bool inq[maxv]={false};//若顶点i曾入过队列,则inq[i]==true,初值为false
void bfs(int u)//遍历u所在的连通块
{
	queue<int> q;//定义队列q
	q.push(u);//将初始点u入队
	inq[u]=true;//设置u已被加入过队列
	while(!q.empty())
	{
		int u=q.front();//取出队首元素
		q.pop();//将队首元素出队
		for(int i=0;i<Adj[u].size();i++)
		{
			int v=Adj[u][i];
			//如果u的邻接点v未曾加入过队列
			if(inq[v]==false)
			{
				q.push(v);//将v入队
				inq[v]=true;//标记v为已被加入过队列
			}
		}
	}
}
void bfstrave()//遍历图G
{
	for(int u=0;u<n;u++)//枚举所有点
	{
		if(inq[u]==false)
		{
			bfs(q);
		}
	}
}

3、在给定BFS初始点的情况下,可能需要输出该连通块内所有其他顶点的层号。由于需要存放顶点层号,则需要定义结构体Node,并在其中存放顶点的编号和层号

struct Node
{
	int v;//顶点编号
	int layer;//顶点层号
};

在这种情况下,vector邻接表中的元素就不再是int,而变为Node ——vector<Node> Adj[N];

如果当前顶点的层号为L,那么它所有出边的终点的层号都为L+1

struct Node
{
	int v;//顶点编号
	int layer;//顶点层号
};
void BFS(int s)//s为起始点编号
{
	queue<Node> q;//BFS队列
	Node start;//起始顶点
	start.v=s;//起始顶点编号
	start.layer=0;//起始顶点层号为0
	q.push(start);//将起始顶点压入队列
	inq[start.v]=true;//起始顶点的编号设为已被加入过队列
	while(!q.empty())
	{
		Node topNode=q.front();//取出顶点的编号
		q.pop();//队首顶点出队
		int u=topNode.v;//队首顶点的编号
		for(int i=0;i<Adj[u].size();i++)
		{
			Node next=Adj[u][i];//从u出发能到达的顶点next
			next.layer=topNode.layer+1;//next层号等于当前顶点层号+1
			//如果next的编号未被加入过队列
			if(inq[next.v]==false)
			{
				q.push(next);
				inq[next.v]=true;//next的编号设为已被加入过队列
			}
		}
	}
}

 

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值