Ruby中的递归介绍

152 阅读5分钟

卢比中的递归介绍

递归主要关注的是函数如何在自身内部定义。递归函数有两个主要部分;递归和基例。递归部分决定了一个方法何时被调用,而基例则在函数停止时被调用。

递归可以用来解决复杂的数学问题,把它们分解成更小的组成部分。在这篇文章中,我们将讨论简单的递归调用,复杂的函数,以及递归编程的策略。

前提条件

要想跟着学习,你应该具备以下条件。

  1. Ruby编程语言有基本了解。
  2. 一个文本编辑器,最好是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 错误。

递归方法的部分内容

当使用递归方法时,我们应该实现一种方法来阻止我们的程序永远循环下去。为了达到这个目的,我们使用一个停止递归的语句。

正如介绍中指出的,递归方法有两个重要部分。

  1. 基本情况下,我们通过不进行另一次调用来停止递归。
  2. 递归步骤,在这个步骤中,我们通过进行其他调用来推进递归。

让我们来写一个递归工作方法。

这个递归方法将计算任何给定数字(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

如何以递归方式解决一个问题

  1. 确定基本情况。基准案例应该完全涵盖参数很小的情况,即我们不用做任何计算就能知道结果。
  2. 解决问题的immediate next case ,并测试它。
  3. 在每个层次上对问题进行归纳。

迭代与递归

所有的递归方法都可以用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

递归编程的步骤

  • 创建一个递归分解。弄清楚问题是如何递归分解的。通过这个过程,你能够找出关键和有帮助的点。
  • 弄清基本情况。一旦达到基例,堆栈就会停止增长,并开始对其他递归调用进行评估。
  • 从基本情况向上解决一级问题。评估方法的结果,以防有一个递归调用要做。
  • 检查从任何情况下返回的值是否属于相同的数据类型。如果你的递归方法的结果是一个字符串,那么所有的案例都应该返回一个字符串。

总结

递归的确可以帮助你节省大量的时间。然而,你应该使用基例来指定执行或程序应该何时停止。

忽视这一关键环节可能会导致软件崩溃。此外,你的计算机可能会变慢,因为大量的内存和处理能力将被分配给递归函数的执行。