Java 为什么链表删除和插入操作的复杂性为O(1)?不应该';它不是O(n)的吗

Java 为什么链表删除和插入操作的复杂性为O(1)?不应该';它不是O(n)的吗,java,data-structures,collections,linked-list,time-complexity,Java,Data Structures,Collections,Linked List,Time Complexity,据说LinkedList remove和add操作的复杂性是O(1)。在ArrayList的情况下,它是O(n) 计算大小为“M”的ArrayList:如果我想删除第N个位置的元素,那么我可以直接使用索引一次转到第N个位置(我不必遍历到第N个索引),然后我可以删除元素,直到这一点复杂性为O(1),那么我必须移动其余元素(M-N移位)因此,我的复杂性将是线性的,即O(M-N+1)。因此,在最后一次删除或插入会给我最好的性能(如N~M),而在开始时删除或插入会给我最差的性能(如N~1) 现在,大小为

据说LinkedList remove和add操作的复杂性是
O(1)
。在
ArrayList
的情况下,它是
O(n)

计算大小为“M”的ArrayList:如果我想删除第N个位置的元素,那么我可以直接使用索引一次转到第N个位置(我不必遍历到第N个索引),然后我可以删除元素,直到这一点复杂性为O(1),那么我必须移动其余元素(M-N移位)因此,我的复杂性将是线性的,即O(M-N+1)。因此,在最后一次删除或插入会给我最好的性能(如N~M),而在开始时删除或插入会给我最差的性能(如N~1)

现在,大小为“M”的LisnkedList:由于我们无法直接到达LinkedList中的第N个元素,为了访问第N个元素,我们必须遍历N个元素,因此LinkedList中的搜索比ArrayList中的搜索成本更高……但是Remove和add操作被称为O(1),对于LinkedList as,在LinkedList中不涉及移位,但这涉及到导线操作,对吗?因此,复杂性应为O(n)级,其中最差性能将出现在尾部节点,而最佳性能将出现在头部节点

谁能解释一下为什么我们在计算链接删除操作的复杂性时不考虑遍历成本?< /P>
所以我想了解它在java.util包中是如何工作的。如果我想在C或C++中实现相同的方法,我如何实现在Link表中随机删除和插入的O(1)?< /P> < P>,认为删除的复杂性已经有了指针,指向要删除的元素的正确位置……/P> 这不算你找到它的费用吗

Information on this topic is now available on Wikipedia at: Search data structure

    +----------------------+----------+------------+----------+--------------+
    |                      |  Insert  |   Delete   |  Search  | Space Usage  |
    +----------------------+----------+------------+----------+--------------+
    | Unsorted array       | O(1)     | O(1)       | O(n)     | O(n)         |
    | Value-indexed array  | O(1)     | O(1)       | O(1)     | O(n)         |
    | Sorted array         | O(n)     | O(n)       | O(log n) | O(n)         |
    | Unsorted linked list | O(1)*    | O(1)*      | O(n)     | O(n)         |
    | Sorted linked list   | O(n)*    | O(1)*      | O(n)     | O(n)         |
    | Balanced binary tree | O(log n) | O(log n)   | O(log n) | O(n)         |
    | Heap                 | O(log n) | O(log n)** | O(n)     | O(n)         |
    | Hash table           | O(1)     | O(1)       | O(1)     | O(n)         |
    +----------------------+----------+------------+----------+--------------+

 * The cost to add or delete an element into a known location in the list (i.e. if you have an iterator to the location) is O(1). If you don't know the location, then you need to traverse the list to the location of deletion/insertion, which takes O(n) time. 
** The deletion cost is O(log n) for the minimum or maximum, O(n) for an arbitrary element.
LinkedList
as的情况下,移除和添加操作被称为O(1),在
LinkedList
中不涉及移位,但涉及遍历操作,对吗

添加到链表的两端不需要遍历,只要保留对列表两端的引用。这就是Java为其和/或方法所做的

无参数和/方法也是如此——它们在列表末尾操作


