深度优先算法与广度优先算法的理解与常用场景

深度优先算法:

我的理解
就像走迷宫,每次遇到岔道口就要选一条路来走,每次遇到死胡同就要原路返回找到上一个路口选择其他的路,直到找到出口,显然最坏的情况是遍历完所有的道路后才找到出路,适用于在一个大整体内找一个达到某种条件或者最优的小整体,最优就必须要遍历完所有组合方案才能找到最优的。这就是深度优先算法。
通过两个例子(也是常用场景)来理解:
(1)有n件物品,每个物品的重量为w[i],对应价格c[i],让我们选出若干件物品放入最大承重为V的背包中,要求使背包中物品价值之和最大。求最大价值。(就当是抢银行吧。。。)
分析:
显然这是找最优方案,每个物品都有选和不选两种方案,这是岔路口;而物品重量之和应不大于背包称重,这是死胡同。经过这样的分析我们可以看出其实深度优先算法适合用递归的方式进入多个递归口对应有多种选择,错了就会return再进入其他选择
传入递归函数的参数往往是死胡同和我们需要计算得出的变量
就像这道题的index:物品件数的序号(<=n)、sumw:物品重量之和(<=V)、sumc:物品价值之和。

#define maxn 30
//把需要的变量定义为全局变量,方便递归函数调用
int n,V;
int w[maxn],c[maxn];
int maxc=0;//记录最大价值
void DFS(int index,int sumw,int sumc)
{
	if(index==n)
		return;
	DFS(index+1,sumw,sumc);//不选
	if(sumw+w[index]<=V){//选的条件
		if(sumc+c[index]>maxc){
			maxc=sumc+c[index];
		}	
		DFS(index+1,sumw+w[index],sumc+c[index]);//选
	}
}
int main()
{
	cin>>n>>V;	
	for(int i=0;i<n;i++)
		cin>>w[i]>>c[i];
	DFS(0,0,0);
	cout<<maxc;
	return 0;
}

(2)从N个数中选k个数使得这k个数之和为X,如果有多种方案,输出元素平方和最大的那组。(每个数只能选一次)
这道题的关键在于让我们输出整个方案,我们就需要一个容器来存放每一步插入的元素,而下面的代码中用tmp存放选中的A[index]后,在选这条分支结束后就把它从tmp中删除,这样就不会影响不选的这条分支,由此可以体现出递归与深度优先算法结合的奥妙。

#include <iostream>
#include <vector>
using namespace std;
#define maxN 30
int N,k,X;
int A[maxN];//N个数的序列 
int maxsum=-1;//记录最大平方和 
vector<int> tmp,ans;//tmp记录每次选择的方案,ans记录符合条件的方案 
void DFS(int index,int nowk,int sum,int sumq)
//参数为当前元素的序号,当前已选个数,已选的和,已选的平方和 
{
	if(nowk==k&&sum==X){
		if(sumq>maxsum){
			maxsum=sumq;
			ans=tmp;
		}
		return;
	} 
	if(index==N||nowk>k||sum>X)//这个判断不能放在第一步,因为index==N就return可能会错过最佳方案的更新
		return;
	//选 
	tmp.push_back(A[index]);
	DFS(index+1,nowk+1,sum+A[index],sumq+A[index]*A[index]);
	tmp.pop_back();
	//不选 
	DFS(index+1,nowk,sum,sumq);
} 
int main() 
{
//不知道为什么用cin和cout就没有对应的输出。。。。。。
	int i;
	scanf("%d %d %d",&N,&k,&X);
	for(i=0;i<N;i++)
		scanf("%d",&A[i]);
	DFS(0,0,0,0);
	for(i=0;i<ans.size();i++)
		printf("%d ",ans[i]);	
	return 0;
} 

此外,如果不限制每个数只能选一次,我们只需要把选的入口改为:

DFS(index,nowk+1,sum+A[index],sumq+A[index]*A[index]);

这样遇到死胡同也会通过不选的入口实现index+1。

广度优先算法

