泛化数学归纳,如何将复杂的问题简单化

内容整理自:极客时间—程序员的数学基础课 05 | 泛化数学归纳,如何将复杂问题简单化

1. 提出一个问题

        假设有四种面额的钱币,1元,2元,5元和10元,现在你要赏给我10元,你可以给我一张10元,也可以给我两种5元等等,如果考虑每次奖赏的金额和先后顺序,那么最终一共有多少种不同的奖赏方式呢?

2. 如何把复杂的问题简单化

        上述问题是在限定总额的情况下,求所有可能的加和方式。如果采用迭代法,n=1(即第一步),我们可以取四种面额中的任一种,那么当地的奖赏面额就有1元,2元,5元和10元四种可能,当 n=2时,奖赏的总和就有很多种可能性了,依次类推,所有的可能的奖赏情况如下图所示:

        如何把复杂的问题简单化:

        首先,我们来看看如何将数学归纳法的思想泛化为更一般的情况?数学归纳法考虑了两种情况:

  1. 初始状态:就是n=1时,命题是否成立
  2. 假设n=k-1时命题成立,那么只要证明n=k时命题也成立。k为大于1的自然数。

        将上述两点顺序更换一下,再抽象化一下,得出这样的递推关系:

  1. 假如n=k-1时问题已经解决(或者已经找到解)。那么只需要找到n=k的时候,问题如何解决(或者解是多少)。
  2. 初始状态,就是n=1时,问题如何解决(或者解是多少)。

        我认为这种思想就是将复杂的问题,每次都解决一点点,并将剩下的任务转化为更简单的问题等待下次求解,如此反复,直到最简单的形式

        回到提出的问题,我们在将这种思想具体化:

  1. 假设n=k-1时,已经直到如何去求所有奖赏的组合。那么只要求解n=k的时候,会有那些金额选择,以及每种选择后,剩下多少金额需要支付就可以了。
  2. 初始状态,就是n=1时,会有多少种奖赏。

3. 问题的实现

        根据上述思路,具体的实现如下:

package match_programing.lesson5_recursion;

import java.util.ArrayList;

/**
 * 使用递归的方法,将复杂问题简单化
 * 问题: 总共10元奖金,当前有 1元,2元,5元和10元纸币,求一共有多少种赏赐方式
 * 思路:
 *    1. 假设 n=k-1 的时候,我们已经知道了如何去求所有奖赏的组合。那么只要求解当 n=k时,会有哪些金额选择,
 *       以及每种选择后,还剩下多少奖金需要奖赏就可以了
 *    2. 初始状态,当n=1时,会有多少种奖赏
 * 
 * @author hjh
 *
 */
public class Reward {
	
	static int[] rewards = {1, 2, 5, 10};    // 对应纸币的面额
	
	/**
	 * 递归获取所有可能的奖赏方式,奖金总数递减的方式实现
	 * 
	 * @param totalReward
	 *        当前剩余的奖金额度
	 * @param result
	 *        已分配奖金的分配方式集合
	 */
	public static void getReward1(int totalReward, ArrayList<Integer> result) {
		
		if (totalReward == 0) {   // 如果奖金已经分配完成,直接输出分配方案
			System.out.println(result);
			return;
		}
		
		if (totalReward < 0) {    // 证明它不是满足条件的解,不输出
			return;
		}
		
		for(int i : rewards) {
			// 由于有四种情况,需要 clone 当前的解,并传入被调用的函数
			ArrayList<Integer> newResult = (ArrayList<Integer>)result.clone();
			newResult.add(i);   // 添加新的解
			getReward1(totalReward - i, newResult);
			
		}
	}
	
