递归函数[通俗易懂]

递归函数[通俗易懂]如果一个函数在内部调用自身,这个函数就叫做递归函数递归函数的简单定义如下:这只是一个简单的定义,什么也做不了。当然,你可以尝试会发生什么结果,理论上会永远运行下去,但实际操作时发现不一会儿程序就

大家好,又见面了,我是你们的朋友全栈君。

如果一个函数在内部调用自身,这个函数就叫做递归函数

递归函数的简单定义如下:

def recursion():
    return recursion()

这只是一个简单的定义,什么也做不了。

当然,你可以尝试会发生什么结果,理论上会永远运行下去,但实际操作时发现不一会儿程序就报错了,因为每次调用函数都会用掉一点内存,在足够多的函数调用发生后,空间几乎被占满,程序就会报错。

RecursionError: maximum recursion depth exceeded
#超过最大递归深度

这类递归被称为无穷递归(infinite recursion),理论上永远都不会结束,当然,我们需要能实际做事情的函数,有用的递归函数应该满足如下条件:

(1)当函数直接返回值时有基本实例(最小可能性问题)

(2)递归实例,包括一个或多个问题最小部分的递归调用

使用递归的关键在于将问题分解为小部分,递归不能永远进行下去,因为它总是以最小可能性问题结束,而这些问题又存储在基本实例中。

函数调用自身怎么实现呢??

其实函数每次被调用时都会创建一个新的命名空间,也就是当函数调用‘自己’时,实际上运行的是两个不同的函数(也可以说一个函数具有两个函数的命名空间)。

我们来看一个递归示例,计算阶乘n!=1*2*3*4*5*……*n,用函数fact(n)表示,可以看出:

fact(n)=1*2*3*……*(n-1)*n=(n-1)!*n=fact(n-1)*n

所以,fact(n)可以表示为n*fact(n-1),只有n=1时需要特殊处理

于是,fact(n)用递归方式定义函数如下:

def fact(n):
    if n==1:
        return 1
    return n*fact(n-1)

执行该函数:

>>> fact(1)
1
>>> fact(5)
120
>>> fact(100)
93326215443944152681699238856266700490715968264381621468592963895217599993229915608941463976156518286253697920827223758251185210916864000000000000000000000000L

如果我们计算fact(5),可以根据函数定义看到计算过程如下:

===> fact(5)
===> 5 * fact(4)
===> 5 * (4 * fact(3))
===> 5 * (4 * (3 * fact(2)))
===> 5 * (4 * (3 * (2 * fact(1))))
===> 5 * (4 * (3 * (2 * 1)))
===> 5 * (4 * (3 * 2))
===> 5 * (4 * 6)
===> 5 * 24
===> 120

由函数定义可知,递归函数的有点是定义简单,逻辑清晰。

理论上,所有递归函数都可以写成循环的方式,不过循环的逻辑不如递归清晰。

使用递归函数需要注意仿制栈溢出,在计算机中,函数调用通过栈(stack)这种数据结构实现的。每当进入一个函数调用,栈就会增加一层栈帧,每当函数返回,栈就会减一层栈帧,忧郁栈的大小不是无线的,因此递归调用的次数过多会导致栈溢出。可以试试fact(1000),执行结果如下:

RecursionError: maximum recursion depth exceeded in comparison

由执行结果看到,执行出现异常,异常提示超过最大递归深度。

那么怎么解决这个问题呢?

首先我们可以通过修改最大递归深度来增加递归深度。通过sys模块的setrecursionlimit()方法来修改。

import sys
sys.setrecursionlimit(2000)#这样就可以增加最大递归深度

但是这样也治标不治本,如果递归次数太多,那就说明这个问题就不适合用递归来解决。

还有一种方法,就是通过尾递归优化,事实上尾递归和循环的效果一样,把循环看成一种特殊尾递归函数也是可以的。

尾递归是指在函数返回时只能调用函数本身,return语句不能包含表达式,这样,编译器或解释器就可以对尾递归进行优化,使递归本身无论调用多少次都只占用一个栈帧,从而避免栈溢出的情况。

