入门

首先先对递归进行入门。

递归是以自相似的方式重复项目的过程。在编程语言中,如果程序允许您在同一函数内调用函数,则称其为函数的递归调用。

简而言之,递归就是函数的自身调用。可以看看下面的递归使用:

void Recursive() {
Recursive();//call itself
} int main(void)
{
Recursive(); system("PAUSE");
return ;
}

借前辈一句话,递归定义就是:递归中的“递”就是入栈,递进;“归”就是出栈,回归。

因为递归在整个函数结束时才释放数据区,而每一次调用函数都会存储临时的变量,因此递归次数过多,会造成栈溢出,上面的例子就会出现这种状况。

如果你会将递归与return联系起来,但实际上return的作用只是将值返回给调用参数的函数。

N项求和

我们以前都计算过求1+2+3+4+...+nn项求和。现在要求我们使用递归写出来。

1.我们设第n项的和为sum(n),而前n项之和,可以由前n-1项之和加第n项。用表达式就是:sum(n-1) + n

可以得到关系式:sum(n) = sum(n -1) + n;

2.接下来我们可以想一下,sum(n-1)又等于前一项加n-1一直循环下去计算,直到sum(2) = sum(1) + 2;计算完毕,此时sum(2)是我们要求的值,sum(1)是未知的,因此我们还需要知道sum(1)的值,才能求前n项和。

由1, 2的叙述,我们列出:

sum(n) = sum(n-) + n;
sum() = ;

我们将第一个式子称作为“关系”, 第二个式子称作“出口”(可以理解为结束递归的条件)。

由此我们可以写出程序:

#include <stdio.h>
#include <stdlib.h> int sum(int n) {
if (n == ) {
return ;
}
else {
return sum(n - ) + n;
}
} int main(void)
{
int k = sum(); printf("%d\n", k); system("PAUSE");
return ;
}

Question:

接着我们可以试着自己做一下n!的递归计算,同样是第n项等于 前n-1项相乘 *  第n项,出口为第1项,当然出口也可以为第m项(m>0&&m<=n),但我们这里算n!,就不管了。

奇/偶数求和

同样,对于奇数,偶数求和也就是前n项的变型,这里不再说,我们这里可以对奇/偶数求第n项的值,进行递归计算。这里举例奇数计算:1+3+5+7...,设num(n)为第n个奇数。

1.通过第一个例子我们首先可以列出关系,num(n) = num(n - 1) + 2;

2.写出出口,num(1) = 1;

写出主要程序:

int num(int n) {
if (n == ) {
return ;
}
else {
return num(n - ) + ;
}
}

斐波那契数列(Fibonacci sequence)

接着我们看看 斐波那契数列:1, 1, 2, 3, 5, 8, 13...

得出规律,后一项等于前两项相加。写出关系式f(n) = f(n-1) + f(n-2);

随之我们对关系式的出口(结束条件)进行判断,我们需要求f(n),而f(n-1)f(n-2)都是未知的,我们只写其中一项为出口都是不够的,因此我们需要两个出口。f(1) = 1; f(2) = 1;

通过关系和出口,我们写出:

f(n) = f(n-) + f(n-);
f() = ;
f() = ;

写出程序:

#include <stdio.h>
#include <stdlib.h> int f(int n) {
if (n == ) return ;
if (n == ) return ; return f(n - ) + f(n - );
} int main(void)
{
int k = f(); printf("%d\n", k); system("PAUSE");
return ;
}

可以发现越高层的函数调用,自身调用的次数越多。

数组求和

使用递归,对数组array[] = { 1, 2, 3, 4, 5, 6};求和。

和之前n项求和思想相似,不过这里多了将数组地址传入,同样我们可以将数组关系写出 sum(array, n) = sum(array, n-1) + array[n];   注意:我们这里传入的n应当是数组的最大下标(数组从0~n-1,n个数)。

很显然作为递归出口的应当是当数组下标为0时,sum(array, 0) = array[0];

我们可以写出程序:

