递归

当一个函数调用它自己来定义时称它为递归函数。(什么叫它自己调用它自己呢?)

1.1、引出递归

从一个简单的问题考虑递归,求0,1,2, 3,4,5......n的和。

首先定义一个求和公式:sum(n);

显然对于(n > 0): sum(n) = sum(n - 1) + n ;

​ (n = 0 ) : sum(0) = 0;

​ 成立。

将上述公式翻译成C++函数:

unsigned int sum(unsigned int n)
{
if(0 == n)
{
return 0; //基准情况(递归的出口),sum不能一直调用它自己吧,总归要有一个出口结束递归吧
}
else
{
return sum(n - 1) + n; //sum(unsigned int)调用了它自己
}
}

假设 n = 5 分析一下计算过程:

sum(5) = sum(4) + 5;

sum(4) = sum(3) + 4;

sum(3) = sum(2) + 3;

sum(2) = sum(1) + 2;

sum(1) = sum(0) + 1;

sum(0) = 0; 当sum(0)时,sum()不再调用它自己,作为递归的出口结束递归。

假设没有n = 0, sum(0) = 0 这个基准情况作为递归的出口跳出递归,递归就会一直递归下去,没完没了直至崩溃。因此递归函数必须有一个基准情况作为递归出口

1.2、失败的递归

给出一个所谓的递归函数:

int bad(unsigned int n)
{
if(0 == n)
{
return 0;
}
else
{
return bad(n/3 + 1) + n - 1;
}
}

分析一下以上函数,函数给出了 n = 0 的情况作为递归的出口,看似没什么问题。

还是假设n = 5;

bad(5) : 调用bad(5/3 + 1), 即bad(2);

bad(2) : 调用bad(2/3 + 1), 即bad(1);

bad(1) : 调用bad(1/3 + 1), 即bad(1);

bad(1) : 调用bad(1/3 + 1), 即bad(1)..........

bad(1)一直调用bad(1), 一直调用到程序崩溃。很明显bad()函数定义虽然给出了 n = 0 作为递归出口,但是bad()函数根本不会推进到n = 0 的这种情况。因此递归调用必须总能够朝着产生基准情况(递归出口)的方向推进

1.3、递归和归纳

考虑一个问题:现在需要将一个正整数 n 打印出来,但是I/O给出的函数接口(printDigit)只能处理单个数字(即n < 10)。

我们随便假设一个n值:n = 2019,那么单个数字打印的顺序就是2, 0, 1, 9。换句话说,9是最后一个打印的,在打印9之前要先打印201,即先打印“201”,再打印“9”;依次类推对于“201”先打印“20”,再打印“1”;对于“20”先打印“2”,再打印“0”;对于2已经是单个数字,可以直接打印了, 不需要再划分,再递归了,也就是说单个数字n < 10即为递归的出口。

我们按上述思路细致的分析一下:

对2019分成2部分: 201 = 2019 / 10; 9 = 2019 % 10;

对201分成2部分:20 = 201 / 10; 1 = 201 % 10;

对20分成2部分:2 = 20 / 10; 0 = 20 % 10;

对于 2 满足 n < 10 的条件,不再递归,直接打印。

现在递归已经很明显了,尝试编写一下代码:

//假设printDigit((unsigned int n)如下,
void printDigit(unsigned int n)
{
std::cout << n;
} void print(unsigned int n)
{
if(n >= 10)
{
print(n / 10);
}
printDigit(n % 10);
}

代码编写好了,现在需要证明以下代码是否正确:对于n >= 0,数的递归打印算法总是正确的。

证明:用k表示数字n的包含单个数字的个数。当k = 1,即 n < 10 时,很明显程序是正确的,因为它不需要递归,print()只调用一次printDigit(), 不调用它自己。然后假设print()对于所有k位数都能正常工作,任何k + 1位的数字n都可以通过它的前k位的数字和最低1位数字来表示。前k 位的数字恰好是[ n / 10], 归纳假设它能正常工作,而最低1位数字是[ n % 10],因此该程序能够正确的打印出任意k + 1位。于是根据归纳法[1],所有数字都能被正确打印出来。

