定义
链表通过指针将一组零散的内存块串联在一起。其中,我们把内存块称为链表的“结点”。为了将所有的结点串起来,每个链表的结点除了存储数据之外,还需要记录链上的下一个结点的地址。
特点
- 不需要连续的内存空间。
- 有指针引用
- 三种最常见的链表结构:单链表、双向链表和循环链表
图形
从单链表图中,可以发现,有两个结点是比较特殊的,它们分别是第一个结点和最后一个结点。我们一般把第一个结点叫作头结点,把最后一个结点叫作尾结点。
其中,头结点用来记录链表的基地址。有了它,我们就可以遍历得到整条链表。而尾结点特殊的地方是:指针不是指向下一个结点,而是指向一个空地址NULL,表示这是链表上最后一个结点。
- 双向链表:双向链表有两个指针域,一个指向性上一个节点,一个指向下一个节点,头节点的上一个节点为NULL
- 循环链表:循环链表是一种特殊的单链表。实际上,循环链表也很简单。它跟单链表唯一的区别就在尾结点。环链表的尾结点指针是指向链表的头结点。
数组和链表的区别
- 数组结构使用的是连续的内存空间,可以借助cpu缓存的机制,预读数组中的数据
- 链表在内存中并不是连续存储,所以对CPU缓存不友好,没办法有效预读。
- 数组的缺点是大小固定,一经声明就要占用整块连续内存空间。如果声明的数组过大,系统可能没有足够的连续内存空间分配给它,导致“内存不足(out of memory)”。如果声明的数组过小,则可能出现不够用的情况。
- 动态扩容:数组需再申请一个更大的内存空间,把原数组拷贝进去,非常费时。链表本身没有大小的限制,天然地支持动态扩容。
- 时间复杂度:数组的访问为O(1),修改为O(n);链表的访问为O(n),修改为O(1)
这里也不是绝对的,如果数组和链表都是在尾部或者头部进行操作,那数组和链表的访问和修改都是O(1),如栈和队列
手写一个LinkedList
public class MyLinkedList<E> implements MyList<E> {
private int size;
private Node<E> first;
private Node<E> last;
@Override
public int size() {
return size;
}
@Override
public void add(E e) {
Node<E> newNode = new Node<>(null, e, null);
if (first == null) {
first = newNode;
last = newNode;
size++;
return;
}
last.next = newNode;
newNode.pre = last;
last = newNode;
size++;
}
@Override
public void add(E e, int index) {
if (index < 0 || index > size) throw new IndexOutOfBoundsException();
Node<E> newNode = new Node<>(null, e, null);
if (index == size) {//加在末尾
add(e);
return;
}
Node<E> cur = node(index);
newNode.next = cur;
if (cur.pre == null) {//头
cur.pre = newNode;
first = newNode;
} else {
newNode.pre = cur.pre;
cur.pre.next = newNode;
cur.pre = newNode;
}
size++;
}
@Override
public void remove(int index) {
if (index < 0 || index > size) throw new IndexOutOfBoundsException();
if (index == size) {
last.pre.next = null;
last = last.pre;
size--;
return;
}
Node<E> cur = node(index);
if (cur.pre == null) {
cur.next.pre = null;
first = cur.next;
} else {
cur.pre.next = cur.next;
cur.next.pre = cur.pre;
}
size--;
}
private Node<E> node(int index){
Node<E> cur = null;
if (index < (size >> 1)) {//插入位置小于链表的一半
//从前遍历
cur = first;
for (int i = 0; i < index; i++) {
cur = cur.next;
}//index=3 插入在第四个位置 cur为第4个链表元素
} else {
//从后遍历
cur = last;
for (int i = size - 1; i > index; i--) {
cur = cur.pre;
}//index=3 插入在第四个位置 cur为第4个链表元素 size =5 last.index=4
}
return cur;
}
@Override
public void remove(E e) {
remove(indexOf(e));
}
@SuppressWarnings("unchecked")
@Override
public E get(int index) {
return node(index).element;
}
@Override
public Iterator<E> iterator() {
return null;
}
@Override
public int indexOf(E e) {
Node<E> cur = first;
for (int i = 0 ;i<size;i++){
if(e.equals(cur.element)){
return i;
}
cur = cur.next;
}
return -1;
}
private class Node<E> {
private E element;
private Node<E> pre;
private Node<E> next;
public Node(Node<E> pre, E element, Node<E> next) {
this.pre = pre;
this.element = element;
this.next = next;
}
}