1. 什么是链表?
1.1 链表的基本概念
链表在内存中的保存是不连续的,每个结点通过指针的方式联系在一起。以单向链表为例,它包含两个部分,一部分保存数据内容,一部分保存下一个结点的引用。
1.2 链表的特点
- 内存中不连续存在
- 通过指针建立联系
- 大小不是固定的,不需要扩容的操作
-
1.3 链表的分类
单向链表
- 双向链表
- 循环链表
1.3.1 单向链表
单向链表,顾名思义就是记录的引用信息时单向的,一般的单向链表都是记录下一个结点的引用信息。
```java
package com.muzili.list;
import org.w3c.dom.Node;
import java.util.Objects;
/*
- @author: muzili(李敷斌)
- @date: 2021/7/13
- @version: v0.0.1
@description: 单向链表 */ public class LinkedList
{ /**
- 头结点
/
private Node
first; /* - 尾结点
/
private Node
last; /* 链表大小 */ private int size;
public Node
getFirst() { return first; } public void setFirst(Node
first) { this.first = first; } public Node
getLast() { return last; } public void setLast(Node
last) { this.last = last; } public int getSize() { return size; }
public void setSize(int size) { this.size = size; }
public LinkedList() { }
public LinkedList(Node
node) { this.first = node; this.last = node; this.size = 1; } /**
- 添加到头部
@param data */ public void addFirst(T data){ Node node = new Node(data); node.next = first; first = node; if (size==0){
last = node;
} size++; }
/**
- 添加到尾部
@param data */ public void addLast(T data){ Node node = new Node(data); if (Objects.isNull(last) && Objects.isNull(first)){
last = node;first = node;size++;return;
}
if (Objects.isNull(last)){
last = node;size++;return;
} last.next = node; last = node; size++; }
/**
- 添加原始到指定位置
- @param loc
@param data */ public void addNth(int loc,T data){
if (size==0){
addFirst(data);return;
}
if (loc > size){
addLast(data);return;
}
Node curr = first; for (int i = 1; i < loc; i++) {
curr = curr.next;
} Node node = new Node(data); node.next = curr.next; curr.next = node; size++; }
/**
删除第一个结点 */ public void removeFirst(){
if (size==0){
return;
}
first = first.next; size—; if (size==1){
last = first;
} }
- 头结点
/
private Node
public void removeLast(){if (size==0){return;}Node cur = first;for (int i = 1; i < size - 1; i++) {cur = cur.next;}last = cur;cur.next = null;size--;}/*** 删除指定位置的元素* @param loc*/public void removeNth(int loc){if (loc < 0 || loc > size){throw new IllegalArgumentException();}Node cur = first;for (int i = 1; i < loc; i++) {cur = cur.next;}cur.next = cur.next.next;}/*** 获取指定位置的元素值* @param loc* @return 元素值*/public T getNth(int loc){if (loc >= size){throw new IllegalArgumentException();}Node cur = first;for (int i = 1; i < loc; i++) {cur = cur.next;}return (T) cur.getData();}/*** 结点* @param <T>*/class Node<T>{private T data;private Node next;public Node(T data) {this.data = data;}public T getData() {return data;}public void setData(T data) {this.data = data;}public Node getNext() {return next;}public void setNext(Node next) {this.next = next;}}public static void main(String[] args) {LinkedList list = new LinkedList<String>();list.addLast("aaaa");list.addFirst("bbbb");list.addLast("ccc");list.addNth(4,"dddd");list.addNth(4,"ffff");list.addNth(4,"eeee");list.addNth(4,"gggg");list.addNth(4,"g4");System.out.println(list);list.removeLast();list.removeLast();list.removeNth(2);System.out.println(list);System.out.println(list.getNth(1));}
}
<a name="BUFFb"></a>### 1.3.2 双向链表双向链表顾名思义,就是记录了两个方向的指针信息,双向链表记录上一个和下一个结点的信息。<br />```javapackage com.muzili.list;/**** @author: muzili(李敷斌)* @date: 2021/7/16* @version: v0.0.1* @description: 双向链表*/public class DoubleLinkedList<T> {/*** 头结点*/private Node<T> first;/*** 尾结点*/private Node<T> last;/*** 链表大小*/private int size = 0;public Node<T> getFirst() {return first;}public void setFirst(Node<T> first) {this.first = first;}public Node<T> getLast() {return last;}public void setLast(Node<T> last) {this.last = last;}public int getSize() {return size;}public void setSize(int size) {this.size = size;}public DoubleLinkedList() {}public DoubleLinkedList(T data) {Node<T> node = new Node<>();node.data = data;first = node;last = node;size++;}/*** 将数据添加到头部* @param data*/public void addFirst(T data){Node node = new Node();node.data = data;if (first!=null){node.next = first;first.prev = node;}first = node;if (size==0){last = node;}size++;}/*** 将数据添加到尾部* @param data*/public void addLast(T data){if (size==0){addFirst(data);return;}Node node = new Node();node.data = data;node.prev = last;last.next = node;last = node;size++;}/*** 向指定位置添加数据* @param loc* @param data*/public void addNth(int loc,T data){if (loc < 0 || loc > size){throw new IllegalArgumentException();}//如果loc为0添加到头部if (loc==0){addFirst(data);return;}//如果loc等于链表大小 添加到尾部if (loc == size){addLast(data);return;}Node node = new Node();node.data = data;//获取到待添加位置的元素Node curr = first;for (int i = 1; i <= loc; i++) {curr = curr.next;}node.next = curr;curr.prev = node;size++;}/*** 删除头部的元素*/public void removeFirst(){if (size == 0){return;}first = first.next;size--;}/*** 删除尾部的元素*/public void removeLast(){if (size==0){return;}last = last.prev;last.next = null;size--;}/*** 删除指定位置的元素* @param loc*/public void removeNth(int loc){if (loc < 0|| loc >= size){throw new IllegalArgumentException();}//如果删除的元素位置为0 则删除头部元素if (loc==0){removeFirst();return;}//如果删除元素的位置为size-1 则删除尾部的元素if (loc == size-1){removeLast();return;}//找到指定位置的元素Node curr = first;for (int i = 1; i <= loc; i++) {curr = curr.next;}Node next = curr.next;Node prev = curr.prev;if (next!=null){next.prev = curr.prev;}if (prev!=null){prev.next = next;}size--;}/*** 获取指定位置的元素值* @param loc* @return*/public T getNth(int loc){if (loc < 0|| loc >= size){throw new IllegalArgumentException();}Node<T> curr = first;for (int i = 1; i <= loc; i++) {curr = curr.next;}return curr.data;}class Node<T>{/*** 上一节点*/private Node<T> prev;/*** 下一节点*/private Node<T> next;/*** 数据值*/private T data;public Node<T> getPrev() {return prev;}public void setPrev(Node<T> prev) {this.prev = prev;}public Node<T> getNext() {return next;}public void setNext(Node<T> next) {this.next = next;}public T getData() {return data;}public void setData(T data) {this.data = data;}}public static void main(String[] args) {DoubleLinkedList<String> list = new DoubleLinkedList<>();list.addFirst("11111");list.addLast("22222");list.addNth(0,"0000");list.addLast("3333");System.out.println(list.getNth(2));list.removeLast();list.removeNth(1);list.removeFirst();System.out.println(list);}}
1.3.3 循环链表
循环链表是一种特殊的结构,它的最后一个结点指针不是null而是指向第一个结点。循环链表可分为循环单向链表,循环双向链表。
package com.muzili.list;/**** @author: muzili(李敷斌)* @date: 2021/7/19* @version: v0.0.1* @description: 循环链表*/public class CurcularLinkedList<T> {/*** 头结点*/private Node<T> first;/*** 尾结点*/private Node<T> last;/*** 链表长度*/private int size=0;public Node<T> getFirst() {return first;}public void setFirst(Node<T> first) {this.first = first;}public Node<T> getLast() {return last;}public void setLast(Node<T> last) {this.last = last;}public int getSize() {return size;}public void setSize(int size) {this.size = size;}public CurcularLinkedList() {}public CurcularLinkedList(T data) {Node node = new Node();node.data = data;first = node;last = node;size++;}/*** 添加到头部* @param data*/public void addFirst(T data){Node node = new Node();node.data = data;if (size >= 1){node.next = first;}first = node;if (size==0){last = node;last.next = first;}}/*** 添加到尾部* @param data*/public void addLast(T data){Node node = new Node();node.data = data;if (size == 0){first = node;last = node;last.next = first;size++;return;}last.next = node;last = node;last.next = first;size++;}/*** 添加到指定位置* @param loc* @param data*/public void addNth(int loc,T data){if (loc > size){throw new IllegalArgumentException();}if (size == 0){addFirst(data);return;}if (loc == size){addLast(data);return;}Node curr = first;for (int i = 1; i < loc; i++) {curr = curr.next;}Node node = new Node();node.data = data;node.next = curr.next;curr.next = node;}/*** 删除头结点*/public void removeFirst(){if (size == 0){throw new IllegalArgumentException();}first = first.next;last.next = first;size --;if (size <= 1){last = first;}}/*** 删除最后一个元素*/public void removeLast(){if (size == 0){throw new IllegalArgumentException();}//获取最后一个元素的前一个元素Node curr = first;for (int i = 1; i < size - 1; i++) {curr = curr.next;}curr.next = null;last = curr;curr.next = first;size --;}/*** 删除指定位置的元素* @param loc*/public void removeNth(int loc){if (size==0 || loc < 0){throw new IllegalArgumentException();}if (loc == 0){removeFirst();return;}if (loc == size-1){removeLast();return;}//获取待删除元素的前一个元素Node curr = first;for (int i = 1; i < loc - 1; i++) {curr = curr.next;}curr.next = curr.next.next;size --;}/*** 约瑟夫问题解决* @param factor*/public void josephRemove(int factor){Node<T> start = first;int count = 0;while (size > 1){count ++;Node curr = start;for (int i = 1; i < factor - 1; i++) {curr = curr.next;}System.out.println("第"+count+"次淘汰的元素内容:"+curr.next.data);curr.next = curr.next.next;start = curr.next;size --;}System.out.println("最后留下的的元素是:"+start.data);}public static void main(String[] args) {CurcularLinkedList<Integer> linkedList = new CurcularLinkedList<Integer>(1);linkedList.addLast(2);linkedList.addLast(3);linkedList.addLast(4);linkedList.addLast(5);linkedList.addLast(6);linkedList.josephRemove(5);}/*** 获取指定位置的元素* @param loc* @return*/public T getNth(int loc){if (size == 0 || loc >= size){throw new IllegalArgumentException();}if (loc == 0){return getFirst().data;}if (loc == size - 1){return getLast().data;}Node<T> curr = first;for (int i = 0; i <= loc; i++) {curr = curr.next;}return curr.data;}class Node<T>{/*** 数据内容*/private T data;/*** 下一个结点*/private Node<T> next;public T getData() {return data;}public void setData(T data) {this.data = data;}public Node<T> getNext() {return next;}public void setNext(Node<T> next) {this.next = next;}}}
2. 链表VS数组
| 时间复杂度 | 数组 | 链表 |
|---|---|---|
| 插入、删除 | O(n) | O(1) |
| 随机查找 | O(1) | O(n) |
主要区别:
- 数组使用比较简单,存储空间是连续的;链表的存储空间是不连续的,结点与结点间通过指针建立联系。
- 数组的大小是固定的;链表的大小不是固定的。
- 数组可能会出现下标越界问题。
- 声明数组时如果内存不够容易内存溢出。
- 数组如果声明不当可能导致内存的浪费,因为即使数组声明的空间没有使用也会占用内存。
3. 链表的使用场景
- HashMap:Java1.8中,HashMap的底层结构是,数组+链表+红黑树。
- 红黑树
- B+tree
- LRU淘汰算法(最少使用淘汰算法):通过限制链表大小,并且通过时间进行排序,超出链表大小的结点被淘汰。

