百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 编程网 > 正文

理解递归函数之返回机制、与循环的对应关系、n递归与n叉树

yuyutoo 2024-12-17 17:24 13 浏览 0 评论

代码顺序存储,逐条执行。
代码的控制结构(if, while, for, continue, break, return),可以进行跳转。

函数调用机制也是一种控制结构机制,因为每个函数有一条显式或隐式的return语句。

1 函数的调用与return机制

函数都有一条或多条显式的return语句,或一条隐式的return语句(void函数,执行到函数体最后一句时,会隐式执行一条return语句,对应汇编的ret指令)。

函数调用,也就是主调函数(caller)调用被调函数(callee),调用时,代码跳转,离开主调函数caller,执行callee函数体,遇到return语句或执行完函数体后,return回caller的调用点,有返回值的返回给caller中的变量。
callee return回caller调用点就是跳转,能准确跳回是因为存储了一个调用点的地址。这是编译器背后的支持,通过一个栈机制保存了调用点地址(每次调用都会在栈空间内开辟一个栈帧空间给函数)。当有嵌套调用时,能逐级调用,逐级返回。怎样逐级返回?就是先返回到最近存储的调用点,也就是后进先出。如同你从A点出发,经过n个路口,每经过一个路口,用一张扑克牌记录好需要返回的路口,每过一个路口,记录一张牌,放在前面一张牌的上面,最后要返回时,读取最上面的牌上的返回地址,依次返回。这些牌依次叠起来,依次从上面拿牌,十分类似函数嵌套调用时的栈帧机制。

2 递归函数的参数迭代与循环的对应关系

递归函数就是自己调用自己。

同样的代码按约定条件重复执行n次,完全可行。这样一个洋葱,层层相套,代码量一样,执行代码的顺序和代码量可能不一样,问题规律可能逐层递减,参数可能不断迭代变化。

写代码时,对有重复利用价值的功能代码模块可以抽象出函数(包括抽象出函数参数和返回值)。在调用点调用时,可以想象为适当处理好函数参数和返回值后,将函数体扩充到该位置。递归调用也是如此,就是将自己的函数体扩充到自己的调用点(可能也有参数迭代)。

嵌套调用可以想象为嵌套膨胀,或嵌套套容器。

如果递归调用了几次,可以理解为将代码重复次。

另外要注意代码的执行顺序,通常按先后顺序,以调用点(也是返回点)为基准,分为三部分:
a 调用点前的代码,在递推阶段执行,通常有一个if语句,也有可能包含一个return语句,提前return。

b 调用点处的调用代码,是调用点(跳转点),也是回归点(return回)。

c 调用点后的代码,在回归阶段执行,直到return语句或函数体最后一条语句,如果是尾递归,则没有这一部分。如果不是尾递归,历史的局部变量与实参值保存在返回的栈桢上(如同上述记录的扑克牌),如果递归函数用循环同等实现时,是需要一个额外的栈数据结构来辅助保存这些历史的实参与局部变量。

有一点微妙之处在于参数的迭代及与循环的对应关系。
为什么迭代函数内只有if语句,却可以构成循环?理解一下递归函数,同行功能实现的循环实现及对应的goto实现就知道了。

int factRecur(int n){  // 阶乘递归版
    if(n<=1)
        return 1;
    return n*factRecur(n-1); // 递归调用时,参数迭代:n = n-1
}

int factLoop(int n){  // 阶乘循环版
    int sum = 1;
    while(n>1)
        sum *= n--;
    return sum;
}

int factGoto(int n){ // 阶乘goto版
    int sum = 1;
loop:
    if(n<=1)
        goto end;
    sum *= n--;
    goto loop;
end:
    return sum;
}

3 单递归

单递归,一个函数只有1次调用自己。求阶乘就是典型的单递归。单递归如果是尾递归时,用循环替代时,比较简单,不需要栈数据结构辅助。如果不是尾递归,需要栈数据结构来辅助记录函数参数(如果有)和局部变量。

单递归是一种线性的call和return。

4 双递归

双递归,一个函数在函数体内有2次调用自己的语句。汉诺塔和斐波那契数列都是典型的双递归。

二递归对应一棵二叉树,递推和回归(return)的顺序,就是二叉树的深度优先遍历。

