输入两个整数n和m, 从数列1,2,...,n中任意选择几个数,使其和等于m, 要求编写程序输出所有的组合

/*
 * problem precisely description:
 *          input: given two integer arguments: n and m
 *          output: all the subsets of array 1,2,...,n with sum of m
 */

解题思路:
    显而易见, 当n>m时, 肯定存在满足题目要求的子集. 由于数列1,2,...,n是正整数数列, 这使得所有的大于m的数加上数列最小元素1都会大于m, 所以当n>m时我们只需要考虑子数列1,2,...,m即可.
     当n<=m时,我们需要搜索1,2,...,n中符合条件的子集. 实际上, 我们可以将原问题分解成两个子问题:
子问题一: 在n个数的数列,我们取n, 然后原问题就变为找出1,2,...,n-1中和为m-n的所有子集,然后将所有的子集都加上元素n就得到了原问题的部分解集;
子问题二: 我们不取n, 原问题就变为找出1,2,...,n-1中和为m的所有子集.
联合两个子问题的解, 便得到了原问题的解集.

    我们用subsets( n, m) 表示原问题, 那么原问题可表示为子问题 subsets(n-1, m) 和subsets( n-1, m-n)的解集的并集.
    即:   subsets( n, m)  = subsets(n-1, m) U subsets( n-1, m-n)
    实际中,可采用回溯的方法进行求解. 后面附有完整的代码.

    实际的搜索方法类似于二叉树的方法, 去除不符合条件的解. 例如下面的二叉树就表示了1,2,...,n的全组合的求解过程. 左边分支表示取,右边的分支表示舍. 针对于0-1背包问题, 八皇后问题, 射击十次命中九十环的打法问题等等, 则需要剪除掉一些不符合条件的子树.

 



附解题代码:

/* 
 * problem description:
 * 	Input two integers n and m, output all the subset of array 1,2,3,...,n with the 
 * sum of m.
 *
 * shaw 2011/12/17
 */

/*
 * solutions:
 *      problem precisely description:
 *  		input: given two integer arguments: n and m
 *  		output: all the subsets of array 1,2,...,n with sum of m
 *
 * 	thought: 
 * 		Apparently, if n>m, some( one or more ) subsets are must existed here. And 
 * 	    cause the array are all positive integers, so we don't need to consider (m,n].
 * 	    If n<m, then we can first compare the sum of array 1,2,...,n with m, if sum is 
 * 	    less than m, then there is no subset fulfills the conditions, or if sum is equal
 * 	    to or larger than m, there are one or more subsets fulfills the request.
 * 	    	Here we given the solutions:
 * 	   	We start from the array of 1,2,..., min(n,m), if we take i=min(n,m) as
 * 	    one of the element of subset, the next we'll consider the following formulation:
 *			    | == 0, now we have one subset, then we also have to backtrace
 *			    |       to get all those subset of array 1,2,...,i-1 with sum of i.	 
 *			    |	
 * 	    		m-i | 
 * 	    		    | <> 0, we first take i as one of the subset element, then
 * 	    		    |      we have a sub-problem with the same request of the 
 * 	    		    |	   original problem. We need to find a all subsets of array 
 * 	    		    |	   1,2,...,i-1 with sum of m-i
 *
 * 	    in the searching process, we can use a stack to store the subset
 */

#include <stdio.h>
#include <assert.h>
#include <time.h>

#define MAXN 1000


/*stack for storing the subsets*/
int stack[MAXN];
int top = -1;
void push( int e )
{
	assert( top < MAXN );
	stack[++top] = e;
}

void pop( )
{
	assert( top > -1 );
	top--;
}

void clear()
{
	top = -1;
}

void printStack( )
{
	int i=top;
	while( i>-1 )
		printf("%d\t", stack[i--] );
	printf("\n");
}

/*
 * print all the subsets of array 1,2,...,n with sum of m 
 * constraints: m is less than 1000
 */
int subsets( int n, int m )
{
	int i;
	int num=0;
	int flag = 1;

	/* set n = min(m,n), i.e. if n>m just ignore the (m,n] */
	if( n > m )
		n = m;

	/* we search from min(n,m) to 1*/
	for( i = n; flag && i>=1; i-- )
	{
		/*take i */
		push( i );
		
		/* m-i == 0 */
		if( m-i == 0 )
		{
			num++;
			printStack();
		}
		else
		{
			/*if sum of array 1,2,...,i-1 is less than m-i, then start
 			  searching with n-1*/
			/* cut branch */
			if( (i-1)*i/2 < (m-i) )
			{
				pop();
				flag = 0;
				continue;
			}

			/*search for subsets of array 1,2,...,i-1 with sum of m-i*/
			num += subsets( i-1, m-i );
		}
		
		/*back trace*/
		pop();
	}	

	return num;
}

void testSubsets()
{
	assert( 1 == subsets( 1, 1 ) );
	printf( "\n\n" );
	assert( 0 == subsets( 1, 2 ) );
	printf( "\n\n" );
	assert( 3 == subsets( 5, 6 ) );
	printf( "\n\n" );
	assert( 5 == subsets( 7, 7 ) );
}

int main()
{
	testSubsets( );

	clock_t start = clock();
	subsets( 50, 50 );
	clock_t duration = clock() - start;
	printf("it takes %d seconds\n", duration/CLOCKS_PER_SEC );
	return 0;
}




评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值