算法笔记8.1 深度优先搜索 DFS

1.背包问题切入dfs,深度优先,递归找出所有路径(拿法),最终取最大的那个

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

#include<iostream>
using namespace std;

const int maxn=20;
int n,v,w[maxn],c[maxn];//n:物品数量	v:总容量   w[]重量数组	c[]价值数组
int maxValue;//最大价值

void dfs(int index,int sumW,int sumC){
	if(index==n) return; //下标0~n-1 n肯定就直接结束了
	dfs(index+1,sumW,sumC);//不选第index件   能过来 sumW和sumC肯定合法  不选肯定没有任何操作,一直不选,直接return;
	//选第index件
	if(sumW+w[index]<=v){//否则直接不执行任何操作,到最后一行自动返回 void可不写return
		if(sumC+c[index]>maxValue){
			maxValue=sumC+c[index];//最大值更新
		} 
		dfs(index+1,sumW+w[index],sumC+c[index]);
	}
}

int main(){
	freopen("1.txt","r",stdin);
	while(cin>>n>>v){
		maxValue=0;
		for(int i=0;i<n;i++){
			cin>>w[i];
		}
		for(int i=0;i<n;i++){
			cin>>c[i];
		}
		dfs(0,0,0);//第0件开始 上面下标1~n-1 正常下标
		cout<<maxValue<<endl;
	}
	return 0;
}

算法笔记8.1 深度优先搜索 DFS

 

 

2.由上可知,深度优先搜索是一种枚举完所有完整路径以遍历所有情况的搜索方法。给定一个序列,枚举这个序列的所有子序列(可以不连续),然后选择一个“最优”子序列,使得它的某个特征是所有序列中最优的。

eg:给定一个序列,枚举这个序列的所有子序列(可以不连续)

  (也即是求子集,完全无限制的情况下复杂度最差为2^n,实际问题中肯定不能直接写两个dfs(x1);dfs(x2);要根据问题限制剪枝,如上面容量<=v)

#include<iostream>
using namespace std;

const int N=3;
int a[N]={1,2,3};

//index当前数序号 nowK当前已选数	b[]当前已选数数组
void dfs(int index,int nowK,int b[]){
	if(index==N){
		if(nowK!=0){
			for(int i=0;i<nowK;i++){
				cout<<b[i]<<" ";
			}
		}else{
			cout<<"空集";
		}
		cout<<endl;
		return;
	}

	//不选第index个
	dfs(index+1,nowK,b);

	//选第index个
	b[nowK++]=a[index];
	dfs(index+1,nowK,b);//每次b表示的始址都相同,但是每次都会根据nowK及赋值操作更新那段地址的值
}

int main(){
	int b[N];//已选的数存到b里
	dfs(0,0,b);//第0个开始遍历

	// cout<<"\n最终的数组b:\n";//证明传递数组名是地址操作
	// cout<<b[0]<<" "<<b[1]<<" "<<b[2]<<endl;
	return 0;
}

算法笔记8.1 深度优先搜索 DFS

 

2.1对于上述写法,其实很不安全,虽然数组看起来是个局部变量,但是传递的数组名是个指针,每次操作的都是同一段内存,很危险,如,将选第index个放到不选前面就会影响下面不选时候的判断,此处的标尺实际上是nowK,选时的nowK++对下面明显有影响。

#include<iostream>
using namespace std;

const int N=3;
int a[N]={1,2,3};

//index当前数序号 nowK当前已选数	b[]当前已选数数组
void dfs(int index,int nowK,int b[]){
	if(index==N){
		if(nowK!=0){
			for(int i=0;i<nowK;i++){
				cout<<b[i]<<" ";
			}
		}else{
			cout<<"空集";
		}
		cout<<endl;
		return;
	}

    //顺序调换
	
	//选第index个
	b[nowK++]=a[index];
	dfs(index+1,nowK,b);//每次b表示的始址都相同,但是每次都会根据nowK及赋值操作更新那段地址的值

	//不选第index个
	dfs(index+1,nowK,b);

}

int main(){
	int b[N];//已选的数存到b里
	dfs(0,0,b);//第0个开始遍历

	// cout<<"\n最终的数组b:\n";//证明传递数组名是地址操作
	// cout<<b[0]<<" "<<b[1]<<" "<<b[2]<<endl;
	return 0;
}

算法笔记8.1 深度优先搜索 DFS

 

如何修改,其实很简单,选第n个的情况结束后,将再第n个从数组中删除就是咯,此处表现为nowK--,也即加一行
代码:nowK--;即可

#include<iostream>
using namespace std;

const int N=3;
int a[N]={1,2,3};

//index当前数序号 nowK当前已选数	b[]当前已选数数组
void dfs(int index,int nowK,int b[]){
	if(index==N){
		if(nowK!=0){
			for(int i=0;i<nowK;i++){
				cout<<b[i]<<" ";
			}
		}else{
			cout<<"空集";
		}
		cout<<endl;
		return;
	}

	//选第index个
	b[nowK++]=a[index];
	dfs(index+1,nowK,b);//每次b表示的始址都相同,但是每次都会根据nowK及赋值操作更新那段地址的值
	nowK--;

	//不选第index个
	dfs(index+1,nowK,b);

}

int main(){
	int b[N];//已选的数存到b里
	dfs(0,0,b);//第0个开始遍历

	// cout<<"\n最终的数组b:\n";//证明传递数组名是地址操作
	// cout<<b[0]<<" "<<b[1]<<" "<<b[2]<<endl;
	return 0;
}

