Java 递归 - 引用传递的替代方案:

Posted

技术标签:

【中文标题】Java 递归 - 引用传递的替代方案:【英文标题】:Java Recursion - Alternative to passing-by-reference: 【发布时间】:2017-03-10 23:29:28 【问题描述】:

我正在从 C 迁移到 Java,但在递归方面遇到了困难,特别是因为在 Java 中您不能通过引用传递参数。

我正在寻找的不是强制 Java 通过引用传递参数的解决方案/技巧,而是在 Java 中解决此类问题的推荐方法。

让我们在二叉树中递归节点插入:

void nodeInsert(Node n, int a) 
    if (n == null)
        n = new Node(a);
...

在 C 中,在执行结束时,树中的节点 n 将指向新创建的节点。然而,在 Java 中,n 仍然是 null(因为 n 是按值传递的)。

对于此类问题,建议的 Java 方法是什么? 我已经尝试过的一些方法:

使用静态对象跟踪父对象(使用泛型时问题变得复杂)。 将父节点作为函数的一部分传递。它可以工作,但会使代码有点复杂,看起来不是一个好的解决方案。 创建一个指向父节点的附加成员,但这不是一个好的解决方案,因为它增加了 O(n) 所需的空间;

欢迎任何建议。

【问题讨论】:

呃,你有没有考虑返回一个节点:Node nodeInsert(Node n, int a)?或者您是否需要返回任何内容 - 您不能只创建节点并将其添加到insertNode() 内的列表或树中吗?为什么你认为你需要一个“输出参数”? 这种方法确实可行,但它不会超越递归的目标,即解决基本情况(在本例中,当节点为空时)? 如果有效,则达到目标。 但这适用于这种非常简单的情况,其中只需要返回一个指针。肯定不会在平衡树的实现中起作用。这就是我要求提供最佳实践而不仅仅是解决方案的原因。 如果 Node 是一个自定义对象,它已经是一个 ReferenceType ,基本上当你传递 'n' 进行下一次传递时它不应该为空,你可能想再次检查代码,我想说的是对 Node 的引用在 java 中作为值传递,它只是地址 【参考方案1】:

在 Java 中,我们不使用 引用变量,而是使用 return 值并将其分配给必须更改的变量。

    Node nodeInsert(Node n, int a) 
        if (n == null)
            n = new Node(a);
            return n;
        
        else
        
            ....
            return nodeInsert(n,a); //this is how a recursion is done.
            ....
        

    

如果你需要更多关于递归的知识,http://www.toves.org/books/java/ch18-recurex/ 会教你正确的。

【讨论】:

【参考方案2】:

一种常见的实现方式是在节点自身内部维护节点关系。在各种 JDK 数据结构的实现中可以找到很多示例。因此 Node 是值的容器,包含对其他节点的引用,具体取决于数据结构。

如果您需要节点之间的子->父关系,Node 类看起来像

class Node<T> 
    T value;
    Node parent;

在插入的情况下,您创建一个新节点,将父引用设置为原始节点,并将新节点作为结果返回(这是可选的,但并不罕见,因此调用具有新孩子)

Node<T> insert(Node<T> parent, T value) 
  Node<T> child = new Node<>();
  child.value = value;
  child.parent = parent;
  return child;

是的,这增加了每个节点 4 字节的小开销(或 8 字节,在没有压缩指针的 64 位 JVM 上)

【讨论】:

【参考方案3】:

我提出以下解决方案:

在类Node 中实现一个添加子节点的方法。这利用了 OO 的可能性将数据和功能一起封装在一个类中。

更改nodeInsert 以返回新节点并将其添加到调用者中的父节点(在 cmets 中也提到过)。 nodeInsert 的职责是创建节点。这是一个明确的责任,方法签名显示方法的结果是什么。如果创建的数量不超过new Node(),则可能不值得为它使用单独的方法。

【讨论】:

【参考方案4】:

您可以传递一个持有者对象,该对象又引用您的新 Node 对象

void nodeInsert(AtomicReference<Node> r, int a) 
    if (r.get() == null)
        r.set(new Node(a));
...

或者您可以传递一个包含一个元素的空间的数组。

【讨论】:

有趣的方法。我会试试的。 @Nelsao 请注意,AtomicReference 是一种并发数据结构,可能会影响性能。 传递一个数组或列表,以及被调用者修改该容器中的元素,是“输出参数”的常用习语。 “AtomicReference”(不是“AtomicReverence”;))对于这个问题可能是一个糟糕的选择。但真正的重点是:“如果你不需要一个“输出参数”......那么不要试图拼凑一个”。恕我直言...【参考方案5】:

在发布这个问题几个月后,我意识到了另一种解决方案,事实上,Java 设计模式中已经考虑过但这里没有提到:Null Object Pattern。 缺点是每个空值都会占用内存(在某些情况下,例如大型红黑树,这可能会变得很重要)。

【讨论】:

以上是关于Java 递归 - 引用传递的替代方案:的主要内容,如果未能解决你的问题,请参考以下文章

java8新特性→方法和构造函数引用:替代Lambda表达式

Java学习——方法中传递参数分简单类型与复杂类型(引用类型)编程计算100+98+96+。。。+4+2+1的值,用递归方法实现

将向量作为对递归函数的引用传递

在递归函数的函数调用中修改通过引用传递的参数

Java基本类型和引用类型(值传递)

java中的参数传递——值传递引用传递