另一方面,and运算不是O(1)。它们需要遍历,所以您正确地将它们的成本标识为O(n)。

< P>是的,如果您一次考虑两个操作(索引和插入),则是正确的。在这种情况下,这是不正确的,因为当您在链表中间插入一个节点时,假设您已经在必须插入节点的地址。

访问节点的时间复杂度为O(n),而仅插入节点的时间复杂度为O(1)

在头部插入需要添加元素并更新头部指针

newnode->next = head;
head = newnode;
在尾部插入需要保留指向尾部元素的指针,在尾部添加元素并更新尾部指针

tail->next = newnode;
tail = newnode;
删除head元素需要更新head并删除以前的head元素

temp = head;
head = head->next;
delete temp; /* or free(temp); */
以上所有操作都是琐碎的操作,不依赖于链表中元素的数量。因此,它们是O(1)

但是,删除tail元素将是一个O(n)操作,因为即使您可能有一个tail指针,您仍然需要倒数第二个节点,该节点将被设置为新的tail节点(通过更新tail指针并将节点的下一个成员设置为NULL)。为此,需要遍历整个链接列表

penultimate_el = find_penultimate_el(head); /* this is O(n) operation */
delete tail; /* or free(tail) */
tail = penultimate_el;
tail->next = NULL;

ArrayList提供可调整大小的数组和。如果数组扩展超过其分配的大小,则必须重新创建此引用数组。换句话说,在开始处插入一个节点需要将所有现有元素上移一个,或者在列表超出其分配大小时重新分配整个列表。这就是为什么插入是
O(n)

LinkedList由一系列节点组成;每个节点是分开分配的。因此,在插入时,不必遍历所有节点。这就是为什么它具有复杂性
O(1)
。 然而,如果您在末尾插入,并且只有第一个节点的引用,那么您可能必须遍历整个列表,因此这种情况下的复杂性将是O(n)

编辑

如果查看
java.util.LinkedList
的源代码,您会发现
LinkedList
始终跟踪最后一个元素

下面是来自实际
java.util.LinkedList
类的一些代码片段

package java.util;

...

public class LinkedList<E>
    extends AbstractSequentialList<E>
    implements List<E>, Deque<E>, Cloneable, java.io.Serializable
{
    transient int size = 0;

    /**
     * Pointer to first node.
     */
    transient Node<E> first;

    /**
     * Pointer to last node.
     */
    transient Node<E> last;


    ...
    ...


    /**
     * Appends the specified element to the end of this list.
     */
    public boolean add(E e) {
        linkLast(e);
        return true;
    }



    ...
    ...


    /**
     * Links e as last element.
     */
    void linkLast(E e) {
        final Node<E> l = last;
        final Node<E> newNode = new Node<>(l, e, null);
        last = newNode;
        if (l == null)
            first = newNode;
        else
            l.next = newNode;
        size++;
        modCount++;
    }


    ...
    ...

}
包java.util;
...
公共类链接列表
扩展抽象顺序列表
实现List、Deque、Cloneable、java.io.Serializable
{
瞬时整数大小=0;
/**
*指向第一个节点的指针。
*/
瞬变节点优先;
/**
*指向最后一个节点的指针。
*/
瞬时节点最后;
...
...
/**
*将指定的元素追加到此列表的末尾。
*/
公共布尔加法(E){
最后链接(e);
返回true;
}
...
...
/**
*链接e作为最后一个元素。
*/
无效链接last(E){
最终节点l=最后一个;
最终节点newNode=新节点(l,e,null);
last=newNode;
if(l==null)
第一个=新节点;
其他的
l、 next=newNode;
大小++;
modCount++;
}
...
...
}

具体请参见
linkLast()
方法。它不会遍历整个列表。它只是在列表的末尾插入元素,这就是为什么从C语言的角度来看,时间复杂度在数组中是
O(1)

然后它会给我们一个清晰的认识。虽然我们可以在数组中的固定时间添加和删除元素,也就是说,我们不需要遍历 EN