内容整理自:极客时间—程序员的数学基础课 05 | 泛化数学归纳,如何将复杂问题简单化
1. 提出一个问题
假设有四种面额的钱币,1元,2元,5元和10元,现在你要赏给我10元,你可以给我一张10元,也可以给我两种5元等等,如果考虑每次奖赏的金额和先后顺序,那么最终一共有多少种不同的奖赏方式呢?
2. 如何把复杂的问题简单化
上述问题是在限定总额的情况下,求所有可能的加和方式。如果采用迭代法,n=1(即第一步),我们可以取四种面额中的任一种,那么当地的奖赏面额就有1元,2元,5元和10元四种可能,当 n=2时,奖赏的总和就有很多种可能性了,依次类推,所有的可能的奖赏情况如下图所示:
如何把复杂的问题简单化:
首先,我们来看看如何将数学归纳法的思想泛化为更一般的情况?数学归纳法考虑了两种情况:
- 初始状态:就是n=1时,命题是否成立
- 假设n=k-1时命题成立,那么只要证明n=k时命题也成立。k为大于1的自然数。
将上述两点顺序更换一下,再抽象化一下,得出这样的递推关系:
- 假如n=k-1时问题已经解决(或者已经找到解)。那么只需要找到n=k的时候,问题如何解决(或者解是多少)。
- 初始状态,就是n=1时,问题如何解决(或者解是多少)。
我认为这种思想就是将复杂的问题,每次都解决一点点,并将剩下的任务转化为更简单的问题等待下次求解,如此反复,直到最简单的形式。
回到提出的问题,我们在将这种思想具体化:
- 假设n=k-1时,已经直到如何去求所有奖赏的组合。那么只要求解n=k的时候,会有那些金额选择,以及每种选择后,剩下多少金额需要支付就可以了。
- 初始状态,就是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. 小结
- 在递归中,每次嵌套调用都会让函数体生成自己的局部变量,正好可以用来保存不同状态下的数值,为我们省去了大量中间变量的操作,极大地方便了设计和编程。
- 递归就是将复杂的问题,每次都解决一点点,并将剩下的任务转化成更简单的问题等待下次求解,如此反复,直到最简单的形式。
- 递归和循环其实都是迭代法的实现,而且在某些场合下,它们的实现是可以相互转化的。
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]
*/
说明:文中插图来自极客时间,图片版权归极客时间所有。