【问题标题】:Java Recursion - Alternative to passing-by-reference:Java 递归 - 引用传递的替代方案:
【发布时间】: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 中作为值传递,它只是地址

标签: java recursion pass-by-value


【解决方案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。 缺点是每个空值都会占用内存(在某些情况下,例如大型红黑树,这可能会变得很重要)。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2015-06-17
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2013-07-22
          • 2011-05-02
          相关资源
          最近更新 更多