卢比中的递归介绍
递归主要关注的是函数如何在自身内部定义。递归函数有两个主要部分;递归和基例。递归部分决定了一个方法何时被调用,而基例则在函数停止时被调用。
递归可以用来解决复杂的数学问题,把它们分解成更小的组成部分。在这篇文章中,我们将讨论简单的递归调用,复杂的函数,以及递归编程的策略。
前提条件
要想跟着学习,你应该具备以下条件。
- 对Ruby编程语言有基本了解。
- 一个文本编辑器,最好是Visual Studio Code。
简介
许多人使用辅助方法来解决复杂的问题。在一个辅助方法中,你从一个不同的方法中调用一个函数。递归几乎是类似的,只不过你是在同一个方法里面调用这个方法。递归编程大多用于解决数学问题。
让我们来写第一个简单的递归方法。
def say_hi
p "hi"
say_hi
end
say_hi # prints "hi" until it crashes
你会注意到,say_hi 方法仍然像普通方法一样遵守所有规则,除非我们调用它,否则不会运行。
让我们看看这段代码是如何工作的。
- 当我们第一次调用
say_hi,我们打印hi,并再次调用say_hi函数。 - 第二次调用时,运行方法定义,并再次打印
hi。这种模式一直持续到该方法崩溃的时候,stack error。
我们的say_hi 方法进入了一个无限的循环。每次我们调用一个函数,一些系统内存就被分配给该方法的执行。
由于我们在不停地打印hi ,我们将耗尽内存,程序将崩溃,显示这个SystemStackError: stack level too deep 错误。
递归方法的部分内容
当使用递归方法时,我们应该实现一种方法来阻止我们的程序永远循环下去。为了达到这个目的,我们使用一个停止递归的语句。
正如介绍中指出的,递归方法有两个重要部分。
- 基本情况下,我们通过不进行另一次调用来停止递归。
- 递归步骤,在这个步骤中,我们通过进行其他调用来推进递归。
让我们来写一个递归工作方法。
这个递归方法将计算任何给定数字(n)的阶乘。
为了得到一个数字的阶乘,我们需要得到1和提供的数字之间的所有整数的乘积。例如,如果我们写下几个数字的阶乘,我们会发现一个模式。
# factorial(4) = 4 * 3 * 2 * 1
# factorial(3) = 3 * 2 * 1
# factorial(2) = 2 * 1
# factorial(1) = 1
在上面的阶乘中,这些数字一直在减少1。
这些阶乘也可以写成。
# factorial(4) = 4 * factorial(3)
# factorial(3) = 3 * factorial(2)
# factorial(2) = 2 * factorial(1)
# factorial(1) = 1
阶乘的递归实现将是。
def factorial(num)
return 1 if num == 1 # base case
num * factorial(num - 1); # recursive step
end
factorial(4) # => 24
如何以递归方式解决一个问题
- 确定基本情况。基准案例应该完全涵盖参数很小的情况,即我们不用做任何计算就能知道结果。
- 解决问题的
immediate next case,并测试它。 - 在每个层次上对问题进行归纳。
迭代与递归
所有的递归方法都可以用loops 和不recursion 来迭代实现。
让我们试着用迭代来实现阶乘方法。
def factorial(num)
facto = 1
(1..num).each do |i|
facto *= i
end
facto
end
虽然可以用迭代方式编写递归方法,但这种技术比较复杂。因此,当人们决定是用递归方法还是迭代方法来解决问题时,他们应该选择一种他们很熟悉的方法。
递归调用中的堆栈创建
stack 是一种用于存储对象的数据结构。使用push 操作,项目可以被单独添加到堆栈中进行存储。pop 函数使你能够从堆栈中删除对象。
每次进行递归调用时,都会向堆栈中添加,直到到达基本情况。Stack frames 是堆栈中的元素,它们包含方法的local variables 。
在发生infinite loop ,堆栈不断增长,直到系统的内存耗尽,这被称为stack overflow 。
递归编程的步骤
- 创建一个递归分解。弄清楚问题是如何递归分解的。通过这个过程,你能够找出关键和有帮助的点。
- 弄清基本情况。一旦达到基例,堆栈就会停止增长,并开始对其他递归调用进行评估。
- 从基本情况向上解决一级问题。评估方法的结果,以防有一个递归调用要做。
- 检查从任何情况下返回的值是否属于相同的数据类型。如果你的递归方法的结果是一个字符串,那么所有的案例都应该返回一个字符串。
总结
递归的确可以帮助你节省大量的时间。然而,你应该使用基例来指定执行或程序应该何时停止。
忽视这一关键环节可能会导致软件崩溃。此外,你的计算机可能会变慢,因为大量的内存和处理能力将被分配给递归函数的执行。