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的值,用递归方法实现