【问题标题】:Unable to understand when a return statement must be used in association with recursion无法理解何时必须将 return 语句与递归关联使用
【发布时间】:2019-06-27 03:25:47
【问题描述】:

我正在复习递归的概念并使用二叉​​树。我不明白在递归调用中使用 return 语句的实例与其他不使用 return 的实例之间的区别。我将给出说明我的问题的代码。

这是一个检查 BST 中是否存在值的函数:

public boolean containsNodeRecursive(Node current, int value) {
    if (current == null) {
        return false;
    }

    if (value == current.data) {
        return true;
    }

    //HERE, THE RECURSIVE CALL IS PRECEDED BY A RETURN STATEMENT
    return value < current.data
      ? containsNodeRecursive(current.left, value)
      : containsNodeRecursive(current.right, value);
   }
}

这是一个将数据插入 BST 的函数:

public Node insert(Node current, int data) {
    if (current == null) {
        return createNode(data);
    } else if (data < current.data) {
        //RECURSIVE CALL HERE WITHOUT USE OF A RETURN STATEMENT
        current.left = insert(current.left, data);
    } else if (data > current.data) {
        current.right = insert(current.right, data);
    }

    return current;
}

我认为我的问题可以归结为:“我们什么时候应该返回递归调用,什么时候不应该?”

【问题讨论】:

  • 递归调用之前有一个return语句” - 不是真的,包含递归调用的语句仍然被评估以获得返回值
  • 就像return 1 + 2 需要在返回之前计算加法的结果一样,return somefunction() 需要计算函数调用的结果,不管递归有多深。如果你的递归不需要返回任何东西,你不一定需要返回任何东西——你只需要使用你的基本情况来终止递归。
  • 简单的答案是:永远不会返回“递归调用”[!] - 你的例子都没有返回调用。(句点)另一方面,方法的返回值(!)是什么该方法应该“计算”。添加数字的方法可能会返回总和,所以这完全取决于您决定该方法应该做什么 - 没有一般规则。
  • 在递归方面有两条规则:a) 尽量避免它。 b)如果你不能避免它,试着让它结束递归(return 是最后一个语句)。 [如果 b) 是可能的,编译器将尝试删除递归...]
  • @kai 尽量避免它,为什么?一些算法,比如这个算法,自然是递归的,最好用这种方式表达

标签: java recursion binary-search-tree


【解决方案1】:

提供了两种类型的函数,因此我们需要遵循两个单独的规则。

取你提供的第一个函数:

public boolean containsNodeRecursive(Node current, int value) {
    if (current == null) {
        return false;
    }

    if (value == current.data) {
        return true;
    }

    //HERE, THE RECURSIVE CALL IS PRECEDED BY A RETURN STATEMENT
    return value < current.data
      ? containsNodeRecursive(current.left, value)
      : containsNodeRecursive(current.right, value);
   }
}

此函数旨在查找单个目标值并将其返回。 在这种情况下,当我们想要返回一个我们知道将由被测试节点的单个子节点提供的值时,我们会返回一个递归调用。

但是,以插入为例:

public Node insert(Node current, int data) {
    if (current == null) {
        return createNode(data);
    } else if (data < current.data) {
        //RECURSIVE CALL HERE WITHOUT USE OF A RETURN STATEMENT
        current.left = insert(current.left, data);
    } else if (data > current.data) {
        current.right = insert(current.right, data);
    }

    return current;
}

此代码旨在完成不同类型的任务。此递归函数旨在修改现有树,而不是寻求单个目标值。 在这种情况下,我们只返回我们想要对现有树(或重复值)进行的修改。

【讨论】:

  • 我不确定我是否遵循您的第二个代码-sn-p。如果你将根节点传递给这个函数,并返回 current.left - 那么你基本上扔掉了树的右边部分,对吧?
  • ...我的大脑严重停机。让我改正过去的错误。
  • 我的代码现已修复。我现在返回插入的节点而不覆盖任何现有节点。感谢您向我指出这一点,Jeppe!
  • 你试过运行它吗?您的代码仍然错误,OP发布的代码是正确的。你不应该为重复返回 null,你应该返回 current 而不修改它 - 并返回递归的结果,为每个节点丢弃一半的树。
  • return current; 显示插入的节点不会被返回,而是实际上是“新”树。 OP 提供了两个递归示例 - 他们没有错 AFAIK。但是,您的代码将一个值向下传递到树,在底部创建一个新节点并将其返回。但它从不将它附加到树上,这就是current.left = insert(current.left, data); 的重点。尝试使用原始代码绘制一个小示例。
【解决方案2】:

由于 Java 不进行尾递归优化,您永远不必“返回递归调用”。见“Why doesn't Java have optimization for tail-recursion at all?

如果您想了解它在函数式语言中的工作原理,请参阅“What is tail recursion?”,但它不适用于 Java。

在 Java 中,您可以在方法中对您更有意义的任何地方进行递归调用。您不必将其作为 return 声明的一部分。

【讨论】:

  • 如果我们对这个术语有相同的理解:java 确实会尽可能地替换递归。但是,如果结构太复杂,它就做不到。 “尾递归”结构简单,有助于编译器优化代码。
  • @kai Java 进行尾递归优化。刚刚在 Java 11 中使用上面link 中显示的tailrecsum 函数进行了测试,我得到了StackOverflowError
  • 现在我只能给你一个IBM的链接。对于甲骨文,我需要挖掘。 ibm.com/support/knowledgecenter/en/SSYKE2_8.0.0/… Shure 你是对的:如果你调用一个“未知函数”,它就会变得太复杂了。
【解决方案3】:

递归前的return关键字与此没有区别:

boolean res = value < current.data
    ? containsNodeRecursive(current.left, value)
    : containsNodeRecursive(current.right, value);

return res;

无论哪种方式,递归都在当前函数调用返回之前被完全评估。

“我们什么时候应该返回递归调用,什么时候不应该?” - 你没有返回递归调用,你返回的是递归的结果,例如 @987654323 @。如果您没有更多的处理要做,您可以立即返回函数调用的结果。这显然要求函数调用返回与当前函数预期返回的类型相同的类型,这在它调用“自身”时保证递归。

对于您的第一个 code-sn-p,请考虑该函数的作用。它搜索一个值,如果在树的下方找到,则该布尔值将简单地转发回树。

在您的第二个示例中,树正在作为递归的一部分进行更改。对于任何节点current,递归返回左/右子树,然后必须覆盖现有的左/右子树才能返回。

【讨论】:

    猜你喜欢
    • 2014-05-26
    • 1970-01-01
    • 2020-01-04
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-09-10
    • 1970-01-01
    相关资源
    最近更新 更多