摘要:
随着课程的推进,我们进入了特殊线性表——栈与队列的学习。而栈有一个重要应用是在程序设计语言中实现递归。递归是算法设计中常用的手段,它通常可把一个大型复杂问题的描述和求解变得简洁和清晰。因此递归算法常常比非递归算法更易设计,尤其是当问题本身或所涉及的数据结构是递归定义的时候,使用递归方法更加合适。除此之外,递归在人工智能方面也有着广泛的应用,如深度学习领域的递归神经网络、人工智能分支之一人工生命。人工智能的本质其实就是算法智能。总之,递归算法在很多领域都有显著体现和重要应用。
关键字:递归、数据结构、人工智能、算法、深度学习
Abstract:
With the advancement of the course, we entered into the study of special linear tables - stacks and queues. One important application of stack is to realize recursion in programming language. Recursion is a common method in algorithm design, which can make the description and solution of a large complex problem concise and clear. Therefore, recursive algorithms are often easier to design than non recursive algorithms, especially when the problem itself or the data structure involved is recursively defined. In addition, recursion is also widely used in artificial intelligence, such as recurrent neural networks in the field of deep learning, fractal geometry of large objects, chaos and artificial life, which are branches of artificial intelligence. The essence of AI is actually algorithmic intelligence. In a word, recursive algorithm has significant embodiment and important application in many fields.
Keywords: recursion, data structure, artificial intelligence, algorithm, deep learning
目录
第1章:递归算法的背景及定义
1.2递归算法的定义及内涵
1.3采用递归算法解决的问题
第2章:递归算法的理解
2.1.用归纳法理解递归
2.2递归的三要素
2.3递归算法的编程模型
2.4递归的程序特性
2.5递归与循环
第3章:递归算法的实现原理
3.1子程序实现原理
3.2递归过程与递归工作栈
3.3递归算法的效率分析
第4章:递归算法与人工智能
4.1递归算法与智能
4.2深度学习中的递归神经网络
4.3递归与人工生命
参考文献
正文:
1.1.1数学中的递归
(1)类似于求解数列的递推公式:
1.1.2.生活故事中的递归
“从前有座山,山里有座庙”的故事等
1.1.3计算机中的递归
数据结构中的单链表、二叉树等
1.1.4递归的重要性
1.2.1定义:在数学与计算机科学中,递归是指在函数的定义中使用函数自身的方法。递归,顾名思义,包含两个意思:“递”和“归”,这正是递归思想的精华所在。
1.2.2递归思想的内涵:将规模大的问题转化为规模小的相似的子问题来解决。格外重要的是,这个解决问题的函数必须有明确的结束条件,否则就会导致无限递归的情况。
1.2.3递归过程的流程图如下:
两类流程图样式不同,但都精简的表达出了递归的思想。
1.3.1定义是递归的(分治法)
1.3.2数据结构是递归的
1.3.3问题的解法是递归的:n阶Hanoi问题、八皇后问题、迷宫问题。
2.1.1归纳法适用于想解决的一个问题可转化为解决它的子问题,而它的子问题又可变成子问题的子问题,也就是说存在相同的逻辑归纳处理项。
2.1.2递归的基本思想就是把规模大的问题转化为规模小的相似的子问题来解决。即,递归的数学模型就是归纳法。
2.2.1明确递归终止条件
递归就是有去有回,必然有一个明确的临界点,程序一旦到了这个临界点,就不用继续往下递去而是开始归来。防止无限递归
2.2.2给出递归终止的处理方法
一般的,在这种情况下,问题的解决方案是直观的、容易的。
2.2.3提取重复的逻辑,缩小问题规模
从程序实现的角度而言,我们需要抽象出一个干净利落的重复的逻辑,以便使用相同的方式解决子问题。
2.3.1模型一:在递去的过程中解决问题
function recursion(大规模){
if (end_condition){ // 明确的递归终止条件
end; // 简单情景
}else{ // 在将问题转换为子问题的每一步,解决该步中剩余部分的问题
solve; // 递去
recursion(小规模); // 递到最深处后,不断地归来
}
}
2.3.2模型二:在归来的过程中解决问题
function recursion(大规模){
if (end_condition){ // 明确的递归终止条件
end; // 简单情景
}else{ // 先将问题全部描述展开,再由尽头“返回”依次解决每步中剩余部分的问题
recursion(小规模); // 递去
solve; // 归来
}
}
2.4.1优雅性:使用递归法,只需少量程序可描述出解题过程,大大减少了程序的代码量,而且很好理解。递归的能力在于用有限的语句来定义对象的无限集合。
2.4.2反向性:由于递归调用程序需要维护栈,而栈具有后进后出的特征,因此递归程序适合满足取反类需求。
2.4.3递推关系:递归程序可以明显的发现递推关系,即具有递推关系的问题基本上都可以通过递归求解。
递归与循环是两种不同的解决问题的典型思路。
2.5.1递归较为直白地描述了一个问题的求解过程,循环和递归具有相同的特性,即重复做任务,但有时使用循环的算法并不会较为清晰地描述解决问题的步骤。
2.5.2从算法设计上看,递归与循环并无优劣之分。然而在实际开发中,由于函数调用的开销,递归常常会带来性能问题,特别是在求解规模不确定的情况下;而循环由于没有函数调用开销,效率会更高。
2.5.3问题的递归实现转换成非递归实现一般需要两步:
3.1.1首先从一般的直接调用一次和n次子程序的形式讨论
3.1.2讨论值传回方式
3.1.3子程序调用的内部操作
·返回地址进栈,开辟子程序的局部变量空间
·为子程序准备数据,即将实参赋值给形参
·指令流转入子程序入口
·将变参或函数的值保存到回传变量中
·从栈顶取出返回地址
·按地址返回
·将回传变量中的值传送给相应的变量或位置上
3.2.1递归函数在函数的执行过程中,需多次进行自我调用。为探索递归函数的执行过程,需看任意两个函数之间进行调用的情形。
3.2.2与汇编语言程序设计中主程序和子程序之间的链接及信息交换类似,在高级语言编制的程序中,调用函数和被调用函数之间的链接及信息交换需通过栈来进行。
3.2.3通常,一个函数在运行期间调用另一个函数时,在运行被调用函数之间,系统需先完成3件事,此过程与子程序的实现原理类似。
而从被调用函数返回函数之前,系统也应该完成3件事:
3.2.4当有多个函数构成嵌套时,按照“后调用先返回”的原则,上述函数之间的信息传递和控制转移必须通过“栈”来实现。系统将整个程序运行时所需的数据空间安排在一个栈中。每调用一个函数,就为它在栈顶分配一个存储区,每从一个函数退出,就释放它的存储区。如此,当前正运行的函数的数据区必在栈顶。
*代码实例:
图中所示的主函数main()中调用了函数first(),而在函数first()中又调用了函数second()。此时second()函数的参数位于栈顶,当second()函数退出之后正执行函数first()中某个语句时,first()函数的参数位于栈顶。
3.2.5递归工作栈与“活动记录”
(1)递归函数的运行过程类似于多个函数的嵌套调用,只是调用函数和被调用函数是同一个函数,因此,和调用相关的一个重要概念是递归函数运行的“层次”。假设调用该递归函数的主函数为第0层,则从主函数调用递归函数为进入第1层;从第i层递归调用奔波函数为进入“下一层”,即i+1层。反之,退出第i层递归应返回至“上一层”,即i-1层。为了保证递归函数正确执行,系统需设立一个“递归工作栈”作为整个递归函数运行期间使用的数据存储区。
(2)每一层递归所需信息构成一个工作记录,其中包括所有的实参、所有的局部变量,以及上一层的返回地址。每进入一层递归,就产生一个新的工作记录并将其压入栈顶。每退出一层递归,就从栈顶弹出一个工作记录,则当前执行层的工作记录必是递归工作栈栈顶的工作记录,称这个记录为“活动记录”。
3.2.6代码实例:
递归流程图如下
主函数执行后依次启动了5个函数调用。主函数外部调用fact(4)的活动记录在栈底,fact(1)调用fact(0)进栈的活动记录在栈顶。
递归结束条件出现于函数fact(0)的内部,执行fact(0)引起了返回语句的执行。退出栈顶的活动记录,返回地址到上一层fact(1)的递归处,继续执行语句temp=1*1,接着执行return temp又引起新的退栈操作。此退栈过程直至fact(4)执行完毕后,将控制权转移给主函数为止。
3.3.1时间复杂度分析
在算法分析中,当一个算法中包含递归调用时,其时间复杂度的分析可以转化为一个递归方程求解。迭代法是求解递归方程的一个常用方法,基本步骤是迭代地展开递归方程的右端,使之成为一个非递归的和式,然后通过对和式的估计来达到对方程左端(方程的解)的估计。
以阶乘函数fact()为例,使用迭代法求解递归方程:设fact()的执行时间为T(n)。此递归函数中语句if(n==0) return 1;的执行时间为O(1).递归调用fact(n-1)的执行时间为T(n-1),所以else return*fact(n-1);的执行时间是O(1)+T(n-1).其中,设两数相乘和赋值操作的执行时间为O(1),则对常数C、D有如下递归方程:
T(n)=D,n=0;T(n)=C+T(n-1),n>=1;
展开类推后可求得递归方程的解:T(n)=O(n)
3.3.2空间复杂度分析
由上分析知:递归函数在执行时,系统需设立一个“递归工作栈”存储每一层递归所需的信息,此工作栈是递归函数的辅助空间,因此,分析递归算法的空间复杂度需要分析工作栈的大小。对于递归算法,空间复杂度S(n)=O(f(n));其中,f(n)为“递归工作栈”中工作记录的个数与问题规模的函数关系。
4.1.1广义的递归
4.1.2智能与广义递归
智能作为理解其他现象的工具,相对而言更加易于用广义递归解释。智能能够在一定程度上脱离具体的特定的现象。智能就像一个初始值或一条途径,若我们掌握了智能的规律,或许只需要用相对简单和纯粹的广义递归就能创造优秀的智能,再由它用广义递归解释其他的现象。换句话说,智能的本质就是递归。
4.2.1递归神经网络简介
(3)递归神经网络具有灵活的拓扑结构且权重共享,适用于包含结构关系的机器学习任务,在自然语言处理领域具有重要作用。
4.2.2 递归神经网络(RNN)
(1)递归神经网络(RNN)基本结构
上图左侧为递归神经网络的原始结构,即简单“输入层=>隐藏层=>输出层”的三层结构。
(2)大多数使用RNN的时候,一般指代的是时间递归神经网络(Recursive Neural Network)。其处理的对象是一种时间序列数据,它将数据信息流以一种循环的方式进行传递处理。
I术语:当前时间步t的输入为xt,它的前缀序列输入数据为(x1,x2,……,xt-1)。
II 持续性:由于时间序列信息,前后数据不是相互独立,当前阶段的输出不仅和当前的输入有关,还会受到过去的决策影响。
III 记忆性:RNN可以保留序列的记忆信息。对于当前时刻t,上一个时刻的状态表示为Ht-1,可以说St-1编码记录了Xt的前缀序列数据的信息,即所谓的记忆性。
4.2.3递归算法在递归神经网络中的应用与体现
(1)由于神经网络的输入层单元个数是固定的,因此必须用循环或者递归的方式来处理长度可变的输入;循环神经网络实现了前者,通过将长度不定的输入分割为等长度的小块,然后再依次的输入到网络中,从而实现了神经网络对变长输入的处理。
(2)递归神经网络的输入是两个子节点,输出则是将这两个子节点编码后产生的父节点,父节点的维度和每个子节点是相同的。子节点和父节点组成一个全连接神经网络,即子节点的每个神经元都和父节点的每个神经元两两相连。然后,我们把产生的父节点的向量和其他子节点的向量再次作为网络的输入,再次产生它们的父节点。如此递归下去,直至整棵树处理完毕,最终得到根节点的向量,将其看成整棵树的表示。
(3)递归神经网络在网络中出现环形结构,可让一些神经元的输出反馈回来作为输入信息。使得网络在t时刻的输出状态不仅与t时刻的输入有关,还与t-1时刻的网络状态有关,从而能处理与时间有关的动态变化
4.3.1人工生命与混沌系统、分形几何
4.3.2作为人工智能的一大分支,人工生命靠算法的递归来形成其“生命”的演化,人工生命中的递归操作对应于真实生命中的细胞分裂和自我复制。足以可见,人工生命的“生命力”,全靠递归,它体现了递归的威力。