我在上一篇文章中已经为读者对于函数的概念以及它的使用开了一个头,本文的目的是让读者进一步更加深入的了解C++中的函数部分。
首先我先将上一章内容最后的每日一练附上答案:
#include <iostream>
using namespace std;
bool isprime(int n) // 实现对素数的判断
{
bool prime = true;
if (n == 1)
{
return false;
}
for (int i = 2; i <= n - 1; i++) //从2 到 n - 1进行判断
{ //其实读者大可不必这样,判断到根号n也可以,还能降低程序的运行时间
if (n % i != 0)
{
prime = true;
}
else
{
prime = false;
break;
}
}
return prime;
}
int main() //主函数部分实现输出1-10000以内的素数
{
for (int i = 1; i <= 10000; i++)
{
if (isprime(i))
{
cout << i << " ";
}
}
return 0;
}
好了,以上是上期每日一练的答案,有问题的小伙伴可以私信作者哦!
好,回到正题,首先我们继续讨论一下关于函数的调用,首先,如果有多个函数,函数是允许在自身中调用其他的函数的,这叫做函数的嵌套调用,调用别的函数的函数叫做主调函数,被别的函数调用的函数叫做被调函数,其实这个很好理解的,其实上面的答案就可以看出来了,我们在主函数main()中调用了我们自己写的函数isprime(),这个时候main函数就叫做主调函数,而我们自己写的函数就叫做被调函数了,这其实就是个简单的概念,读者需要了解就足够了。
无独有偶,既然函数可以调用别的函数,那么请读者思考一下,函数能调用自身吗?
很明显,这是允许的,我们把函数调用自身的情况叫做递归调用,我们从下面的一个实例来了解一下递归调用:
我们知道,斐波那契数列的第n项为前两项之和,即 Fn = Fn-1 + Fn-2,并且F1 = 1,F2 = 1,此时,我们就可以写一个函数来计算斐波那契数列的第n项的值,代码如下:
int f(int n)
{
if (n == 1 || n == 2)
{
return 1;
}
else
return f(n - 1) + f(n - 2);
}
我们来分析一下这段代码,首先第一行的if,如果n为1或者n为2,那么函数直接返回一个具体的值,否则就返回前两项之和,读者可以想一下,比如说我们要计算第五项的和,即f(5),那我们是不是要计算f(4)+f(3),那么,f(4)又会继续分解为f(3)+ f(2),直到f(3)分解为f(1)+ f(2),这时,我们已经知道了f(1)和f(2)的值,就可以往回算出f(3)从而继续算出f(4)的值,最后就可以得到f(5)的值,这就是我们的目标答案,我们可以发现,在计算f(5)的时候分为了两个步骤,首先将f(5)不断拆分,直到得到f(1)和f(2),并且我们知道f(1)和f(2)的值,这时候在往回算,我们把类似将f(5)不断拆解为f(4)+f(3)的这个过程叫做递推,,把从f(1)开始往前算的这个过程叫做回归,事实上,所谓递归,就是由递推和递归两个部分组成,先不断递推,在往回回归,这样就构成了一个完整的递归过程。
诺有读者在刚开始接触递归的话,一定要注意一下几点:
首先,递归算法一定要有一个递归边界,何为递归边界呢,例如上面的例子中,if(n == 1 || n == 2)return 1;这个就是递归边界,也就是说递推的过程到n为1或n为2时就停止了,没有递归边界的递归算法是没有意义的,因为没有递归边界的话,递归算法就不会停止,可以说是一个死循环。
所以我们说递归算法是有穷的。
讲到这里,请读者思考一下,递归算法有没有缺陷呢?
答案是:有!!!!
为什么?
首先,我们先来讲一下递归的好处,递归符合人思考的过程,通过递归思想写出来的代码可读性强,有些问题如果没有使用递归,那么写出来的代码将及其难看。
现在我们来讲一下递归的缺陷,那就是程序的效率低,首先,我们知道,递归是在函数中调用它本身,那么每调用一次,系统就会不断给重新调用的函数开辟新的空间,只要运行的次数一多,程序的运行效率就会急转直下,读者可以自己去试一下,计算第100项,或者第1000项,这时候程序很有可能会崩溃。那么,有没有解决的办法呢?
答案是有的,这个方法就叫记忆话递归。
还是上面斐波那契数列的例子,我们不难看出,当我们计算f(5)的时候,就要计算f(4)和f(3),计算f(4),又要计算f(3)和f(2)的值,可以看到,这里f(3)被计算了两次,所以,记忆化递归的原理就在这里,我们将计算过的值储存起来,这样下次在计算到这个值的时候,我们就可以直接使用,这样,程序的效率就会大大提升。代码实现如下:
bool vis[10000]; //用于判断该数是否被计算过
int dp[10000]; //用于储存目标数字和中间被计算过的数
int f(int n)
{
if (n == 1 || n == 2)
return 1;
if (vis[n]) return dp[n];
vis[n] = true;
return dp[n] = f(n - 1) + f(n - 2);
}
int main()
{
int n;
cin >> n;
cout << f(n);
return 0;
}
首先,这里可能会有读者有疑问,这个vis数组什么用?其实bool型数组的初值都为假,读者可以取带入几个数字去深入体会一下,因为记忆化递归在处理动态规划的问题时也有极大的作为。
好了,以上是本期的全部内容了,有问题的小伙伴可以私信博主哦。
一下是每日一练
利用递归算法完成汉诺塔问题。(在输出是打印出移动过程)
答案在下期公布。