【问题标题】:ArrayList vs LinkedList complexity when moving element to the first place将元素移动到第一位时的 ArrayList 与 LinkedList 的复杂性
【发布时间】:2017-05-10 17:58:39
【问题描述】:

我想分析使用 LinkedList 或 ArrayList 将 1 2 3 4 移动到 3 1 2 4(整数列表)。

我做了什么:

aux = arraylist.get(2); // O(1)
arraylist.remove(2); // O(n)
arraylist.add(0, aux); // O(n), shifting elements up.

aux = linkedlist.get(2); // O(n)
linkedlist.remove(2); // O(n)
linkedlist.addFirst(aux); // O(1)

那么,在这种情况下,我们可以说它们是相同的还是我错过了什么?

【问题讨论】:

  • 尝试用一些非常大的数据集编写一个基准测试,看看行为是什么。只有四个元素,很难真正看出区别。
  • 对于arraylist.remove(2),您忘记了向下移动元素。元素的移动是一项非常昂贵的操作。在任何情况下,您都在混合本质上非常不同的操作(搜索和复制)的复杂性,因此在单一尺度上比较 ArrayListLinkedList 没有多大意义。
  • 您忽略的一件事是remove() 返回已删除的项目,因此对于您的链接列表,没有理由单独创建get()。你可以linkedlist.addFirst(linkedlist.remove(2));
  • @azurefrog 谢谢,这是一个很好的提示。我正在摆脱一个 O(n)
  • 理论上你不能将元素以相反的顺序保存在 ArrayList 中,然后从最后一个到第一个读取,所以 addFirst 将成为一个简单的添加,并且因为列表已经有空间,所以复杂性将再次与 LinkedList 相同(但当然不太实用)。

标签: java algorithm arraylist linked-list complexity-theory


【解决方案1】:

您确实可以说这个特定操作对于 LinkedList 和 ArrayList 都需要 O(n) 时间。

但你不能说它们因此花费了相同的实时时间。

Big-O 表示法仅告诉您运行时间将如何随着输入变大而扩展,因为它忽略了常数因素。因此,O(n) 算法最多可以进行 1*n 操作或 100000*n 操作或更多操作,并且每个操作都可能花费大量不同的时间。这也意味着对于小输入,它通常是一个相当不准确的性能衡量标准,因为与小输入的大小相比,常数因素对运行时间的影响更大(但对于小输入,性能差异往往不太重要)。

另请参阅:What is a plain English explanation of "Big O" notation?

【讨论】:

    【解决方案2】:

    这是一个快速而肮脏的基准。我对这两个操作都进行了计时并重复了一百万次:

    import java.util.ArrayList;
    import java.util.Arrays;
    import java.util.LinkedList;
    
    public class Main {
    
        public static void main(String[] args) {
            int arrayListTime = 0;
            int linkedListTime = 0;
            int n = 10000000;
            for (int i=0; i<n; i++) {
                ArrayList<Integer> A = new ArrayList<>(Arrays.asList(1,2,3,4));
                long startTime = System.currentTimeMillis();
                int x = A.remove(2);
                A.add(0, x);
                long endTime = System.currentTimeMillis();
                arrayListTime += (endTime - startTime);
    
                LinkedList<Integer> L = new LinkedList<>(Arrays.asList(1,2,3,4));
                long startTime2 = System.currentTimeMillis();
                int x2 = L.remove(2);
                L.addFirst(x2);
                long endTime2 = System.currentTimeMillis();
                linkedListTime += (endTime2 - startTime2);
            }
    
            System.out.println(arrayListTime);
            System.out.println(linkedListTime);
        }
    }
    

    差别很小。我的输出是:

    424
    363
    

    因此,在 1,000,000 次操作过程中,使用 LinkedList 仅快 61 毫秒。

    【讨论】:

    • 请注意,此基准测试忽略了缓存性能,因为对于 ArrayList 和 LinkedList 循环 - 它们可能在开始计时之前已经在 CPU 缓存中。这需要验证,但我相信缓存将成为这里性能的最大贡献者。
    【解决方案3】:

    当输入非常小时,大 O 复杂性毫无意义,就像你的情况一样。回想一下,大 O 的定义仅适用于“足够大的 n”,我怀疑您是否已达到 n==4 的阈值。

    如果这在一个小数据大小的紧密循环中运行(并且每次使用不同的数据),唯一真正重要的是缓存性能,并且数组列表解决方案比链接的缓存更友好列表解决方案,因为链表中的每个Node 都可能需要缓存查找。
    在这种情况下,我什至更喜欢使用原始数组 (int[]),以避免触发更多缓存未命中的冗余包装对象。

    【讨论】:

      猜你喜欢
      • 2022-08-17
      • 2022-01-12
      • 1970-01-01
      • 2017-02-16
      • 2018-10-15
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-03-31
      相关资源
      最近更新 更多