Python-函数递归
一、递归的定义
函数的递归调用:是函数嵌套调用的一种特殊形式,具体是指在调用一个函数的过程中又直接或者间接地调用到函数本身。
直接调用本身:
def f1():
print('是我是我还是我')
f1()
f1()
间接调用本身:
def f1():
print('===>f1')
f2()
def f2():
print('===>f2')
f1()
f1()
像上面两种情况,会无限循环调用下去,导致死循环。所以Python对函数循环调用的次数做了要求,最大为1000次。虽然支持可以更改最大循环调用次数,但最好不要。
所以递归的本质就是循环
至此,我们知道了一段代码的循环运行方式有两种
方式一:while、for循环
while True:
print(1111)
print(2222)
print(3333)
方式二:递归
def f1():
print(1111)
print(2222)
print(3333)
f1()
f1()
需要强调的的一点是:递归调用不应该无限地调用下去,必须在满足某种条件下结束递归调用(使用return)。
n=0
while n < 10:
print(n)
n+=1
def f1(n):
if n == 10:
return
print(n)
n+=1
f1(n)
f1(0)
二、递归调用的两个阶段[参考文章]
举例说明:
某公司四个员工坐在一起,问第四个人薪水,他说比第三个人多1000,问第三个人薪水,第三个人他说比第二个人多1000,问第二个人薪水,他说比第一个人多1000,最后第一人说自己每月5000,请问第四个人的薪水是多少?
要知道第四个人的月薪,就必须知道第三个人的,第三个人的又取决于第二个人的,第二个人的又取决于第一个人的,而且每一个员工都比前一个多一千,数学表达式即:
salary(4) = salary(3) + 1000
salary(3) = salary(2) + 1000
salary(2) = salary(1) + 1000
salary(1) = 5000
总结为:
salary(n) = salary(n-1) + 1000 (n>1)
salary(1) = 5000 (n=1)
在回溯阶段:要得到第n个员工的薪水,需要回溯得到第(n-1)个员工的薪水,以此类推,直到得到第一个员工的薪水。
此时,salary(1)已知(满足结束条件),因而不必再向前回溯了。
然后进入递推阶段:从第一个员工的薪水可以推算出第二个员工的薪水(6000),从第二个员工的薪水可以推算出第三个员工的薪水(7000),以此类推,一直推算出第第四个员工的薪水(8000)为止,递归结束。
需要注意的一点是,递归一定要有一个结束条件,这里n=1就是结束条件。
图解分析:
在未满足n==1的条件时,一直进行递归调用,即一直回溯,见图解分析的左半部分。
而在满足n==1的条件时,终止递归调用,即结束回溯。从而进入递推阶段,依次推导直到得到最终的结果。
代码实现:
def salary(n):
if n == 1:
return 5000
return salary(n - 1) + 1000
print(salary(4)) # 8000
三、函数递归调用的案例
有一个嵌套多层的列表,要求打印出所有的元素。
l = [1, [2, [3, [4, [5, [6, [7, [8, 9]]]]]]]]
def f1(list1):
for x in list1:
if type(x) is list:
# 如果是列表,应该再循环、再判断,即重新运行本身的代码
f1(x)
else:
print(x)
f1(l)
二分法
需求:有一个按照从小到大顺序排列的数字列表,需要从该数字列表中找到我们想要的那个一个数字,如何做更高效?
nums = [-3, 4, 7, 10, 13, 21, 43, 77, 89]
find_num = 10
方案一:整体遍历(效率太低)
for num in nums:
if num == find_num:
print('find it')
break
方案二:二分法
思路:将查找的目标值与列表中间值进行比较,若目标值比中间值大,则在中间值的右边部分继续找;若目标值比中间值小,则在中间值的左边部分继续找。
利用这种方法,每次都与中间值进行比较来确定范围,会大大减少程序运行时间。
def binary_search(find_num, 列表):
mid_val = 找列表中间的值
if find_num > mid_val:
# 接下来的查找应该是在列表的右半部分
列表 = 列表切片右半部分
binary_search(find_num, 列表)
elif find_num < mid_val:
# 接下来的查找应该是在列表的左半部分
列表 = 列表切片左半部分
binary_search(find_num, 列表)
else:
print('find it')
列表中间值怎么找?==》找中间索引
mid_index = len(l) // 2 # 长度除2取整
mid_value = l[mid_index]
# 无关于列表的元素个数是奇数还是偶数,目的至是为了找一个值来对整体列表进行切割。
代码实现
nums = [-3, 4, 7, 10, 13, 21, 43, 77, 89]
find_num = 10
def binary_search(find_num, l):
print(l)
if len(l) == 0:
print('找的值不存在')
return
mid_index = len(l) // 2
if find_num > l[mid_index]:
# 接下来的查找应该是在列表的右半部分
l = l[mid_index + 1:] # 顾头不顾尾原则,和l[mid_index]已经比较过了,没有保留它的必要。
binary_search(find_num, l)
elif find_num < l[mid_index]:
# 接下来的查找应该是在列表的左半部分
l = l[:mid_index]
binary_search(find_num, l)
else:
print('find it')
binary_search(find_num, nums)
# [-3, 4, 7, 10, 13, 21, 43, 77, 89]
# [-3, 4, 7, 10]
# [10]
# find it
特殊情况:找一个不存在的数
nums = [-3, 4, 7, 10, 13, 21, 43, 77, 89]
find_num = 99
def binary_search(find_num, l):
print(l)
if len(l) == 0:
print('找的值不存在')
return
mid_index = len(l) // 2
if find_num > l[mid_index]:
# 接下来的查找应该是在列表的右半部分
l = l[mid_index + 1:] # 顾头不顾尾原则,和l[mid_index]已经比较过了,没有保留它的必要。
binary_search(find_num, l)
elif find_num < l[mid_index]:
# 接下来的查找应该是在列表的左半部分
l = l[:mid_index]
binary_search(find_num, l)
else:
print('find it')
binary_search(find_num, nums)
# [-3, 4, 7, 10, 13, 21, 43, 77, 89]
# [21, 43, 77, 89]
# [89]
# []
# 找的值不存在
如果列表元素不是由小到大排列的呢?
使用sort方法让列表元素按照由小到大的顺序排列。
nums = [-3, 4, 1, 5, 2, 0, 1, 3]
nums.sort()
print(nums) # [-3, 0, 1, 1, 2, 3, 4, 5]