迭代的是人,递归的是神。——L. Peter Deutsch
递归,数学里面叫recursion,其实就是递推关系. 中学数学有一部分其实就是递归的非常典型的做法,不过老师们都没怎么扩展,新课标必修五第二章数列应该算是我们第一次接触递推的概念了.
其实说到递归,大伙都知道就是自己调自己,这样其实大家都明白,但是说来怎么调?如何控制?又如何看得到结果是想要的呢?相信还是很晕,下面从中学数学里面来看看吧.
第一部分、两个典型的例子,等差数列与等比数列
其实这实际上是一个例子,教书的时候我常常会问学生:“什么是等差数列?”当然同学们都会回答:“就是后一项总比前一项多一个数,这个数不变...”也有的说:“就是后一项减去前一项为一定常数...”
所以我们经常用表达式表示为
a(n)=a(n-1)+d
TeX语法为
\[a_n=a_{n-1}+d\]
那又有问题了,这里确定了一个数列没有呢?当然没有,我说后一项比前一项多2,这是什么数列?
是1,3,5,7,9, ...还是2,4,6,8,10, ...
当然弄不清楚,为什么呢?因为我们谈到的数列需要有一个首项,即第一项的值,所以一旦谈到解递推数列,就应该有两个内容,一个是连续项间的关系,另一个就是首项关系.
那么就可以利用叠加的方法来计算了:
假设这里首项为1,也就是a(1)=1,而这个常数就为2
那么
a(n)=a(n-1)+2
a(n-1)=a(n-2)+2
a(n-2)=a(n-3)+2
a(n-3)=a(n-4)+2
...
a(3)=a(2)+2
a(2)=a(1)+2
将等号左边的依次相加,右边的也依次相加
这样很容易发现,左边有一部分从a(2)一直加到a(n-1),右边也有一部分从a(2)一直加到a(n-1),那么消去,就左边剩下a(n),右边就剩下a(1)和n-1个2相加了,数学公式就成了
a(n)=a(1)+2(n-1)
即
a(n)=1+2(n-1)
LaTeX代码为
\[a_n=1+2(n-1)\]
就是等差数列的通向公式啦,那么这个递推关系中可以看到a(n)=a(n-1)+x即为连续项间的关系,而a(1)=1就是首项啦. 那这个和递归间的关系就很明显啦
a(n)=a(n-1)+x表示,调用a(n)即执行a(n-1)+x,那么同时调用a(n-1),...这样一直下去,最后当a(1)时不在调用函数,直接返回1,再一路加回去,结束递归.
下面看看用代码来实现,还是假定首项为1,公差为2
#include <stdio.h>
int main()
{
int AddFun(int);
int num;
printf("Please input num:");
scanf("%d",&num);
printf("The result is %d",AddFun(num));
return 0;
}
int AddFun(int n)
{
if(n == 1)
{
return 1;
}
return AddFun(n-1) + 2;
}
这里呢AddFun(n)就是之前看到的a(n),在调用的过程中先判断n是否为1,如果为1当然就表示现在是首项了,不在继续调用,直接返回结果1
这里使用的不是数学中的公式计算,纯粹的事利用递推关系得到的结果.
再看看等比数列. 相信已经很清楚了,等比数列就是后一项是前一项的定常数被,表示为
a(n)=a(n-1)*q
LaTeX代码为
\[a_n=a_{n-1}\cdot q\]
将函数改一改就能使用了,假设首项为1,公比为2:
int
ProductFun(int n)
{
if(n == 1)
{
return
1;
}
return AddFun(n-1)*2;
}
那么留个练习,这可是一道高中数学的典型例题(LaTeX代码):
已知数列~$\{a_n\}$~满足~$a_n = 2a_{n-1}+1$,且首项~$a_1 = 1$,用递归调用写出代码过程.
(递推关系为a(n)=2a(n-1)+1,a(1)=1)
参考代码:
#include
<stdio.h>
#include
<stdlib.h>
int main()
{
int SumCon(int n);
printf("%d",SumCon(10));
return 0;
}
int SumCon(int n)
{
if(n<=0)
{
return 1;
}
return 2 * SumCon( n - 1 ) + 1;
}
当然递归不一定要调用函数开完成,使用for循环一样可以做到
比如还是公比为2,首项为1的等比数列求第10项的值
可以写成
#include
<iostream>
using
namespace std;
int
main()
{
int res = 1;
for(int i=1; i <10; i++)
{
res = res * 2;
}
cout<<res<<endl;
return 0;
}
这个一样可以粗略的看成一种递归,res = res * 2表示将前一项的2倍赋值给后一项,也就是说后一项与前一项的2倍相等,不就是递推关系了吗...
第二部分、递归调用注意(直接递归)
递归调用在编程里面就是对一个函数,或方法进行自己掉自己,那么在编写递归代码时有三点要注意,首先要注意一个问题,就是递推关系,这里需要抽象出一个递推的关系,不一定只有两层,或许是三层甚至更多
例如:楼梯有n阶台阶,上楼可以一步上1阶,也可以一步上2阶,编一程序计算共有多少种不同的走法.
先来分析一下,如果只有一个台阶,那么显然只有1种,所以a(1)=1
如果有两个台阶,呢么就有每次上一层,上两次,和一次上两层,即1+1和2共2种,即a(2)=2
当n=3时,那么就有1+1+1、1+2、2+1共3种了,似乎还看不出规律,那么分析一下递推关系,即前后项间的关系
到达第三层有两个方法,一是从第二层走一步到第三层,那么有a(2)种方法,又可以从第一层走两步到第三层,即a(1)种方法,拍脑袋了,显然到达第三层有a(1)+a(2)种方法,那么是不是呢?验证一下
a(1)+a(2)=1+2=3,咦,正好,看来有点像了,再来看看第四层是不是
当n=4时,按照上边的分析肯定为a(2)+a(3)=2+3=5,下面我们枚举一下:1+1+1+1、1+1+2、1+2+1、2+1+1、2+2,刚刚好,看来就是她了!
那么递推关系就有了,即a(n)=a(n-1)+a(n-2),同时还有了首项的值:a(1)=1,a(2)=2,由于递推关系有三项,很显然必须有两个初始值
好啦,上面讨论了递归的一个注意,下面是第二个注意,临界条件
既然递归就是自己调自己,那么怎么停止呢,显然不做逻辑判断,计算机会一直运行下去,知道程序崩溃(栈溢出),所以就需要加上一个if判断来跳出递归,其实就是前面提到的数列首项,就是这里的a(1)和a(2)
那么代码就可以这么写了:
int Count(int n)
{
if(2 == n)
{
return 2;
}
else if(1 == n)
{
return 1;
}
return Count(
n - 1 ) + Count( n - 2 );
}
实际上这就是一个典型的Fibonacci数列,只是初始值不同罢了,下面留两个练习(著名的数列):
1、反Fibonacci数列,满足的递推关系为:a(n+2)=a(n)-a(n+1)
2、巴都万数列,满足递推关系:a(n)=a(n-2)+a(n-3)
现在剩下第三个要注意的事项了,就是递归体.
先看两个函数代码(C++代码):
1、
void
Test_1(int n)
{
cout<<n<<endl;
if(n < 6)
{
Test_1( n + 1 );
}
}
2、
void
Test_2(int n)
{
if(n < 6)
{
Test_2( n + 1 );
}
cout<<n<<endl;
}
如果在main()函数中分别调用这两个函数,会有什么执行结果呢?
现在我们假定n为0
第一个函数打印出
0
1
2
3
4
5
6
第二个函数打印出
6
5
4
3
2
1
0
这个就很奇怪了,这是为什么呢?
实际上前面我们考虑的递归关系是单一的递归,但是这两个递归体中,除了完成递归外,还有其他执行代码.
其实之前也有,只是没有显示出来,就没有被察觉而已
那么怎么去分析呢?
其实很简单,递归体本身可分为三个部分,一个是头代码,一个是递归表达式,一个是尾代码,区分头尾的自然就是递归表达式了
看这段代码:
#include
<iostream>
using
namespace std;
int
main()
{
void Test(int);
Test(1);
return 0;
}
void
Test(int n)
{
cout<<n<<endl;
if(n < 3)
{
Test( n + 1 );
cout<<n<<endl;
}
}
输出结果为
1
2
3
2
1
那么在这段代码中if()前面的输出流为头代码,中间的Test( n + 1 )为递归表达式,这里的尾代码依然是一个输出流
那么分析此程序怎么做呢?很简单,八个字
“头尾分开,等待归来”
头尾分开,就是将两个cout<<n<<endl分开,第一个cout<<n<<endl执行,然后转入第一次递归,等待递归结束后回到第二个cout<<n<<endl语句执行
这样说很抽象,我们一步步分析一下
第一次调用,n=1
执行头cout<<n<<endl,输出1,并且尾cout<<n<<endl等待,其值也是1,但未输出;
进入第一次递归,n=2
执行头cout<<n<<endl,输出2,并且尾cout<<n<<endl等待,其值是2,但未输出;
进入第二次递归,n=3
执行头cout<<n<<endl,输出3,但判断n < 3 不成立,即不再执行尾cout<<n<<endl语句,函数体结束
回到第一次递归体,执行等待的尾cout<<n<<endl,输出2,第一次递归体结束,会带最外层
执行等待的cout<<n<<endl,输出1,整个函数调用结束,回到main()函数体继续执行其他命令.
形象的说就像是一架手风琴一样,函数调用开始就拉开手风琴,然后进入递归,等待内层递归结束才执行后面的代码
也就八个字“头尾分开,等待归来”
其实这就是实现的栈的结构
到这里就基本结束了我所理解的递归含义,总结一下:
递归调用即递推关系
递归三个注意:注意递推表达、注意临界判断、注意递归体
摘自http://www.cnblogs.com/zzdxpq/
迭代的是人,递归的是神。——L. Peter Deutsch的更多相关文章
- python学习笔记 | 递归思想
1.引子 大师 L. Peter Deutsch 说过: To Iterate is Human, to Recurse, Divine. 中文译为:人理解迭代,神理解递归 2.什么是递归 简单理解: ...
- 数据结构基础(2) --顺序查找 & 二分查找
顺序查找 适用范围: 没有进行排序的数据序列 缺点: 速度非常慢, 效率为O(N) //实现 template <typename Type> Type *sequenceSearch(T ...
- 正则表达式匹配可以更快更简单 (but is slow in Java, Perl, PHP, Python, Ruby, ...)
source: https://swtch.com/~rsc/regexp/regexp1.html translated by trav, travmymail@gmail.com 引言 下图是两种 ...
- 2. 引用计数法(Reference Counting)
1960年,George E. Collins 在论文中发布了引用计数的GC算法. 引用计数法意如了一个概念,那就是"计数器",计数器表示的是对象的人气指数, 也就是有多少程序引用 ...
- 《编程人生:15位软件先驱访谈录》【PDF】下载
<编程人生:15位软件先驱访谈录>[PDF]下载链接: https://u253469.pipipan.com/fs/253469-230382231 内容简介 本书适合所有程序员,也适合 ...
- Reference Counting GC (Part one)
目录 引用计数法 计数器值的增减 new_obj()和update_ptr()函数 new_obj()生成对象 update_ptr()更新指针ptr,对计数器进行增减 优点 可即可回收垃圾 最大暂停 ...
- 微软、IBM、GitLab 等大厂全部到齐的 OCS 第一天有什么看点?
在本周一的推文中我们大致介绍了下 Open Core 峰会及到场嘉宾,(≧▽≦) 当然还有 Nebula Graph 在会场的展位位置图,本文我们来看看 Open Core 峰会第一天有哪些值得一看的 ...
- 函数:递归是神马 - 零基础入门学习Python022
函数:递归是神马 让编程改变世界 Change the world by program 我们这节课的主题叫递归是神马,将通过小甲鱼带感的讲解,来告诉大家神马是递归!如果说优秀的程序员是伯乐,那么把递 ...
- 零基础入门学习Python(22)--函数:递归是神马
知识点 递归是神马? 递归是属于算法的范畴. 递归就是函数调用自身的一种行为. >>> def g(): return g() >>> g() Traceback ...
随机推荐
- 了解 : Odata 的 $filter
api/jobPosts?$filter=company/name eq "string" //基本 api/orders?$filter=orderItem/product/EF ...
- Laravel路由
Laravel安装,这里使用一键安装包. 使用PHP内置的Web服务器,在PHP文件夹下运行命令行 php -S 0.0.0.0:1024 一.设置路由 路由文件在app\HTTP\routes.ph ...
- Asp.Net MVC 之 Autofac 初步使用2 集成mvc 属性注入以及自动注入
首先看下Demo2的结构 然后下面一步步将Autofac集成到mvc中. 首先,定义Model Product.cs public class Product { public int Id ...
- VisualStudio2017下ASP.NET CORE的TagHelper智能提示解决办法
之前在VS2017RC中就发现该问题,安装了依赖,但是前段一直点不出来asp-for,后来查了发行说明, 才知道在VS2017rc中暂时无法解决,所以一直等到VS2017正式版的发布,急冲冲的装好, ...
- JS日期加减指定天数
JS中没有直接操作日期加减的方法,只能通过Date对象获取当前天数加减之后setDate,以此来达到操作日期的目的 JS中对指定日期加减指定天数,具体方法如下: function addDate(da ...
- enum类型的本质(转)
原地址:http://www.cppblog.com/chemz/archive/2007/06/05/25578.html 至从C语言开始enum类型就被作为用户自定义分类有限集合常量的方法被引入到 ...
- matplotlib根据Y轴数量伸缩画图的py脚本
#coding:utf-8import numpy as npimport matplotlib.pyplot as plt #X,Y轴数据y = [20,59,11,12,16,20,15,12,1 ...
- C++ Primer 5 CH2 变量和基本类型
C++ 是一种静态数据类型语言,它的类型检查发生在编译时.因此,编译器需要知道每一个变量对应的数据类型. 2.1 基本内置类型 算术类型 C++ 标准并没有规定带符号类型应如何表示,但是约定了在表示范 ...
- Java基础之路(四)--流程控制语句
本次我们来聊一聊Java当中的循环语句. 循环语句分三种:1.for2.while3.do--while. 三种循环语句的任务是不同的,方法也是不同的.当然他们各自的流程图也是不一样的. 3.1 wh ...
- 利用Arcgis for javascript API绘制GeoJSON并同时弹出多个Popup
1.引言 由于Arcgis for javascript API不可以绘制Geojson,并且提供的Popup一般只可以弹出一个,在很多专题图制作中,会遇到不少的麻烦.因此本文结合了两个现有的Arcg ...