1. 链表
链表是一种物理存储结构上非连续 存储结构,数据元素的逻辑顺序 是通过链表中的引用链接次序实现的 。
链表中包含两种域:
-
存储数值的域
-
存储引用(相当于c语言中的地址)的域

2. 链表的分类
链表根据3种分类方式分类
-
单向或双向
-
带头或不带头
-
循环或不循环
更据以上3种分类方式,共有8种。
3. 单向链表
这里我们实现的是单向不带头不循环的链表。
java
public class SingleLinkedList {
//头插法
public void addFirst(int data){
}
//尾插法
public void addLast(int data){
}
//任意位置插入,第一个数据节点为0号下标
public void addIndex(int index,int data){
}
//查找是否包含关键字key是否在单链表当中
public boolean contains(int key){
return false;
}
//删除第一次出现关键字为key的节点
public void remove(int key){
}
//删除所有值为key的节点
public void removeAllKey(int key){
}
//得到单链表的长度
public int size(){
return -1;
}
public void clear() {
}
}
3.1 内部类
首先我们要创建一跟内部类包含存储数值的域和存储引用的域,之后我们需要引用来把我们的链表串联起来,第一个节点用head来表示。
java
static class ListNode {
public int val;
public ListNode next;
public ListNode(int val) {
this.val = val;
}
}
public ListNode head;
3.2 头插法
思路:
将插入节点的下一个节点(next)指向头节点(head)
把插入节点改为头节点
java
//头插法
public void addFirst(int data) {
ListNode node = new ListNode(data);
node.next = head;
head = node;
}
3.3 尾插法
思路:
先判断链表是否为空(因为尾插涉及到next域,链表如果为空会空指针异常)
遍历链表,找到尾节点,使尾点的next指向尾插节点
java
//尾插法
public void addLast(int data) {
ListNode node = new ListNode(data);
if(head == null) {
head = node;
return;
}
ListNode cur = head;
while (cur.next != null) {
cur = cur.next;
}
cur.next = node;
}
3.4 任意位置插入,第一个数据节点为0号下标
思路:
判断下标是否违法
判断是否是头结点或尾结点
遍历链表找到插入下标
java
//任意位置插入,第一个数据节点为0号下标
public void addIndex(int index, int data) {
ListNode node = new ListNode(data);
if (index < 0 || index >= size()) {
System.out.println("index下标不合法");
return;
}
if(index == 0) {
addFirst(data);
return;
}
if(index == size()) {
addLast(data);
return;
}
ListNode cur = head;
while (index - 1 >= 0) {
cur = cur.next;
index--;
}
node.next = cur.next;
cur.next = node;
}
3.5 查找是否包含关键字key是否在单链表当中
思路:
遍历链表找到关键字即可
java
//查找是否包含关键字key是否在单链表当中
public boolean contains(int key) {
ListNode cur = head;
while (cur != null) {
if (cur.val == key) {
return true;
}
cur = cur.next;
}
return false;
}
3.6 删除第一次出现关键字为key的节点
思路:
判断链表是否为空,为空直接返回
判断头节点是否为要删的节点,是就直接头节点指向下一个节点
遍历链表找到要删的节点的前一个节点,当前节点的next指向下一个节点的next
java
//查找关键字key
private ListNode findNodeOfKey(int key) {
ListNode cur = head;
while (cur.next != null) {
if (cur.next.val == key) {
return cur;
}
cur = cur.next;
}
return null;
}
//删除第一次出现关键字为key的节点
public void remove(int key) {
if (head == null) {
return;
}
if (head.val == key) {
head = head.next;
return;
}
ListNode cur = findNodeOfKey(key);
if(cur == null) {
return;
}
cur.next = cur.next.next;
}
3.7 删除所有值为key的节点
思路:
判断链表是否为空,为空返回
遍历链表当前节点的val值为关键字key,prev的next指向cur的next
判断头节点的val是否为要删除为key的节点,是就直接头节点指向下一个节点
java
//删除所有值为key的节点
public void removeAllKey(int key) {
if (head == null) {
return;
}
ListNode prev = head;
ListNode cur = head.next;
while (cur != null) {
if (cur.val == key) {
prev.next = cur.next;
cur = cur.next;
} else {
prev = cur;
cur = cur.next;
}
}
if (head.val == key) {
head = head.next;
}
}
3.8 得到单链表的长度
思路:
定义一个计数器,遍历链表,只要节点不为空,就计数器++
java
//得到单链表的长度
public int size() {
int len = 0;
ListNode cur = head;
while (cur != null) {
len++;
cur = cur.next;
}
return len;
}
3.9 清空单链表
思路:
遍历链表,定义一个临时变量存储当前节点的下一个节点,然后将当前节点的next置为空,最 后将头节点置为空
java
public void clear() {
ListNode cur = head;
while (cur != null) {
ListNode curN = cur.next;
cur.next = null;
cur = curN;
}
head = null;
}
3.10 单向链表的优缺点
优点:对于增删查改操作简单
缺点:只能从头到尾遍历,只能找到后驱,不能找到前驱
4. 双向链表

