回溯法 —— 求解子集和问题

求解子集和问题的解

题目描述:

给定有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开始 
 	
 }
  

在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/gl620321/article/details/108801724