38、递归算法的深入解析与应用

递归算法的深入解析与应用

1. 直接递归与间接递归

递归函数是编程中一种强大的工具,它可以分为直接递归和间接递归。
- 直接递归 :函数直接调用自身。例如,一个函数在其函数体中直接调用自己来解决问题。
- 间接递归 :当函数 A 调用函数 B,而函数 B 又调用函数 A 时,就形成了间接递归。甚至可能有多个函数参与到这种递归中,比如函数 A 调用函数 B,函数 B 调用函数 C,函数 C 再调用函数 A。

2. 递归算法示例
2.1 列表元素范围求和

使用递归对列表中一定范围的元素进行求和。以下是 range_sum 函数的定义和使用示例:

def range_sum(num_list, start, end):
    if start > end:
        return 0
    else:
        return num_list[start] + range_sum(num_list, start + 1, end)

numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9]
my_sum = range_sum(numbers, 3, 7)
print(my_sum)

该函数的基本情况(base case)是当 start 参数大于 end 参数时,函数返回 0。否则,函数返回 num_list[start] 加上递归调用的返回值。

以下是完整的示例程序:

def main():
    numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9]
    my_sum = range_sum(numbers, 2, 5)
    print('The sum of items 2 through 5 is', my_sum)

def range_sum(num_list, start, end):
    if start > end:
        return 0
    else:
        return num_list[start] + range_sum(num_list, start + 1, end)

main()

程序输出:

The sum of elements 2 through 5 is 18
2.2 斐波那契数列

斐波那契数列是一个著名的数学序列,从第三项开始,每一项都等于前两项之和。其递归函数的实现如下:

def fib(n):
    if n == 0:
        return 0
    elif n == 1:
        return 1
    else:
        return fib(n - 1) + fib(n - 2)

for number in range(1, 11):
    print(fib(number))

该函数有两个基本情况:当 n 等于 0 时返回 0,当 n 等于 1 时返回 1。否则,函数返回 fib(n - 1) 加上 fib(n - 2) 的结果。

2.3 求最大公约数

使用递归计算两个数的最大公约数(GCD)。以下是 gcd 函数的实现:

def gcd(x, y):
    if x % y == 0:
        return y
    else:
        return gcd(y, x % y)

num1 = int(input('Enter an integer: '))
num2 = int(input('Enter another integer: '))
print('The greatest common divisor of')
print('the two numbers is', gcd(num1, num2))

该函数的基本情况是当 x 能被 y 整除时,返回 y 。否则,递归调用 gcd(y, x % y)

2.4 汉诺塔问题

汉诺塔是一个经典的数学游戏,常用于展示递归的强大之处。游戏规则如下:
- 有三根柱子和一组中间有孔的圆盘,圆盘按大小顺序堆叠在其中一根柱子上,最大的圆盘在最下面。
- 目标是将所有圆盘从第一根柱子移动到第三根柱子,中间的柱子可以作为临时存放点。
- 移动规则:
- 每次只能移动一个圆盘。
- 圆盘不能放在比它小的圆盘上面。
- 除了移动过程中,所有圆盘都必须放在柱子上。

以下是实现汉诺塔问题的递归函数:

def move_discs(num, from_peg, to_peg, temp_peg):
    if num > 0:
        move_discs(num - 1, from_peg, temp_peg, to_peg)
        print('Move a disc from peg', from_peg, 'to peg', to_peg)
        move_discs(num - 1, temp_peg, to_peg, from_peg)

num_discs = 3
from_peg = 1
to_peg = 3
temp_peg = 2
move_discs(num_discs, from_peg, to_peg, temp_peg)
print('All the pegs are moved!')

该函数的基本情况是当 num 等于 0 时,不进行任何操作。否则,先将 num - 1 个圆盘从 from_peg 移动到 temp_peg ,然后将最后一个圆盘从 from_peg 移动到 to_peg ,最后将 num - 1 个圆盘从 temp_peg 移动到 to_peg

3. 递归与循环的比较

任何可以用递归实现的算法也可以用循环来实现。然而,递归函数调用通常比循环效率低,因为每次函数调用都会带来额外的系统开销。在许多情况下,使用循环的解决方案比递归解决方案更明显。但有些问题使用递归更容易解决,例如计算最大公约数的数学定义就非常适合递归方法。

4. 总结

递归是一种强大的编程技术,但在使用时需要谨慎考虑其效率和适用性。对于一些问题,递归可以提供简洁而优雅的解决方案;而对于其他问题,循环可能是更好的选择。在实际编程中,需要根据具体情况选择最合适的方法。

以下是一个简单的流程图,展示了递归函数的基本结构:

graph TD;
    A[开始] --> B{是否为基本情况};
    B -- 是 --> C[返回结果];
    B -- 否 --> D[递归调用自身];
    D --> E[处理结果];
    E --> C;
5. 复习问题

以下是一些关于递归的复习问题,以帮助巩固所学知识:
1. 选择题
- 递归函数 _ _
a. 调用不同的函数
b. 异常终止程序
c. 调用自身
d. 只能被调用一次
- 一个函数从程序的主函数中被调用一次,然后它自己调用自己四次。递归深度是 _ _

a. 一
b. 四
c. 五
d. 九
- 问题中可以不使用递归解决的部分是 _ _ 情况。
a. 基本
b. 可解
c. 已知
d. 迭代
- 问题中使用递归解决的部分是 _ _
情况。
a. 基本
b. 迭代
c. 未知
d. 递归
- 当一个函数显式地调用自身时,称为 _ _ 递归。
a. 显式
b. 模态
c. 直接
d. 间接
- 当函数 A 调用函数 B,函数 B 又调用函数 A 时,称为 _ _
递归。
a. 隐式
b. 模态
c. 直接
d. 间接
- 任何可以用递归解决的问题也可以用 _ _ 解决。
a. 决策结构
b. 循环
c. 顺序结构
d. 案例结构
- 计算机在调用函数时所采取的操作,如为参数和局部变量分配内存,被称为 _ _

a. 开销
b. 设置
c. 清理
d. 同步
- 递归算法在递归情况下必须 _ _
a. 不使用递归解决问题
b. 将问题简化为原问题的较小版本
c. 确认发生错误并终止程序
d. 将问题扩大为原问题的较大版本
- 递归算法在基本情况下必须 _ _

a. 不使用递归解决问题
b. 将问题简化为原问题的较小版本
c. 确认发生错误并终止程序
d. 将问题扩大为原问题的较大版本
2. 判断题
- 使用循环的算法通常比等效的递归算法运行得更快。
- 有些问题只能通过递归解决。
- 并非所有递归算法都必须有基本情况。
- 在基本情况下,递归方法用原问题的较小版本调用自身。
3. 简答题
- 在之前的示例中, message 函数的基本情况是什么?
- 计算一个数的阶乘的规则如下:如果 n = 0 ,则 factorial(n) = 1 ;如果 n > 0 ,则 factorial(n) = n * factorial(n - 1) 。如果根据这些规则设计一个函数,基本情况和递归情况分别是什么?
- 递归是否是解决问题所必需的?对于具有重复性的问题,还可以使用什么其他方法?
- 当使用递归解决问题时,为什么递归函数必须调用自身来解决原问题的较小版本?
- 递归函数通常如何简化问题?
4. 算法工作台
- 以下程序将显示什么?

def main():
    word = 'test'
    show_me(word)

def show_me(word):
    print(word)
    new_word = word[1:]
    if len(new_word) > 0:
        show_me(new_word)

main()
- 当运行以下代码时,`halver` 函数将被调用多少次?
def main():
    num = 10
    halver(num)

def halver(number):
    print(number)
    half = number / 2
    if half >= 1:
        halver(half)