二叉树深度优先遍历,一个节点的代码三次进入,三次return回:

非叶节点:3次递推进入和3次return回的机会(叶子节点只有一次机会,遇到基准条件即返回)。

void midOrder(struct treeNode* tree)    // void函数默认一个return
{
    if(tree!=NULL)                      // 非空时递归,空时return回
    {
        // printData(tree);				// 写在前面就是前序遍历,第1次进入时操作
        midOrder(tree->LChild);         // 叉1 参数迭代,回溯时往下走
        printData(tree);				// 写在中间就是中序遍历,第2次进入时操作
        midOrder(tree->RChild);         // 叉2 
        // printData(tree);				// 写在后面就是后序遍历,第3次进入时操作
    }
}

可以理解为代码的重复:

总结一下:递归函数调用自己2次,二叉,加上自己,代码要重复执行3次,三次被call,三次return,形成3个调用和回归点。

5 三递归

三递归,一个函数3次调用自己。

示例代码:

#include <stdio.h>
void r(int n)
{
    if(n<=0)
        //printf("\n__%d__\n",n);
        return;
    r(n-3);
    r(n-2);
    r(n-1);
    printf("%d ",n);
}

int main()
{
    r(5);
    return 0;
}

形成三叉树的调用关系:

后序遍历,及4次进入的机会。

如r(1),

第1次:调用r(-2),再return到r(1);

第2次:调用r(-1),再return到r(1);

第2次:调用r(0),再return到r(1);

完整的三叉树:

三叉树深度优先遍历,一个节点的代码四次进入,四次return回:

6 更多次递归函数

如分书问题:

#include <iostream>
using namespace std;
#define NUMS 5

int like[NUMS][NUMS]={ // like[i][j] = 1 表示第i人喜欢第j本书
    {0,0,1,1,0},
    {1,1,0,0,1},
    {0,1,1,0,1},
    {1,0,0,1,0},
    {0,1,0,0,1}};

int take[NUMS]={0,0,0,0,0}; // 记录每一本书的分配情况
int n = 0;                  // n表示分书方案数

void trynext(int i);

int main()
{
    trynext(0);    // 书(列)1-5,人1-5 ,A-E
    cin.get();
    return 0;
}

void trynext(int j)         // 对第 j 本书进行分配
{
    for(int i=0;i<NUMS;i++)  // 对每个人进行穷举
        if(like[i][j]&&take[i]==0)
        {
            take[i]=j+1;    // 把第i本书分配给第j个人
            if(j==NUMS-1)   // 第NUMS本书分配结束,也即所有的人已经分配完毕,
            {               // 可以将方案进行输出
                n++;
                cout<<"分配方案"<<n<<":"<<endl;
                for(int k=0;k<NUMS;k++)
                    cout<<"人"<<k<<"→"<<"分到第"<<take[k]<<"本书"<<endl;
                cout<<endl;
            }
            else{
                cout<<j+1<<" ";  // 调用时的参数记录,辅助理解形成了几次调用关系
                trynext(j+1);    // 递归,对下一个人进行分配
            }
            take[i]=0;           // 回溯,寻找下一种方案
        }
}

/*
1 2 3 4 分配方案1:
人0→分到第3本书
人1→分到第1本书
人2→分到第2本书
人3→分到第4本书
人4→分到第5本书

2 3 4 分配方案2:
人0→分到第3本书
人1→分到第1本书
人2→分到第5本书
人3→分到第4本书
人4→分到第2本书

3 4 4 1 2 3 3 4 分配方案3:
人0→分到第4本书
人1→分到第2本书
人2→分到第3本书
人3→分到第1本书
人4→分到第5本书

2 3 2 3 3 4 分配方案4:
人0→分到第4本书
人1→分到第5本书
人2→分到第3本书
人3→分到第1本书
人4→分到第2本书
*/  

如果是5个人5本书,因为有回溯的情况,一个分配方案的递归函数,可能多于或小于4递归。

-End-

相关推荐

Mysql和Oracle实现序列自增(oracle创建序列的sql)

