递归
通常我们创建一个函数是为了在其他地方进行调用或在函数内调用其他函数,但有一种特殊情况是,函数也可以调用自身,这就是递归的概念。
递归式函数没有严格的格式定义,最主要的特征在于其结果返回函数自身或有关自身的一个表达式。其定义大概如下:
def func1():
<函数体>
return func1()
显然,如果不对递归函数进行一些条件约束,理论上就会陷入无限循环。为避免这种情况,需要为递归函数增加递归条件和基线条件。
递归条件:将递归问题逐步减小问最小问题(基线条件)的条件,通常包含一个或多个调用。
基线条件:递归内的最小问题,满足该条件即结束递归,通常返回一个值。
案例一:斐波那契数列
在斐波那契数列中,第二项之后的任一项都是其前两项之和,比如一个7项的斐波那契数列为[1, 1, 2, 3, 5, 8, 13]。现在创建一个函数通过递归的方式,根据函数输入的正整数项来生成对应长度的斐波那契数列:
def Fibonacci(n, k = [1, 1]):
if (n-2) > 0:
k.append(k[-1] + k[-2])
n = n - 1
return Fibonacci(n, k)
else:
return k
print(Fibonacci(6)) #输出结果为[1, 1, 2, 3, 5, 8]
在定义的函数Fibonacci中有两个参数,其中必需参数n为数列的目标项数,默认参数k为初始数列。因为每次递归都需要输入上一次递归的数列结果,所以函数需要参数k。在进行第一次递归时需要有两个初始值的数列,否则无法进行相加操作,所以在此将参数k设为具有初始值[1, 1]的默认参数。
在调用该函数时只需要给出第一个参数n的值,第二个参数仅用于每次递归的内部传递和最后递归完成时的返回值。
在代码中,if语句中的内容即为其递归条件,将问题逐步减小为基线条件(在这里体现为减小n的值),并再次调用自身。
else语句中的内容为基线条件,即当n=0时的情况,此时数列中的项数已和目标项数保持一致,满足最小问题,结束递归。
案例二:二分法查找
二分法是一种非常经典的查找算法,一种常见的情况是猜数游戏。对方心里想一个1~100之间的数字让我们来猜是哪一个。如果完全随机地一个一个猜,最差的情况肯定是第100次才猜对,但如果采用二分法,其实最多只需要猜7次。
首先问对方:这个数大于50吗?如果是,那么再问:这个输大于75吗?类似这般每次将问题可能的区间减半直到猜对答案,这便是二分法的实现方法。
现在将二分法应用在递归式函数中,函数的参数需要搜索序列、上区间、下区间、目标结果这四个参数。
继续回顾刚才的定义,该函数的基线条件为上区间和下区间相同,即找到了目标数值,满足该条件时结束递归并返回该值。递归条件为,找到区间的中间位置,再确定是在左半位置还是右半位置,然后继续在对应位置查找。
实现二分查找的代码如下:
def binary_search(list1, target, upper, lower):
if upper == lower:
return upper
else:
middle = (upper + lower)//2
if target > middle:
return binary_search(list1, target, upper, middle+1)
else:
return binary_search(list1, target, middle, lower)
print(binary_search(range(1,101), 59, 100, 1)) #输出结果为59
上述代码虽然实现了二分法查找的功能,但缺乏一些实用性,比如输入查找59输出也是59,好像什么也没发生一样,所以对代码进行如下修改:
def binary_search(list1, target, upper=None, lower=0, n=0):
if upper == None: #如果没有指定上下界,默认从列表边界开始搜索
upper = len(list1) - 1
return binary_search(list1, target, upper, lower, n)
else:
if list1[upper] == list1[lower]:
return upper, n
else:
middle = (upper + lower) // 2
if target > list1[middle]:
return binary_search(list1, target, upper, middle+1, n+1)
else:
return binary_search(list1, target, middle, lower, n+1)
print(binary_search(range(1,101), 59)) #输出结果为(58, 7)
首先对函数的参数进行了一些修改,upper和lower在函数中由原来的数值变成了列表的索引,并增加了默认值并在开头增加了一个判断条件,这样如果调用函数时没有给出搜索上下界,就会默认从列表的第一个和最后一个索引为上下界开始搜索。
此外还增加了一个参数n来表示搜索次数,调用时不需要给入参数。经过修改后,函数最终输出目标值在列表中的索引和搜索次数n。
经过修改后的代码会输出目标值在列表中的索引和搜索次数,由于采用了索引,所以对不等差的顺序列表也能实现二分查找的功能。如果对完全无序的数字列表就需要先用sort()函数先进行排序。