深度优先搜索会走遍所有路径,并且每次走到死胡同就代表一条完整路径的形成,这就是说,深度优先搜索是一种枚举所有完整路径以遍历所有情况的搜索方法
例题1:
有n件物品,每件物品的重量为w[ i ],价值为c[ i ],现在需要选出若干件物品放入容量为V的背包中,使得物品重量和不超过V的前提下,物品的价值最大。()
分析:每件物品都有选或不选两种选择,这就是所谓的“岔道口”,而当物品的重量和超过了V,就会到达“死胡同”,需要返回最近的“岔道口”。
每次都要对物品进行选择,因此DFS函数的参数中必须记录当前处理的物品编号index,已选物品的总重量sumW和总价值sumC,于是DFS函数看起来是这个样子:
void DFS(int index, int sumW, int sumC) {...}
如果不选择index号物品,那么就进入DFS(index+1, sumW, sumC),如果选择index号物品,将前往DFS(index+1, sumW+w[index], sumC+c[index] )这条分支。当选择物品的Index增长到了n,就说明已经把n件物品处理完毕,此时比较sumW,如果不超过V且sumC超过maxvalue,则更新maxvalue,下面为实现代码:
#include<iostream>
#include<cstdio>
using namespace std;
int n,V,maxvalue=0; //n件物品,背包容量V,最大价值maxvalue
int w[30],c[30]; //每件物品的重量和价值
void DFS(int index, int sumW, int sumC)
{
if(index==n){ //已处理完所有物品,物品下标为0到n-1
if(sumW<=V && sumC>maxvalue){
maxvalue = sumC;
}
return;
}
DFS(index+1, sumW, sumC); //不选第index件物品
DFS(index+1, sumW+w[index], sumC+c[index]); //选index件物品
}
int main()
{
cin>>n>>V;
for(int i=0;i<n;i++) cin>>w[i];
for(int i=0;i<n;i++) cin>>c[i];
DFS(0,0,0);
cout<<"maxvalue: "<<maxvalue<<endl;
return 0;
}
由于每次需要处理完所有的物品再判断重量和价值,故复杂度为O(),事实上忽视了物品重量不能超过V这个特点,可以将这个条件的判断加入“岔道口”中,只有sumW不超过V才能继续加入物品,DFS函数代码如下:
void DFS(int index, int sumW, int sumC)
{
if(index==n) return;
DFS(index+1, sumW, sumC ); //不选index号物品
if(sumW+w[index]<=V){
if(sumC+c[index] > maxvalue) maxvalue=sumC+c[index];
DFS(index+1, sumW+w[index], sumC+c[index]);
}
}
原来第二条岔路是直接进入的,而现在要判断加入第index件物品时重量不超过V才进入,这样可以减少计算量,这种通过题目条件的限制来节省DFS计算量的方法称作剪枝。
这个问题也可以看作一类常用的DFS问题:给定一个序列,枚举这个序列的所有子序列 ,从中选择最优的子序列。
例题2:
给定n个整数,从中选择k个数,使得这k个数的和恰好等于一个给定的整数x,如果有多种方
案,选择它们中元素平方和最大的一个,数据保证这样的方案唯一。
#include<iostream>
#include<cstdio>
#include<vector>
using namespace std;
int n,k,x,maxsqu=-1,A[30];
vector<int> temp,ans; //temp存放临时方案,ans存放平方和最大的方案
void DFS(int index,int nowk,int sum,int sumsqu)
{
if(nowk==k && sum==x){ //满足选择k个整数且和为x
if(sumsqu>maxsqu) {
maxsqu = sumsqu;
ans = temp;
}
return;
}
if(index==n||nowk>k||sum>x) return; //此时应返回
temp.push_back(A[index]); //选择index号数
DFS(index+1, nowk+1, sum+A[index], sumsqu+A[index]*A[index]);
temp.pop_back(); //将index号数从temp中去除,使之不会影响“不选
//index号数”这条分支
DFS(index+1, nowk, sum, sumsqu);
}
int main()
{
cin>>n>>k>>x;
for(int i=0;i<n;i++) cin>>A[i];
DFS(0,0,0,0);
cout<<maxsqu<<endl;
for(vector<int>::iterator it=ans.begin();it!=ans.end();it++)
cout<<*it<<" ";
return 0;
}
上面这个问题每个数都只能选择一次,现在稍微修改题目:假设n个整数中的每一个都可以被选择多次,那么选择k个数,使得k个数的和恰为x。 那么当选择了Index号数时不应直接进入Index+1号数的选择,而应继续选择index号数,直到某个时刻决定不再选择index号数,就会进入Index+1号数的处理。因此只要把选择Index这条分支的代码修改为:
DFS(index, nowk+1, sum+A[index], sumsqu+A[index]*A[index]);