函数递归

一 函数递归调用介绍

函数不仅可以嵌套定义,还可以嵌套调用,即在调用一个函数的过程中,函数内部又调用另一个函数,而函数的递归调用指的是在调用一个函数的过程中又直接或间接地调用该函数本身

例如

在调用f1的过程中,又调用f1,这就是直接调用函数f1本身

def f1():
    print('from f1')
    f1()
f1()

配图:递归调用1

img

在调用f1的过程中,又调用f2,而在调用f2的过程中又调用f1,这就是间接调用函数f1本身

def f1():
    print('from f1')
    f2()

def f2():
    print('from f2')
    f1()
    
f1()

img

从上图可以看出,两种情况下的递归调用都是一个无限循环的过程,但在python对函数的递归调用的深度做了限制,因而并不会像大家所想的那样进入无限循环,会抛出异常,要避免出现这种情况,就必须让递归调用在满足某个特定条件下终止。

提示:

#1. 可以使用sys.getrecursionlimit()去查看递归深度,默认值为1000,虽然可以使用
sys.setrecursionlimit()去设定该值,但仍受限于主机操作系统栈大小的限制

#2. python不是一门函数式编程语言,无法对递归进行尾递归优化。

二 回溯与递推

下面我们用一个浅显的例子,为了让读者阐释递归的原理和使用:

例4.5

某公司四个员工坐在一起,问第四个人薪水,他说比第三个人多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就是结束条件。

代码实现:

def salary(n):
    if n==1:
        return 5000
    return salary(n-1)+1000

s=salary(4)
print(s)

执行结果:

8000

程序分析:

在未满足n1的条件时,一直进行递归调用,即一直回溯。而在满足n1的条件时,终止递归调用,即结束回溯,从而进入递推阶段,依次推导直到得到最终的结果。

递归本质就是在做重复的事情,所以理论上递归可以解决的问题循环也都可以解决,只不过在某些情况下,使用递归会更容易实现,比如有一个嵌套多层的列表,要求打印出所有的元素,代码实现如下

items=[[1,2],3,[4,[5,[6,7]]]]
def foo(items):
    for i in items:
        if isinstance(i,list): #满足未遍历完items以及if判断成立的条件时,一直进行递归调用
            foo(i) 
        else:
            print(i,end=' ')
    
foo(items) #打印结果1 2 3 4 5 6 7

使用递归,我们只需要分析出要重复执行的代码逻辑,然后提取进入下一次递归调用的条件或者说递归结束的条件即可,代码实现起来简洁清晰

三 算法之二分法

算法是解决问题的高效率方法,并都是算数方法
二分法: 容器类型里面的数字必须是有大小顺序

target_num = 666
def get_num(l,target_num):
    if not l:
        print('你给的工资 这个任务怕是没法做')
        return
    # 获取列表中间的索引
    print(l)
    middle_index = len(l) // 2
    # 判断target_num跟middle_index对应的数字的大小
    if target_num > l[middle_index]:
        # 切取列表右半部分
        num_right = l[middle_index + 1:]
        # 再递归调用get_num函数
        get_num(num_right,target_num)
    elif target_num < l[middle_index]:
        # 切取列表左半部分
        num_left = l[0:middle_index]
        # 再递归调用get_num函数
        get_num(num_left, target_num)
    else:
        print('find it',target_num)

get_num(l,target_num)

四 三元表达式

def my_max(x,y):
    if x > y:
        return x
    else:
        return y
"""
当x大的时候返回x当y大的时候返回y
当某个条件成立做一件事,不成立做另外一件事
"""
x = 99999
y = 9898898

"""
三元表达式固定表达式
    值1 if 条件 else 值2
        条件成立 值1
        条件不成立 值2
"""
x = 1
y = 2
m = 3
n = 4
res = x if x > y else (m if m >n else (...))

三元表达式的应用场景只推荐只有两种的情况的可能下
is_free = input('请输入是否免费(y/n)>>>:')
is_free = '免费' if is_free == 'y' else '收费'
print(is_free)


username = input('username>>>:')
res = 'NB' if username == '攀少' else '垃圾'
print(res)

五 列表生成式

l = ['tank','nick','oscar','sean']
l1 = []
for name in l:
    l1.append('%s_sb'%name)
    l1.append(name + '_sb')  # 不推荐使用
print(l1)

l = ['tank_sb', 'nick_sb', 'oscar_sb', 'sean_sb','jason_NB']
列表生成式
res = ['%s_DSB'%name for name in l]
print(res)

res = [name for name in l if name.endswith('_sb')]  # 后面不支持再加else的情况for循环依次取出列表里面的每一个元素
然后交由if判断  条件成立才会交给for前面的代码
如果条件不成立 当前的元素 直接舍弃
print(res)

