算法笔记之DFS
我们可以从下面这个图进行深入的理解:
从起点开始,当碰到岔道口时,总会随机选择其中的一条岔道口前进,如果在路上又碰到新的岔道口再次随机选择一条新的岔道口,直到遇到死胡同在返回到最近的一条新岔道口进行重新选择。直至遇到出口为止。
这里有两个重要的点:岔道口和死胡同(我也叫它递归边界或剪枝)
总而言之:DFS是一种枚举所有完整路径以遍历所有情况的搜索方法。
在枚举过程中寻找某种符合题目要求的方案。
以下通过几个例子加深理解:
- n个物品,即每种物品一个 重量w[i],价值c[i] 选入容量为V的背包,求其最大价值。【n<=20】
这可以看成一到道搜索题,当然贪心也可以做(按照c/w 从大到小排序,复杂度大概是O(logn) )
岔道口:第i件物品是否选择 选? 不选?
死胡同:a、已经判断过第n个物品是否选择了
b、当前所选重量>背包重量 ==> 只有在now_v<V 时才能选择第1个岔路 口, 即选择,这两步称为剪枝
Note: 可以定义一个全局变量 MAX_C来记录最大价值
#include<iostream>
#include<cmath>
#include<cstring>
#include<string>
#include<cstdio>
#include<ctime>
#include<algorithm>
#include<iterator>
#include<vector>
#include<stack>
#include<set>
#include<queue>
#include<map>
#include<utility>
using namespace std;
const int N=21;
int w[N];
int c[N];
int MAX_C=0;
int n,V;
//index 当前处理的物品编号, 当前重量,当前价值
void dfs(int index,int now_v,int now_c){
if(index==n)
return ;
//不选第Index 件物品
dfs(index+1,now_v,now_c);
//只有在now_v<V 时才能选择第1个岔路口,即选择
if(now_v+w[index]<=V){
if(now_c+c[index]>MAX_C){
MAX_C=now_c+c[index];
}
dfs(index+1,now_v+w[index],now_c+c[index]);
}
}
int main()
{
ios::sync_with_stdio(false);
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<<MAX_C<<endl;
system("pause");
return 0;
}
/*
5 8
3 5 1 2 2
4 5 2 1 3
*/
2
/*
枚举所有子序列的目的很明显,可以从中选择一个"最优子序列",使得它的某个特征是所有子序列中最优的
显然,这个问题也等价于枚举从n个整数中选择K个数的所有方案。
例题:从n个数里选出k个数,使得这k个数的和为x,并且要求这k个数的平方和最大
每个数仅仅可以被选择1次
输入:n k x
a[0] a[1] a[2]....a[n-1]
输出: 最优子序列
input:
4 2 6
2 3 3 4
output:
2 4
Notes:
面对岔道口有选择a[index]和不选a[index两种方案。
当选择a[index]时就要temp.push_back(a[index]),而当这条分支结束时,就要temp.pop_back()
即将它从temp中去除,使它不会影响”不选a[index]“这条分支
*/
#include<iostream>
#include<cmath>
#include<cstring>
#include<string>
#include<cstdio>
#include<ctime>
#include<algorithm>
#include<iterator>
#include<vector>
#include<stack>
#include<set>
#include<queue>
#include<map>
#include<utility>
using namespace std;
const int N=50;
int n,k,x;
int a[N];
int max_squsum=0;
//temp存放临时方案,ans存放最优方案
vector<int> temp,ans;
//index 当前处理index号整数 nowk 当前已经选数的个数
//sum 当前已经选的数之和 当前已选数平方和
//该dfs() 是在数组a中所有的数只能选择一次的情况下
void dfs(int index,int nowk,int sum,int squsum){
//注意分岔口和死胡同
if(nowk==k && sum==x){
if(squsum>max_squsum){
max_squsum=squsum; //更新最大平方和
ans=temp; //更新最优方案
}
return ;
}
if(index==n||nowk>k||sum>x){
return ;
}
//选a[index]
temp.push_back(a[index]);
dfs(index+1,nowk+1,sum+a[index],squsum+a[index]*a[index]);
temp.pop_back();
//不选a[index]
dfs(index+1,nowk,sum,squsum);
}
int main()
{
ios::sync_with_stdio(false);
while(cin>>n>>k>>x){
for(int i=0;i<n;i++)
cin>>a[i];
dfs(0,0,0,0);
for(vector<int>::iterator it=ans.begin();it!=ans.end();it++)
cout<<*it<<" ";
cout<<endl;
cout<<"max_squsum is:"<<max_squsum<<endl;
}
system("pause");
return 0;
}
3、第2题的延申
/*
例题:从n个数里选出k个数,使得这k个数的和为x,并且要求这k个数的平方和最大,每个数都可以被选择多次
输入:n k x
a[0] a[1] a[2]....a[n-1]
输出: 最优子序列
input: 从3个数{1,4,7}中选出5个数,使得这5个数和为17且平方和最大
3 5 17
1 4 7
output:
1 1 1 7 7
Notes:
这个问题可由上面的例题改动即可。由于每个数可以被选择多次,因此在岔道口选择了a[index]时
不因该进入(index+1)号数进行处理,应当能够继续选择a[index]。
当然不选a[index]则正常进入(index+1)号数进行处理。
*/
#include<iostream>
#include<cmath>
#include<cstring>
#include<string>
#include<cstdio>
#include<ctime>
#include<algorithm>
#include<iterator>
#include<vector>
#include<stack>
#include<set>
#include<queue>
#include<map>
#include<utility>
using namespace std;
const int N=50;
int n,k,x;
int a[N];
int max_squsum=0;
//temp存放临时方案,ans存放最优方案
vector<int> temp,ans;
//index 当前处理index号整数 nowk 当前已经选数的个数
//sum 当前已经选的数之和 当前已选数平方和
//该dfs() 是在数组a中所有的数只能选择一次的情况下
void dfs(int index,int nowk,int sum,int squsum){
//注意分岔口和死胡同
if(nowk==k && sum==x){
if(squsum>max_squsum){
max_squsum=squsum; //更新最大平方和
ans=temp; //更新最优方案
}
return ;
}
if(index==n||nowk>k||sum>x){
return ;
}
//选a[index]
temp.push_back(a[index]);
dfs(index,nowk+1,sum+a[index],squsum+a[index]*a[index]); //!!不同之处!!!!!!!
temp.pop_back();
//不选a[index]
dfs(index+1,nowk,sum,squsum);
}
int main()
{
ios::sync_with_stdio(false);
while(cin>>n>>k>>x){
for(int i=0;i<n;i++)
cin>>a[i];
dfs(0,0,0,0);
for(vector<int>::iterator it=ans.begin();it!=ans.end();it++)
cout<<*it<<" ";
cout<<endl;
cout<<"max_squsum is:"<<max_squsum<<endl;
}
system("pause");
return 0;
}