前一篇我们使用小明跳楼梯
的例子说明了递归函数求解的方法:递归函数求解的方法
这一篇我们继续了解递归算法的内容。对于一个参数的递归函数,我们找到了一种通用的方法,那就是直接寻找f(n)
与f(n-1)
之间的关系即可。但对于有些问题,一个参数是不够的。这一篇,我们将查看一个使用两个参数的递归算法练习题来讲解相关内容。
给定两个整数,计算其最大公约数和最小公倍数。
这个题干很简单,就是求最大公约数。如果你的第一反应是从大到小进行循环,如果某一个数刚好能够被这两个数整除,这个数字就是最大公倍数的话,这一篇可能是比较适合你的。
首先,我还是希望简单聊一下什么叫最大公约数。比如说,12
的因数有1,2,3,4,6,12
,而18
的因数有1,2,3,4,6,9,18
,这两个数字公共的因数有1,2,3,4,6
,而6
是其中最大的一个。因此,12
和18
的最大公约数为6
。在这个推理中,我们发现6
正好是12
与18
的差。那么最大公约数等于两个数之差吗?显然这个结论是错误的。当我们举例10
与30
时,结果最大公约数是10
并不是相差的20
,但我们发现,20
与10
的差是10
。难道说这个差是减多次?我们可以使用求余运算
表示减多次,求余运算的减多次指的是一直减,直到不能减为止。也就是余数必须小于除数。当我们把求余运算放在10
和30
之间,得到的结果是0
。而12
与18
之间使用求余运算得到结果为6
。这之间似乎没有任何规律。但我们都注意到,在求余运算中,都出现了我们想要的数字10
和6
。
其实,如果我们稍作注意,也会发现一些规律,比如说,12
与18
的差与12
的最大公约数是相同的,都是6
。12
与18
进行求余运算的结果6
与12
的最大公约数相同。放在10
与30
之间也是一样的。那么我们推断,a
和b
的最大公约数与a%b
和b
的最大公约数相同。
这个结论是否正确呢?我们在举一些实例来证明,36
与48
的最大公约数等于48%36
与36
的最大公约数吗?答案是肯定的。那么按照这个道理,我们就可以得到这样的一个公式:f(a,b) = f(b%a, a)
。那么临界条件呢?当a==0
时,不能在进行求余运算了,也就是结束的时候了。
int f(int a, int b)
{
if(a == 0)
return b; //此时b为最大公约数
return f(b%a, a);
}
这是包含两个参数的递归函数,在这个函数中,并不想上一篇一样,直接寻找f(n-1)
与f(n)
之间的关系,而是减少参数的规模。参数不断减少,不断接近临界值,最终通过临界值得到结果。这是递归算法的真谛。下面这个例子能很好的说明。
将m个相同的苹果,放在k个相同的盘子里,问一共有多少种方法?
在这个问题中,我们想到的是能否定义一个f(m,k
的递归函数来求解呢?如果可以,我们就需要找到临界值与减少参数规模的规律。我们可以先找临界值,这个通常会简单一些,当只有一个苹果的时候,只有一种方法。不仅如此,当只有一个盘子的时候,也只有一种方法。还有其他情况吗?当然,如果有10
个盘子,只有3
个苹果,不论怎么放,一定至少会有7
个盘子是空的,因此,这种情况可以看做3
个苹果放在3
个盘子里即可。
排除掉这些临界状态,其他的如何考虑呢?第一种,有空盘子。如果有空盘子的话,我们可以考虑空一个盘子的情况,毕竟如果空两个盘子的话,也可以通过空一个盘子推导出来。那么,空一个盘子该如何处理呢?如果空一个盘子的话,我们就需要把m
个苹果放在k-1
个盘子里。第二种,没有空盘子。为了保证没有空盘子,我们可以先将每一个盘子里放一个苹果,那么还剩下m-k
个苹果。这m-k
个苹果可以放在k
个盘子中。这样的话,对于f(m,k)
我们拆解成两个方案的总和,也就是f(m,k)=f(m-k,k) + f(m,k-1)
.这样减少了参数规模,使得递归函数可以正常进行下去。我们尝试使用代码描述具体的方案吧。
int f(int m, int k)
{
if(k <= 1 || m <= 1)
return 1;
if(m < k) //苹果没有盘子多
return f(m, m);
return f(m-k, k) + f(m, k-1);
}
这样的话,关于递归算法对于多个参数的求解方法也已经描述清晰了。希望能给你一些启示。