一、递归
1、递归定义
在定义一个过程或函数时出现调用本过程或本函数的成分,称之为递归。
若调用自身,称之为直接递归。
若过程或函数A调用过程或函数B,而B又调用A,称之为间接递归。
在算法设计中,任何间接递归算法都可以转换为直接递归算法来实现,所以主要讨论直接递归。
如果一个递归过程或递归函数中递归调用语句是最后一条执行语句,称为尾递归。
递归算法通常通常把一个大的复杂问题层层转化为一个或多个与原问题相似的规模较小的问题来求解。
递归策略只需少量的代码就可以描述出解题过程所需要的多次重复计算,大大减少了算法的代码量。
(1)、解决递归问题满足的三个条件:
需要解决的问题可以转化为一个或多个子问题来求解,而这些子问题的求解方法与原问题完 全相同,只是在数量规模上不同。
递归调用的次数必须是有限的。
必须有结束递归的条件来终止递归。
2、何时使用递归:
(1)、定义是递归的
有许多数学公式、数列等的定义是递归的。例如,求n!和Fibonacci(斐波那契)数列等。
def Fib(n): #求Fibonacci数列的第n项
if n==1 or n==2:
return 1
else:
return Fib(n-1)+Fib(n-2)
(2)数据结构是递归的:单链表
class LinkNode: #单链表结点类
def __init__(self,data=None): #构造函数
self.data=data #dat属性
self.next=None #next属性
eg: 求一个不带头结点单链表p中所有data成员(假设为int型)之和。
def Sum(p): #求不带头结点单链表p所有结点值之和
if p==None:
return 0
else:
return p.data+Sum(p.next)
(3)、问题的求解方法是递归的:Hanoi
def Hanoi(n,X,Y,Z): #Hanoi递归算法
if n==1: #只有一个盘片的情况
print("将第%d个盘片从%c移动到%c" %(n,X,Z))
else: #有两个或多个盘片的情况
Hanoi(n-1,X,Z,Y)
print("将第%d个盘片从%c移动到%c" %(n,X,Z))
Hanoi(n-1,Y,X,Z)
3、递归模型:递归模型是递归算法的抽象,它反映一个递归问题的递归结构。
def fun(n):
if n==1: #语句1
return 1 #语句2
else: #语句3
return fun(n-1)*n #语句4
一般地,一个递归模型是由递归出口和递归体两部分组成。
递归出口确定递归到何时结束,即指出明确的递归结束条件。
递归体确定递归求解时的递推关系。
4、递归的执行过程
求f(sn)的分解过程如下:
eg:求5!
5、系统内部如何执行递归算法
一个递归函数的调用过程类似于多个函数的嵌套的调用,只不过调用函数和被调用函数是同一个函数。 为了保证递归函数的正确执行,系统需设立一个工作栈。
(1)执行开始时,首先为递归调用建立一个工作栈,其结构包括值参、局部变量和返回地址。
(2)每次执行递归调用之前,把递归函数的值参和局部变量的当前值以及调用后的返回地址进栈。
(3)每次递归调用结束后,将栈顶元素出栈,使相应的值参和局部变量恢复为调用前的值,然后转向返回地址指定的位置继续执行。
用递归算法的形参值表示状态,由于递归算法执行中系统栈保存了递归调用的值参、局部变量和返回地址。
所以在递归算法中一次递归调用后会自动恢复该次递归调用前的状态。
def f(n): #递归函数
if (n==0): #递归出口
return
else: #递归体
print("Pre: n=%d" %(n))
print("执行f(%d)" %(n-1))
f(n-1)
print("Post: n=%d" %(n))
def f(n): #递归函数
if (n==0): #递归出口
return
else: #递归体
print("Pre: n=%d" %(n))
print("执行f(%d)" %(n-1))
f(n-1)
print("Post: n=%d" %(n))
6、Python中递归函数的参数
Python递归函数中的参数分为可变类型和不可变类型。
不可变类型的参数保存在系统栈中,具有自动回退。
可变类型的参数类似全局变量,不具有自动回退的功能。
eg:id()返回对象的唯一身份标识(相当于对象地址)
def fun(i,lst):
print(id(i),id(lst))
if i>=0:
print(lst[i],end=' ')
fun(i-1,lst)
#主程序
L=[1,2,3]
fun(len(L)-1,L)
eg: 可以直接用全局变量lst替代形参lst
lst=[1,2,3]
def fun(i):
print(id(i),id(lst))
if i>=0:
print(lst[i],end=' ')
fun(i-1)
#主程序
fun(len(lst)-1)
7、递归算法的时空分析
递归算法执行过程不同于非递归算法,所以其时空分析也不同于非递归算法。
非递归算法分析是定长时空分析。
递归算法分析就是变长时空分析。
(1)、递归算法的时间分析
def Hanoi(n,X,Y,Z): #Hanoi递归算法
if n==1: #只有一个盘片的情况
print("将第%d个盘片从%c移动到%c" %(n,X,Z))
else: #有两个或多个盘片的情况
Hanoi(n-1,X,Z,Y)
print("将第%d个盘片从%c移动到%c" %(n,X,Z))
Hanoi(n-1,Y,X,Z)
设大问题Hanoi(n,x,y,z)的执行时间为T(n),则小问题Hanoi(n-1,x,y,z)的执行时间为T(n-1)。递推式:
(2)、 递归算法的空间分析
def Hanoi(n,X,Y,Z): #Hanoi递归算法
if n==1: #只有一个盘片的情况
print("将第%d个盘片从%c移动到%c" %(n,X,Z))
else: #有两个或多个盘片的情况
Hanoi(n-1,X,Z,Y)
print("将第%d个盘片从%c移动到%c" %(n,X,Z))
Hanoi(n-1,Y,X,Z)
设大问题Hanoi(n,x,y,z)的占用空间为S(n),则小问题Hanoi(n-1,x,y,z)的占用空间为S(n-1)。递推式:
二、 递归算法的设计
1、递归算法设计的步骤
eg:采用递归算法求整数数组a[0..n-1]中的最小值。
解:假设f(a,i)求数组元素a[0..i](共i+1个元素)中的最小值。
当i=0时,有f(a,i)=a[0]。
假设f(a,i-1)已求出,显然有f(a,i)=MIN(f(a,i-1),a[i]),其中MIN()为求两个值较小值函数。
def Min(a,i): #求a[0..i]中的最小值
if i==0: #递归出口
return a[0]
else: #递归体
min=Min(a,i-1)
if (min>a[i]): return a[i]
else: return min
2、基于递归数据结构的递归算法设计
eg:假设有一个不带头结点的单链表p,完成以下两个算法设计:
(1)设计一个算法正向输出所有结点值。
(2)设计一个算法反向输出所有结点值。
def Positive(p): #正向输出所有结点值
if p==None:
return
else:
print("%d" %(p.data),end=' ')
Positive(p.next)
def Reverse(p): #反向输出所有结点值
if p==None:
return
else:
Reverse(p.next)
print("%d" %(p.data),end=' ')
def Positive(p): #正向输出所有结点值
if p==None:
return
else:
print("%d" %(p.data),end=' ')
Positive(p.next)
def Reverse(p): #反向输出所有结点值
if p==None:
return
else:
Reverse(p.next)
print("%d" %(p.data),end=' ')
3、基于归纳方法的递归算法设计
通过对求解问题的分析归纳来转换成递归方法求解(如皇后问题等)。 关键是对问题本身进行分析,确定大、小问题解之间的关系,构造合理的递归体,而其中最重要的又是假设出“合理”的小问题。
eg:若算法pow(x,n)用于计算xn(n为大于1的整数)。完成以下任务:
(1)采用递归方法设计pow(x,n)算法。
(2)问执行pow(x,10)发生几次递归调用?求pow(x,n)对应的算法复杂度是多少?
解:(1)设f(x,n)用于计算xn,则有以下递归模型:
def pow(x,n): #求x的n次幂
if n==1:
return x;
p=pow(x,n//2)
if n%2==1:
return x*p*p #n为奇数
else:
return p*p #n为偶数
(2)执行pow(x,10)的递归调用顺序是: pow(x,10) → pow(x,5) → pow(x,2) → pow(x,1) 共发生4次递归调用。 求pow(x,n)对应的算法复杂度是O(log2n)。