递归

递归

什么是递归

迭代的是人,递归的是神 ——  L.Peter Deutsch

简单定义:

当函数直接或者简介调用自己的时候,发生递归。

基本要素
  1. 边界条件:确定递归到何时终止,也称为递归出口
  2. 递归模式:大问题如何分解成小问题的,也成为递归体

举个栗子:

# 计算阶乘
def func(n):
    nub = 1
    while n != 1:
        nub *= n
        n -= 1
    else:
        return nub

print(func(5))

用递归实现呢

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

第一种是平常使用的迭代的思想:当n不等于1时,每次循环 乘 ,全部计算结束以后在返回值

而递归,因为 func(N) = N* func(N-1).换句话说,为了获得 func(N)的值,需要计算 func(N-1).而且,为了找到 func(N-1),需要计算func(N-2)等等。计算到func(1)的时候拿到结果在一次计算func(2)… func(N)

理解递归:

在初识递归的时候,我们经常会陷入不停的回溯验证之中,因为回溯验证就像反过来思考迭代,这是我们习惯的思考方式,但是对于递归,我们并不需要这样验证。比如上面的阶乘计算:

用回溯的方式思考:比如 n = 4 那么func(4) 等于 4 * func(3) 而 func(3) 等于 3 * func(2),func(2) 等于 2 * func(1), 而func(1) 等于1 所以func(4) = 4 * 3 * 2 * 1 这个结果整好是等于阶乘4的迭代定义

但是这种方法对于简单的还好 ,如果是复杂的就可以GG, 而且还无益于理解

Paul Graham提到一种方法,如下:

  1. 当n=0,1的时候,结果正确(你必须要示范如何解决问题的一般情况, 通过将问题切分成有限小并更小的子问题)

  2. 假设函数对于n是正确的,函数对n+1的结果也是正确的

    如果这两点成立,我们知道这个函数对于所有可能的n都是正确的

    (你必须要示范如何通过有限的步骤, 来解决最小的问题(基本用例).如果这两件事完成了, 那问题就解决了. 因为递归每次都将问题变得更小, 而一个有限的问题终究会被解决的, 而最小的问题仅需几个有限的步骤就能解决.)

这种方法很像 数学归纳法, 也是递归正确的思考方式, 事实上, 阶乘的递归表达方式就是1!=1,n!=(n-1)!×n(见wiki). 当程序实现符合算法描述的时候, 程序自然对了, 假如还不对, 那是算法本身错了…… 相对来说, n,n+1的情况为通用情况, 虽然比较复杂, 但是还能理解, 最重要的, 也是最容易被新手忽略的问题在于第1点, 也就是基本用例(base case)要对. 比如, 上例中, 我们去掉if n <= 1的判断后, 代码会进入死循环, 永远不会结束.

当函数被调用时,它的变量的空间是创建于运行时堆栈上的。以前调用的函数的变量扔保留在堆栈上,但他们被新函数的变量所掩盖,因此是不能被访问的。 当递归函数调用自身时,情况于是如此。每进行一次新的调用,都将创建一批变量,他们将掩盖递归函数前一次调用所创建的变量。当我追踪一个递归函数的执行过程时,必须把分数不同次调用的变量区分开来,以避免混淆。

递归的优缺点:

优点:

会让代码变简单

缺点:

占用内存(解决方法尾递归)

什么时候使用递归

当问题可用递归来解决所需要具备的条件

  1. 子问题与原问题为同样的事,而且规模更小
  2. 程序具有停止的条件

递归的实例

例一 汉诺塔

有三根杆子A,B,C。A杆上有N个(N>1)穿孔圆盘,盘的尺寸由下到上依次变小。要求按下列规则将所有圆盘移至C杆:
    1.每次只能移动一个圆盘.
    2.大盘不能叠在小盘上面.   

思路:假设 现在我们一共在A杆 上 一共 有3 个盘子,我们要想把最大的盘子放到C杆 上,我们需要把除去最大的一个盘子(n-1) 全部放到B杆上,然后将最大的盘子放到C杆上,然后在将其余的通过A杆放到C杆上。

def move(n, a, buffer, c, count = [0,]):
  #   n :       杆上有多少个盘
  #   a :       目标所在杆
  #   buffer :  过程杆
  #   c :       目标杆
  #   count :  通过默认参数是可变数据类型时公用同一个内存空间,存放走的步数
    if(n == 1):# 
        count[0] += 1
        print('第',count,'步',a,"->",c)
        return
    move(n-1, a, c, buffer, count)# 将除最大的盘之外的所有盘 从a 通过c 放到b
    move(1, a, buffer, c, count)  # 将最大盘 从a 通过b 放到c 
    move(n-1, buffer, a, c, count)# 将除最大的盘之外的所有盘 从b 通过a 放到c
move(3, "a", "b", "c")

例二 上楼梯

有个小孩正在上楼梯,楼梯有n阶台阶,小孩一次可以上1阶、2阶。计算小孩上楼梯的方式有多少种
# 1     1
# 2     2
# 3     3
# 4     5
# 5     8
由规律可知这个是一个变形的斐波那契数列

def func(n, a = 1, b = 2):
    if n == 1:
        return a
    return func(n-1, b, a+b)

例三 上楼梯

有个小孩正在上楼梯,楼梯有n阶台阶,小孩一次可以上1阶、2阶、3阶。计算小孩上楼梯的方式有多少种

思路:由于最开始查数差错了,导致没找到任何规律 我们将本题想象成下楼梯,当阶梯为0的时候算下完楼梯, 下楼梯有3种方案,我们可以下一层,也可以2、3层,只要下面的楼梯足够,我们有3种选择,而不是三选一 可能有点难理解但是想表达的意思是 代码中 使用三个if 而不是 if-elif-elif 的意思

也可以套用例一的思想,我下一层后,让第二个人告诉我要下几层台阶

def func(n, l=[0]):
    if n == 0:
        l[0] += 1
        return
    if n >= 1:
        func(n-1, l)
    if n >=2:
        func(n-2, l)
    if n >= 3:
        func(n-3, l)
    return l[0]

print(func(5))

例四 二分法查找

L = [1,2,3,4,5]
def func(L, aim, start = 0, end = None):
    end = len(L) if end is None else end
    min_index = (end - start) // 2 + start
    if end <= start: return -1
    start, end = (min_index + 1, end) if L[min_index] < aim else (start, min_index - 1)
    if L[min_index] == aim:
        return min_index
    return func(L, aim, start, end)

例五 n层菜单

这个是凑数的

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

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'
        elif (not dic.get(key)) or (not dic[key]) :
            continue

threeLM(menu)
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值