【问题标题】:How are values returned back when the base case is reached达到基本情况时如何返回值
【发布时间】:2019-07-06 05:00:19
【问题描述】:

我正在学习一些基本算法,然后我遇到了欧几里得算法来找到两个数字的 GCD。

我理解纸上的算法。 有一个迭代代码可以做同样的事情

int euclid_gcd(int a, int b){
    int dividend = a>=b ? a : b;
    int divisor = a<=b ? a : b;
    while(divisor!=0){
        int remainder = dividend % divisor;
        dividend = divisor;
        divisor = remainder;
        }
    return dividend; 
}

我对上面的迭代代码很满意 然后还有两个相同代码的递归版本

int gcd(int a, int b){
    if(a==b) 
        return a;
    if(a>b) 
        return gcd(a-b,b);
    return gcd(a,b-a);
}

这是线数最小的]

int gcd(int a, int b){ 
    if (a == 0) 
        return b; 
    return gcd(b % a, a);
} 

根据我对递归的理解,在递归中,我们尝试使用我们知道的答案(基本情况)来找到复杂问题(一般情况)的答案

随着递归调用的叠加,我们基本上会解决更简单的问题,直到遇到基本情况。基本情况返回一个值,并且由于该值被返回,所有堆叠子问题的答案开始冒泡到原始函数调用,最终我们得到了问题的答案。

我不明白上面放置的函数调用如何使用基本情况返回的值

这是我尝试干运行上述代码(第三个)。函数调用是

gcd(20,8);

gcd(20,8) -> gcd(8,20) -> gcd(4,8) -> gcd(0,4)

现在我们使用函数调用gcd(0,4) 达到基本情况

它返回了4

现在前面的函数调用 gcd(4,8) 是如何使用 4

我们没有“捕捉”任何变量中的返回值,那么该值究竟会发生什么以及最终答案(在本例中为 4)如何冒泡并由原始函数调用返回?

【问题讨论】:

  • “我们没有‘捕捉’任何变量中的返回值 [...]” 不,但是你正在“捕捉”它在您的return gcd(b % a, a); 语句中,它实际上是说“调用此函数,获取返回的值,并将其用作我自己的返回值(或者换句话说,“转发”返回值)。”
  • "根据我对递归的理解,在递归中,我们尝试使用我们知道的答案(基本情况)来找到复杂问题(一般情况)的答案" -不,你太过分了。
  • @Cornstalks 明白了,所以 gcd(20,8) 称为 gcd(8,20),gcd(8,20) 称为 gcd(4,8),gcd(4,8) 称为 gcd( 0,4)。 gcd(0,4) 将 4 返回给它的调用者,然后 4 被所有这些函数返回给它们上面的调用者。知道了。谢谢
  • 在每次递归调用之前添加std::cout 将映射您正在发生的事情并帮助您可视化流程。当你返回一个值时,你就完成了。当你返回一个函数时,控制只是在带有新参数的新函数中获得。它一直持续到您返回一个值。例如,添加 std::cout &lt;&lt; a &lt;&lt; " == " &lt;&lt; b &lt;&lt; " - returning " &lt;&lt; a &lt;&lt; '\n'; 或,例如std::cout &lt;&lt; a &lt;&lt; " &gt; " &lt;&lt; b &lt;&lt; " - returning gcd(a-b,b)\n"; 等...应该可以解决问题。

标签: c++ recursion greatest-common-divisor


【解决方案1】:

考虑这个例子:

int functionA() {
    return functionB();
}

这段代码将调用functionB,并直接将functionB的结果返回给functionA的调用者,无需对其进行操作。相当于写:

int functionA() {
    int toReturn = functionB();
    return toReturn;
}

正如您所说,函数 B 返回的值“没有被functionA 中明确定义的变量捕获”。

也许递归让您感到困惑,但您的问题实际上与递归无关。它对任何函数调用都有效。

【讨论】:

  • pastebin.com/cQbxuTMK 检查这段代码。我已经实现了递归二进制搜索。有两种功能,一种是注释的,另一种是非注释的。一个在递归调用之前“返回”,而另一个没有,但两个函数仍然工作相同。那么这是否意味着递归调用函数时不需要添加'return'?
  • 哦!一个非常有趣的例子,谢谢。令我惊讶的是,这两个代码都产生了相同的结果。但是你不应该依赖这个。如果您未能在函数中明确地返回语句,则行为是未定义的(意味着编译器会返回一些东西,但您不能确定它会返回什么)。恰好编译器在您的特定情况下做出了“好的”选择。看看这个问题/答案stackoverflow.com/a/2784137/9070798
【解决方案2】:

我认为你对递归的理解是正确和完整的。你只需要了解一点调用栈在函数调用中的作用。你可以在网上找到很多帖子来掌握这个概念。在这里,我将尝试为您提供 调用堆栈 概念的简要定义。

程序的内存分为多个段。其中最重要的两个是 StackHeap。在这里,我们将重点关注 Stack

您调用的每个局部变量和每个函数都在那里。这是有关您的程序的相关信息所在的位置——调用了哪些函数、您创建了哪些变量以及更多信息。此内存也由程序管理,而不是由开发人员管理。堆栈是一个有序的插入位置。

堆栈是一种 LIFO(后进先出)数据结构。您可以将其视为一盒完美贴合的书——您放置的最后一本书是您取出的第一本书。通过使用这种结构,程序可以使用两个简单的操作轻松管理其所有操作和范围:push 和 pop。

为了跟踪当前的内存位置,有一个称为堆栈指针的特殊处理器寄存器。每次你需要保存一些东西——比如一个变量或者一个函数的返回地址——它都会向上推栈指针并向上移动。每次从函数退出时,它都会弹出堆栈指针中的所有内容,直到保存的函数返回地址。

我想现在你知道递归调用会发生什么了。每个函数都有自己的变量存储在堆栈中。当一个函数到达它的返回语句时,将结果压入堆栈,另一个调用此函数的函数将从堆栈中弹出结果。

现在您知道调用堆栈的工作原理了。这是理解递归的关键概念。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2020-03-08
    • 1970-01-01
    • 2014-03-03
    • 1970-01-01
    • 2013-07-09
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多