递归函数背后的心态

时间:2016-10-07 10:40:55

标签: python recursion

我试图学习递归函数,但我似乎无法理解递归的想法。我已经观看了有关递归和观看教程的视频,但是当我尝试自己解决这些问题时,我无法想到所需的递归。但是,我可以使用循环和迭代很快地解决这些问题。

例如,我看到了一个关于查找数字中位数的问题,答案是:

def digit(n):
  if n < 10:
      return 1
  else:
      return 1 + digit(n/10)

我知道if是递归停止的一个点,但即使看了答案,我也不明白else部分的工作原理或原因。

使用递归函数时,我的思维过程应该是什么样的?

5 个答案:

答案 0 :(得分:5)

如果手头的问题也是递归的,递归非常有用。一个这样的问题是遍历树状数据结构。正在编译的任何计算机程序都会产生称为语法树的结构。编译器遍历树并为它找到的分支生成代码。我知道这本身并不能帮助你理解递归,但它只是要明确递归是一个非常实用的概念。只有给出的例子大部分都是人为的,因为“真实的”是真实的。例子需要太多的先验知识。

至于你的例子,一些印刷品应该明确发生了什么:

def digit(n):
    print ('Entering "digit" with n == {}'.format (n))

    if n < 10:
        print ('In the "if" now')
        return 1
    else:
        print ('In the "else" now')
        return 1 + digit(n/10)

print (digit (10000))

修改后的代码使其更加清晰,请尝试逐步执行:

def digit(n):
    print ('\nCalling "digit" with n == {}'.format (n))

    if n < 10:
        print ('In the "if" now for n == {}'.format (n))
        result = 1
        print ('Exiting "digit" from the "if" with n == {}, result now is {}'.format (n, result))
        return result
    else:
        print ('In the "else" now for n == {}'.format (n))
        result = 1 + digit(n/10)
        print ('Exiting "digit" with n == {}, result now is {}'.format (n, result))
        return result


print ('Nr of digits is: {}'.format (digit (10000)))

打印:

D:\aaa>call C:\Python35\python.exe so.py 

Calling "digit" with n == 10000
In the "else" now for n == 10000

Calling "digit" with n == 1000.0
In the "else" now for n == 1000.0

Calling "digit" with n == 100.0
In the "else" now for n == 100.0

Calling "digit" with n == 10.0
In the "else" now for n == 10.0

Calling "digit" with n == 1.0
In the "if" now for n == 1.0
Exiting "digit" from the "if" with n == 1.0, result now is 1
Exiting "digit" with n == 10.0, result now is 2
Exiting "digit" with n == 100.0, result now is 3
Exiting "digit" with n == 1000.0, result now is 4
Exiting "digit" with n == 10000, result now is 5
Nr of digits is: 5

以下内容也有所帮助:每次调用函数时,都会在内存中堆积一些新的本地数据。在这种情况下,该数据块只是参数n,它被存储为局部变量。并且随着调用的每个退出(所以在其中一个返回),这个数据块被从堆栈中取出并丢弃。简洁来说:每个函数调用都有自己的堆栈框架。

拿一些纸,每次调用(见输出),在上面写n并将其放在堆栈上。然后为每个出口扔掉顶部的纸张。虽然这不是灵丹妙药,但它可能有助于你的想象力。

底线:在点击&#34;之前可能需要相当长的时间。在你的大脑中制造。但它真的很值得。如果需要一周或更长时间,请不要惊讶。这是正常的,虽然并非所有程序员都会承认这一点。尝试使用我的答案中的输出和一堆纸质笔记逐步执行程序执行。过了一会儿:点击......如果你头晕,不要把问题盯住超过四分之一,请在第二天再试一次(从经验......)。

Python专家注意:&#39;堆栈&#39; Python中的模型只是在概念上,而在例如C ++它是真实的。但它是递归行为的一个很好的模型。

答案 1 :(得分:1)

递归的关键是你通过使用相同问题的较小版本的解决方案来解决问题。