这里我们实现一个双向不带头不循环的链表
java
public class MyLinkedList {
//头插法
public void addFirst(int data){ }
//尾插法
public void addLast(int data){}
//任意位置插入,第一个数据节点为0号下标
public void addIndex(int index,int data){}
//查找是否包含关键字key是否在单链表当中
public boolean contains(int key){}
//删除第一次出现关键字为key的节点
public void remove(int key){}
//删除所有值为key的节点
public void removeAllKey(int key){}
//得到链表的长度
public int size(){}
//清空链表
public void clear(){}
}
4.1 内部类
和上面的单向链表差不多,只需新增一个存储前驱的域。第一个节点用head来表示,最后一个节点用last来表示。
java
static class ListNode {
public int val;
public ListNode next;
public ListNode prev;
public ListNode(int val) {
this.val = val;
}
}
4.2 头插法
思路:
判断链表是否为空,是就将头节点,尾节点指向插入节点
否则,插入节点的next指向头节点,头节点的prev指向插入节点,头节点指向插入节点
java
//头插法
public void addFirst(int data){
ListNode node = new ListNode(data);
if(head == null && last == null) {
head = node;
last = node;
}else {
node.next = head;
head.prev = node;
head = node;
}
}
4.3 尾插法
思路:
判断链表是否为空,是就将头节点,尾节点指向插入节点
否则,尾节点的后驱next指向插入节点,插入节点的前驱prev指向尾节点,尾节点指向插入节 点
java
//尾插法
public void addLast(int data){
ListNode node = new ListNode(data);
if(head == null && last == null) {
head = node;
last = node;
}else {
last.next = node;
node.prev = last;
last = node;
}
}
4.4 任意位置插入,第一个数据节点为0号下标
思路:
判断下标是否合法,不合法直接返回
如果插入的位置是头节点,直接用头插法 ,如果插入的位置是尾节点,直接用尾插法
如果插入的位置是中间,遍历链表,找到插入的对应位置,然后插入节点的后驱next指向对应 节点,插入节点的前驱prev指向对应节点的前驱prev,对应节点的前驱prev的后驱next指向插 入节点,对应节点的前驱prev指向插入节点
java
//判断下标是否合法
public boolean checkIndex(int index) {
if(index < 0 || index >= size()) {
System.out.println("下标违法");
return false;
}
return true;
}
//任意位置插入,第一个数据节点为0号下标
public void addIndex(int index,int data){
ListNode node = new ListNode(data);
if(checkIndex(index) == false) {
return;
}
if(index == 0) {
addFirst(data);
return;
}
if(index == size()) {
addLast(data);
return;
}
ListNode cur = head;
while (index - 1 >= 0) {
cur = cur.next;
index--;
}
node.next = cur;
node.prev = cur.prev;
cur.prev.next = node;
cur.prev = node;
}
4.5 查找是否包含关键字key是否在单链表当中
思路:
遍历链表找到关键字即可
java
//查找是否包含关键字key是否在单链表当中
public boolean contains(int key){
ListNode cur = head;
while(cur != null) {
if(cur.val == key) {
return true;
}
cur = cur.next;
}
return false;
}
4.6 删除第一次出现关键字为key的节点
思路:
如果链表为空,直接返回
遍历链表,找关键字为key的节点,如果头节点是,直接头接点指向头节点的下一个节点,然 后把头节点的前驱prev置为空,如果是尾节点,尾节点指向尾节点的前驱prev,尾节点的后 驱next置为空,其他位置,删除节点的前驱prev的后驱next指向删除节点的后驱next,删除节 点的后驱next的前驱prev指向删除节点的前驱prev
java
//删除第一次出现关键字为key的节点
public void remove(int key){
if(head == null) {
return;
}
ListNode cur = head;
while(cur != null) {
if(cur.val == key) {
if(cur == head) {
head = head.next;
head.prev = null;
}else {
if(cur.next == null) {
last = last.prev;
last.next = null;
}else {
cur.prev.next = cur.next;
cur.next.prev = cur.prev;
}
}
return;
}
cur = cur.next;
}
}
4.7 删除所有值为key的节点
思路:
和删除第一次出现关键字为key的节点的思路一样
java
//删除所有值为key的节点
public void removeAllKey(int key){
if(head == null) {
return;
}
ListNode cur = head;
while(cur != null) {
if(cur.val == key) {
if(cur == head) {
head = head.next;
head.prev = null;
}else {
if(cur.next == null) {
last = last.prev;
last.next = null;
}else {
cur.prev.next = cur.next;
cur.next.prev = cur.prev;
}
}
}
cur = cur.next;
}
}
4.8 得到链表的长度
思路:
定义一个计数器,遍历链表,只要节点不为空,就计数器++
java
//得到链表的长度
public int size(){
int count = 0;
ListNode cur = head;
while(cur != null) {
count++;
cur = cur.next;
}
return count;
}
4.9 清空链表
思路:
遍历链表,定义一个临时变量存储当前节点的下一个节点,然后将当前节点的后驱next,前 驱prev置为空,最后将头节点,尾节点置为空
java
//清空链表
public void clear(){
ListNode cur = head;
while(cur != null) {
ListNode curNext = cur.next;
cur.next = null;
cur.prev = null;
cur = curNext;
}
head = last = null;
}
4.10 双向链表的优缺点
优点:有了前驱,增加链表的灵活型
缺点:删除节点操作复杂
5. LinkedList

-
LinkedList的底层使用了双向链表
-
LinkedList没有实现RandomAccess接口,因此LinkedList不支持随机访问
-
LinkedList的任意位置插入和删除元素时效率比较高,时间复杂度为O(1)
5.1 LinkedList的构造

5.2 LinkedList的常用方法

5.3 ArrayList 和 LinkedList 的对比
