当前课程知识点:数据结构 >  第3章 栈和队列 >  3.4 栈与递归 >  讲解(上)

返回《数据结构》慕课在线视频课程列表

讲解(上)在线视频

下一节:讲解(下)

返回《数据结构》慕课在线视频列表

讲解(上)课程教案、知识点、字幕

在学习了栈数据结构之后

我们接下来看看栈和递归。先来看递归,递归是一类常用的算法思想

递归的本质就是:算法当中的某一步或者某几步,是由算法本身构成的

而后者涉及的问题规模,通常是更小的

对于编程来说

递归是指函数自己调用了自己

递归可以用于解决一些用常规方式难以解决的问题

我们后面会看到几个例子

现在,我们来看看,哪些问题

可以采用递归,或者说递归问题可以分为哪几类

我们先来看第一类:问题的定义是递归的

这里,我们以数学上的求阶乘为例

我们知道,n!=n*(n-1)!

而0!是已知的

直接等于1

求(n-1)!

实际上,又会递归到求(n-2)!

一直往下,直到要求的阶乘n

是等于0的

这时候,就不用再递归了

而直接得到结果1

然后,再逐层地返回

现在,我们来看,用递归方式来求阶乘的代码

函数名叫f,参数是n

我们要求n!,返回值

是long型的

因为,n!可能会很大

如果n等于0

是不需要递归的

直接返回1

除此之外

n>0的情况,都进行递归

那么,就套用上面这个式子

n*f(n-1),大家看到,函数体当中,某一步自己调用了自己

但是,传入的参数

不太一样

一般来说,这个参数是越来越小的

也就是所谓的问题

规模越来越小

这样一个程序

我们把它写的简化一点

实际上,用一行语句就可以了

我们用C语言的选择运算符

条件运算符

如果n等于0,我们直接返回1

除此之外

我们返回n*f(n-1)

这是最简单形式的递归

现在,我们来看,第二类递归:问题结构是递归的

我们给出的问题是:得到非空单链表的最后一个元素

大家看到,这样一个单链表,一共包含4个元素

最后一个元素

是a4

如果我们用常规的方式来求解

肯定是写一个循环,从头指针

L开始

不断地让一个指针,指向后面的结点

当指针指向的

结点的next为空的时候

这时候,指针p指向的结点的数据域,就是我们要求解的结果

现在,我们可以换一种思路

原问题是求

L指向这个头结点的单链表

它的最后一个元素

现在把问题转换成:L指向

a1这个结点,也就是以a1为头结点的单链表

你会发现,这个单链表,它的最后一个元素跟原问题的最后一个元素是相同的

但是,表长却少了1

我们可以采用类似的思想

让L继续往后,一直往后

当L是一个空链表的时候

注意,L指向的是头结点

当L指向的结点

它的next为空的时候

这时候,L指向的结点

它的数据域,就是我们要求解的原问题中的最后一个结点

现在,我们给出代码的实现

得到单链表的最后一个元素

单链表的头指针是L

因为要求结点的数据域

所以,我们给出的返回值类型是ElemType

我们直接用一行语句

如果L->next是空,注意,!L->next等价于

L->next为空

也就是这种情形

一个空的单链表

L->next为空

我们直接返回L->data

返回它的数据域

除此之外

如果

L->next不为空

那么,我们就递归,自己调用自己

但是,这时候传入的实参是有变化的

不再是L

而是L->next

也就是

指向这里

一直递归下去

最终,L会指向最后一个结点

这是问题结构是递归的

我们再来看第三类递归

求解思想是递归的

这里,我们引入的例子

是著名的汉诺塔游戏,汉诺塔游戏

它的规则很简单

三个柱子

A

B

C

A柱子上,有大小不等的若干个盘子,编号从上到下

是#1到#n

原问题是这样要求的

将A上的n个盘子,移动到C上

也就是,最终要到达这样一种状态

在移动的过程当中

有几个限定条件

第一,每次只能移动一个盘子

第二个限定条件

在移动的过程当中,只能将小盘子

压在大盘子上,不能将大盘子压在小盘子上

这样一个问题,我们如何来做呢

我们采用递归的思想

可以分为三步来做

第一步

我们先将A上的前n-1个盘子

注意,是不包含

#n盘的

是#1到#n-1盘

前n-1个盘子