#include <stdio.h>
#include <stdlib.h> int sum(int *arr, int n) {
if (n == ) return arr[]; return sum(arr, n - ) + arr[n];
} int main(void)
{
int array[] = { , , , , , };
int k = sum(array, sizeof(array) / sizeof(int) - );//这里填数组最大下标
//int k = sum(array, 5);
printf("数组元素之和:%d", k);
system("PAUSE");
return ;
}

汉诺塔问题

有三根杆子A,B,C。A杆上有N个(N>1)穿孔圆环,盘的尺寸由下到上依次变小。要求按下列规则将所有圆盘移至C杆:

  1. 每次只能移动一个圆盘;
  2. 大盘不能叠在小盘上面。

这道题的解题步骤就三个:

  1. A(source)杆中前n - 1个盘移到B(auxiliary)杆;
  2. A(source)杆最后一个盘移到C(destination)杆;
  3. B(auxiliary)杆n - 1个盘移到C(destination)杆;

动态图演示(借前辈图一用)

如果这样说你还是不能理解过程,那么我们就回想一下之前的n项求和,我们将前n-1项 + 第n项。那么在这里,我们将前n-1个盘看成一个整体(盘的位置不变),将最后一个大盘看成一个整体,先将那一大坨移到B杆,再把A杆剩下的那个大盘移到C杆,然后我们再把那一大坨移到C杆。

整体过程:

 a.同样的这道题我们通过解题步骤去找关系式:(整个函数的声明是void Hanoi(int n, char SourcePole, char AuxiliaryPole, char DestinationPole);)

  1. Hanoi(n - 1,  SourcePole, DestinationPole, AuxiliaryPole);
  2. printf("将盘%d,从%c柱------>%c柱\n", n ,SourcePole, DestinationPole);
  3. Hanoi(n - 1,  AuxiliaryPole, SourcePole, DestinationPole);

(因为输出对象是SourcePoleDestinationPole,因此我们要将A杆的盘转移到B杆上,就需要在递归调用函数,传入参数时,将参数换位。)

b.接着我们写出口,移动n - 1个盘,也就是1~(n -1),当n = 0时结束函数。

因此写出程序:

#include <stdio.h>
#include <stdlib.h> void Hanoi(int n, char SourcePole, char AuxiliaryPole, char DestinationPole){
if(n == ){
return;
}
Hanoi(n - , SourcePole, DestinationPole, AuxiliaryPole);
printf("将盘%d,从%c柱------>%c柱\n", n ,SourcePole, DestinationPole);
Hanoi(n - , AuxiliaryPole, SourcePole, DestinationPole);
} int main(void) {
Hanoi(, 'A', 'B', 'C');
system("PAUSE");
return ;
}

当然,对于出口也有另一种,盘数是从1~(n-1)的,当n = 0时结束入栈,当n = 1时恰好是最后一个入栈的。因此,可以当n = 1时进行一次移盘操作之后结束入栈。

此时的代码为(将SourcePole...更换变量名,便于读者阅读):

#include <stdio.h>
#include <stdlib.h> void Hanoi(int n, char A, char B, char C){
if(n == ){
return printf("将盘%d,从%c柱------>%c柱\n", n ,A, C);
}
Hanoi(n - , A, C, B);
printf("将盘%d,从%c柱------>%c柱\n", n ,A, C);
Hanoi(n - , B, A, C);
} int main(void)
{ Hanoi(, 'A', 'B', 'C');
system("PAUSE");
return ;
}

还有一道从N个球中取M个球的递归问题也不错,有兴趣可以看:点击链接

深入

接着我们对递归进行深一步的挖掘,了解递归的运算过程和在栈中的处理情况。

了解递归的运算过程,我们需知递归在栈中运算:

  1. 后进先出,先进后出
  2. 自顶向下移动指针

对于前面提到的n项求和,我们理解sum(6) 可以通过下面的图例理解,后进前出,先进后出的情况:

对于在栈中的运算过程我们可以结合下图理解:

递归在栈中的运算过程如下图:

图片来源

#include <bits\stdc++.h>

using namespace std;

int main()
{
stack<int> val; cout << "输入:";
for (int i = ; i <= ; ++i) {
cout << i << ' ';
val.push(i);//将数据压入栈中(1~10)
} cout << endl; cout << "输出:";
while (!val.empty()) {
cout << val.top() << ' ';//输出顶层数据 val.pop();
//删除顶层数据,下一次输出顶层数据将是原来的第二个数据
//以此循环,直到栈中数据全部释放
} system("PAUSE");
return ;
}

