【问题标题】:Iterator Time complexity for a LinkedList in Java?Java中LinkedList的迭代器时间复杂度?
【发布时间】:2017-10-09 15:25:04
【问题描述】:

下面的代码将调用迭代器并将整数发送回调用它的方法。我只是想知道使用Iterator 是否会使我的时间复杂度变得恒定? (即O(1))。因为如果我使用带有LinkedList 的get,它将给我线性时间(即O(n))。

protected int least(int i) {
    if(i >= digits.size()) {
        return 0;           
    }

    // temp++;
    // System.out.println("Time taken=" + temp);
    // long start = System.nanoTime();

    ListIterator<Integer> itr1 = digits.listIterator(digits.size() - i);

    // System.out.println("Time elapsed:" + (System.nanoTime() - start));

    return itr1.previous();
}

【问题讨论】:

    标签: java linked-list time-complexity


    【解决方案1】:

    从第 i 个元素开始的迭代器

    如果您创建一个直接从LinkedListi-th 索引开始的Iterator,您需要知道这也需要O(n)。在LinkedList 中查找元素总是

    LinkedList只记住列表的head(和tail)元素。需要通过遍历整个列表来找到其他所有元素。

    这是一个双向链表的示意图(Javas LinkedList 也是双向链接的):

    因此,如果您从i-th 元素开始创建Iterator,它将从head(或tail)开始,并跟随指针直到i-th 元素。这就像打电话:

    list.get(i);
    

    这显然要花费O(n)


    替代方案:ArrayList

    如果您需要快速基于索引的访问,也称为随机访问,您可以考虑改用ArrayList。它的结构允许在O(1)中访问(可以直接通过start + i * sizeof(type)计算元素在内存中的位置)。

    提供如此快速随机访问的数据结构通常实现接口RandomAccess (documentation and implementing classes) 作为指标。


    如何迭代

    如上所述,迭代LinkedList 不应通过list.get(i) 的基于索引的访问来完成。因此,您应该使用Iterator(或ListIterator,如果您需要在迭代时修改列表)。

    这是使用Iterator 的常用方法:

    Iterator<E> iter = list.iterator();
    while (iter.hasNext()) {
        E element = iter.next();
        ...
    }
    

    或者您也可以使用 增强的 for 循环,它在内部执行相同的操作,但看起来更紧凑:

    for (E element : list) {
        ...
    }
    

    由于Javas LinkedList是一个双向链表,你也可以从尾部开始,反向迭代列表。因此只需使用 LinkedList#descendingIterator 方法 (documentation) 而不是 LinkedList#iterator

    最后一个例子展示了如何在迭代时使用ListIterator修改列表:

    ListIterator<E> listIter = list.listIterator(0);
    while (listIter.hasNext()) {
        E element = listIter.next();
        ...
        // Remove the element last polled
        listIter.remove();
        // Inserts an element right before the last polled element
        listIter.add(new Element());
    }
    

    您还可以使用ListIteratorhasPrevious()previous() 方法来前后遍历列表。这是它的documentation

    【讨论】:

    • Java 有标记接口RandomAccess,它从性能的角度指示数据结构是否可以安全地调用get(index)
    • Iterator itr1 = digits.descendingIterator(); // O(1) a=itr1.next(); // O(1) 数字.removeLast(); // O(1) ?这会从尾巴上移除吗?
    • digits.removeLast() 已经删除了O(1) 中的最后一个元素。之前无需调用 Iterator 的东西。这是因为它是一个双向链表,因此对最后一个元素的引用始终在LinkedList 中可用。要向后删除所有元素,您可以这样做:while (!list.isEmpty()) list.removeLast().
    • listIter.remove()的运行时间是多少?
    • @AnnaVopureta 这取决于。它不需要再次定位该项目,所以这很好。对于ArrayList,它必须将所有元素向右移动到左侧,所以O(n)。对于LinkedList,它显然是即时的,所以O(1),因为它只需要调整相邻节点的prevnext 指针。所以它是数据结构remove 方法的运行时间,减去它需要首先找到元素的部分。请注意,ArrayList 在实践中通常仍然快得多,这是由于数组的极端优化和优势。
    【解决方案2】:

    没有。

    链接列表并非设计为在O(1) 时间内随机访问。使用迭代器访问它仍然是O(n)

    要理解为什么,我们需要深入研究listIterator(int)的实现:

    public ListIterator<E> listIterator(int index) {
        checkPositionIndex(index);
        return new ListItr(index);
    }
    

    这个方法返回一个新的ListItr,让我们看看它是如何创建的:

        ListItr(int index) {
            // assert isPositionIndex(index);
            next = (index == size) ? null : node(index);
            nextIndex = index;
        }
    

    构造函数调用node,实现如下:

    Node<E> node(int index) {
        // assert isElementIndex(index);
    
        if (index < (size >> 1)) {
            Node<E> x = first;
            for (int i = 0; i < index; i++) <<<<< HERE!
                x = x.next;
            return x;
        } else {
            Node<E> x = last;
            for (int i = size - 1; i > index; i--) <<<<<< HERE!
                x = x.prev;
            return x;
        }
    }
    

    现在你看到了吗?一个 for 循环遍历节点一直到索引 index 处的那个。这意味着它是O(n)!获取迭代器的速度随着链表中的元素和index 的增加而线性增加。

    【讨论】:

      【解决方案3】:

      如果您的意图是获取最后一个元素,那么由于LinkedList 实现是一个双向链表,那么应该有对实例可用的尾部和头部的引用,以及大小。

      这意味着List.descendingIterator() 可能是一个更好的起点,如果你必须从最后开始,但你总是要在 O(n) 时间内索引到 LinkedList 结构。

      如果您定期按索引取消引用,则通常应使用随机访问结构,例如 ArrayList

      如果您实际上是在任一方向上迭代列表,那么您应该返回您的迭代器,而不是值。

      【讨论】:

      • 是的,要获取最后一个元素,获取降序迭代器然后迭代一次,将是 O(1)。
      猜你喜欢
      • 1970-01-01
      • 2022-07-18
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2019-05-09
      • 1970-01-01
      相关资源
      最近更新 更多