由以上实例总结可以出一条递归的设计法则:假设所有递归调用都能运行。

1.4、递归的合成效益法则

用递归实现一个斐波那契数列:

//斐波纳契数列:1、1、2、3、5、8、13、21、34
int f(int n)
{
if(n < 1)
{
return 0;
}
else if(n <= 2)
{
return 1;
} return f(n-1) + f(n-2); }

假设n = 8, 函数调用f(8), 递归调用如下图:

graph TB
8-->7;
7-->6;
6-->5;
5-->4;
4-->3;
3-->2;
8-->id0(6);
id0(6)-->id1(5);
id1(5)-->id2(4);
id2(4)-->id3(3);
id3(3)-->id4(2);
7-->id5(5);
id5(5)-->id6(4);
id6(4)-->id7(3);
id7(3)-->id8(2);
6-->id9(4);
id9(4)-->id10(3);
id10(3)-->id11(2);
5-->id12(3);
id12(3)-->id13(2);
4-->id14(2);
3-->id15(1);
id12(3)-->id16(1);
id9(4)-->id17(2);
id10(3)-->id18(1);
id5(5)-->id19(3);
id19(3)-->id20(2);
id19(3)-->id21(1);
id6(4)-->id22(2);
id7(3)-->id23(1);
id0(6)-->id24(4);
id24(4)-->id25(3);
id24(4)-->id28(2);
id25(3)-->id26(2);
id25(3)-->id27(1);
id1(5)-->id29(3);
id29(3)-->id30(2);
id29(3)-->id31(1);
id2(4)-->id32(2);
id3(3)-->id33(1);

由上图我们不厌其烦的数一下:

n = 1时,f()调用1次;

n = 2时,f()调用1次;

n = 3时,f()调用3次;

n = 4时,f()调用5次;

n = 5时,f()调用9次;

n = 6时,f()调用15次;

n = 7时,f()调用25次;

n = 8时,f()调用41次;

增长的是不是太快了,在f()里加一个计数器测试一下,可以看到在n = 30 的时候,f()的调用次数大约在160万。

究其原因,是因为我们在求解的过程时,重复了大量的计算过程, 在n = 8 的时候单单是f(3)就重复调用了8次。

由上我们可以得出一个结论:在求解一个问题的同一实例时,在不同的递归中做重复性的工作,对资源的消耗可能是灾难性的。

最后归纳一下要牢记的递归四条基本法则:

  1. 基准情形。必须总有某些基准情况,它无须递归就能求解,即递归必须有出口。
  2. 不断推进。对于那些需要递归求解的情形,每一次递归调用都必须要使求解状态朝基准情形的方向推进。
  3. 设计法则。假设所有的递归调用都能运行。
  4. 合成效益法则。在求解一个问题的同一实例时,切勿在不同的递归中做重复性的工作。

  1. 1、证明当n= 1时命题成立。2、假设n=m时命题成立,那么可以推导出在n=m+1时命题也成立。(m代表任意自然数)。3、归纳结论。 ↩︎