二分法:

二分法顾名思义就是将数据分半进行查找。(前提是数据是按顺序排列好的)

思路(假设数组的值从小到大排列):

  1. 找到数组下标中间位置mid;
  2. array[mid]与寻找值num比较;

若值相等结束查找,若不相等再次进行二分法查找。

具体的比较方式是:

  • array[mid] = num 结束查找
  • array[mid] > num 说明array{mid, mid+1... ...end} > num,则此时应该在array{beg... ...mid-1}中查找,end = mid - 1;
  • array[mid] < num 说明array{beg, beg+1... ...mid} < num, 则此时应该在array{mid+1... ...end}中查找, beg = mid + 1

C语言(非递归):

#include <stdio.h>
#include <stdlib.h> int Search(int *arr, int beg, int end, int num) {
int mid; while (beg <= end) {
mid = (beg + end) / ;//定义中间位置
if (arr[mid] < num) {
beg = mid + ;
//当中间位置对应数组值小于寻找的数
//则数组的寻找区间起点改变为 mid+1
}
else if (arr[mid] > num) {
end = mid - ;
//当中间位置对应数组值大于寻找的数
//数组的寻找终点变为 mid - 1
}
else {
return mid;
//相等时,找到寻找的数
}
} return -;
} int main(void)
{
int array[] = { , , , , , , }; printf("%d\n",Search(array, , , )); system("PAUSE");
return ;
}

C语言(递归)

首先找出口,当beg > end时退出递归

找关系式, 三种大小关系就行,对begend的修改,在函数的再次调用上体现。

#include <stdio.h>
#include <stdlib.h> int Search(int *arr, int beg, int end, int num) {
int mid; while (beg <= end) {
mid = (beg + end) / ;
if (arr[mid] < num) {
return (arr, mid + , end, num);
}
else if (arr[mid] > num) {
return (arr, beg, mid - , num);
}
else {
return mid;
}
} return -;
} int main(void)
{
int array[] = { , , , , , , }; printf("%d\n",Search(array, , , )); system("PAUSE");
return ;
}

尾递归

定义:是指一个函数里的最后一个动作是返回一个函数的调用结果的情形,即最后一步新调用的返回值直接被当前函数的返回结果。此时,该尾部调用位置被称为尾位置。尾调用中有一种重要而特殊的情形叫做尾递归

简而言之:在执行递归操作时,将算术的结果作为参数传入。

这种方法编译器可以在下次调用函数前,销毁当前的栈空间,亦或者直接覆盖当前栈空间数据,降低了栈空间损耗,但依然存在着当前环境优化问题的问题。

有兴趣的可以看看前辈的这篇文章:点击查看

各位读者能够有收获便是我最大的快乐!写教程不易,熬夜伤身,有个赞什么的,我也是不介意滴!哈哈哈!

