【问题标题】:Java merge 2 collections in O(1)Java 在 O(1) 中合并 2 个集合
【发布时间】:2012-01-12 04:16:14
【问题描述】:

我需要能够将 2 个大型集合合并为 1 个。我最适合使用哪种集合类型?我不需要随机访问各个元素。通常我会选择链表,但是我不能将 Java 中的 2 个链表与 O(1) 的运行时合并,这可以在许多其他语言中完成,因为我必须将每个元素复制到新列表.

编辑:感谢您的所有回答。您的回答都非常有帮助,我设法完成了工作。下次我将使用我自己的链表实现开始。

【问题讨论】:

  • 排序列表的延迟合并听起来如何?合并的结果可以在 O(1) 中构建,并为列表中的每个操作添加一个摊销的 O(1),直到它被实际评估。
  • 你可以自己实现一个 LinkedList,但 LinkedList 本身就很耗时。
  • I can't merge 2 linkedlist in Java with a runtime of O(1) 这显然不是真的。如果你在 Java 中实现了自己的链表,你可以在 Java 中合并 2 个链表,运行时间为 O(1)。该语句仅适用于标准库实现,因此您的语句可能应为“我无法将 2 java.util.LinkedList 与 O(1) 的运行时合并”。
  • @LieRyan 吹毛求疵?我认为每个人都非常清楚,他在这里清楚地谈论 JDK 提供的类。我们现在真的必须完全量化我们提到的每一个类吗?我已经可以看到它:“嘿,我编写了自己的番石榴 Iterables 类,它不能这样做,你错了!你应该说改用 com.google.common.collect.Iterables”。

标签: java algorithm collections merge


【解决方案1】:

通过使用两个链表作为您的集合,并存储指向每个列表的第一个最后一个元素的指针(添加/删除项目时可能需要更新两个指针),您可以合并O(1) 中的两个列表 - 只需将第一个列表的最后一个元素连接到第二个列表的第一个元素,并相应地调整第一个/最后一个指针。

恐怕您需要在 Java 中滚动您自己的链表实现,因为您无法直接访问 LinkedList 的底层节点,因此您无法连接第一个列表到第二个列表的第一个元素。

幸运的是,在 Java 中很容易找到链表实现,因为它是数据结构课程中非常常见的主题。例如,here 是一个 - 我知道,名称是西班牙语,但 ListaEncadenada ("LinkedList") 和 NodoLista ("ListNode") 中的代码非常简单,应该是不言自明的,并且最重要的是 - 该实现包含指向列表的第一个和最后一个元素的指针,并且可以轻松修改以满足您的需求。