由于上面的fact(n)函数return n*(n-1)引入了乘法表达式,因此不是尾递归,要改成尾递归方式需要多一点代码,主要是把每一步乘积传入递归函数(通过把乘积结果传入函数参数的方式),看如下函数定义方式:

def fact(n,ret=1):
    if n==0:
        return ret
    return fact(n-1,ret=ret*n)
print(fact(5))#输出120

可以看到return fact(n-1,ret=ret*n)仅返回函数本身,n-1和ret=ret*n在函数调用前就会被计算,不影响函数的调用。

尾递归调用时,如果做了优化,栈不会增长,因此,无论多少次调用也不会导致栈溢出。

遗憾的是,大多数编程语言没有针对尾递归做优化,Python解释器也没有做优化,所以,即使把上面的fact(n)函数改成尾递归方式,也会导致栈溢出。

def fact(n,ret=1):
    if n==0:
        return ret
    return fact(n-1,ret=ret*n)
print(fact(1000))
#输出
RecursionError: maximum recursion depth exceeded in comparison

但是可以通过装饰器方法手动进行尾递归优化,这里暂不叙述,详细方法百度

递归与三级菜单:

递归函数[通俗易懂]
递归函数[通俗易懂]

menu = {
    '北京': {
        '海淀': {
            '五道口': {
                'soho': {},
                '网易': {},
                'google': {}
            },
            '中关村': {
                '爱奇艺': {},
                '汽车之家': {},
                'youku': {},
            },
            '上地': {
                '百度': {},
            },
        },
        '昌平': {
            '沙河': {
                '老男孩': {},
                '北航': {},
            },
            '天通苑': {},
            '回龙观': {},
        },
        '朝阳': {},
        '东城': {},
    },
    '上海': {
        '闵行': {
            "人民广场": {
                '炸鸡店': {}
            }
        },
        '闸北': {
            '火车战': {
                '携程': {}
            }
        },
        '浦东': {},
    },
    '山东': {},
}

menu

递归函数[通俗易懂]
递归函数[通俗易懂]

def threeLM(dic):
     while True:
         for k in dic:print(k)
         key = input('input>>').strip()
         if key == 'b' or key == 'q':return key
         elif key in dic.keys() and dic[key]:
             ret = threeLM(dic[key])
             if ret == 'q': return 'q'
 
threeLM(menu)

#会迷惑的一个地方就是当输入b和q时,返回key(此时key=b或q)是返回给上一次return的地方,

递归函数实现三级菜单

递归函数[通俗易懂]
递归函数[通俗易懂]

l = [menu]
while l:
    for key in l[-1]:print(key)
    k = input('input>>').strip()   # 北京
    if k in l[-1].keys() and l[-1][k]:l.append(l[-1][k])
    elif k == 'b':l.pop()
    elif k == 'q':break

用堆栈实现的三级菜单

 

二分查找法

lis=[2,3,5,10,15,16,18,22,26,30,32,35,41,42,43,55,56,66,67,69,72,76,82,83,88]


def find(aim,li,start=0,end=None):
    end=len(li) if end is None else end
    middle=(end-start)//2+start
    if start<=end:
        if li[middle]>aim:
            return find(aim,li,start=start,end=middle-1)
        elif li[middle]<aim:
            return find(aim,li,start=middle+1,end=end)
        else:
            return middle
    else:
        return 'not find'
print(find(16,lis))

 

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

发布者:全栈程序员-用户IM,转载请注明出处:https://javaforall.cn/154826.html原文链接:https://javaforall.cn

【正版授权,激活自己账号】: Jetbrains全家桶Ide使用,1年售后保障,每天仅需1毛

【官方授权 正版激活】: 官方授权 正版激活 支持Jetbrains家族下所有IDE 使用个人JB账号...

(0)


相关推荐

发表回复

您的电子邮箱地址不会被公开。

关注全栈程序员社区公众号