目录
认识链表
链表的定义
计算机科学中,链表是数据元素的线性集合,每个元素都指向下一个元素,元素存储上并不连续
链表的分类
- 单向链表:链表中每个元素只指向下一个元素
- 双向链表:链表中每个元素既会指向下一个元素,也会指向上一个元素
- 环形链表:链表中最后一个元素的next指针不指向null,而是指向链表中某个已有元素,从而形成一个闭环结构
性能
- 随机查找元素:从头节点或尾节点依次查找,时间复杂度为O(n)
- 插入或删除元素:增删元素本身的操作只需要改变指针指向,时间复杂度为O(1),但是定位到从何处增删元素这个过程的时间复杂度为O(n)
特殊节点
链表内还可以有一种特殊的节点,叫dummy节点,它不存储数据,通常用作头尾,用来简化边界判断
设计链表(LeetCode-707题)
实现方式一:单向链表
题解
java
class MyLinkedList {
private final Node dummy = new Node();
private int size;
public MyLinkedList() {
}
public int get(int index) {
Node node = getNodeByIndex(index);
// 题目要求:下标无效返回-1
if (node == null) {
return -1;
}
return node.val;
}
/**
* 获取链表中指定下标处的节点
*/
private Node getNodeByIndex(int index) {
if (index < 0 || index >= size) {
return null;
}
Node p = dummy;
for (int i = 0; i <= index; i++) {
p = p.next;
}
return p;
}
public void addAtHead(int val) {
dummy.next = new Node(val, dummy.next);
size++;
}
public void addAtTail(int val) {
// 拿到尾节点
Node tailNode;
if (size == 0) {
tailNode = dummy;
} else {
tailNode = getNodeByIndex(size - 1);
}
// 新节点追加到尾节点
tailNode.next = new Node(val, null);
size++;
}
public void addAtIndex(int index, int val) {
// 下标不合法
if (index < 0 || index > size) {
return;
}
// addHead
if (index == 0) {
addAtHead(val);
return;
}
// addTail
if (index == size) {
addAtTail(val);
return;
}
// other
Node prev = getNodeByIndex(index - 1);
Node next = prev.next;
prev.next = new Node(val, next);
size++;
}
public void deleteAtIndex(int index) {
// 下标不合法
if (index < 0 || index >= size) {
return;
}
Node node = getNodeByIndex(index);
if (index == 0) {
dummy.next = node.next;
} else {
Node prev = getNodeByIndex(index - 1);
prev.next = node.next;
}
size--;
}
private static class Node {
int val;
Node next;
public Node() {
}
public Node(int val, Node next) {
this.val = val;
this.next = next;
}
}
}
测试结果

实现方式二:双向链表
题解
java
class MyLinkedList {
private final Node dummyHead;
private final Node dummyTail;
private int size;
public MyLinkedList() {
// 初始状态
dummyHead = new Node();
dummyTail = new Node();
dummyHead.next = dummyTail;
dummyTail.prev = dummyHead;
}
public int get(int index) {
// 为提高查找性能
// 查找索引在中间位置之前的元素,从头节点开始查找
// 查找索引在中间位置之后的元素,从尾节点开始查找
Node node;
if (index <= (size >> 1)) {
node = getNodeByHead(index);
} else {
node = getNodeByTail(size - index - 1);
}
if (node == null) {
return -1;
}
return node.val;
}
/**
* 从头节点开始查找元素
*/
private Node getNodeByHead(int index) {
if (index < 0 || index >= size) {
return null;
}
Node p = dummyHead;
for (int i = 0; i <= index; i++) {
p = p.next;
}
return p;
}
/**
* 从尾节点开始查找元素
*/
private Node getNodeByTail(int index) {
if (index < 0 || index >= size) {
return null;
}
Node p = dummyTail;
for (int i = 0; i <= index; i++) {
p = p.prev;
}
return p;
}
public void addAtHead(int val) {
// 链表头部添加元素
// 创建新节点
// 改变原本的头节点的指针指向
// 设置新节点的指针指向
Node curr = new Node(val, null, null);
Node next = dummyHead.next;
dummyHead.next = curr;
curr.prev = dummyHead;
curr.next = next;
next.prev = curr;
size++;
}
public void addAtTail(int val) {
Node curr = new Node(val, null, null);
Node prev = dummyTail.prev;
dummyTail.prev = curr;
curr.next = dummyTail;
curr.prev = prev;
prev.next = curr;
size++;
}
public void addAtIndex(int index, int val) {
// 下标不合法
if (index < 0 || index > size) {
return;
}
// 头部添加元素
if (index == 0) {
addAtHead(val);
return;
}
// 尾部添加元素
if (index == size) {
addAtTail(val);
return;
}
// 其他位置添加元素
Node node;
if (index <= (size >> 1)) {
node = getNodeByHead(index - 1);
} else {
node = getNodeByTail(size - index);
}
Node curr = new Node(val, null, null);
Node next = node.next;
node.next = curr;
curr.next = next;
next.prev = curr;
curr.prev = node;
size++;
}
public void deleteAtIndex(int index) {
if (index < 0 || index >= size) {
return;
}
Node node;
if (index <= (size >> 1)) {
node = getNodeByHead(index - 1);
if (node == null)
node = dummyHead;
} else {
node = getNodeByTail(size - index);
}
Node next = node.next.next;
node.next = next;
next.prev = node;
size--;
}
private static class Node {
int val;
Node prev;
Node next;
public Node() {
}
public Node(int val, Node prev, Node next) {
this.val = val;
this.prev = prev;
this.next = next;
}
}
}
测试结果

