同样是List的数据结构,LinkedList是使用了前后指针,指明节点的方式来表示链表的,这与之前介绍的ArrayList

http://www.cnblogs.com/yakovchang/p/java_arraylist.html

中使用数组的方式是截然不同的。LinkedList中的存储节点被称作节点(Node),一个节点的定义如下所示:

    private static class Node<E> {
E item;
Node<E> next;
Node<E> prev; Node(Node<E> prev, E element, Node<E> next) {
this.item = element;
this.next = next;
this.prev = prev;
}
}

这是LinkedList的一个内部类,不需要使用LinkedList的程序员关心。

LinkedList正是通过next,prev这两个指针来串联起整个List的。

注意:Node节点在初始化的时候同时指明了初始化的节点的前后节点是什么,所以在之后的代码中,往往没有明显的写明新的节点的前后指针指向了哪里。

在LinkedList本身中仅仅记录了List的开始和结束节点,当然,也记录了size:

    transient int size = 0;

    transient Node<E> first;

    transient Node<E> last;

在开始节点上添加一个节点,需要先判断是否为空,如果为空,则直接first和last都指向这个节点,否则就需要把原来的first的prev指向新节点,把新节点作为新的first节点,时间复杂度是o(1):

    private void linkFirst(E e) {
final Node<E> f = first;
final Node<E> newNode = new Node<>(null, e, f);
first = newNode;
if (f == null)
last = newNode;
else
f.prev = newNode;
size++;
modCount++;
}

在结束节点上添加一个新节点,需要先判断是否为空,如果为空,就直接把last和firs都指向这个节点,否则就把原来的last的next指向新节点,把新节点作为新的last节点,时间复杂度是o(1):

    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++;
}

在一个非空节点之前加入一个节点,如果这个非空节点的prev是null,那么说明非空节点是first节点,直接让first节点指向新节点,否则就让这个非空节点的next指向新的节点,时间复杂度是o(1):

    void linkBefore(E e, Node<E> succ) {
// assert succ != null;
final Node<E> pred = succ.prev;
final Node<E> newNode = new Node<>(pred, e, succ);
succ.prev = newNode;
if (pred == null)
first = newNode;
else
pred.next = newNode;
size++;
modCount++;
}

删除一个非空的first节点,是把指向这个节点的指针都移除,同时把first指向他的next节点,如果next节点也为空,说明这个节点是List中的最后一个节点,那么first和last都指向空,时间复杂度是o(1):

    private E unlinkFirst(Node<E> f) {
// assert f == first && f != null;
final E element = f.item;
final Node<E> next = f.next;
f.item = null;
f.next = null; // help GC
first = next;
if (next == null)
last = null;
else
next.prev = null;
size--;
modCount++;
return element;
}

同样的,删除一个非空的last节点,也是把指向这个节点的指针都移除,同时把last指向他的prev节点,如果prev节点为空,说明这个节点是List中的最后一个节点,那么first和last都指向空,时间复杂度是o(1):

    private E unlinkLast(Node<E> l) {
// assert l == last && l != null;
final E element = l.item;
final Node<E> prev = l.prev;
l.item = null;
l.prev = null; // help GC
last = prev;
if (prev == null)
first = null;
else
prev.next = null;
size--;
modCount++;
return element;
}

删除一个非空节点,则是具有较多的判断条件,主要是取出来当前节点的prev和next,让他们之间建立连接,当然还需要判断是否为空,如果prev是空说明是第一个节点,如果next是空说明是最后一个节点,如果两者为空,说明List中只有这一个节点,这个操作的时间复杂度是o(1):

    E unlink(Node<E> x) {
// assert x != null;
final E element = x.item;
final Node<E> next = x.next;
final Node<E> 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;
}

获取first,last,删除first,last,添加first,last,都是基于上述的操作进行的,所以它们的时间复杂度也都是o(1):

    public E getFirst() {
final Node<E> f = first;
if (f == null)
throw new NoSuchElementException();
return f.item;
} public E getLast() {
final Node<E> l = last;
if (l == null)
throw new NoSuchElementException();
return l.item;
} public E removeFirst() {
final Node<E> f = first;
if (f == null)
throw new NoSuchElementException();
return unlinkFirst(f);
} public E removeLast() {
final Node<E> l = last;
if (l == null)
throw new NoSuchElementException();
return unlinkLast(l);
} public void addFirst(E e) {
linkFirst(e);
} public void addLast(E e) {
linkLast(e);
}