细讲递归(recursion)的更多相关文章

  1. Atitit  循环(loop), 递归(recursion), 遍历(traversal), 迭代(iterate).

    Atitit  循环(loop), 递归(recursion), 遍历(traversal), 迭代(iterate). 1.1. 循环算是最基础的概念, 凡是重复执行一段代码, 都可以称之为循环. ...

  2. 循环(loop), 递归(recursion), 遍历(traversal), 迭代(iterate)的区别

    表示“重复”这个含义的词有很多, 比如循环(loop), 递归(recursion), 遍历(traversal), 迭代(iterate). 循环算是最基础的概念, 凡是重复执行一段代码, 都可以称 ...

  3. JProfiler远程监控Linux上Tomcat的安装过程细讲(步骤非常详细!!!)

    JProfiler远程监控Linux上Tomcat的安装过程细讲(步骤非常详细!!!) 1.文件准备: 服务器:CentOS Linux release 7.3.1611 (Core)     Apa ...

  4. JavaScript基础细讲

    JavaScript基础细讲   JavaScript语言的前身叫作Livescript.自从Sun公司推出著名的Java语言之后,Netscape公司引进了Sun公司有关Java的程序概念,将自己原 ...

  5. 003_循环(loop), 递归(recursion), 遍历(traversal), 迭代(iterate)的区别

    表示“重复”这个含义的词有很多, 比如循环(loop), 递归(recursion), 遍历(traversal), 迭代(iterate). 循环算是最基础的概念, 凡是重复执行一段代码, 都可以称 ...

  6. 算法与数据结构基础 - 递归(Recursion)

    递归基础 递归(Recursion)是常见常用的算法,是DFS.分治法.回溯.二叉树遍历等方法的基础,典型的应用递归的问题有求阶乘.汉诺塔.斐波那契数列等,可视化过程. 应用递归算法一般分三步,一是定 ...

  7. Celery定时任务细讲

    Celery定时任务细讲 一.目录结构 任务所在目录 ├── celery_task # celery包 如果celery_task只是建了普通文件夹__init__可以没有,如果是包一定要有 │ ├ ...

  8. 数据结构与算法--递归(recursion)

    递归的概念 简单的说: 递归就是方法自己调用自己,每次调用时传入不同的变量.递归有助于编程者解决复杂的问题,同时可以让代码变得简洁. 递归调用机制 我列举两个小案例,来帮助大家理解递归 1.打印问题 ...

  9. 细讲前端设置cookie, 储存用户登录信息

    细讲前端设置cookie 引言 正文 一.设置cookie 二.查看cookie 三.删除cookie 四.封装cookie操作 结束语 引言 我们都知道如果想做一个用户登录并使浏览器保存其登录信息, ...

随机推荐

  1. [译]学习Javascript的工具

    本文翻译youtube上的up主kudvenkat的javascript tutorial播放单 源地址在此: https://www.youtube.com/watch?v=PMsVM7rjupU& ...

  2. Web性能权威指南 PDF扫描版​

    Web性能权威指南是谷歌公司高性能团队核心成员的权威之作,堪称实战经验与规范解读完美结合的产物.<Web性能权威指南>目标是涵盖Web开发者技术体系中应该掌握的所有网络及性能优化知识.全书 ...

  3. 在ubuntu 14.04 编译android 2.3.1 错误解决办法

    首先必须降低gcc版本: sudo apt-get install gcc-4.4sudo apt-get install g++-4.4sudo rm -rf /usr/bin/gcc /usr/b ...

  4. 使用Shader Graph实现《塞尔达传说:旷野之息》风格的着色器

    https://mp.weixin.qq.com/s/19Xq6wYuXP0gQJDvC9Fh0g

  5. P2381 圆圆舞蹈

    题意:一个圆,上面有n头牛(卧槽) 给出相邻两头牛顺时针的距离 问两只最远的牛的距离(min(顺时针距离,逆时针距离)) 最远距离一定$\le$距离和/2 先求个前缀和 那么问题转化为:找到 $s_j ...

  6. SpriteBuilder 不能对设置spriteframe的sprite进行设置dynamic Physics解决办法

    可能spriteBuilder是最新推出 cocos2d 可视化,在学习过程中遇到一些Bug,比如你对一个精灵设置了一个动画帧(spriteframe),这并不会改变他的物理属性,正常来说是可以设置他 ...

  7. [HAOI2012]音量调节 BZOJ2748 dp

    题目描述 一个吉他手准备参加一场演出.他不喜欢在演出时始终使用同一个音量,所以他决定每一首歌之前他都需要改变一次音量.在演出开始之前,他已经做好一个列表,里面写着每首歌开始之前他想要改变的音量是多少. ...

  8. maven 配置jetty 插件

    <plugin> <groupId>org.eclipse.jetty</groupId> <artifactId>jetty-maven-plugin ...

  9. python 面向对象及封装继承和多态

    ######装饰器######装饰器的概念 - 装饰器的实现是函数里面嵌套函数;- 装饰器的本质是一个函数, 它可以让其他函数在不需要做任何代码改动的前提下增加额外的功能;- 装饰器需要传递一个函数, ...

  10. BigDecimal取整

    Java中BigDecimal取整方法 BigDecimal bd = new BigDecimal("12.1"); long l = bd.setScale( 0, BigDe ...