【讨论】:

    【解决方案2】:

    如果您只是想要拥有对象集合并在 O(1) 时间内合并它们,并且不介意实现自己的数据结构,那么最简单的方法是使用不平衡二叉树:每个节点要么是叶子(存储值),要么是两棵树的组合,您可以将它们实现为具有抽象超类或接口的两个类。可以使用深度优先遍历来提取元素。

    这与 ColinD 关于迭代器连接的建议基本相同,但更简单。

    关键是迭代这个集合不会是 O(n)!它将是 O(n + m) 其中 m 是您执行的合并次数(因为每个都是要遍历的节点)。我的解决方案和 ColinD 的解决方案都是如此。我不知道这个问题的所有可能解决方案是否都是如此。

    别管上面的了。在这种方案下,每次合并至少增加一个元素,所以 m n,所以迭代成本仍然是 O(n)。 (如果您确实使用迭代器连接,请确保您不经常连接空迭代器,因为这会增加成本。)

    【讨论】:

      【解决方案3】:

      您可以使用GuavaIterables.concat 方法之一在O(1) 中创建串联的Iterable 视图:

      Iterable<T> combined = Iterables.concat(list1, list2);
      

      这将允许您将两个列表的所有元素作为一个对象进行迭代,而无需复制任何元素。

      【讨论】:

        【解决方案4】:

        我想建议 apache.commons 中的 CompositeCollection 类,但看看 source code 这也运行在 O(n) 中。 如果您只需要迭代元素并且不想使用 ColinD 建议的 Google Collections,您可以轻松创建自己的复合迭代器,例如

        public class CompositeCollectionIterator<T> implements Iterator<T>{
        
          private Iterator<T>[] iterators;
          private int currentIteratorIndex = 0;
          public CompositeCollectionIterator( Collection<T>... aCollections ) {
            iterators = new Iterator[ aCollections.length];
            for ( int i = 0, aCollectionsLength = aCollections.length; i < aCollectionsLength; i++ ) {
              Collection<T> collection = aCollections[ i ];
              iterators[i] = collection.iterator();
            }
          }
        
          public boolean hasNext() {
            if ( iterators[currentIteratorIndex].hasNext() ) return true;
            else if ( currentIteratorIndex < iterators.length - 1 ){
              currentIteratorIndex++;
              return hasNext();
            }
            return false;
          }
        
          public T next() {
            return iterators[currentIteratorIndex].next();
          }
        
          public void remove() {
            iterators[currentIteratorIndex].remove();
          }
        }
        

        【讨论】:

          【解决方案5】:

          合并链表确实是 O(1),并且您可以以相同的方式考虑基于数组的列表,即在其间链接多个 Object[]。

          上面有实现,从中间/开始删除/插入时比ArrayList快。 迭代几乎是一样的。不过,随机访问可能会稍微慢一些。

          【讨论】:

            【解决方案6】:

            我认为最好的办法是创建一个 List 的实现,它以 List> 作为其参数,然后进行委托。换句话说,有一个列表列表,并将它们连接起来作为一个列表。当您越过列表 1 的元素时,您会开始查看列表 2。

            出于某种原因,我认为番石榴有这样的清单。但我在他们的 javadocs 中找不到。

            【讨论】:

            • 并且由于它实现了标准的 List 接口,它可以在其他代码中使用。 +1
            【解决方案7】:

            理论上,您可以在 O(1) 中合并 2 个链表,因为您所要做的就是将第一个的最后一个节点指向第二个的第一个节点(假设您有这些引用)。

            addAll 的收集方法似乎暗示了 O(n) 的运行时间,因为他们在谈论迭代器。细节可能是 JVM 特定的......

            我认为没有任何集合可以在 O(n) 中组合。您可能需要自己动手。

            【讨论】:

            • 我认为问题是如何在 Java 中做到这一点。因此,他可以像处理任何其他集合一样处理最终结果,而无需创建自己的类。 +1 顺便问一下问题
            • 我知道,但正如 Jan 所说,我想知道这在 Java 本身中是否已经可行。
            • 最后一个假设在 Java 中真的成立吗?
            • @hvgotcodes addAll 方法必须复制节点,否则对源列表的修改将在两者中可见。 moveAll 方法将允许 O(1),因为原始列表将失去对节点的访问。
            • @Tiddo 我认为这是您问题的真正解决方案,使用包装器或迭代器不会合并实际列表
            【解决方案8】:

            这里最简单的解决方案实际上是列表列表。意味着您需要一些简单的包装函数,但并不复杂。

            【讨论】:

            • 这可能是一个解决方案,我要试试这个
            • 虽然这个问题有点老了,但我必须在这个解决方案中添加一条评论:在我的特殊情况下,我从集合开始,每个集合都只有一个元素,需要合并在某些条件下一起,直到只剩下一个列表。但是,如果您使用列表技术,您将创建一个非常深的列表层次结构,因此这不会非常有效。因此,此解决方案仅在列表不经常合并时才有效。
            猜你喜欢
            • 1970-01-01
            • 2017-07-21
            • 2020-06-18
            • 2012-05-30
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2016-12-02
            相关资源
            最近更新 更多