Mysql和Oracle实现序列自增/*ORACLE设置自增序列oracle本身不支持如mysql的AUTO_INCREMENT自增方式,我们可以用序列加触发器的形式实现,假如有一个表T_WORKM...

关于Oracle数据库12c 新特性总结(oracle数据库19c与12c)

概述今天主要简单介绍一下Oracle12c的一些新特性,仅供参考。参考:http://docs.oracle.com/database/121/NEWFT/chapter12102.htm#NEWFT...

MySQL CREATE TABLE 简单设计模板交流

推荐用MySQL8.0(2018/4/19发布,开发者说同比5.7快2倍)或同类型以上版本....

mysql学习9:创建数据库(mysql5.5创建数据库)

前言:我也是在学习过程中,不对的地方请谅解showdatabases;#查看数据库表createdatabasename...

MySQL面试题-CREATE TABLE AS 与CREATE TABLE LIKE的区别

执行"CREATETABLE新表ASSELECT*FROM原表;"后,新表与原表的字段一致,但主键、索引不会复制到新表,会把原表的表记录复制到新表。...

Nike Dunk High Volt 和 Bright Spruce 预计将于 12 月推出

在街上看到的PandaDunk的超载可能让一些球鞋迷们望而却步,但Dunk的浪潮仍然强劲,看不到尽头。我们看到的很多版本都是为女性和儿童制作的,这种新配色为后者引入了一种令人耳目一新的新选择,而...

美国多功能舰载雷达及美国海军舰载多功能雷达系统技术介绍

多功能雷达AN/SPY-1的特性和技术能力,该雷达已经在美国海军服役了30多年,其修改-AN/SPY-1A、AN/SPY-1B(V)、AN/SPY-1D、AN/SPY-1D(V),以及雷神...

汽车音响怎么玩,安装技术知识(汽车音响怎么玩,安装技术知识视频)

全面分析汽车音响使用或安装技术常识一:主机是大多数人最熟习的音响器材,有关主机的各种性能及规格,也是耳熟能详的事,以下是一些在使用或安装时,比较需要注意的事项:LOUDNESS:几年前的主机,此按...

【推荐】ProAc Response系列扬声器逐个看

有考牌(公认好声音)扬声器之称ProAcTablette小音箱,相信不少音响发烧友都曾经,或者现在依然持有,正当大家逐渐掌握Tablette的摆位设定与器材配搭之后,下一步就会考虑升级至表现更全...

#本站首晒# 漂洋过海来看你 — BLACK&amp;DECKER 百得 BDH2000L无绳吸尘器 开箱

作者:初吻给了烟sco混迹张大妈时日不短了,手没少剁。家里有了汪星人,吸尘器使用频率相当高,偶尔零星打扫用卧式的实在麻烦(汪星人:你这分明是找借口,我掉毛是满屋子都有,铲屎君都是用卧式满屋子吸的,你...

专题|一个品牌一件产品(英国篇)之Quested(罗杰之声)

Quested(罗杰之声)代表产品:Q212FS品牌介绍Quested(罗杰之声)是录音监听领域的传奇品牌,由英国录音师RogerQuested于1985年创立。在成立Quested之前,Roger...

常用半导体中英对照表(建议收藏)(半导体英文术语)

作为一个源自国外的技术,半导体产业涉及许多英文术语。加之从业者很多都有海外经历或习惯于用英文表达相关技术和工艺节点,这就导致许多英文术语翻译成中文后,仍有不少人照应不上或不知如何翻译。为此,我们整理了...

Fyne Audio F502SP 2.5音路低音反射式落地音箱评测

FyneAudio的F500系列,有新成员了!不过,新成员不是新的款式,却是根据原有款式提出特别版。特别版产品在原有型号后标注了SP字样,意思是SpecialProduction。Fyne一共推出...

有哪些免费的内存数据库(In-Memory Database)

以下是一些常见的免费的内存数据库:1.Redis:Redis是一个开源的内存数据库,它支持多种数据结构,如字符串、哈希表、列表、集合和有序集合。Redis提供了快速的读写操作,并且支持持久化数据到磁...

RazorSQL Mac版(SQL数据库查询工具)

RazorSQLMac特别版是一款看似简单实则功能非常出色的SQL数据库查询、编辑、浏览和管理工具。RazorSQLformac特别版可以帮你管理多个数据库,支持主流的30多种数据库,包括Ca...

取消回复欢迎 发表评论: