递归算法的深入解析与应用
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.')
-
编程练习
-
递归打印
:设计一个递归函数,接受一个整数参数
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;
通过不断练习和应用递归算法,我们可以提高编程能力和解决问题的能力。同时,了解递归算法的原理和应用场景,也有助于我们更好地理解计算机科学中的其他概念和技术。希望本文能为你在递归算法的学习和应用中提供帮助。
超级会员免费看
1434

被折叠的 条评论
为什么被折叠?