以上说的都是时间复杂度较低的操作,但是下面会有时间复杂度较高的操作,这也正是LinkedList被吐槽的地方。

查找一个对象的index是多少,就是通过遍历对比的方式进行的,其时间复杂度是o(n):

    public int indexOf(Object o) {
int index = 0;
if (o == null) {
for (Node<E> x = first; x != null; x = x.next) {
if (x.item == null)
return index;
index++;
}
} else {
for (Node<E> x = first; x != null; x = x.next) {
if (o.equals(x.item))
return index;
index++;
}
}
return -1;
}

而通过index去确定对应的节点,也是使用了便利的办法,当然,这里采取了一个巧妙的方式是判断index距离头部更近,还是尾部更近,然后选择从哪里开始进行遍历,其时间复杂度也是o(n):

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

所有使用了上面两个函数的地方,其时间复杂度都是o(n)。

事实上,由于LinkedList的特殊性,LinkedList本身支持了很多其它的数据结构特性:

实现队列操作:

    public E peek() {//查看第一个,但不删除
final Node<E> f = first;
return (f == null) ? null : f.item;
} public E element() {//查看第一个,但不删除
return getFirst();
} public E poll() {//获取第一个,并且删除
final Node<E> f = first;
return (f == null) ? null : unlinkFirst(f);
} public E remove() {//获取第一个并且删除
return removeFirst();
} public boolean offer(E e) {//在队列尾部添加一个
return add(e);
}

实现双向队列操作:

    public boolean offerFirst(E e) {//在队列头部添加一个
addFirst(e);
return true;
} public boolean offerLast(E e) {//在队列尾部添加一个
addLast(e);
return true;
} public E peekFirst() {//查看队列头部但不删除
final Node<E> f = first;
return (f == null) ? null : f.item;
} public E peekLast() {//查看队列尾部但不删除
final Node<E> l = last;
return (l == null) ? null : l.item;
} public E pollFirst() {//获取并且删除队列头部元素
final Node<E> f = first;
return (f == null) ? null : unlinkFirst(f);
} public E pollLast() {//获取并且删除队列尾部元素
final Node<E> l = last;
return (l == null) ? null : unlinkLast(l);
}

实现栈的操作:

    public E poll() {//查看栈顶但不删除
final Node<E> f = first;
return (f == null) ? null : unlinkFirst(f);
} public void push(E e) {//压栈
addFirst(e);
} public E pop() {//出栈
return removeFirst();
}

实现移除第一次发生和最后一次发生:

    public boolean removeFirstOccurrence(Object o) {//移除第一次出现
return remove(o);
} public boolean removeLastOccurrence(Object o) {//移除最后一次出现
if (o == null) {
for (Node<E> x = last; x != null; x = x.prev) {
if (x.item == null) {
unlink(x);
return true;
}
}
} else {
for (Node<E> x = last; x != null; x = x.prev) {
if (o.equals(x.item)) {
unlink(x);
return true;
}
}
}
return false;
}

LinkedList是很多数据结构的基础,在之后的很多数据结构里面还会看到。

LinkedList最大的好处在于头尾和已知节点的插入和删除时间复杂度都是o(1)。

但是涉及到先确定位置再操作的情况,则时间复杂度会变为o(n)。

当然,每个节点都需要保留prev和next指针也是经常被吐槽是浪费了空间。

