入门

首先先对递归进行入门。

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

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

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. TCP/IP与套接字

    以前我们讲过进程间通信,通过进程间通信可以实现同一台计算机上不同的进程之间通信. 通过网络编程可以实现在网络中的各个计算机之间的通信. 进程能够使用套接字实现和其他进程或者其他计算机通信. 同样的套接 ...

  2. .Net Core 使用Session

    1. NUGET包引用 icrosoft.AspNetCore.Session 2.Startup中添加一下代码: public void ConfigureServices(IServiceColl ...

  3. Go语言环境安装-Mac

    1.环境安装 Go SDK下载和安装,下载地址为:https://golang.org/dl/ Visual Studio Code下载和安装,下载地址为:https://code.visualstu ...

  4. 在Python中正确使用Unicode

    正确处理文本,特别是正确处理Unicode.是个老生常谈的问题,有时甚至会难倒经验丰富的开发者.并不是因为这个问题很难,而是因为对软件中的文本,开发者没有正确理解一些关键概念及其表示方法.在Stack ...

  5. group by 两个或以上条件的分析

    首先group by 的简单说明: group by 一般和聚合函数一起使用才有意义,比如 count sum avg等,使用group by的两个要素:   (1) 出现在select后面的字段 要 ...

  6. SpringBoot Junit测试Controller

    原文链接:https://blog.csdn.net/xiaolyuh123/article/details/73281522 import java.util.List; import org.sp ...

  7. 浅谈 关于ARC循环引用得问题

    这段时间在研究关于ARC得循环引用导致变量不能释放,在此先介绍一本书英文书: <Pro Multithreading and Memory Management for iOS and OS X ...

  8. Step by Step: 基于MFC下的COM组件开发-Helloworld

    http://blog.csdn.net/sybifei/article/details/45008745 [这篇文章有问题, 仅供参考] http://blog.csdn.net/define_us ...

  9. thinkphp 3.0 核心函数U的一个致命bug

    最近在玩thinkphp,感觉内置函数 U 挺强大的! 传递多个参数时,出乱子了(window环境下,xampp) 例如 echo U('Blog/cate',array('cate_id'=> ...

  10. 南昌大学航天杯第二届程序设计竞赛校赛网络同步赛 I

    链接:https://www.nowcoder.com/acm/contest/122/I来源:牛客网 题目描述 小q最近在做一个项目,其中涉及到了一个计时器的使用,但是笨笨的小q却犯难了,他想请你帮 ...