	/**
	 * 递归获取所有可能的奖赏方式,奖金总数递增的方式实现
	 * 
	 * @param total
	 *        奖金的总额度
	 * @param totalReward
	 *        当前已分配奖金的总额度
	 * @param result
	 *        已分配奖金的分配方式集合
	 */
	public static void getReward2(int total, int totalReward, ArrayList<Integer> result) {
		
		if (totalReward == total) {    // 如果奖金分配总和与奖金额度相等,则输出分配方案并返回
			System.out.println(result);
			return;
		}
		
		if (totalReward > total) {    // 分配方案不满足,直接返回
			return;
		}
		
		for (int i : rewards) {
			// 由于有四种情况,需要 clone 当前的解,并传入被调用的函数
			ArrayList<Integer> newResult = (ArrayList<Integer>) result.clone();
			newResult.add(i);    // 添加新的解
			getReward2(total, totalReward + i, newResult);
		}
	}
	
	
	/**
	 * 测试方法
	 */
	public static void main(String[] args) {
		int totalReward = 10;
		ArrayList<Integer> list1 = new ArrayList<Integer>();
		Reward.getReward1(totalReward, list1);    // 第一种执行方式
		System.out.println("------------------------分割线--------------------------");
		ArrayList<Integer> list2 = new ArrayList<Integer>();
		Reward.getReward2(totalReward, 0, list2);    // 第二种执行方式
	}
}

4. 小结

  1. 在递归中,每次嵌套调用都会让函数体生成自己的局部变量,正好可以用来保存不同状态下的数值,为我们省去了大量中间变量的操作,极大地方便了设计和编程。
  2. 递归就是将复杂的问题,每次都解决一点点,并将剩下的任务转化成更简单的问题等待下次求解,如此反复,直到最简单的形式。
  3. 递归和循环其实都是迭代法的实现,而且在某些场合下,它们的实现是可以相互转化的。

5. 作业及实现

        思考题:一个整数可以被分解为多个整数的乘积,例如,6可以分解为2*3。请使用递归编程的方法,为给定的整数n找到所有可能的分解(1 在解中最多只能出现 1 次)。例如,输入 8,输出可以是 1*8,8*1,2*4,4*2,1*2*2*2,1*2*4,......

        具体实现如下:

package match_programing.lesson5_recursion;

import java.util.ArrayList;

/**
 * 题目:
 *     一个整数可以被分解为多个整数的乘积,例如 6 可以分解为 2*3,请使用递归编程的方法,为给定的正数n,找到所有可能的分解。
 *     (1在解中最多只能出现一次)
 *     例如:输入 8,输出可以是:1*8,8*1,2*4,4*2,1*2*2*2,1*2*4,...
 * 
 * @author hjh
 *
 */
public class Factorization {    // factorization:/,fæktərai'zeiʃən/ 因式分解
	
	/**
	 * 采用乘积大小递减的方式进行因式分解
	 * 
	 * @param leftover
	 *        当前待因式分解的值
	 * @param result
	 *        因式分解元素集合
	 */
	public static void recursion(int leftover, ArrayList<Integer> result) {    //recursion: /rɪ'kɜːʃ(ə)n/ 递归
		
		if (leftover == 1) {
			if (!result.contains(1)) {
				if (result.size() > 1) {
					System.out.println(result);
				}
				result.add(1);
			}
			System.out.println(result);
			return;
		}
		
		for (int i = 1; i <= leftover; i++) {
			if (i==1 && result.contains(1)) {    // 只能包含一个1
				continue;
			}
			
			if (leftover % i != 0 ) {    // 不满足因式分解的条件
				continue;
			}
			
			ArrayList<Integer> newResult = (ArrayList<Integer>) result.clone();
			newResult.add(i);
			recursion(leftover/i, newResult);
		}
	}
	
	
	/**
	 * 测试方法
	 */
	public static void main(String[] args) {
		int num = 8;
		ArrayList<Integer> result = new ArrayList<Integer>();
		Factorization.recursion(num, result);
	}
}


/**
* 输出结果:
* [1, 2, 2, 2]
* [1, 2, 4]
* [1, 4, 2]
* [1, 8]
* [2, 1, 2, 2]
* [2, 1, 4]
* [2, 2, 1, 2]
* [2, 2, 2]
* [2, 2, 2, 1]
* [2, 4]
* [2, 4, 1]
* [4, 1, 2]
* [4, 2]
* [4, 2, 1]
* [8, 1]
*/

说明:文中插图来自极客时间,图片版权归极客时间所有。

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值