线性表的链式存储结构
n 个结点(a1 的存储映像)链接成一个链表,即为线性表 (a1 , a2 ,… , an) 的链式存储结构,因为此链表的每个结点中只包含一个指针域 ,所以叫做单链表。单链表正是通过每个节点的指针域将线性表的数据元素按其逻辑次序链接在一起。
结点: 数据域+指针域
头结点:头结点是为了操作的统一和方便而设立的,放在第一个元素的结点之前,其数据域一般毫无意义;有了头结点,对在第一元素结点前插入结点和删除第一结点,其操作与其他结点的操作就统一了;头结点不一定是链表的必要元素。
头指针:指链表指向第一个结点的指针,若链表有头结点,则是指向头结点的指针;头指针具有标识作用,常用头指针冠以链表的名字;无论链表是否为空,头指针均不为空。头指针是链表的必要元素。
(图片来源《大话数据结构》)
Java LinkedList 定义 —— 双向链表
双向链表 Java 定义
/**
* Doubly-linked list implementation of the List and Deque interfaces. Implements all optional list operations, and permits all elements (including null).
*
*/
public class LinkedList<E>
extends AbstractSequentialList<E>
implements List<E>, Deque<E>, Cloneable, java.io.Serializable
{
transient int size = 0;
/**
* Pointer to first node.
* Invariant: (first == null && last == null) ||
* (first.prev == null && first.item != null)
*/
transient Node<E> first;
/**
* Pointer to last node.
* Invariant: (first == null && last == null) ||
* (last.next == null && last.item != null)
*/
transient Node<E> last;
/**
* Constructs an empty list.
*/
public 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 CRUD
/**
* 链表获取
*/
public E get(int index) {
//检查 index 越界情况
checkElementIndex(index);
return node(index).item;
}
/**
* 链表添加
*/
public void add(int index, E element) {
checkPositionIndex(index);
if (index == size)
//尾结点
linkLast(element);
else
//链表插入
linkBefore(element, node(index));
}
/**
* 链表删除
*/
public E remove(int index) {
checkElementIndex(index);
return unlink(node(index));
}
/**
* Returns the (non-null) Node at the specified element index.
*/
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;
}
}
/**
* Links e as first element.
*/
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++;
}
/**
* 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++;
}
/**
* Inserts element e before non-null Node succ.
*/
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++;
}
LinkedList 整表创建与删除
整表创建
/**
* Appends all of the elements in the specified collection to the end of
* this list, in the order that they are returned by the specified
* collection's iterator. The behavior of this operation is undefined if
* the specified collection is modified while the operation is in
* progress. (Note that this will occur if the specified collection is
* this list, and it's nonempty.)
*
* @param c collection containing elements to be added to this list
* @return {@code true} if this list changed as a result of the call
* @throws NullPointerException if the specified collection is null
*/
public boolean addAll(Collection<? extends E> c) {
return addAll(size, c);
/**
* Inserts all of the elements in the specified collection into this
* list, starting at the specified position. Shifts the element
* currently at that position (if any) and any subsequent elements to
* the right (increases their indices). The new elements will appear
* in the list in the order that they are returned by the
* specified collection's iterator.
*
* @param index index at which to insert the first element
* from the specified collection
* @param c collection containing elements to be added to this list
* @return {@code true} if this list changed as a result of the call
* @throws IndexOutOfBoundsException {@inheritDoc}
* @throws NullPointerException if the specified collection is null
*/
public boolean addAll(int index, Collection<? extends E> c) {
//插入的 index 是否在链表的边界之内
checkPositionIndex(index);
Object[] a = c.toArray();
int numNew = a.length;
if (numNew == 0) return false;
//初始化
Node<E> pred, succ;
if (index == size) {
succ = null;
pred = last;
} else {
succ = node(index);
pred = succ.prev;
}
for (Object o : a) {
@SuppressWarnings("unchecked") E e = (E) o;
Node<E> newNode = new Node<>(pred, e, null);
if (pred == null)
first = newNode;
else
pred.next = newNode;
pred = newNode;
}
if (succ == null) {
last = pred;
} else {
pred.next = succ;
succ.prev = pred;
}
size += numNew;
modCount++;
return true;
}
整表删除
值得注意的是,链表的整表删除是把整个链表的元素遍历至空是不必要的。根本目的在于释放对象的引用,帮助 GC 进行垃圾回收。
/**
* Removes all of the elements from this list.
* The list will be empty after this call returns.
*/
public void clear() {
// Clearing all of the links between nodes is "unnecessary", but:
// - helps a generational GC if the discarded nodes inhabit
// more than one generation
// - is sure to free memory even if there is a reachable Iterator
for (Node<E> x = first; x != null; ) {
Node<E> next = x.next;
x.item = null;
x.next = null;
x.prev = null;
x = next;
}
first = last = null;
size = 0;
modCount++;
}
链表结构与顺序存储结构的对比
存储方式
ArrayList 用一段连续的存储单元依次存储线性表的数据元素
LinkedList 采用链式存储结构,用一组任意的存储单元存放线性表的元素。
时间性能
查找
ArrayList O(1)
LinkedList O(n)
插入和删除
ArrayList 需要平均移动表长一半的元素。
LinkedList 在找出某位置的指针后,插入和删除仅为 O(1) 。
空间性能
ArrayList 需要预分配存储空间,动态扩容会有性能损耗。
LinkedList 不需要分配存储空间,元素个数不受限。
总结
当场景需要频繁的查找时,很少进行插入和删除操作时适宜采用 ArrayList . 当需要频繁的插入和删除,宜采用 LinkedList.
当线性表中的元素个数变化较大或者根本不知道有多大时,最好用 LinkedList. 若事先知道长度宜采用 ArrayList.