算法笔记8.1 深度优先搜索 DFS

 

-----------------------------------------------------------------------------------------------------------------

其实这种不定长数组最好的写法当然还是专业的vector了,

专业写法

#include<iostream>
#include<vector>
using namespace std;

const int N=3;
int a[N]={1,2,3};
vector<int> vi;

//index当前数序号
void dfs(int index){
	if(index==N){
		if(!vi.empty()){
			for(int i=0;i<vi.size();i++){
				cout<<vi[i]<<" ";
			}
		}else{
			cout<<"空集";
		}
		cout<<endl;
		return;
	}

	//选第index个
	vi.push_back(a[index]);
	dfs(index+1);
	vi.pop_back();

	//不选第index个
	dfs(index+1);
}

int main(){
	dfs(0);//第0个开始遍历
	return 0;
}

算法笔记8.1 深度优先搜索 DFS

 

 

3.经典例题

给定N个整数(可能有负数),从中选择K个数,使得这K个数之和恰好等于一个给定的整数X;如果有多种方案,选择
他们中元素平方和最大的一个。数据保证这样的方案唯一

#include<iostream>
#include<vector>
using namespace std;

const int N=1000;
int n,k,x,maxSquSum,a[N];
vector<int> vi,ans;

//当前第index个数  nowK:当前已找到了几个(记录下方便许多)
//sum:当前已选数字之和  squeSum:当前已选数字平方和
void dfs(int index,int nowK,int sum,int squSum){
	
	if(nowK==k&&sum==x){
		//找到了符合条件的 看看是否更新标答(平方和是否最大)
		if(squSum>maxSquSum) {
			maxSquSum=squSum;
			ans=vi;//别忘了
		}
		return;//千万别忘了
	}

	//★★★★★★★
	//放在后面判断,不能提前判断,因为刚好到最后一个的时候是最优的话,
	//还没来得及更新ans就return了 不好,后置判断顺序很重要//所以尽量前置判断较好
	if(index==n||sum>x||nowK>k) return;

	//选择第index个
	vi.push_back(a[index]);//不合条件的交给第一行判断 差不多
				//注意nowK+1 不要nowK++ 下面还有分支,不要轻易改变参数值
	dfs(index+1,nowK+1,sum+a[index],squSum+a[index]*a[index]);
	vi.pop_back();//选完了 执行下面不选第index个的逻辑时,一定要将a[index]个拿走删除,否则严重影响下面的分支

	//不选第index个
	dfs(index+1,nowK,sum,squSum);
}

int main(){
	while(cin>>n>>k>>x){
		maxSquSum=-1;
		for(int i=0;i<n;i++){
			cin>>a[i];
		}
		dfs(0,0,0,0);//第0个数开始选 当前找到0个 当前和sum=0   平方和squSum=-1
		if(maxSquSum!=-1){
			for(int i=0;i<ans.size();i++){
				cout<<ans[i]<<" ";
			}
			cout<<endl;
		}else{
			cout<<"未找到!\n";
		}
	}
	
	return 0;
}

注意代码中★处的解析

算法笔记8.1 深度优先搜索 DFS

 

 

 

扩展:若每个数可以重复选多次呢?其他条件不变,只需要将代码该一点点点就行了,选择第index的时候将index+1改成index即可。此外本次将判断改成了前置判断,题目千变万化,边界何时return,觉得还是得靠自己细心(第n-1个尚未判断最优或合法时,绝对不可以直接在第一行if(index==n) return;了)

#include<iostream>
#include<vector>
using namespace std;

const int N=1000;
int n,k,x,maxSquSum,a[N];
vector<int> vi,ans;

//当前第index个数  nowK:当前已找到了几个(记录下方便许多)
//sum:当前已选数字之和  squeSum:当前已选数字平方和
void dfs(int index,int nowK,int sum,int squSum){
	
	//★★★★★★★
	//放在后面判断,不能提前判断,因为刚好到最后一个的时候是最优的话,
	//还没来得及更新ans就return了 不好,后置判断顺序很重要
	//所以尽量前置判断较好,但是此题前置判断后参数不好写,如下
	if(index==n||sum>x||nowK>k) return;


	//选择第index个
	vi.push_back(a[index]);//不合条件的交给第一行判断 差不多
				
	if(nowK+1==k&&sum+a[index]==x){
		//找到了符合条件的 看看是否更新标答(平方和是否最大)
		if(squSum+a[index]*a[index]>maxSquSum) {
			maxSquSum=squSum+a[index]*a[index];
			ans=vi;//别忘了
		}
		return;//千万别忘了
	}
	//注意nowK+1 不要nowK++ 下面还有分支,不要轻易改变参数值
	dfs(index,nowK+1,sum+a[index],squSum+a[index]*a[index]);
	vi.pop_back();//选完了 执行下面不选第index个的逻辑时,一定要将a[index]个拿走删除,否则严重影响下面的分支

	//不选第index个
	dfs(index+1,nowK,sum,squSum);
}

int main(){
	while(cin>>n>>k>>x){
		maxSquSum=-1;
		for(int i=0;i<n;i++){
			cin>>a[i];
		}
		dfs(0,0,0,0);//第0个数开始选 当前找到0个 当前和sum=0   平方和squSum=-1
		if(maxSquSum!=-1){
			for(int i=0;i<ans.size();i++){
				cout<<ans[i]<<" ";
			}
			cout<<endl;
		}else{
			cout<<"未找到!\n";
		}
	}
	
	return 0;
}

算法笔记8.1 深度优先搜索 DFS