我们先从A

移动到B上

也就是,到这样一种情况

然后,A上剩的#n盘

编号为n的那个盘子

我们直接移到C上

这是第二步

第三步

我们再将B上的n-1个盘子,从B挪到C上

这样,问题就最终求解了

但是

这三步当中,有两步是无法直接求解的

只有第二步,是可以直接移动的

比如,第二步将A上的#n盘,直接从A移到C上,这是可以直接移的

因为只有一个盘子

而C上没有其他的盘子

第一步和第三步都是递归

但是,问题的规模变小了

第一步,是移A上的#1到#n-1盘

它比原来的#1到#n盘

少了一个

我们将n-1个盘子,从A挪到B上

这是第一步做的事情

第三步

是将B上的

#1到#n-1盘子,从B挪到C上

这一步,也是无法直接求解的

所以一、三两步是递归,我们在这里,可以简单地写一下原问题

它是:移动

#1到#n盘,从A到C

这是原问题

我们把这个原问题

拆成三步,第一步

是:移动

#1到#n-1盘

从A挪到B上

B实际上是用于暂存盘子的,临时存放

一些盘子;第二步,A上只有编号为n的一个盘子了

这时候,我们可以直接移动,移动#n盘子,从A

到C上,这一步是可以直接求解的

第三步

将B上的

#1到#n-1盘子,从B

再挪到C上

大家观察一下,原问题

拆分成的三个步骤

第二步,是可以直接求解的

而第一步

跟第三步,是无法直接求解的

但是,你会发现,这两步它们的问题性质,跟原问题是一模一样的

都是将若干个盘子,从某一个柱子移到另外一个柱子

但是

一、三两步

它们的问题规模变小了

原来是移n个盘子

一、三却是移n-1个盘子

一、三它们可以继续拆成三步

一直往后递归

在拆的过程当中,最终

会到达只移1个盘子的情况

这时候,递归就返回了

最终,将原问题求解

现在,我们给出完整的代码

参数是这样的

第1个参数,是代表一共多少个盘子

从哪个柱子,经过哪个临时的柱子,移到哪个柱子上

我们这里,从A柱,经过

temp这样一个临时的柱子

最终,移到C上,一共n个盘子

如果n等于1

也就是,仅仅只有一个盘子

那我们就直接移动,move

我们在上面给出来,它的第1个参数,实际上是用来做计数的

表示当前是第几次移动;后面3个参数:移动

哪一个盘子,从哪个柱子,移到哪个柱子上

我们把它打印出来

以方便大家理解

我们这里

直接将A上的#1盘

因为,这时候n等于1

实际上,也可以写成n,将A上的#n盘,从A移到C上

当n>1的时候

我们就进入递归

我们现在将A上的#1到

#n-1盘子

以C为临时的柱子

把它们移到temp上

注意,这2个参数的顺序有变化

我们现在是以C为临时的柱子

将它们移到temp上

暂存起来,也就是相当于图当中的B

然后,第二步是可以直接求解的

因为A上的前

n-1盘子移出来之后,只剩下#n盘了

这时候,我们直接将#n盘

从A移到C上

第三步

我们再将temp上的

#1到#n-1号盘子,从temp,经过A为临时的柱子

最终移到C上,注意,我们在声明count的时候

前面加了一个关键字static

因为,如果不加这个关键字

每一次递归进来,都要重新初始化

count为0,那我们打印的第几次移动,就不对了

我们只让它初始化一次

加一个static

第一次执行到这一行的时候,初始化它为0,以后

再执行到这一行,就不执行了

而是用上一次count的值

否则,每次这里打印的count,它的值就不对了

而且,我们这里采用了引用参数的语法

因为,我们在函数内部会对count进行自增

现在,来看递归算法

任何递归算法实际上都由两部分组成

第一部分:终结条件,终结条件是指无需递归,就能直接得到结果的那些部分

比如,我们刚才将一个柱子上的一个盘子,移到另外一个柱子上

这时候,是不需要递归的

直接移就可以了

第二部分,就是所谓的递归部分

这时候,将原问题拆分成性质相同

但规模更小的若干个子问题

所谓性质相同

就是子问题要做的事情,跟原问题是一样的

只不过,相关的参数不一样,参数的大小、要处理的数据规模不一样而已

所以

