小于等于n的正整数相加等于m的一个算法

package suanfa;


public class Suanfa {


	/**
	 * 对任意的自然数n,m,取任意多个小于等于n的自然数相加,结果等于m,求所有的这种组合。
	 * 如n=3,m=5可以有:1,1,3;1,2,2;2,3;5等
	 * 算法:
	 * 可将此问题看做是将m个圆环往n跟柱子上套,不分先后次序,求所有套法的问题。
	 * 主要思想是:
	 * 将所有的组合分类为,
	 * 第1类:k=0,即至少有一根柱子上套了0个环,其他n-1个柱子上套m个环;
	 * 第2类:k=1,即至少有一根柱子上的环数为1,其他n-1个柱子上套m-1个环,并且这n-1根柱子的每根柱子套的环数必须大于或等于k=1,
	 * 即大于等于1;因为在第1类里的所有组合中必定有一个柱子上的环数为0,而此类中的所有组合中每根柱子上的环数都必须大于等于1,
	 * 所以此类和第1类没有重复的组合;
	 * 第3类:k=2,即至少有一根柱子上的环数等于2,且每根柱子上的环数不小于2,因为第2类中至少有一根柱子上环数为1,所以此类和
	 * 第2类中的组合没有重复,同理和第1类中的组合也没有重复;
	 * 。。。
	 * 第r类:k=r-1,即至少有一根柱子上的环数等于r-1,且每根柱子上的环数不小于r-1,此类和前面的所有类中的组合都不重复
	 * 如上述分法,假设任意一种满足题意的组合,此组合必定属于上面的第i类组合,i为此组合中最小的元素。如果此组合的元素
	 * 个数小于n,则不足的补0,即最小的元素必定为0,即此组合必定属于第1类组合。
	 * 下面确定总共有多少个类:r = [m/n] + 1,[m/n]为向下取整.
	 * 因为总环数为m,假设按以上方法可以分为k类,则第k类里的环数 M = m1+m2+...+mn,因为mi(i属于1...n)>=k,所以n*k<=M<=m;即k<=m/n
	 * 主要实现步骤:
	 * 先按k循环,然后递归找出每一类中的所有组合。
	 * @param n 用于相加的数可以取值的最大值。
	 * @param m 目标数,相加后的目标结果数。
	 * @param k 拥有限制求和表达式里的元素的,即调用该方法所生成的表达式里的元素都必须大于或等于k,且有一个元素必须等于k。
	 * 第一次调用时为0,即生成的表达式里必须有一个元素为0,且所有元素都必须大于0.
	 * @param door 元素取值的限定数。用于限定元素取值的最大数。主要用处是:当n<m的时候,所有表达式里的元素值都应小于n,而不是m,
	 * 因为递归调用的时候,此函数的第一个参数是变化的,所以不能用第一个参数来限定。
	 * @param a 用于存储一种组合。a[i]代表第i根柱子上的环数。
	 */
	public static void func1_2(int n, int m, int k, int door, int[] a){
		//当n>m时,所有大于m的数是用不到的
		if(n>m) n = m;
		//当m=1时,只有一种组合,就是一根柱子上环数为1,其他柱子上环数为0
		if(m==1&&m>=k){
			a[a.length-n]=1;
			printA(a);
			return;
		}else if(n==1){    //n=1的时候只有一种组合,就是这根柱子上的环数为m
			if(m<=door){   //door的值就是n,如果m>n,则组合中不能出现比n大的数
				a[a.length-n]=m;
				printA(a);
			}
			return;
		}else {
			int i = 0;
			while(n*i<=m){  //按上面的分类方法循环
				if(i>=k){   //每根柱子上的环数必须大于k
					if(i>0) //因为a[i]默认为0
						/* 当递归到第n层时,表明前面的0到a.length-n-1根柱子上的环数已经确定。
						 * 此处就是确定第a.length-n跟元素上的环数
						 */
						a[a.length-n]=i;
					/*递归调用此函数,因为上面确定了一根柱子上的环数,所以未确定的柱子数为n-1,因为上面确定的柱子上的环数为i,
					 * 所以此处待确定的环数为m-i;此处的n,m为递归调用时传入的n,m和初始的n,m可能不一样
					*/
					func1_2(n-1,m-i,i,door,a);
				}
				//类别自增
				i++;
			}
		}
	}
	//输出一个组合
	public static void printA(int[] a){
		for(int i = 0; i < a.length; i++){
			if(a[i]>0&&i!=a.length-1){
				System.out.print(a[i]+"+");
			}else if(a[i]>0){
				System.out.print(a[i]);
			}
		}
		System.out.println();
	}
	public static void main(String[] args) {
		//func1_1(6,6,0,6);
		func1_2(3,6,0,3,new int[3]);
	}


}



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值