程序调用自身的编程技巧称为递归( recursion)。
简单来说,循环是有去无回,而递归则是有去有回(因为存在终止条件)。
递归的能力在于用有限的语句来定义对象的无限集合。
构成递归需具备的条件:
- 子问题须与原始问题为同样的事,且更为简单;
2 不能无限制地调用本身,须有个出口,化简为非递归状况处理。
某人的双亲是他的祖先(基本情况)。某人祖先的双亲同样是某人的祖先(递归步骤)。斐波纳契数列(Fibonacci Sequence),又称黄金
分割数列,指的是这样一个数列:1、1、2、3、5、8、13、21…
斐波那契弧线,也称为斐波那契扇形线。
斐波那契数列中的斐波那契数会经常出现在我们的眼前——比如松果、凤梨、树叶的排列、某些花朵的花瓣数(典型的有向日葵花瓣),蜂
巢,蜻蜓翅膀,超越数e(可以推出更多),黄金矩形、黄金分割、等角螺线,十二平均律等。
def fib(n):
a, b = 1, 1
while a < n:
print(a, end=' ')
a, b = b, a+b
fib(100) #输出的是100以内的斐波那契数列
i = 1
j = 1
print(i,\n,j)
for x in range(2,100):
if x == i + j:
print(x)
j = i
i = x
递归算法一般用于解决三类问题:
(1)数据的定义是按递归定义的。(Fibonacci函数)
(2)问题解法按递归算法实现。
这类问题虽则本身没有明显的递归结构,但用递归求解比迭代求解更简单,如Hanoi问题。
(3)数据的结构形式是按递归定义的。
如二叉树、广义表等,由于结构本身固有的递归特性,则它们的操作可递归地描述。
递归的缺点:
递归算法解题相对常用的算法如普通循环等,运行效率较低。因此,应该尽量避免使用递归,除非没有更好的算法或者某种特定情况,递归
更为适合的时候。在递归调用的过程当中系统为每一层的返回点、局部量等开辟了栈来存储。递归次数过多容易造成栈溢出等。
递归工作栈
IA-32使用栈来支持过程的嵌套调用。每个过程都有自己的栈区,称为栈帧(stack frame) 。因此,一个栈由若干栈帧组成,每个栈帧用专
门的帧指针寄存器EBP指定起始位置,当前栈帧的范围在其和栈指针寄存器ESP指向区域之间。IA-32规定,寄存器EAX、ECX和EDX是调
用者保存寄存器。当过程P调用过程Q时,Q 可以直接使用这三个寄存器,不用将它们的值保存到栈中,这也意味着,如果P在从Q返回后还
要用这三个寄存器的话,P应在转到Q之前先保存它们的值,并在从Q返回后先恢复它们的值再使用。寄存器EBX、ESl、EDI是被调用者保
存寄存器,Q必须先将它们的值保存到栈中再使用它们,并在返回P之前先恢复它们的值。
(1)每次递归调用前,先将参数n~参数1按序复制到调用过程栈帧中
(2)执行call指令:首先将返回地址(call指令要执行时EIP的值,即call指令下一条指令的地址)压入栈顶,然后将程序跳转到当前调用的
方法的起始地址,相当于执行了push和jump指令。
递归调用时,每一层调用过程栈帧中存放的返回地址都是相同的。
(3)每次递归,必定要先push %ebp(把原帧指针保存在栈顶)和mov %esp,%ebp(把存放原帧指针的栈顶,设置为新栈底)
被调用者定义的非静态局部变量仅存放于当前栈帧,调用结束后就被释放了。
最后往往通过EAX寄存器将结果返回给调用者。
(4)执行leave指令:将栈指针指向帧指针,然后pop备份栈顶存放的原帧指针到EBP。
(5)最后执行ret指令:将栈顶的返回地址弹出到EIP,然后按照EIP此时指示的指令继续执行程序。
过程调用中使用的栈机制和寄存器使用约定,使得可以进行过程的嵌套调用和递归调用。
import os
def file_display(filepath):
for each in os.listdir(filepath):
# 得到文件的绝对路径:
absolute_path = os.path.join(filepath, each)
# 得到是否为文件还是目录的布尔值:
is_file = os.path.isfile(absolute_path)
if is_file:
# 当前的绝对路径为文件:
print(each)
else:
# 当前的绝对路径为目录:
file_display(absolute_path)
file_display('/home/pushy')
递归典型问题: 梵塔问题(汉诺塔问题)
已知有三根针分别用A, B, C表示,在A中从上到下依次放n个从小到大的盘子,现要求把所有的盘子从A针全部移到B针,移动规则是:可以
使用C临时存放盘子,每次只能移动一块盘子,而且每根针上不能出现大盘压小盘,找出移动次数最小的方案。
/*Name:HANOITOWER
*Description:solvethehanoitowerproblembyrecursion
*/
#include<stdio.h>
#include<stdlib.h>
/*movenplates:from-->to,
*thebuffercanbeusedifneeded*/
inthanoi(intn,charfrom,charbuffer,charto)
{
if(n==1)
{
/*movetheNO.1platedirectly:from-->to*/
printf("Moveplate#%dfrom%cto%c\n",n,from,to);
/*theNO.1plateismovedsoreturn*/
return0;
}
else
{
/*nplatestobemoved:from-->to,
*movethen-1platesabove:from-->buffer,
*givethistasktothenextrecursion*/
hanoi(n-1,from,to,buffer);
/*then-1platesaboveweremovedtobuffer,
*sotheNO.nplatecanbemoveddirectly*/
printf("Moveplate#%dfrom%cto%c\n",n,from,to);
/*howeverthen-1platesarestillinbuffer,
*movethemtotheterminalposition,
*(the"from"positionhasnoplate,&canbeoneso-calledbuffer)*/
hanoi(n-1,buffer,from,to);
/*thetaskgivenisdonesoreturn*/
return0;
}
}
intmain()
{
#defineN4
/*NplatesinA,let'smovethemtoC*/
hanoi(N,'A','B','C');
return0;
}
微信公众号“计算机基础学”关注我哟