六 字典生成式

l1 = ['name','password','hobby']
l2 = ['jason','123','DBJ','egon']

d = {}
for i,j in enumerate(l1):
    d[j] = l2[i]
print(d)


l1 = ['jason','123','read']
d = {i:j for i,j in enumerate(l1) if j != '123'}
print(d)

res = {i for i in range(10) if i != 4}
print(res)
res1 = (i for i in range(10) if i != 4)  # 这样写不是元组生成式 而是生成器表达式
print(res1)
for i in res1:
    print(i)

七 匿名函数

7.1 匿名函数与lambda

对比使用def关键字创建的是有名字的函数,使用lambda关键字创建则是没有名字的函数,即匿名函数,语法如下

lambda 参数1,参数2,...: expression

举例

# 1、定义
lambda x,y,z:x+y+z

#等同于
def func(x,y,z):
    return x+y+z

# 2、调用
# 方式一:
res=(lambda x,y,z:x+y+z)(1,2,3)

# 方式二:
func=lambda x,y,z:x+y+z # “匿名”的本质就是要没有名字,所以此处为匿名函数指定名字是没有意义的
res=func(1,2,3)

匿名函数与有名函数有相同的作用域,但是匿名意味着引用计数为0,使用一次就释放,所以匿名函数用于临时使用一次的场景,匿名函数通常与其他函数配合使用,我们以下述字典为例来介绍它

salaries={
    'siry':3000,
    'tom':7000,
    'lili':10000,
    'jack':2000
}

要想取得薪水的最大值和最小值,我们可以使用内置函数max和min(为了方便开发,python解释器已经为我们定义好了一系列常用的功能,称之为内置的函数,我们只需要拿来使用即可)

>>> max(salaries)
'tom'
>>> min(salaries)
'jack'

内置max和min都支持迭代器协议,工作原理都是迭代字典,取得是字典的键,因而比较的是键的最大和最小值,而我们想要的是比较值的最大值与最小值,于是做出如下改动

# 函数max会迭代字典salaries,每取出一个“人名”就会当做参数传给指定的匿名函数,然后将匿名函数的返回值当做比较依据,最终返回薪资最高的那个人的名字
>>> max(salaries,key=lambda k:salaries[k]) 
'lili'
# 原理同上
>>> min(salaries,key=lambda k:salaries[k])
'jack'

同理,我们直接对字典进行排序,默认也是按照字典的键去排序的

>>> sorted(salaries)
['jack', 'lili', 'siry', 'tom']
7.2 map、reduce、filter

函数map、reduce、filter都支持迭代器协议,用来处理可迭代对象,我们以一个可迭代对象array为例来介绍它们三个的用法

array=[1,2,3,4,5]

要求一:对array的每个元素做平方处理,可以使用map函数
map函数可以接收两个参数,一个是函数,另外一个是可迭代对象,具体用法如下

>>> res=map(lambda x:x**2,array)
>>> res
<map object at 0x1033f45f8>
>>> 

解析:map会依次迭代array,得到的值依次传给匿名函数(也可以是有名函数),而map函数得到的结果仍然是迭代器。

>>> list(res) #使用list可以依次迭代res,取得的值作为列表元素
[1, 4, 9, 16, 25]

要求二:对array进行合并操作,比如求和运算,这就用到了reduce函数
reduce函数可以接收三个参数,一个是函数,第二个是可迭代对象,第三个是初始值

# reduce在python2中是内置函数,在python3中则被集成到模块functools中,需要导入才能使用
>>> from functools import reduce 
>>> res=reduce(lambda x,y:x+y,array)
>>> res
15

解析:
1 没有初始值,reduce函数会先迭代一次array得到的值作为初始值,作为第一个值数传给x,然后继续迭代一次array得到的值作为第二个值传给y,运算的结果为3

2 将上一次reduce运算的结果作为第一个值传给x,然后迭代一次array得到的结果作为第二个值传给y,依次类推,知道迭代完array的所有元素,得到最终的结果15

也可以为reduce指定初始值

>>> res=reduce(lambda x,y:x+y,array,100)
>>> res
115

要求三:对array进行过滤操作,这就用到了filter函数,比如过滤出大于3的元素

>>> res=filter(lambda x:x>3,array)

解析:filter函数会依次迭代array,得到的值依次传给匿名函数,如果匿名函数的返回值为真,则过滤出该元素,而filter函数得到的结果仍然是迭代器。

>>> list(res) 
[4, 5]

提示:我们介绍map、filter、reduce只是为了带大家了解函数式编程的大致思想,在实际开发中,我们完全可以用列表生成式或者生成器表达式来实现三者的功能。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值