本篇内容包括:LinkedList 的概述、LinkedList 的结构既双向链表实现与LinkedList-Node 结构、LinkedList 的使用(构造方法&常用方法)、关于 Queue 队列的介绍、关于 ArrayList 和 LinkedList 的区别以及算法:翻转链表!
一、LinkedList 概述
LinkedList 是用链表作为数据存储结构的 List 集合,链表数据结构的特点是每个元素分配的空间不必连续,因此链表很适合数据的动态插入和删除,但是其随机访问和遍历速度比较慢。另外,LinkedList还提供了 List 接口中没有定义的方法,专门用于操作表头和表尾元素,可以当作堆栈、队列和双向队列使用。
LinkedList 是以链表实现的,插入、删除时只需要改变前后两个节点指针指向即可,实现了真正的动态,不需要处理固定容量的问题,但是丧失了随机访问的能力 (索引访问)。
LinkedList 是一个双向链表实现,插入、删除时只需要改变前后两个节点指针指向即可,实现了真正的动态,不需要处理固定容量的问题。LinkedList 在当数据量很大或者操作很频繁的情况下,添加和删除元素时具有比 ArrayList 更好的性能。但在元素的查询和修改方面要弱于ArrayList。
LinkedList 与 ArrayList 一样 LinKedList 也不是线程安全的。
二、LinkedList 的结构
1、双向链表实现
LinKedList 的数据存储是基于双向链表实现。
LinkedList 类每个结点用内部类 Node 表示,LinkedList 通过 first 和 last 引用分别指向链表的第一个和最后一个元素,当链表为空时,first 和 last 都为 NULL 值。
关于 LinKedList 操作数据时:
- 插入数据(很快):先是在双向链表中找到要插入节点的位置 index,找到之后,再插入一个新节点。 双向链表查找 index 位置的节点时,有一个加速动作:若 index < 双向链表长度的 1/2,则从前向后查找,否则,从后向前查找;
- 删除数据(很快):先是在双向链表中找到要插入节点的位置 index,找到之后,进行如下操作:
node.previous.next = node.next; node.next.previous = node.previous; node = null
,查找节点过程和插入一样; - 获取数据(很慢):每次获取数据都需要从 Head 节点从头开始查找;
- 遍历数据(很慢):同获取数据一样,每次遍历数据都需要从头开始。
2、LinkedList-Node 结构
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;}}
Node 节点一共有三个属性:item 代表节点值,prev 代表节点的前一个节点,next 代表节点的后一个节点。每个结点都有一个前驱和后继结点,并且在 LinkedList 中也定义了两个变量分别指向链表中的第一个和最后一个结点。
三、LinkedList 的使用
1、构造方法
方法名 | 方法说明 |
---|---|
public LinkedList() | 此构造函数用于构造一个空列表。 |
public LinkedList(Collection<? extends E> c) | 此构造函数将按照集合的迭代器返回的顺序构造一个包含指定集合元素的列表 |
2、常用方法_作为队列(Linked继承了Queue)
方法名 | 方法说明 |
---|---|
boolean add(E e) | 此方法将指定的元素追加到此列表/队列的末尾 |
E remove() | 此方法检索并删除此列表的头部(第一个元素),如果此列表为空,则报错 |
E removeFirst() | 此方法删除并返回此列表中的第一个元素,如果此列表为空,则报错 |
E removeLast() | 此方法检索并删除此列表的最后一个元素,如果此列表为空,则报错 |
E poll() | 此方法检索并删除此列表的头部(第一个元素) |
E pollFirst() | 此方法检索并删除此列表的第一个元素,如果此列表为空,则返回null |
E pollLast() | 此方法检索并删除此列表的最后一个元素,如果此列表为空,则返回null |
E element() | 此方法检索但不删除此列表的头部(第一个元素) |
3、常用方法_作为栈(FILO 先进后出的栈)
方法名 | 方法说明 |
---|---|
void push(E e) | 此方法将元素推送到此列表所表示的堆栈上 |
E pop() | 此方法从此列表表示的堆栈中弹出一个元素 |
E peek() | 此方法检索但不删除此列表的头部(第一个元素) |
E peekFirst() | 此方法检索但不删除此列表的第一个元素,如果此列表为空,则返回null |
E peekLast() | 此方法检索但不删除此列表的最后一个元素,如果此列表为空,则返回null |
4、常用方法_作为链表
方法名 | 方法说明 |
---|---|
void add(int index, E e) | 此方法将指定的元素插入此列表中的指定位置。 |
E remove(int index) | 此方法删除此列表中指定位置的元素 |
E remove(Object o) | 此方法从该列表中删除指定元素的第一个匹配项(如果存在) |
E set(int index, E e) | 此方法使用指定的元素替换此列表中指定位置的元素 |
E get(int index) | 此方法返回此列表中指定位置的元素 |
E getFirst() | 此方法返回此列表中的第一个元素 |
E getLast() | 此方法返回此列表中的最后一个元素 |
int size() | 此方法返回此列表中的元素数 |
boolean contains(Object o) | 如果此列表包含指定的元素,则此方法返回true |
boolean isEmpty() | 如果此列表为空,则此方法返回true |
int indexOf(Object o) | 此方法返回此列表中第一次出现的指定元素的索引,如果此列表不包含该元素,则返回-1 |
int lastIndexOf(Object o) | 此方法返回此列表中指定元素最后一次出现的索引,如果此列表不包含该元素,则返回-1 |
void clear() | 此方法将从此列表中删除所有元素 |
Object clone() | 此方法返回返回此LinkedList的浅表副本 |
Object[] toArray() | 此方法以适当的顺序(从第一个元素到最后一个元素)返回包含此列表中所有元素的数组 |
<T> T[] toArray(T[] a) | 此方法以适当的顺序(从第一个元素到最后一个元素)返回包含此列表中所有元素的数组,返回数组的运行时类型是指定数组的运行时类型 |
四、相关知识点
1、关于 Queue 队列
队列(Queue):也是一种操作受限的线性表,只允许在表的一端进行插入,而在表的另一端进行删除。向队列中插入元素称为入队或进队;删除元素称为出队或离队。
这和我们日常生活中的排队是一致的,最早排队的也是最早离队的。其操作的特性是先进先出(First In First Out, FIFO),故又称为先进先出的线性表。基本上,一个队列就是一个先入先出(FIFO)的数据结构
在Java 中 Queue 接口与 List、Set 同一级别,都是继承了 Collection 接口。LinkedList 实现了 Deque 接口。
2、关于 ArrayList 和 LinkedList 的区别
在结构上,ArrayList 底层是数组,在内存上是连续的;LinkedList 底层是双向链表,在内存上是不连续的
在性能上,ArrayList 由于内存上连续,支持随机查询,查询速度更快,但是在增删时由于会涉及到,数据的移动和数组的扩容,往往是要慢于 LinkedList 的,但并不绝对,可以提前设置好足够的数组空间,并采用尾插的方式
3、算法:翻转链表
假设链表为 1→2→3→∅,我们想要把它改成 ∅←1←2←3。
在遍历链表时,将当前节点的 next 指针改为指向前一个节点。由于节点没有引用其前一个节点,因此必须事先存储其前一个节点。在更改引用之前,还需要存储后一个节点。最后返回新的头引用。
class Solution {public ListNode reverseList(ListNode head) {ListNode prev = null;ListNode curr = head;while (curr != null) {ListNode next = curr.next;curr.next = prev;prev = curr;curr = next;}return prev;}
}