linkedlist增删java,【java容器的刻意练习】【七】LinkedList增删查改

上一篇我们知道了LinkedList的数据结构是双向链表,所以优缺点与双向链表类似。国际惯例,先上结论。

增删查改的优缺点

优点:

add(E) 、addFirst(E)、addLast(E)这三个添加元素函数,不需查找直接添加,时间复杂度O(1),而且不需要自动扩容

删除头尾效率高。remove()删除元素,后面的元素需要逐个移动,时间复杂度O(N)。

缺点:

按索引或者内容删除,都比较慢。remove(int index)通过折半查找所需节点O(N/2);remove(Object o)通过顺序查找所需节点O(N)。

插入效率较低。add(index, E) 通过折半查找后再添加,时间复杂度O(N/2)。

内容查找get(index) 折半查找查找,很慢,时间复杂度 O(N/2)。

内容修改set(index, o) 折半查找查找再修改,很慢,时间复杂度 O(N/2)。

优缺点源码级解读

一、添加元素

add(E)在上一篇已经分析过了,调用了内部保护函数linkLast,直接添加到双向链表最后面。

来看看addFirst(E)。

public void addFirst(E e) {

linkFirst(e);

}

原来调用了内部私有函数linkFirst(E e)。

/**

* Links e as first element.

*/

private void linkFirst(E e) {

final Node f = first;

final Node newNode = new Node<>(null, e, f);

first = newNode;

if (f == null)

last = newNode;

else

f.prev = newNode;

size++;

modCount++;

}

功能和linkedLast类似,先用临时引用f保存上一次第一个节点,然后把双向链表头first引用指向新的节点,新节点与上一次的第一个节点相互前后指向,这样新节点就是第一个节点。上一次的第一个节点就变成第二个节点。

addLast(E)就是调用linkedLast,就不再详述了。

接下来看add(index, E),指定位置的插入。

/**

* Inserts the specified element at the specified position in this list.

* Shifts the element currently at that position (if any) and any

* subsequent elements to the right (adds one to their indices).

*

* @param index index at which the specified element is to be inserted

* @param element element to be inserted

* @throws IndexOutOfBoundsException {@inheritDoc}

*/

public void add(int index, E element) {

checkPositionIndex(index);

if (index == size)

linkLast(element);

else

linkBefore(element, node(index));

}

如果插入的位置是链表尾,就直接调用linkLast,否者就调用linkBefore。这个linkedBefore究竟做了啥,而且有一个参数是node(index),看样子是通过index获取节点?

我们先看node(index)。

/**

* Returns the (non-null) Node at the specified element index.

*/

Node node(int index) {

// assert isElementIndex(index);

if (index < (size >> 1)) {

Node x = first;

for (int i = 0; i < index; i++)

x = x.next;

return x;

} else {

Node x = last;

for (int i = size - 1; i > index; i--)

x = x.prev;

return x;

}

}

原来,从双向链表根据传进来的位置,从中间开始往前或者往后找到需要的节点,那么,时间复杂度就是O(N/2),省略掉常数可以简单记忆为O(N)。

知道了node(index)是获取index对应的节点Node,然后传进linkBefore,把新元素插入到找到的节点前面。

/**

* Inserts element e before non-null Node succ.

*/

void linkBefore(E e, Node succ) {

// assert succ != null;

final Node pred = succ.prev;

final Node newNode = new Node<>(pred, e, succ);

succ.prev = newNode;

if (pred == null)

first = newNode;

else

pred.next = newNode;

size++;

modCount++;

}

现在我们知道,add(E) 、addFirst(E)、addLast(E)这三个添加元素函数,不需查找直接添加,时间复杂度O(1),而且不需要自动扩容。而 add(index, E) 通过折半查找后再添加,时间复杂度O(N/2),省略掉常数可以简单记忆为O(N)。

2388f18eafe2

双向链表add

二、删除元素

先看remove()。

public E remove() {

return removeFirst();

}

public E removeFirst() {

final Node f = first;

if (f == null)

throw new NoSuchElementException();

return unlinkFirst(f);

}

很简单,原来是把第一个节点传入unlinkFirst(f)删除掉。来看看``unlinkFirst(f)`。

/**

* Unlinks non-null first node f.

*/

private E unlinkFirst(Node f) {

// assert f == first && f != null;

final E element = f.item;

// 临时节点引用指向待删除元素的下一个节点

final Node next = f.next;

// 把该节点清空,方便JVM垃圾回收

f.item = null;

f.next = null; // help GC

// 按需改变头尾引用

first = next;

if (next == null)

last = null;

else

next.prev = null;

size--;

modCount++;

return element;

}

到这里,我们知道,这个remove()删除双向链表第一个节点,非常快,时间复杂度O(1)。

再看remove(int index)和remove(Object o)。

public E remove(int index) {

checkElementIndex(index);

return unlink(node(index));

}

public boolean remove(Object o) {

if (o == null) {

for (Node x = first; x != null; x = x.next) {

if (x.item == null) {

unlink(x);

return true;

}

}

} else {

for (Node x = first; x != null; x = x.next) {

if (o.equals(x.item)) {

unlink(x);

return true;

}

}

}

return false;

}

remove(int index)和remove(Object o)都是把需要删除的节点传给unlink(Node x)。前者通过折半查找所需节点;后者通过顺序查找所需节点。

E unlink(Node x) {

// assert x != null;

// 把临时节点引用指向需要删除的节点

final E element = x.item;

// 把临时节点引用指向下一个节点前后

final Node next = x.next;

final Node prev = x.prev;

// 按需处理首尾引用

if (prev == null) {

first = next;

} else {

prev.next = next;

x.prev = null;

}

if (next == null) {

last = prev;

} else {

next.prev = prev;

x.next = null;

}

x.item = null;

size--;

modCount++;

return element;

}

现在我们知道,remove()不用查找,直接删除第一个节点,时间复杂度O(1)。remove(int index)通过折半查找后再添加,时间复杂度O(N/2)。remove(Object o)通过顺序查找,时间复杂度为O(N)。

三、查找元素get(int index)

public E get(int index) {

checkElementIndex(index);

return node(index).item;

}

很简单,就是调用node(index)函数,那还是折半查找,时间复杂度为O(N/2)。就不再详述了。

四、修改元素set(int index, E element)

public E set(int index, E element) {

checkElementIndex(index);

Node x = node(index);

E oldVal = x.item;

x.item = element;

return oldVal;

}

同样调用node(index)函数,那还是折半查找,时间复杂度为O(N/2)。就不再详述了。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值