C/C++ 递归的更多相关文章

  1. .NET 基础 一步步 一幕幕[面向对象之方法、方法的重载、方法的重写、方法的递归]

    方法.方法的重载.方法的重写.方法的递归 方法: 将一堆代码进行重用的一种机制. 语法: [访问修饰符] 返回类型 <方法名>(参数列表){ 方法主体: } 返回值类型:如果不需要写返回值 ...

  2. 算法笔记_013:汉诺塔问题(Java递归法和非递归法)

    目录 1 问题描述 2 解决方案  2.1 递归法 2.2 非递归法 1 问题描述 Simulate the movement of the Towers of Hanoi Puzzle; Bonus ...

  3. Android 算法 关于递归和二分法的小算法

     // 1. 实现一个函数,在一个有序整型数组中二分查找出指定的值,找到则返回该值的位置,找不到返回 -1. package demo; public class Mytest { public st ...

  4. 二叉树的递归实现(java)

    这里演示的二叉树为3层. 递归实现,先构造出一个root节点,先判断左子节点是否为空,为空则构造左子节点,否则进入下一步判断右子节点是否为空,为空则构造右子节点. 利用层数控制迭代次数. 依次递归第二 ...

  5. 递归实现n(经典的8皇后问题)皇后的问题

    问题描述:八皇后问题是一个以国际象棋为背景的问题:如何能够在8×8的国际象棋棋盘上放置八个皇后, 使得任何一个皇后都无法直接吃掉其他的皇后?为了达到此目的,任两个皇后都不能处于同一条横行.纵行或斜线上 ...

  6. C语言用分别用递归和循环求数字的阶乘的方法

    以下代码均为 自己 实现,嘻嘻! 参考文章:http://blog.csdn.net/talk_8/article/details/46289683 循环法 int CalFactorial(int ...

  7. C#递归解决汉诺塔问题(Hanoi)

    using System;using System.Collections.Generic;using System.Linq;using System.Text; namespace MyExamp ...

  8. Java之递归求和的两张方法

    方法一: package com.smbea.demo; public class Student { private int sum = 0; /** * 递归求和 * @param num */ ...

  9. C#语言基础——递归

    递归 一.概念conception: 函数体内调用本函数自身,直到符合某一条件不再继续调用. 二.应满足条件factor: (1)有反复执行的过程(调用自身): (2)有跳出反复执行过程的条件(函数出 ...

  10. SQL Server封闭掉 触发器递归

    SQL Server关闭掉 触发器递归SQL Server  是有一个开关, 可以关闭掉 触发器递归的.EXEC sp_dboption '数据库名字', 'recursive triggers', ...

随机推荐

  1. php+大文件上传

    1.使用PHP的创始人 Rasmus Lerdorf 写的APC扩展模块来实现(http://pecl.php.net/package/apc) APC实现方法: 安装APC,参照官方文档安装,可以使 ...

  2. 珍珠x

    题目描述 有n颗形状和大小都一致的珍珠,它们的重量都不相同.n为整数,所有的珍珠从1到n编号.你的任务是发现哪颗珍珠的重量刚好处于正中间,即在所有珍珠的重量中,该珍珠的重量列(n+1)/2位.下面给出 ...

  3. 计蒜客 A1607 UVALive 8512 [ACM-ICPC 2017 Asia Xi'an]XOR

    ICPC官网题面假的,要下载PDF,点了提交还找不到结果在哪看(我没找到),用VJ交还直接return 0;也能AC 计蒜客题面 这个好 Time limit 3000 ms OS Linux 题目来 ...

  4. nuget push 程序包到nuget服务器时报错 406 (Not Acceptable)

    1.在window服务器上部署nuget服务器时,发布包时出现请求报错 406 (Not Acceptable) 验证用户名.密码正确的情况下,还是出现上面错误.后面跟踪服务器日志,发现window\ ...

  5. 序列式容器————forward_list

    单链表的形式存储元素.forward_list 的模板定义在头文件 forward_list 中.fdrward_list 和 list 最主要的区别是:它不能反向遍历元素:只能从头到尾遍历. for ...

  6. 有色物体检测opencv+python

    import cv2 import numpy as np import matplotlib.pyplot as plt cap=cv2.VideoCapture(0) while(1): ret, ...

  7. 用命令行编译运行java文件的乱码问题

    之前在写的时候没有遇到过这个问题,用惯了eclipse之后突然用Notepad++就出现乱码了 我在编写的时候 指定Noepad++的编码是 UTF-8编码,然后进入命令行,编译的时候就出现了乱码 然 ...

  8. yii 1.1简单文件缓存

    缓存组件配置在config\main.php文件,简单配置下文件缓存 'components'=>array( 'cache' => array( 'class' => 'syste ...

  9. 初始化Thread

    此处初始化的步骤和上文中介绍的一样,也是调用runClinit方法.首先设置初始化线程为CurrentThread,然后由于其父类Object此时的状态为CLASS_READY,因此就不需要初始化父类 ...

  10. 地图服务 纬度、经度对应坐标轴x,y

    记下,供自己参考,中国地区的经纬度,经度大,纬度小 如上海经纬度为:(经度, 纬度)(y, x)(lon, lat) 121.48 31.22 纬度---lat----x轴 经度---lon---y轴