输入两个整数 n 和 m ,从数列 1 , 2 , 3.......n 中随意取几个数 ,使其和等于 m

转载自:http://blog.sina.com.cn/s/blog_7571423b01016707.html

编程求解:输入两个整数 n 和 m ,从数列 1 , 2 , 3.......n 中随意取几个数 ,使其和等于 m , 要求将其中所有的可能组合列出来.

分析:

       主要思想:分治,即m=idx+m-idx
       从最大数字n开始查找,然后逐渐后退,但每次查找只会在比当前数字大的方向进行组合尝试,这样可以保证找到的组合不会重复。
参考代码

 
1: #include
2: #include
3: #include<<SPAN style="COLOR: #0000ff">string.h>
4: void myDump(int aux[], int n)
5: {
6: for (int i=0; i
7: if (aux[i]) // 0标示未选择该数字
8: printf("=", i+1);
9: printf("\n");
10: }
11:
12: void helper(int dest, int idx, int aux[], int n)
13: {
14: if (dest == 0) // m减为0时,找到一个组合,输出
15: myDump(aux, n);
16:
17: // 递归结束条件:dest=0,说明找到一个组合;dest<0,说明该组合不满足(和大于m);
18: // 如果idx=n,则说明已达到规定的最大数字(范围为1-n)
19: if (dest <= 0 || idx==n)
20: return;
21:
22: helper(dest, idx+1, aux, n);
23: aux[idx] = 1; // 标示选择了数字idx+1
24:
25: //因为已经选择了idx,即选择了数字idx+1,所以将m减去idx+1
26: helper(dest-idx-1, idx+1, aux, n);
27: aux[idx] = 0; //无论结果是否满足,都需要取消原来已选择的数字idx+1,然后尝试另一种组合
28: }
29:
30: void findCombination(int n, int m)
31: {
32: if(n>m) //因为和为m,所以大于m的数肯定不行
33: n=m;
34:
35: int* aux=new int[n];
36: memset(aux, 0, n*sizeof(int));
37:
38: helper(m, 0, aux,n);
39:
40: delete[] aux;
41: }
42:
43: int main()
44: {
45: int n,m;
46: scanf("%d,%d",&m,&n);
47:
48: findCombination(n,m);
49:
50: getchar();
51: return 0;
52: }

 

*****************************************************************************

 解题思路:
    显而易见, 当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背包问题, 八皇后问题, 射击十次命中九十环的打法问题等等, 则需要剪除掉一些不符合条件的子树.

 

 

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

#define MAXN 1000



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");
}


int subsets( int n, int m )
{
 int i;
 int num=0;
 int flag = 1;

 
 if( n > m )
  n = m;

 
 for( i = n; flag && i>=1; i-- )
 {
  
  push( i );
  
  
  if( m-i == 0 )
  {
   num++;
   printStack();
  }
  else
  {
   
   
   if( (i-1)*i/2 < (m-i) )
   {
    pop();
    flag = 0;
    continue;
   }

   
   num += subsets( i-1, m-i );
  }
  
  
  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;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值