main()
- 将以下使用循环的函数重写为递归函数,实现相同的操作:
def queue(length):
    while length > 0:
        print('Please wait.')
        length = length - 1
    print('It is your turn.')
  1. 编程练习
    • 递归打印 :设计一个递归函数,接受一个整数参数 n ,并从 n 开始每隔一个数字打印一次,直到最小为 0。假设 n 总是一个正整数。
    • 递归乘法 :设计一个递归函数,接受两个参数 x y ,返回 x 乘以 y 的值。可以将乘法看作重复加法。
    • 递归行 :编写一个递归函数,接受一个整数参数 n ,在屏幕上显示 n 行星号,第一行显示 1 个星号,第二行显示 2 个星号,依此类推,直到第 n 行显示 n 个星号。
    • 列表中的最大值 :设计一个函数,接受一个列表作为参数,返回列表中的最大值。该函数应使用递归方法找到最大值。
    • 递归列表求和 :设计一个函数,接受一个数字列表作为参数,递归地计算列表中所有数字的总和并返回该值。
    • 判断回文 :设计一个函数,接受一个字符串作为参数,假设该字符串只包含一个单词。使用递归方法判断该单词是否为回文(即从前往后和从后往前读都相同)。
    • 递归幂运算 :设计一个函数,使用递归方法将一个数提升到指定的幂。该函数应接受两个参数:要提升的数和指数,假设指数是非负整数。
    • 阿克曼函数 :阿克曼函数是一个递归数学算法,可用于测试系统对递归性能的优化程度。设计一个函数 ackermann(m, n) ,实现阿克曼函数的逻辑:
      • 如果 m = 0 ,则返回 n + 1
      • 如果 n = 0 ,则返回 ackermann(m - 1, 1)
      • 否则,返回 ackermann(m - 1, ackermann(m, n - 1))
        设计完成后,使用较小的 m n 值进行测试。

通过这些复习问题和编程练习,可以进一步加深对递归的理解和掌握。在实际编程中,不断练习和应用递归算法,将有助于提高编程能力和解决问题的能力。

递归算法的深入解析与应用

6. 复习问题答案解析
6.1 选择题答案
题目 答案 解析
递归函数 _ _ _。 c. 调用自身 递归函数的定义就是函数调用自身来解决问题。
一个函数从程序的主函数中被调用一次,然后它自己调用自己四次。递归深度是 _ _ _。 c. 五 从主函数调用算一次,自己调用四次,总共递归深度为 5 次。
问题中可以不使用递归解决的部分是 _ _ _ 情况。 a. 基本 基本情况是递归算法中不需要递归调用就能解决的部分。
问题中使用递归解决的部分是 _ _ _ 情况。 d. 递归 递归情况就是需要通过递归调用函数来解决的部分。
当一个函数显式地调用自身时,称为 _ _ _ 递归。 c. 直接 直接递归就是函数直接调用自身。
当函数 A 调用函数 B,函数 B 又调用函数 A 时,称为 _ _ _ 递归。 d. 间接 这种多个函数相互调用形成的递归是间接递归。
任何可以用递归解决的问题也可以用 _ _ _ 解决。 b. 循环 递归和循环都可以实现重复操作,能递归解决的问题通常也能用循环解决。
计算机在调用函数时所采取的操作,如为参数和局部变量分配内存,被称为 _ _ _。 a. 开销 这些操作会带来额外的系统开销。
递归算法在递归情况下必须 _ _ _。 b. 将问题简化为原问题的较小版本 递归算法需要不断将问题简化,直到达到基本情况。
递归算法在基本情况下必须 _ _ _。 a. 不使用递归解决问题 基本情况就是不需要递归就能解决问题的情况。
6.2 判断题答案
  • 使用循环的算法通常比等效的递归算法运行得更快 :正确。递归函数调用会有额外的系统开销,而循环相对更高效。
  • 有些问题只能通过递归解决 :错误。任何可以用递归解决的问题都可以用循环解决。
  • 并非所有递归算法都必须有基本情况 :错误。基本情况是递归算法终止的条件,没有基本情况会导致无限递归。
  • 在基本情况下,递归方法用原问题的较小版本调用自身 :错误。基本情况是不需要递归调用就能解决问题的情况。
6.3 简答题答案
  • 在之前的示例中, message 函数的基本情况是什么 :由于未给出 message 函数的具体代码,无法确定其基本情况。
  • 计算一个数的阶乘的规则如下:如果 n = 0 ,则 factorial(n) = 1 ;如果 n > 0 ,则 factorial(n) = n * factorial(n - 1) 。如果根据这些规则设计一个函数,基本情况和递归情况分别是什么 :基本情况是 n = 0 时,返回 1;递归情况是 n > 0 时,调用 factorial(n - 1) 并乘以 n
  • 递归是否是解决问题所必需的?对于具有重复性的问题,还可以使用什么其他方法 :递归不是解决问题所必需的,对于具有重复性的问题,还可以使用循环来解决。
  • 当使用递归解决问题时,为什么递归函数必须调用自身来解决原问题的较小版本 :通过将问题简化为较小版本,最终可以达到基本情况,从而避免无限递归,使问题能够得到解决。
  • 递归函数通常如何简化问题 :递归函数通常通过改变参数,使问题规模逐渐变小,例如在计算阶乘时,将 n 变为 n - 1
