回溯法求解子集和问题

问题描述

给定n个不同的正整数集合w=(w1,w2,…,wn)和一个正整数W,要求找出w的子集s,使该子集中所有元素的和为W。
例如,当n=4时,w=(11,13,24,7),W=31,则满足要求的子集为(11,13,7)和(24,7)。

问题求解

该问题的解空间树是一棵子集树。
设解向量x=(x1,x2,…,xn),这里是求所有满足目标条件的解,所以一旦搜索到叶子结点(即i>n),如果相应的子集和为W,则输出x解向量。
当搜索到第i(1≤i≤n)的某个结点时,用tw表示选取的整数和,rw表示余下的整数和,即rw=w[i]+…+w[n](其中包含w[i]),采用的剪枝函数如下:
左剪枝:通过检查当前整数w[i]加入后子集和是否超过W,若是则剪枝,即仅仅扩展满足tw+w[i]≤W的左孩子结点。
右剪枝:如果一个结点满足tw+rw-w[i]

代码

int W;
int n;
int w[MAX];

void dfs(int i, int tw, int rw, int x[])
{
	if (i > n)
	{
		if (tw == W)
			dispasolution(x);
	}
	else
	{
		if (tw + w[i] <= W)//左剪枝
		{
			x[i] = 1;//选择元素
			dfs(i + 1, tw + w[i], rw - w[i], x);
		}
		if (tw + rw - w[i] >= W)//右剪枝
		{
			x[i] = 0;//不选择元素
			dfs(i + 1, tw, rw - w[i], x);
		}
	}
}

算法分析

算法的解空间树中有2n+1-1个结点,最坏情况下算法的时间复杂度为O(2n)。

判断子集和问题是否存在解

采用回溯法一般是针对问题存在解时求出相应的一个或多个解,或者最优解。
如果要判断问题是否存在解(一个或者多个),可以将求解函数改为bool类型,当找到任何一个解时返回true,否则返回false。需要注意的是当问题没有解时需要搜索所有解空间。

int n;
int W;
int w[MAX];

bool dfs(int i, int tw, int rw)
{
	if (i > n)
	{
		if (tw == W)
			return true;
	}
	else
	{
		if (tw + w[i] <= W)//左剪枝
			return dfs(i + 1, tw + w[i], rw - w[i]);
		if (tw + rw - w[i] >= W)//右剪枝
			return dfs(i + 1, tw, rw - w[i]);
	}
	return false;
}

另外一种方法是通过解个数来判断,如设置全局变量count表示解个数,初始化为0,调用搜索解的回溯算法,当找到一个解时置count++。
最后判断count>0算法成立,若为真,表示存在解,否则表示不存在解。

int n;
int W;
int w[MAX];
int count = 0;

void dfs(int i, int tw, int rw)
{
	if (i > n)
	{
		if (tw == W)
			count++;
	}
	else
	{
		if (tw + w[i] <= W)//左剪枝
			dfs(i + 1, tw + w[i], rw - w[i]);
		if (tw + rw - w[i] >= W)//右剪枝
			dfs(i + 1, tw, rw - w[i]);
	}
}

你可能感兴趣的:(算法)