求解子集和问题的解
题目描述:
给定有n个不同正整数的集合w=(w1,w2,… ,wn)和一个正数W,要求找出w的子集s,式该子集中所有元素的和为W。例如,当n=4时,w=(11,13,24,7),W=31,则满足要求的子集为(11,13,7)和(24,7)
分析思路:
n=4时,解空间树如图(结点中的数字是结点的编号,例如结点18对应解向量为(1,1,0,1),选择的整数和=11+13+7=31),从i层到i+1层(1<=i<=n)的每一条边标有xi的值,xi或者为1或者为0,xi为1时表示取wi为整数,xi为0时表示不去wi为整数,从根节点到叶子结点的所有路径定义了解空间。
时间复杂度 O(2^n),解空间树中有 2^ (n+1) -1个结点
求解该问题需要搜索整个解空间树,设解向量x=(x1,x2,…,xn),本问题是求所有解,所以一旦搜索到叶子结点(即i=n+1),如果相应的子集和为W,则输出x解向量。搜索到第i(1<=i<=n)层的某个结点时用tw表示选取的整数和,rw表示余下的整数和,rw表示余下的整数和,rw=w[j] (j从i+1到n)
(1)约束函数:检查当前整数w[i]加入子集和是否超过W,若超过,则不能选择该路径。用于左孩子结点剪枝。
(2)限界函数:一个结点满足tw+rw<W,即即使选择剩余的所有整数,也不可能找到一个解。用于右孩子剪枝。
代码:
#include<stdio.h>
#define MAXN 20
int n=4,W=31;
int w[]={0,11,13,24,7}; //存放所有整数,不要下标为0的元素
int count=0; //累计解个数
void dispsolution(int x[])
{
int i;
printf("第%d个解:\n",++count);
for(int i=1;i<=n;i++)
if(x[i]==1)
printf("%d ",w[i]);
printf("\n");
}
void dfs(int tw,int rw,int x[],int i) //求解子集和
{
//tw 考虑第i个整数时选取的整数和,rw为剩下的整数和
if(i>n) //找到一个叶子结点
{
if(tw==W) //找到一个满足条件的解输出
dispsolution(x);
}
else //尚未找完所有整数
{
if(tw+w[i]<=W) //左孩子结点剪枝:选取满足条件的整数w[i]
{
x[i]=1; //选取第i个整数
dfs(tw+w[i],rw-w[i],x,i+1);
}
if(tw+rw>W) //右孩子结点剪枝:剪出不可能存在解的结点
{
x[i]=0; //不选取第i个整数,回溯
dfs(tw,rw-w[i],x,i+1);
}
}
}
int main(){
int x[MAXN]; //存放一个解向量
int rw=0;
for(int j=1;j<=n;j++) //求所有整数和
rw+=w[j];
dfs(0,rw,x,1); //i从1开始
}