6.4 算法工作台答案
  • 以下程序将显示什么
def main():
    word = 'test'
    show_me(word)

def show_me(word):
    print(word)
    new_word = word[1:]
    if len(new_word) > 0:
        show_me(new_word)

main()

程序将显示:

test
est
st
t
  • 当运行以下代码时, halver 函数将被调用多少次
def main():
    num = 10
    halver(num)

def halver(number):
    print(number)
    half = number / 2
    if half >= 1:
        halver(half)

main()

halver 函数将被调用 4 次,分别处理 10、5、2.5、1.25。
- 将以下使用循环的函数重写为递归函数,实现相同的操作

def queue(length):
    if length > 0:
        print('Please wait.')
        queue(length - 1)
    else:
        print('It is your turn.')
6.5 编程练习答案示例
  • 递归打印
def recursive_print(n):
    if n >= 0:
        print(n)
        recursive_print(n - 2)

recursive_print(10)
  • 递归乘法
def recursive_multiply(x, y):
    if y == 1:
        return x
    else:
        return x + recursive_multiply(x, y - 1)

print(recursive_multiply(3, 4))
  • 递归行
def recursive_lines(n):
    if n > 0:
        recursive_lines(n - 1)
        print('*' * n)

recursive_lines(5)
  • 列表中的最大值
def find_max(lst):
    if len(lst) == 1:
        return lst[0]
    else:
        sub_max = find_max(lst[1:])
        return lst[0] if lst[0] > sub_max else sub_max

numbers = [1, 3, 5, 2, 4]
print(find_max(numbers))
  • 递归列表求和
def recursive_list_sum(lst):
    if len(lst) == 0:
        return 0
    else:
        return lst[0] + recursive_list_sum(lst[1:])

numbers = [1, 2, 3, 4, 5]
print(recursive_list_sum(numbers))
  • 判断回文
def is_palindrome(word):
    if len(word) <= 1:
        return True
    elif word[0] != word[-1]:
        return False
    else:
        return is_palindrome(word[1:-1])

print(is_palindrome('radar'))
  • 递归幂运算
def recursive_power(base, exponent):
    if exponent == 0:
        return 1
    else:
        return base * recursive_power(base, exponent - 1)

print(recursive_power(2, 3))
  • 阿克曼函数
def ackermann(m, n):
    if m == 0:
        return n + 1
    elif n == 0:
        return ackermann(m - 1, 1)
    else:
        return ackermann(m - 1, ackermann(m, n - 1))

print(ackermann(2, 3))
7. 递归算法总结与展望

递归算法是一种强大的编程技术,它通过函数调用自身来解决问题。在本文中,我们介绍了直接递归和间接递归的概念,并通过多个示例展示了递归算法的应用,包括列表元素范围求和、斐波那契数列、最大公约数计算和汉诺塔问题。同时,我们还比较了递归和循环的优缺点,指出递归虽然在某些问题上能提供简洁的解决方案,但通常效率较低。

在实际编程中,我们需要根据问题的特点选择合适的方法。对于一些具有递归性质的问题,如数学定义明确的问题,递归算法可以提供清晰的实现思路;而对于大多数重复性问题,循环可能是更高效的选择。

以下是一个简单的流程图,展示了选择递归或循环的决策过程:

graph TD;
    A[问题] --> B{问题是否具有递归性质};
    B -- 是 --> C{递归算法是否效率可接受};
    C -- 是 --> D[使用递归算法];
    C -- 否 --> E[使用循环算法];
    B -- 否 --> E;

通过不断练习和应用递归算法,我们可以提高编程能力和解决问题的能力。同时,了解递归算法的原理和应用场景,也有助于我们更好地理解计算机科学中的其他概念和技术。希望本文能为你在递归算法的学习和应用中提供帮助。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值