我的理解:
如果你想认识一个陌生人,直接跟他说可能有些尴尬,我们就要通过既认识你又认识那个陌生人的人来让你和陌生人认识,首先我们要遍历你的认识的人(第一层),检查每个你认识的人是否认识那个陌生人,遍历完后如果检测不到你认识的人认识那个陌生人,那就遍历每个你认识的人认识的人(第二层),每次遍历都检查是否认识那个陌生人,依次类推直到检测到有一个人认识那个陌生人。
显然这样的检测是随时的,很符合队列的形式,先进先出,边遍历边检查,所以我们用队列的形式来实现。而且这是按一层层的关系来进行查找。
模板:

void BFS(ElementType X)               //X是开始,就是你自己
{
	flag[X]=0;                        //flag标记是否认识那个陌生人
	queue<ElementType> Q;
	Q.push(X);
	while(!Q.empty()){
		Q.front();                     //弹出第一个人(先进先出)
		for(弹出第一个人他认识的每个人Y){
			if(!flag[Y]){
				flag[Y]=1;
				Q.push(Y);
			/*}else{因为很多时候并不是这样简单的条件,所以通常在if中操作,遍历完再进行判断*/
				//找到flag[Y]=1了的那个人了,也就是认识陌生人的人
				//之后对应的操作由需求来定
			}
		}
		Q.pop();
	}
}

下面通过我的理解中的那个例子来理解:
有一个六度空间理论,就是说你和世界上任何一个人之间最多需要通过六个人就能建立起联系,现在我们要证明这个理论:输入社交网络图(社交网络图中总人数N,相互认识的两个人的对数M以及M对相互认识的人的编号),输出每个人能通过六度空间认识的人数占社交网络图中总人数的比例。
输入样例:-------------------输出样例:
10 9 ----------------------------1 : 0.7
1 2 ------------------------------2 : 0.8
2 3 ------------------------------3 : 0.9
3 4 ------------------------------4 : 1.0
4 5 ------------------------------5 : 1.0
5 6 ------------------------------6 : 1.0
6 7 ------------------------------7 : 1.0
7 8-------------------------------8 : 0.9
8 9 ------------------------------9 : 0.8
9 10 ---------------------------10 : 0.7
思路与我想认识那个陌生人的思路一样。
重点在于逐层遍历,当层数达到6时就是六度空间的上限,而判断每层结束的标志就是当前被遍历的人(代码中的de)是该层中最后一个人。仔细体会tmp和last的作用。

#include <iostream>
#include <vector>
#include <queue>
#define maxN 1000
using namespace std;
int G[maxN][maxN];//用二维数组记录每个人之间是否认识 
int visited[maxN]={0};//记录每个人是否被访问 
int N,M; 
int BFS(int v)
{
	visited[v]=1;
	int count=1,level=0;//count记录通过六个人以内认识的人数,level为遍历的层数 
	queue<int> Q;
	Q.push(v);
	int last=v;//last存队尾元素 
	while(!Q.empty()){
		int de=Q.front();//取队首元素 
		int tmp;//存当前for循环的最后元素 
		for(int i=0;i<N;i++){
			if(G[de][i]&&!visited[i]){
				Q.push(i);
				visited[i]=1;
				tmp=i; 
				count++; 
			} 
		} 
		if(de==last){//如果是当前层最后一个元素 
			level++;
			last=tmp; 
		}
		if(level==6)
			break;
		Q.pop();
	} 
	return count;
}
void isvisited()//重置visited[i]
{
	for(int i=0;i<N;i++)
		visited[i]=0;
}
int main()
{
	scanf("%d %d",&N,&M);
	int i,v,w;//v,w是相互认识的两个人的编号 
	for(i=0;i<M;i++){//因为标号是从1-N,故--
		scanf("%d %d",&v,&w);
		v--;
		w--;
		G[v][w]=1;
		G[w][v]=1;
	}
	int count;//记录每个人能通过六度空间认识的人数
	double rate;//记录每个人能通过六度空间认识的人数占社交网络图中总人数的比例
	for(i=0;i<N;i++){
		isvisited();//坑人之处,每个人被广度前都要重置visited[i]
		count=BFS(i);
		rate=count*1.0/N;
		printf("%d : %.1f\n",i+1,rate);
	} 
}

广度优先搜索的核心在于每次只遍历该元素有直接联系的元素(第一层),把他们存起来(第二层),然后再依次用相同方法遍历他们,直到最后一层。根据需求再加 其他操作。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值