递归算法Recursion

一、什么递归?

递归是一种解决问题的方法,它把一个问题分解为越来越小的子问题,直到问题的规模小到
可以被很简单直接解决。通常为了达到分解问题的效果,递归过程中要引入一个调用自身的函数。
乍一看,递归算法并没有什么特别的地方,但是,利用递归我们能够写出极为简明的解决问题的方
法,而且如果不用递归,这些问题将具有很大的编程难度。

1.计算数字列表的和

我们先从一个简单的问题开始我们的探究,这个问题不需要递归也可以解决。假如你想对一
个数字列表进行求和(例如[1,3,5,7,9]),代码 4.1 所示的是一个通过迭代函数(for 循环)求和的程
序。这个函数用一个变化着的“累加器”变量(theSum)对列表里面所有的数进行累加求和,也就
是从 0 开始,依次加上列表中的每个数。

def list_sum(num_list):
    the_sum = 0
    for i in num_list:
        the_sum = the_sum + i
    return the_sum
print(list_sum([1,3,5,7,9]))

现在,假设我们不能使用 while 循环或者 for 循环,那么你会如何对数字列表中的数进行求和
呢?如果你是个数学家,那么你首先想到的也许是:按照定义,加法是一个有两个参数——两个数
字——的函数。为了将数字列表的问题重新定义为对两个参数求和的问题,我们可以利用全括号的
表达式来重新表示列表,就像下面这种形式:
((((1+3)+5)+7)+9)
我们也可以从另一个方向来加入括号:
(1+(3+(5+(7+9))))
我们注意到最内层的括号中是(7+9),这是不需要任何循环或者特殊的结构就能解决的。事
实上,我们可以用以下一系列简化后的式子来计算最终的加和。
那么我们怎样将这个思想转化为 Python 代码呢?首先让我们从 Python 列表的角度来重新叙述
这个问题。由于数字列表的和是列表中的第一个元素(numList[0])和剩下所有的元素
(numList(1:))之和的和,求和问题可以归纳成以下的式子:
listSum(numList)=first(numList)+listSum(rest(numList))
在这个等式中,first 代表列表中的第一个元素,而 rest 代表的是列表中除了第一个元素以外
的其他所有元素。此式很容易在 Python 中用代码 4.2 表示出来。

def list_sum(num_list):
     if len(num_list) == 1:   #基线条件
        return num_list[0]
     else:    #递归条件
        return num_list[0] + list_sum(num_list[1:])
print(list_sum([1,3,5,7,9]))

在这个代码中,有一些关键点需要注意。首先,在第二行中,我们检查这个列表中是否只有一
个元素。这个检查非常关键,因为它是函数能结束运行的必要条件。对一个长度为 1 的列表来说,
它的和显然只是这个列表中的数,其次,在第五行中,函数调用了自身!这就是我们把后一个计算
程序称为“递归”的原因。递归函数就是一种调用自身的函数。
每个递归函数都有两部分:基线条件(base case)和递归条件(recursive case)。递归条件指的是函数调用自己,而基线条件则指的是函数不再调用自己,从而避免形成无限循环。
图1展示了对列表[1,3,5,7,9]求和的一系列递归调用和返回的过程。你可以将这一系列的
递归调用看做一次次简化问题的过程。每次进行递归调用过程就是在解决一个规模更小的问题,当
问题的规模达到最小时结束递归。
图1
当问题的规模缩小到我们可以轻易解决时,我们开始把每个小规模问题的答案连接起来直到解
决最初提出的问题。
Stack Overflow上有话:“如果使用循环,程序的性能可能更高;如果使用递归,程序可能
更容易理解。如何选择要看什么对你来说更重要。”

2.递归三大定律

1、递归算法必须有个基本结束条件

2、递归算法必须改变自己的状态并向基本结束条件演进

3、递归算法必须递归地调用自身

让我们来更加细致地了解每一条定律,并看看它们是怎样在“数字列表求和”这个算法中体现
的。首先,基本结束条件是一种可以让算法的递归操作结束的情况。基本的结束条件通常是一个规
模小到可以直接解决的问题。在数字列表求和算法中,基本结束条件是一个长度为 1 的列表。
为了遵循第二定律,我们必须改变算法的状态,使之向基本结束条件演进。状态的改变意味着
算法中使用的一些数据被改变了。通常情况下,这些代表着我们问题的数据以某种方式变小了。在
数字列表求和算法中,基本的数据结构是一个列表,所以我们应该专注于在列表上下功夫来改变算
法的状态。由于基本结束条件是长度为 1 的列表,那么向基本结束条件演进的最自然的过程就是缩
短列表的长度。我们在代码 2 的第五行中可以看到具体的做法:将列表的长度减少 1,再调用求和算
法来计算这个更短的列表。
最后一条定律就是算法必须调用自身,这正是”递归“的定义。递归算法对于许多初学者来说是
个难理解的概念。作为一个初学者,你已经看到了递归函数的优越性了吧,因为你可以把一个大问
题不断分解为更小的问题,解决这些小问题只要分别写一些简单的函数就行了。当我们谈论递归的
时候好像我们把自身置入了循环当中了。我们有一个问题需要用一个函数来解决,但是这个函数需
要调用自身来解决问题!但在逻辑上这是完全不循环的,递归的逻辑就是利用优美的程序把问题分解
为更小更简单的子问题来解决的

参考文献

1、算法图解
2、算法与数据结构

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值