所有的递归算法,从整体上都可以认为是一个

if-else语句

我们这里,写出递归算法的大致的架构

算法名字是solution——解决方案

我们这里直接用scale代表原问题的问题规模,当问题规模满足终结条件的时候

这个就是递归的第一部分

当问题规模满足终结条件的时候

我们是直接得到结果

那么

如果不满足终结条件

我们就递归

但是,传入的参数有变化,是更小的问题规模,而不是原来的问题规模

就像前面,原来是n个盘子

但现在是n-1个盘子

还类似于刚才求阶乘,求n的阶乘转换成

求n-1的阶层,是一样的

问题规模越来越小

当小到一定程度的时候,会触发终结条件

从而直接得到结果

数据结构课程列表:

第0章 课程简介

-讲解

-作业

-讨论1

-讨论2

第1章 绪论

-1.1 数据结构是什么

--讲解

--作业

-1.2 概念和术语

--讲解

--作业1

--作业2

-1.3 抽象数据类型

--讲解(上)

--讲解(下)

--作业

-1.4 算法及其设计要求

--讲解

--作业

-1.5 算法分析与度量

--讲解(上)

--讲解(下)

--作业1

--作业2

--讨论

第2章 线性表

-2.1 概念及ADT

--讲解

--作业

-2.2 线性表的顺序实现——顺序表

--讲解(上)

--讲解(中)

--讲解(下)

--作业1

--作业2

-2.3 线性表的链式实现——链表

--讲解(上)

--讲解(中)

--讲解(下)

--作业1

--作业2

-2.4 线性表的应用——多项式

--讲解

--作业

-讨论

第3章 栈和队列

-3.1 栈的定义及ADT

--讲解

--作业

-3.2 栈的顺序实现——顺序栈

--讲解

--作业

-3.3 栈的应用

--讲解

--作业

-3.4 栈与递归

--讲解(上)

--讲解(下)

--作业

-3.5 队列的定义及ADT

--讲解

--作业

-3.6 队列的顺序实现——循环队列

--讲解(上)

--讲解(下)

--作业

-讨论

第4章 数组

-4.1 数组的定义

--讲解

--作业

-4.2 数组的顺序实现

--讲解

--作业1

--作业2

-4.3 特殊矩阵的压缩存储

--讲解

--作业

-4.4 稀疏矩阵的压缩存储

--讲解(上)

--讲解(下)

--作业

-讨论

第5章 树和二叉树

-5.1 概念及术语

--讲解

--作业

-5.2 二叉树及其性质

--讲解

--作业1

--作业2

-5.3 二叉树的存储

--讲解

--作业

-5.4 二叉树的遍历及创建

--讲解(上)

--讲解(下)

--作业

-5.5 线索二叉树

--讲解

--作业

-5.6 树与森林

--讲解

--作业

-5.7 Huffman树

--讲解(上)

--讲解(下)

--作业

-讨论

第6章 图

-6.1 概念和术语

--讲解

--作业

-6.2 存储与实现

--讲解(上)

--讲解(下)

--作业

-6.3 遍历

--讲解(上)

--讲解(下)

--作业

-6.4 最小生成树

--讲解(上)

--讲解(下)

--作业1

--作业2

-6.5 拓扑排序

--讲解

--作业

-6.6 最短路径

--讲解

--作业

-讨论

第7章 查找

-7.1 概念和术语

--讲解

--作业

-7.2 静态查找表

--讲解(上)

--讲解(下)

--作业

-7.3 二叉排序树

--讲解(上)

--讲解(下)

--作业

-7.4 平衡二叉树

--讲解

--作业

-7.5 哈希表

--讲解(上)

--讲解(下)

--作业

-讨论

第8章 排序

-8.1 概念

--讲解

--作业

-8.2 插入排序

--讲解(上)

--讲解(下)

--作业

-8.3 交换排序

--讲解(上)

--讲解(下)

--作业

-8.4 选择排序

--讲解(上)

--讲解(中)

--讲解(下)

--作业

-8.5 归并排序

--讲解

--作业

-讨论

讲解(上)笔记与讨论

也许你还感兴趣的课程:

© 柠檬大学-慕课导航 课程版权归原始院校所有,
本网站仅通过互联网进行慕课课程索引,不提供在线课程学习和视频,请同学们点击报名到课程提供网站进行学习。