【问题标题】:time complexity for iterating through an array list遍历数组列表的时间复杂度
【发布时间】:2017-01-25 07:07:01
【问题描述】:

我有一个遍历的数组列表。在每次迭代中,我调用 get() 来获取一个元素,如果该项目通过了某些条件,则使用 add() 将其添加到新的数组列表中

List<Item> items = new ArrayList<Item>();
List<Item> lessItems = new ArrayList<Item>();
for(int index = 0; index < items.size(); index++){
    Item toCheck = items.get(i);
    if(toCheck meets some condition){
        lessItems.add(toCheck);
    }
}

我不确定这里的时间复杂度是多少。我在所有项目上调用 get(),所以它是 O(n)。然后我还可能在所有项目上调用 add(),所以还有另一个 O(n)。这个不太确定。

【问题讨论】:

  • 它是 O(n) + O(n),即 O(2n),您可以删除 2(因为它相对于输入是恒定的)并说它是 O(n)。
  • @ElliottFrisch 这不是真的。在java中插入arraylist的最后一个元素不是O(n)。请看我的回答。
  • @ElliottFrisch 显然其他人不同意你的观点。足以对我的答案投反对票,这与您的答案相同。
  • @fgb,分歧在于缺乏清晰度和准确性。有一个循环使这个O(n)。之后,您有一些(不是 2 个)恒定时间操作。他们不应该像@Elliott 在之前的 cmets 中那样说。说O(n) + O(n) 是不正确和不准确的,因为第二种复杂性存在于第一种复杂性内部(IOW,它不是加法而是常数项的乘法);这是cn = O(n)`。
  • @fgb,如果您想提取获取和添加并匹配 OP 所做的事情,它们确实是等价的。但它仍然不完整。他们显然“对这个不太确定”,所以为什么不准确或至少不完整O(n) + O(n)?还有其他时间。

标签: java arrays algorithm arraylist data-structures


【解决方案1】:

Big-O 和类似的符号是时间复杂度的渐近界。它们丢弃了数字系数,并用于根据输入大小来估计运行时间。

所以,2*n3*n 等。表示为O(n)2*nlog(n)3*nlog(n)等。表示为O(nlog(n))

由于在这种情况下 add() 操作只插入一个元素,它的运行时间大约为 (some small constant)k*1,总运行时间为 (some constant)j*(n+some constant(k)),即 j*nO(n)

在这种情况下和所有类似的情况下,任何常数 k 乘以 n 都将表示为 O(n),这意味着运行时间随输入 ArrayList 的大小线性变化.

【讨论】:

    【解决方案2】:
    1. 您的第一个循环迭代items 列表:复杂度是O(n)
    2. 将每个项目插入到列表末尾lessItems:在正常数组中它将是O(n),正如其他人所说。但是Java使用amortized array实现ArrayList。这意味着在数组末尾插入时,算法仅花费Amortized O(1)。或者你可以说O(1)

    所以你的代码的复杂度是:O(n) * amortized O(1)。总之就是O(n)

    另一个参考:

    dynamic array

    补充说明 1:

    如果在数组末尾插入的复杂度是O(N),那么总复杂度是O(N^2),而不是其他答案所说的O(2 * N)。因为插入的总复杂度是:1 + 2 + 3 + ...+ n = n*(n+1)/2

    补充说明 2:

    正如official document 所说:

    size、isEmpty、get、set、iterator 和 listIterator 操作运行 在恒定时间内。 加法运算在摊销常数时间内运行, 也就是说,添加 n 个元素需要 O(n) 时间。所有其他 操作以线性时间运行(粗略地说)。常数因子 与 LinkedList 实现相比较低。

    补充说明 3:

    这里是我取自官方java源码的grow方法的逻辑:

    private void ensureExplicitCapacity(int minCapacity) {
            modCount++;
    
            // overflow-conscious code
            if (minCapacity - elementData.length > 0)
                grow(minCapacity);
        }
    
    private void grow(int minCapacity) {
            // overflow-conscious code
            int oldCapacity = elementData.length;
            int newCapacity = oldCapacity + (oldCapacity >> 1);
            if (newCapacity - minCapacity < 0)
                newCapacity = minCapacity;
            if (newCapacity - MAX_ARRAY_SIZE > 0)
                newCapacity = hugeCapacity(minCapacity);
            // minCapacity is usually close to size, so this is a win:
            elementData = Arrays.copyOf(elementData, newCapacity);
        }
    

    正如源代码所说,当程序添加使数组大小大于当前容量的元素时,数组将增长。增长数组的新大小将是:

    int newCapacity = oldCapacity + (oldCapacity >> 1);
    

    这是一个使插入的技巧是amortized O(1)

    【讨论】:

    • 这正是ArrayList 实现不被认为效率低下的原因。
    【解决方案3】:

    你正在做一个迭代,那是 O(n)。

    您还将项目添加到 ArrayList,它是 O(1) (Amortized)

    获取索引也是 O(1)。

    所以你做了 O(n) 次,O(1) 的操作,这将是 O(n)

    【讨论】:

      【解决方案4】:

      对于遍历数组列表,时间复杂度将为 O(n)。 n 将是列表的大小。

      使用 get() 获取值的时间为 O(1),可以使用索引在数组列表中进行随机访问。

      对于使用 add() 来添加值,该值是在最后添加的,所以它将是 O(1)。

      此操作的时间复杂度为 O(n)。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2012-12-14
        • 2022-07-01
        • 1970-01-01
        • 2014-10-01
        • 1970-01-01
        • 2019-09-18
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多