【问题标题】:Why iterating is more preferable than random access为什么迭代比随机访问更可取
【发布时间】:2017-10-09 06:22:22
【问题描述】:

The following page 状态

List接口提供了四种定位(索引)方法 访问列表元素。列表(如 Java 数组)是从零开始的。笔记 这些操作可以按与索引成比例的时间执行 某些实现的值(例如 LinkedList 类)。 因此,迭代列表中的元素通常比 如果调用者不知道实现,则通过它进行索引。

这是什么意思?这是否意味着我应该更喜欢

ListIterator<Integer> iterator = list.listIterator(0);
iterator.next()
iterator.next()

结束

list.get(1)

如果是这样,为什么?如果底层列表实现是普通数组,get 将在恒定时间内执行。但是使用迭代器是O(n)。我在这里错过了什么?

他们的意思是我应该使用list.listIterator(1); 而不是list.get(1)

【问题讨论】:

  • 我认为,文档说迭代比随机访问更好,以防您需要遍历列表的所有或几乎所有元素。如果您只需要获取第 n 个元素 - 最好调用 get()。
  • 如果List 是'LinkedList,会发生什么? list.get(n) 需要遍历它之前的元素。循环变为O(n^2)
  • @greg 完全正确。这里的关键词是“通过它建立索引”;换句话说,通过索引访问列表的所有成员。

标签: java list


【解决方案1】:

没有。但你应该更喜欢

for (Integer value : list) {
    // do something with value
}

(或显式使用列表迭代器的循环)

结束

for (int i = 0; i < list.size(); i++) {
    Integer value = list.get(i);
    // do something with value
}

如果列表是链表,第一个是 O(N),而第二个是 O(N^2)。实际上,在每次迭代中,它都需要遍历列表(从开始或结束,取决于i 的值)直到到达ith 位置。使用 Iterator 或 foreach 循环时不会发生这种情况。

另一个很好的理由是,在并发列表的情况下,迭代器可以是一致的(即迭代创建迭代器时创建的列表的一致快照)。使用列表索引的迭代不可能是这种情况。

【讨论】:

  • 这个解释很完美,但我在这里有疑问。如果 List 是一个 ArrayList,那么两者都可以在 O(n) 中工作吗?我认为他们应该在O(n) 工作,因为ArrayListO(1) 时间提供访问权限。
  • 是的,如果是 ArrayList,两者都是 O(N)。
  • 没有。循环有 N 次迭代。对于每次迭代,平均而言,它必须遍历 N/4 个节点(最佳情况:i == 0 或 i == size -1;最坏情况:i == size / 2)。 N * N / 4 是 O(N^2)。
  • 嗨@JBNizet,我知道这不是正确的地方,但你能看看这个问题吗? stackoverflow.com/questions/43909146/…
【解决方案2】:

我几乎可以肯定,这暗示要遍历列表中的所有元素,最好进行迭代,而不是例如:

// possibly slow depending on List implementation used
for (int i = 0; i < list.size(); ++i)
{
    Foo bar = list.get(i);
}

在随机访问不是 O(1) 的 LinkedList 的情况下,每次调用 list.get(i) 时,都必须从头一直迭代到 i,这是不必要的。


您的纯随机访问示例(而不是遍历所有内容)是:

  1. LinkedList 的情况下,更详细(但基本上是什么 get(i) 在幕后工作)

  2. ArrayList的情况下,实际上比get(i)

【讨论】:

    【解决方案3】:

    Javadoc 所说的是这种情况:

    for (int i = 0; i < list.size(); i++) {
        Object o = list.get(i);
    }
    

    如果list 的实现是ArrayList,那么您是对的,对于每个特定元素(O(n) 用于整个列表),对底层数组的访问将是恒定时间。但是,如果是LinkedList 怎么办?要到达链表中的元素n,您必须在到达元素n 之前遍历[0, 1, ... n - 2, n - 1]。如果您对数组中的每个元素都执行此操作,现在这将非常低效。

    因此,更好的方法是使用迭代器编写 for 循环:

    // uses the iterator under the hood.
    for (Object o : list) {
    
    }
    

    在这种情况下,ArrayList 迭代器将沿着数组移动,因此与第一个 for 循环一样快,而对于 LinkedList,它会跟踪列表中的位置,因此您不必遍历整个列表以到达下一个元素。

    【讨论】:

    • 正确,除了因为LinkedList实际上是一个双向链表,当i比开头更接近结尾时,它将从结尾开始。不过,这不会改变运行时的复杂性。
    • @JBNizet,好点子,谢谢你提出来,我忘记了。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-01-12
    • 1970-01-01
    • 2022-11-14
    • 2015-06-27
    • 2015-12-04
    • 1970-01-01
    相关资源
    最近更新 更多