Java中提供的链表
在Java中,提供了一个类LinkedList,它的底层是用双向链表实现的
底层存储方式
java
package java.util;
// ...
public class LinkedList<E>
extends AbstractSequentialList<E>
implements List<E>, Deque<E>, Cloneable, java.io.Serializable
{
// 记录元素个数
transient int size = 0;
// 链表中第一个节点
transient Node<E> first;
// 链表中最后一个节点
transient Node<E> last;
public LinkedList() {
}
// 数据节点:双向链表
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;
}
}
// ...
}
添加元素
顺序添加元素
即向尾部添加元素,通过last节点链接新元素,时间复杂度为O(1)
java
package java.util;
// ...
public class LinkedList<E>
extends AbstractSequentialList<E>
implements List<E>, Deque<E>, Cloneable, java.io.Serializable
{
// ...
// 该方法就是向尾部添加元素
public boolean add(E e) {
linkLast(e);
return true;
}
// 同样是向尾部添加元素,和add一样,区别是没有返回值
public void addLast(E e) {
linkLast(e);
}
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++;
}
// ...
}
头部添加元素
向头部添加元素,通过first节点链接元素,时间复杂度为O(1)
java
package java.util;
// ...
public class LinkedList<E>
extends AbstractSequentialList<E>
implements List<E>, Deque<E>, Cloneable, java.io.Serializable
{
// ...
// 向头部添加元素
public void addFirst(E e) {
linkFirst(e);
}
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++;
}
// ...
}
随机添加元素
需要先找到插入位置,再进行插入操作,单就插入操作而言的时间复杂度为O(1),但是查找的过程时间复杂度为O(n),所以整体的时间复杂度为O(n)
java
package java.util;
// ...
public class LinkedList<E>
extends AbstractSequentialList<E>
implements List<E>, Deque<E>, Cloneable, java.io.Serializable
{
// ...
// 向随机位置添加元素
public void add(int index, E element) {
checkPositionIndex(index);
if (index == size)
linkLast(element);
else
linkBefore(element, node(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;
}
}
// ...
}
可以用作栈
LinkedList中提供了push方法和pop方法,通过两个方法的组合,可以实现栈,先进后出的特点
- push方法:入栈操作,从头部添加元素
java
public void push(E e) {
addFirst(e);
}
- pop方法:出栈操作,从头部删除元素(如果栈中没有元素了,会抛出异常)
java
public E pop() {
return removeFirst();
}
- 栈先进后出的特点
java
package algorithm.list;
import java.util.LinkedList;
public class LinkedListJava {
public static void main(String[] args) {
LinkedList<String> stack = new LinkedList<>();
// 入栈
stack.push("aaa");
stack.push("bbb");
stack.push("ccc");
stack.push("ddd");
// 出栈
while (!stack.isEmpty()) {
System.out.println(stack.pop());
}
}
}
bash
ddd
ccc
bbb
aaa
可以用作队列
LinkedList中提供了offer方法和poll方法,通过两个方法的组合,可以实现队列,先进先出的特点
- offer方法:入队操作,从尾部添加元素
java
public boolean offer(E e) {
return add(e);
}
public boolean add(E e) {
linkLast(e);
return true;
}
- poll方法:出队操作,从头部删除元素(如果队列中没有元素了,返回null,而不是抛出异常)
java
public E poll() {
final LinkedList.Node<E> f = first;
return (f == null) ? null : unlinkFirst(f);
}
- 队列先进先出的特点
java
package algorithm.list;
import java.util.LinkedList;
public class LinkedListJava {
public static void main(String[] args) {
LinkedList<String> queue = new LinkedList<>();
// 入队
queue.offer("aaa");
queue.offer("bbb");
queue.offer("ccc");
queue.offer("ddd");
// 出队
while (!queue.isEmpty()) {
System.out.println(queue.poll());
}
}
}
bash
aaa
bbb
ccc
ddd