【问题标题】:Concatenate two java.util.LinkedList in constant time在恒定时间内连接两个 java.util.LinkedList
【发布时间】:2016-11-13 05:25:36
【问题描述】:

我正在编写一段代码,它非常热门,我需要将一个 LinkedList (l1) 的元素添加到另一个 LinkedList (l2) 中。

不能使用addAll(Collection) 方法,因为它使用Iterator 迭代整个Collection

在我看来,应该可以将l1 的最后一个Node 设置为指向l2 的第一个Node。但是我找不到任何合适的方法吗?我需要自己的LinkedList 实现来实现吗?

【问题讨论】:

  • 看来标准的LinkedList 实现不支持此功能,q.v。 this SO post.
  • 只使用列表列表并在以后迭代这些列表是否有意义?
  • 我认为 google-collections Iterables.concat 是你要找的东西:stackoverflow.com/a/2494530/3850595
  • 您应该会发现,将 ArrayList 附加到另一个 ArrayList 会更快,因为指针追逐更少。
  • 您是否需要将结果设为具有某些特定功能的LinkedList?或者如果它实现了List 接口就足够了吗?您确定不会对结果列表进行索引访问,而是通过Iterator 进行迭代吗? (可以将两个列表简单地包装到一个扩展 AbstractList 的新类中,并提供一个简单的“连接 Iterator”)

标签: java collections linked-list


【解决方案1】:

没有。您不能添加两个 java.util.LinkedLists 以在恒定时间 O(1) 内输出另一个。

从概念上讲,您确实可以在恒定时间 O(1) 内将两个链表数据结构添加在一起,但它们需要以正确的方式实现。正如您所提到的,实现应该通过将最后一个节点连接到第一个节点来组合两个列表,并且不会遍历任何一个列表。

【讨论】:

    【解决方案2】:

    根据 cmets,目标是在串联列表上创建类似于“视图”的东西 - 意味着数据不应该被复制。相反,给定的列表应该像单个列表一样“出现”。

    实现这一点的一种方法是扩展AbstractListget(int)size() 的实现相当简单。关键是为串联列表创建一个Iterator。以下是一个非常简单的草图,说明了如何实现这一点(但请参阅下面的注释)

    import java.util.AbstractList;
    import java.util.Arrays;
    import java.util.Collections;
    import java.util.Iterator;
    import java.util.List;
    
    public class MergedListTest
    {
        public static void main(String[] args)
        {
            testBasic();
            testEmptyA();
            testEmptyB();
        }
    
        private static void testBasic()
        {
            List<Integer> list0 = Arrays.asList(0,1,2);
            List<Integer> list1 = Arrays.asList(3,4,5);
            List<Integer> expected = Arrays.asList(0,1,2,3,4,5);
            List<Integer> actual = new MergedList<Integer>(list0, list1);
            System.out.println(actual.equals(expected));
        }
    
        private static void testEmptyA()
        {
            List<Integer> list0 = Collections.emptyList();
            List<Integer> list1 = Arrays.asList(3,4,5);
            List<Integer> expected = Arrays.asList(3,4,5);
            List<Integer> actual = new MergedList<Integer>(list0, list1);
            System.out.println(actual.equals(expected));
        }
    
        private static void testEmptyB()
        {
            List<Integer> list0 = Arrays.asList(0,1,2);
            List<Integer> list1 = Collections.emptyList();
            List<Integer> expected = Arrays.asList(0,1,2);
            List<Integer> actual = new MergedList<Integer>(list0, list1);
            System.out.println(actual.equals(expected));
        }
    
    }
    
    
    class MergedList<T> extends AbstractList<T>
    {
        private final List<T> list0;
        private final List<T> list1;
    
        MergedList(List<T> list0, List<T> list1)
        {
            this.list0 = list0;
            this.list1 = list1;
        }
    
        @Override
        public T get(int index)
        {
            if (index < list0.size())
            {
                return list0.get(index);
            }
            return list1.get(index - list0.size());
        }
    
        @Override
        public Iterator<T> iterator()
        {
            return new Iterator<T>() 
            {
                private Iterator<T> current = list0.iterator();
                private boolean first = true;
    
                @Override
                public boolean hasNext() 
                {
                    return current != null && current.hasNext();
                }
    
                @Override
                public T next() 
                {
                    T result = current.next();
                    if (!current.hasNext())
                    {
                        if (first)
                        {
                            current = list1.iterator();
                        }
                        else
                        {
                            current = null;
                        }
                    }
                    return result;
                }
            };
        }
    
        @Override
        public int size()
        {
            return list0.size() + list1.size();
        }
    }
    

    从概念上讲,从AbstractSequentialList 继承会更有意义:AbstractList 提供存根实现,例如iterator(),最终委托给 get(int),而 AbstractSequentialList 提供“相反的”存根实现,例如get(int) 最终委托给 iterator()。但是,这需要一个ListIterator&lt;T&gt; 实现,这比上面的简单草图要复杂一些。

    另外请注意,我假设生成的视图应该是不可修改的 - 但这应该与给定的描述一致。

    最后,请注意(当然)已经有针对此任务和类似任务的实现,并且这些实现可能比上面概述的更复杂。例如,Google Guava 提供了不同的 Iterators#concat 方法,允许您连接多个迭代器。所以如果你已经在使用 Guava,上面的iterator() 方法的实现可以归结为

    @Override
    public Iterator<T> iterator()
    {
        return Iterators.concat(list0.iterator(), list1.iterator());
    }
    

    【讨论】:

      【解决方案3】:

      标准库似乎不支持所需的功能。但是,您可以在合并链表时使用反射来实现恒定时间。 我没有正确测试以下代码,只是为了展示它是如何实现的:

      public class ListConcatenation {
      
      public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException, ClassNotFoundException {
          LinkedList<String> list1 = new LinkedList<>();
          list1.add("a");
          list1.add("b");
          list1.add("c");
      
          LinkedList<String> list2 = new LinkedList<>();
          list2.add("d");
          list2.add("e");
          list2.add("f");
      
          System.out.println(merge(list1, list2));
      }
      
      public static List<String> merge(List<String> list1, List<String> list2) throws NoSuchFieldException, IllegalAccessException, ClassNotFoundException {
          Field first = getDeclaredField(LinkedList.class, "first");
          Field last = getDeclaredField(LinkedList.class, "last");
          Field size = getDeclaredField(LinkedList.class, "size");
      
          Class<?> nodeClass = Class.forName("java.util.LinkedList$Node");
          Field next = nodeClass.getDeclaredField("next");
          next.setAccessible(true);
      
          Object list1Last = last.get(list1);
          Object list2First = first.get(list2);
          Object list2Last = last.get(list2);
      
          // link the last element of list1 with the first element of list2
          next.set(list1Last, list2First);
          // set last element of list1 equal to the last element of list2
          last.set(list1, list2Last);
          // update list1 size
          size.set(list1, list1.size() + list2.size());
      
          return list1;
      }
      
      private static Field getDeclaredField(Class<?> clazz, String name) throws NoSuchFieldException {
          Field field = clazz.getDeclaredField(name);
          field.setAccessible(true);
          return field;
      }
      

      }

      【讨论】:

      • 这确实是一个 hacky 解决方案,并且由于它与 java.util.LinkedList 的实现细节相混淆,因此不能保证在 Java 的未来(或过去)版本上工作。这不是一个值得推荐的解决方案。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-07-25
      • 2013-10-20
      • 1970-01-01
      • 1970-01-01
      • 2011-05-18
      • 1970-01-01
      相关资源
      最近更新 更多