在您的情况下,您可以通过删除最后一位数字来计算位数,然后添加剩余位数的计数

digits(n) = 1 + digits(n/10)
digits(n) = 1 + (1 + digits((n/10)/10))
...

在某些时候,您必须具有digits(n)的具体值,否则它将永远持续下去,因此您将基本案例定义为已知值。当n < 10时,我们知道它只有一位数。

这是一个非常简单的例子,但递归对于理解问题非常有用。

答案 2 :(得分:0)

此图片可能会帮助您更直观地理解:

continued fraction

每个分数都是对digit函数的另一个调用,最终会停止更改。然后程序自下而上地计算它解开&#39;指令堆栈。

对于先前执行的结果执行相同的instructon 非常有用。有时您可以通过迭代更轻松地完成此操作,有时您无法做到。它通常意味着存储必须更新的较少状态。

答案 3 :(得分:0)

想象一下这样的函数:digit(100000)

第一次获得结果1 + digit(10000)

您的下一个电话digit(10000)会产生1 + digit(1000)

这一直持续到digit(1)返回1并且不再有函数调用。

你有这个函数调用链现在都处于活动状态,等待能够完成他们的return语句:

digit(100000) + digit(10000) + digit(1000) + digit(100) + digit(10) + digit(1)

因为它以digit(1)结束而现在不再调用该函数,所以return - 语句从内到外进行评估:

digit(100000) + digit(10000) + digit(1000) + digit(100) + digit(10) + 1
digit(100000) + digit(10000) + digit(1000) + digit(100) + 2
digit(100000) + digit(10000) + digit(1000) + 3
digit(100000) + digit(10000) + 4
digit(100000) + 5
6

也许将每个被调用的函数视为一个单独的实体,它只遵循简单的逻辑规则。它调用另一个函数,获取其返回值并创建自己的返回值。而已。函数的具体实例及其定义是两个不同的东西,就像同一个类可以存在多次,即使它只被定义一次。

答案 4 :(得分:0)

我会尽力向你解释,因为这对我来说也是相当艰巨的。在这个阶段,您可能会发现使用循环解决问题比使用递归解决问题更容易,但是一旦您了解它,您可能会发现它有所不同。

让我们看一些可以使用递归解决的示例问题:

示例1:计算数字的阶乘。

我们知道n!= n *(n-1)*(n-2)* ... 1。现在,在使用递归解决问题时,如果您知道任何子问题的答案,您必须问问题是否更简单。在这种情况下,如果你只知道(n-1)的答案,你会发现它会容易得多!作为n *(n-1)!会给你想要的结果。

因此,在为此问题编写代码时,除了我们已经讨论过的内容之外,您不需要考虑其他任何事情。因此,让我们使用迄今为止所知的函数来编写因子函数:

search

在撰写此行def factorial(n): return n*factorial(n-1) 时,请不要考虑如何计算结果,只是假设计算机正在为您正确计算return n*factorial(n-1)并返回结果(暂时不要考虑它,但后来你必须了解递归如何使用堆栈。尝试使用笔和纸模拟递归程序以使事情更加清晰)。

所以现在你不必担心事情会在后台如何运作,但只要看一下这段代码就可以看出它永无止境。因此,为了结束这个过程,您只需添加一个基本案例。基本案例是最简单的案例,其结果可以通过查看问题来判断。在这种情况下,您可以说factorial(n-1),结果应为n=0。所以只需在您的代码中添加以下行:

1

示例2:计算正数的正整数幂。

现在您必须计算def factorial(n): if(n==0): return 1 return n*factorial(n-1) 并且您知道如果您知道x^n,那么您可以使用x^(n-1)轻松计算x^n。您进一步了解最简单的问题的答案,即x*x^(n-1),即如果x^0=1n,则结果应为0。所以现在你可以像我们之前的情况一样编写代码。

练习更多的递归问题,并使用笔和纸模拟所有问题,以便更好地了解事物的运作方式。并特别模拟this代码以更好地了解递归。希望它有所帮助。