zoukankan      html  css  js  c++  java
  • [ python ] 递归函数

    递归函数

     描述:
        如果一个函数在内部调用自身本身,这个函数就是递归函数

    递归函数特性:
        (1)必须有一个明确的结束条件
        (2)每次进入更深一层递归时,问题规模相比上次递归都应有所减少
        (3)相邻两次重复之间有紧密的联系,前一次要为后一次做准备
        (4)递归效率不高,递归层次过多会导致溢出

    首先,我们可以从字面上来理解递归函数

    递:传递出去的意思
    归:回来的意思

    递归函数就是一个有去有回的过程,以下一个简单的例子来解释递归函数:

    实例:

    计算一个10以下(包括10)整数的加法运算:

    (1)初级写法:

    n = 0
    for i in range(11):
        n += i
    
    print(n)
    

     (2)中级写法:

        使用 reduce 高阶函数进行累计运算

    from functools import reduce
    print(reduce(lambda x, y: x+y, range(11)))
    

     (3)递归函数的写法:

    def add(n):
        if n == 1:
            return n
        else:
            return n + add(n -1)
    
    print(add(10))
    

    这三种方法,显然第二种是最简单的,但是这里是为了研究递归函数的用法,要了解递归函数的工作流程,就需要分解递归函数。

    这里只是为了说明问题,调用 add(5) :

    def add(n):     # n = 5
        if n == 1:
            return n
        else:
            return n + add(n -1)    # 5 + add(5 -1)
    
    def add(n):    # add(4)
        if n == 1:
            return n
        else:
            return n + add(n -1)    # 4 + add(4 -1)
    
    def add(n):    # add(3)
        if n == 1:
            return n
        else:
            return n + add(n -1)    # 3 + add(3 -1)
    
    def add(n):    # add(2)
        if n == 1:
            return n
        else:
            return n + add(n -1)    # 2 + add(2 -1)
        
    def add(n):    # add(1)
        if n == 1:  # n = 1
            return n    # return 1
        else:
            return n + add(n -1)
    

     以上是我们通过代码执行流程分解出来的过程信息。
    每当函数内部调用自身的时候,外部函数挂起,执行内部函数,当内部函数执行完毕,然后在执行外部函数;

    用简单的图形来表示,如下:

    ===> add(5)
    ===> 5 + add(4)
    ===> 5 + (4 + add(3))
    ===> 5 + (4 + (3 + add(2)))
    ===> 5 + (4 + (3 + (2 + add(1))))
    ===> 5 + (4 + (3 + (2 + 1)))
    ===> 5 + (4 + (3 + 3))
    ===> 5 + (4 + 6)
    ===> 5 + 10
    ===> 15
    

    递归函数的优点是定义简单,逻辑清晰。理论上,所有的递归函数都可以写成循环的方式,但循环的逻辑不如递归清晰。

    实例:

      使用递归函数实现一个三级菜单的效果

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

    提示:在编写递归函数的时候要牢记以下三点:

    (1)必须有一个明确的结束条件
    (2)当数据按照一定规律执行的时候,才能考虑递归实现
    (3)只有调用自身的函数才是递归函数

    def treeML(dic):
        while True:
            for i in dic:
                print(i)
    
            key = input('>>>').strip()
            if key == 'q' or key == 'b':
                return key
            elif key in dic:
                res = treeML(dic[key])
                if res == 'q':
                    return 'q'
    
    treeML(menu)
    递归函数实现三级菜单

    二分查找算法与递归函数

    二分查找算法:
        简单来讲,就是一半一半的找。
        
    二份查找实例:
    有这样一个数列:
    1,2,3,4,5

    当我们想要查找数字:4

    原始的办法:
    从数列中一个一个遍历,直到找到 4 为止,查找了 4 次。

    二分查找算法:

    首先切一半得到:3,因为 3< 4 我们获取右半边的数列 4, 5
    然后我们在切一半得到:4,4=4,在二分算法中,我们一共就找了 2 次就得到结果。

     

     当我们想的多了,总结出更加便捷的方式,计算机才能更加高效的工作;

    现在通过递归函数来实现,二分查找算法:

    数列:
    l = [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]

    查找序列中是否有数字:83

    l = [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(l, aim):
        mid_index = len(l) // 2    # 这里需要取整数不能是小数
        if l[mid_index] > aim:  # 当取的值大于要找的值,取左边
            find(l[:mid_index], aim)    # 通过切片取list左边的值
        elif l[mid_index] < aim:    # 当取的值大于要找的值,取右边
            find(l[mid_index+1:], aim)    # 通过切片取list右边的值
        else:
            print(mid_index, l[mid_index])  # 数字比较只有三种情况,大于、小于、等于
    
    find(l, 82)
    

     上面的实例,虽然找到序列中含有 82 但是 索引位置是有问题的。修改如下:

    l = [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(l, aim, start=None, end=None):
        start = start if start else 0
        end = len(l) -1 if end is None else end
        mid_index = (end - start) // 2 + start
        if start > end:
            return None
        if l[mid_index] > aim:
            return find(l, aim, start, mid_index-1)
        elif l[mid_index] < aim:
            return find(l, aim, mid_index+1, end)
        elif l[mid_index] == aim:
            return mid_index, l[mid_index]
    
    res = find(l, 82)
    print(res)
    
    # 执行结果:
    # (22, 82)
    

    以上递归函数,比较疑惑的地方:
    end = len(l)-1 if end is None else end
    这里为什么:len(l)-1

    分析结果如下:
    提示:如果要对递归函数进行分析,需要将代码执行流程分解开,查看就更加明显了。

    l = [2,3,5]
    
    
    def find(l, aim, start=None, end=None):
        start = start if start else 0   # start = 0
        end = len(l) if end is None else end # end = 3
        mid_index = (end - start) // 2 + start  # mid_index = (3-0) // 2 + 0 =1
        if start > end:
            return None
        if l[mid_index] > aim:
            return find(l, aim, start, mid_index-1)
        elif l[mid_index] < aim:    # 3 < 100
            return find(l, aim, mid_index+1, end)   # find(l, 6, 2, 3)
        elif l[mid_index] == aim:
            return mid_index, l[mid_index]
    
    --------------------------------------------------------------------------------------------
    通过第一步我们获取到:
    	find(l, 6, start=2, end=3)
    	l最大的索引为:2
    
    --------------------------------------------------------------------------------------------
    		
    def find(l, aim, start=None, end=None): # find(l, 6, 2, 3)
        start = start if start else 0   # start = 2
        end = len(l) if end is None else end # end = 3
        mid_index = (end - start) // 2 + start  # mid_index = (3-2) // 2 + 2 =2
        if start > end:
            return None
        if l[mid_index] > aim:
            return find(l, aim, start, mid_index-1)
        elif l[mid_index] < aim:    # 5 < 6
            return find(l, aim, mid_index+1, end)   # find(l, 6, 3, 3)
        elif l[mid_index] == aim:
            return mid_index, l[mid_index]
    		
    --------------------------------------------------------------------------------------------
    通过第二步我们获取到:
    	find(l, 6, start=3, end=3)
    	l最大的索引为:2
    
    --------------------------------------------------------------------------------------------
    
    def find(l, aim, start=None, end=None): # find(l, 6, 3, 3)
        start = start if start else 0   # start = 3
        end = len(l)-1 if end is None else end # end = 3
        mid_index = (end - start) // 2 + start  # mid_index = (3-3) // 2 + 3 = 3
        if start > end:
            return None
        if l[mid_index] > aim:  # l 最大的索引为:2 这里:l[3] 报错啦,因此 end = len(l)-1 if end is None else end
            return find(l, aim, start, mid_index-1)
        elif l[mid_index] < aim:
            return find(l, aim, mid_index+1, end)   # find(l, 6, 3, 3)
        elif l[mid_index] == aim:
            return mid_index, l[mid_index]
    

    附加题:

        使用递归函数求斐波拉契数列.

    首先斐波拉契数列如下:

    1,1,2,3,5,8

    规律:从第三位开始,后面的值是前面两个值的和

    def fib(n):
        if n == 1 or n == 2:
            return 1
        return fib(n -1) + fib(n -2)
    

    上面的函数是按照斐波拉契数列规律写出来的,思路是没问题的,但是在函数内部调用两次自身函数,这样的效率非常慢。
    因此在使用递归函数时,一定要注意在函数内部只能调用一个,否则严重影响执行效率

    上面的递归函数修改如下:

    count = 0
    
    def fib(n, a=0, b=1):
        # 每次递归获取全局变量count = 0
        global count
        # 在递归函数中,count = 1
        count += 1
        # 当 1 < n -1 时,进行函数的递归计算
        if count < n-1:
            return fib(n, b, a+b)
        # 当 1 >= n -1 时,n = 2 或者 n =1 返回 a + b = 0 + 1 = 1
        elif count >= n -1:
            return a+b
    
    
    f = fib(10)
    print(f)
    
  • 相关阅读:
    【并发】基于 @Async和 CompletableFuture 实现并发异步操作
    【HTTP】使用 RestTemplete 实现 post请求
    【AICC】2019训练营笔记
    【Hadoop】CDH、Presto配置问题
    【Linux】文件拷贝-Linux当前目录所有文件移动到上一级目录(转)
    【Linux】linux ln文件夹的链接(转)
    【Hadoop】新建hadoop用户以及用户组,给予sudo权限(转)
    【Centos】桌面安装(转)
    【CentOS7】CentOS7各个版本镜像下载地址(转)
    【Spark】ScalaIDE运行spark,A master URL must be set in your configuration
  • 原文地址:https://www.cnblogs.com/hukey/p/9765418.html
Copyright © 2011-2022 走看看