Java数据结构漫谈-LinkedList的更多相关文章

  1. Java数据结构漫谈-Stack

    Stack(栈)是一种比较典型的数据结构,其元素满足后进先出(LIFO)的特点. Java中Stack的实现继承自Vector,所以其天然的具有了一些Vector的特点,所以栈也是线程安全的. cla ...

  2. Java数据结构之LinkedList、ArrayList的效率分析

    前言: 在我们平常开发中难免会用到List集合来存储数据,一般都会选择ArrayList和LinkedList,以前只是大致知道ArrayList查询效率高LinkedList插入删除效率高,今天来实 ...

  3. java数据结构之LinkedList

    一.LinkedList源码注释 //LinkedList源码 jdk版本1.8.0_121 public class LinkedList<E> extends AbstractSequ ...

  4. Java数据结构漫谈-Vector

    List除了ArrayList和LinkedList之外,还有一个最常用的就是Vector. Vector在中文的翻译是矢量,向量,所以大家喜欢把Vector叫做矢量数组,或者向量数组. 其实就底层实 ...

  5. Java数据结构漫谈-ArrayList

    ArrayList是一个基于数组实现的链表(List),这一点可以从源码中看出: transient Object[] elementData; // non-private to simplify ...

  6. Java集合之LinkedList

    一.LinkedList概述 1.初识LinkedList 上一篇中讲解了ArrayList,本篇文章讲解一下LinkedList的实现. LinkedList是基于链表实现的,所以先讲解一下什么是链 ...

  7. JAVA数据结构系列 栈

    java数据结构系列之栈 手写栈 1.利用链表做出栈,因为栈的特殊,插入删除操作都是在栈顶进行,链表不用担心栈的长度,所以链表再合适不过了,非常好用,不过它在插入和删除元素的时候,速度比数组栈慢,因为 ...

  8. Java数据结构之线性表(2)

    从这里开始将要进行Java数据结构的相关讲解,Are you ready?Let's go~~ java中的数据结构模型可以分为一下几部分: 1.线性结构 2.树形结构 3.图形或者网状结构 接下来的 ...

  9. Java数据结构之线性表

    从这里开始将要进行Java数据结构的相关讲解,Are you ready?Let's go~~ java中的数据结构模型可以分为一下几部分: 1.线性结构 2.树形结构 3.图形或者网状结构 接下来的 ...

随机推荐

  1. github 预览html

    在网址前加 http://htmlpreview.github.io/?

  2. NodeJs获取不到POST参数

    NodeJs报错,从网页端获取不到POST参数,提示错误类似如下 TypeError: Cannot read property 'username' of undefined     at C:\U ...

  3. ORACLE的order by中文排序

    在使用order by排序的时候,出现如下情况:   印象中中文排序应该默认是按照拼音排序的,为何"鑫"会排在"中"的后面呢?猜想order by是不是根据对应 ...

  4. 常见Oracle数据库问题总结及解决办法(一)

    开发中常使用Oralce数据库,使用中也许会碰到形形色色的各类错误提示,如:ORA-00933:SQL命令未正确结束.ORA-009242等等,为此记录积累对于自己来说还是很有帮助的,今天就记录以前出 ...

  5. 解读CSS文本(text)样式

    通过文本属性,您可以改变文本的颜色.字符间距.对齐文本.装饰文本.文本缩进,等等. color: 该属性用于改变文本的颜色,注意区分background-color. Line-height: 该属性 ...

  6. WinFrm访问MVC数据

    WinFrm使用HttpWebRequest访问MVC中的Controller,以注册为例,客户端输入注册码后点击注册. WinFrm注册代码:代码中使用的是Post提交,UTF8编码方式. priv ...

  7. 类和对象:继承 - 零基础入门学习Python038

    类和对象:继承 让编程改变世界 Change the world by program 上节课的课后作业不知道大家完成的怎样?我们试图模拟一个场景,里边有一只乌龟和十条鱼,乌龟通过吃鱼来补充体力,当乌 ...

  8. 渐变颜色的进度条WGradientProgress-备用

    今天我们来实现一个iOS平台上的进度条(progress bar or progress view).这种进度条比APPLE自带的更加漂亮,更加有“B格”.它拥有渐变的颜色,而且这种颜色是动态移动的, ...

  9. SmartBusinessDevFramework架构设计-2:结构图示

    架构设计一览图 下图表示了本架构的设计草稿. 接下来  ,我们将逐步细述,各个模块之间的松散耦合关系. 核心的实现原理.敬请关注.

  10. 为什么Linux不需要碎片整理?

    如果你是一个 Linux 用户,你可能会听说 Linux 的文件系统不需要碎片整理.你也可能会注意到 Linux 的发行版本也都没有磁盘碎片整理的功能.这是为什么呢? 要理解为什么 Linux 的文件 ...