在问题求解时,对所有可能的问题解构成一棵树,而最优树或者符合要求的解就是该树的一条路径或者一个结点。这种树称为解答树。
1、全排列问题。求1,2…n的全排列有n!个。可以通过图的深度优先遍历输出全排列。
递归调用深度优先遍历。递归函数的参数为遍历的结点数。递归结束的条件为:所有的数都已经在输出的路径上。返回上一层,重新查找另一条输出路径。
#include<iostream>
#include<string.h>
using namespace std;
const int N=10;//n的最大值
int S[N];//记录解
int V[N];//记录当前值是否已经被遍历,0表示没有,1表示有
int n;//输入的n值
void DFS(int depth)
{
if(depth>=n)//深度遍历到了叶子结点,输出当前一条路径,depth代表了遍历的结点数
{
for(int i=0;i<n;i++)
cout<<S[i]<<" ";
cout<<endl;
return;
}
for(int i=1;i<=n;i++)//遍历数1,2…n
{
if(!V[i])//当前结点没有遍历
{
V[i]=1;//已遍历
S[depth]=i;//记录当前结点到路径上
DFS(depth+1);//遍历的路径上结点数增1
V[i]=0;//返回上一层结点重新查找下一个结点
}
}
}
void main()
{
cin>>n;
memset(V,0,n);//将数组V大小为n的内容全部置为0
DFS(0);
}
2、硬币组合问题。有1分、2分、5分、10分四种硬币,每种硬币数量无限,给定Target分钱,求有多少种组合可以合成Target分钱?
同样可以采用递归调用深度优先遍历搜索的方法。递归函数的参数为当前要加入的硬币的索引。递归终止的条件:已经达到了给定的Target分钱或者超过了给定的Target分钱。
#include<iostream>
#include<vector>
using namespace std;
int Target;//给定的金额
const int N=4;//硬币的种类数
int coin[]={1,2,5,10};//四种硬币
int total;//组合的金额
vector<int> solution;//解集,一种解中的硬币组合
int count;//记录有多少种组合
void DFS(int index)
{
if(total==Target)//是一种解
{
count++;//组合数增1
cout<<count<<": ";
for(int i=0;i<solution.size();i++)
cout<<solution[i]<<" ";//输出当前的硬币组合
cout<<endl;
return;
}
if(total>Target)//组合的金额大于给定的金额
return;
for(int i=index;i<N;i++)
{
total+=coin[i];
solution.push_back(coin[i]);//当前硬币加入当前组合中
DFS(i);
solution.pop_back();//递归结束,返回上一层,加入下一种硬币
total-=coin[i];
}
}
void main()
{
cin>>Target;
DFS(0);
}
3、求1到n个自然数组成的集合的所有组合问题。在构造的树中,每遍历到一个结点都有两条路径,路径边上分别为1或者0。我们所要得到的集合是每遍历到一个结点输出路径上边为1代表的结点即可。
#include<iostream>
using namespace std;
const int N=100;//允许的n最大取值
int solution[N];//解向量,为1表示当前值在集合中,为0表示不在
int n;//n的值
void DFS(int depth)
{
if(depth>=n)
{
for(int i=0;i<n;i++)
if(solution[i])//不为0在当前集合中则输出
cout<<i+1<<" ";
cout<<endl;
return;
}
solution[depth]=0;//当前结点不加入集合中
DFS(depth+1);
solution[depth]=1;//当前结点加入集合中
DFS(depth+1);
}
void main()
{
cin>>n;
DFS(0);
}
4、0/1背包问题。有N件物品和一个容量为W的背包,第i件物品的重量是w[i],价值是v[i],求解将哪些物品装入背包可使这些物品的费用总和不超过背包容量,且价值总和最大。可以用深度优先遍历解决。构造解答树,边上权值为1表示加入当前物品到背包中,否则不加。
#include<iostream>
using namespace std;
const int N=100;//物品的最大件数
int solution[N];//保存解,为1表示当前物品在背包中,为0不在
int ans;//保存解,记录当前背包的总价值
int n=3;//物品件数
int W=10;//背包载重量
int weight[N]={3,4,5};//每件物品的重量
int value[N]={4,5,6};//每件物品的价值
//参数depth表示树的深度即遍历的物品数目
//cur_weight表示当前背包的重量,cur_value表示当前背包物品的价值
void DFS(int depth,int cur_weight,int cur_value)
{
if(cur_weight>W)//背包重量超重,返回回到树的上一层
return;
if(cur_value>ans)//如果当前解更优则更新
ans=cur_value;
if(depth>n)//超过了树的深度即所有物品都遍历了,返回回到树的上一层
return;
//深度优先搜索,将当前物品加入或者不加入背包中
DFS(depth+1,cur_weight,cur_value);//不加入
DFS(depth+1,cur_weight+weight[depth],cur_value+value[depth]);//加入
}
void main()
{
//测试
DFS(0,0,0);
cout<<ans<<":"<<endl;
cout<<endl;
}