1. 前言
在上一篇《Java数据结构篇——实现顺序表的增删查改》,我们已经熟悉了 ArrayList
的使用并且进行了简单的模拟实现。ArrayList
底层使用数组来存储元素,由于其底层是一段连续的空间,当ArrayList
任意位置插入或者删除元素时,就需要将后序元素整体往前或者往后移动,时间复杂度为O(n),效率比较低,因此ArrayList
不适合做任意位置插入和删除比较多的场景。因此:Java集合这种又引入了 LinkedList
,即链表结构。
2. 链表
链表是一种物理存储结构上非连续存储结构,数据元素的逻辑顺序是通过链表中引用链接次序实现的。
注意:
- 从上图可看出,链表结构正在逻辑上是连续的,但是在物理上(内存)不一定连续。
- 现实中的节点一般都是从堆上申请出来的。
- 从堆上申请的空间,是按照一定的额策略来分配的,两次申请的空间可能连续,也可能不连续。
3. 单链表的实现
创建一个链表
public class MySingleList {
// 节点
static class ListNode {
public int val; // 数值域 - 存放当前节点的值
public ListNode next; // next域 指向下一个节点
public ListNode(int val) {
this.val = val;
}
}
// 链表的属性 链表的头节点
public ListNode head; // null
public void createList() {
ListNode node1 = new ListNode(1);
ListNode node2 = new ListNode(2);
ListNode node3 = new ListNode(3);
ListNode node4 = new ListNode(4);
node1.next = node2;
node2.next = node3;
node3.next = node4;
this.head = node1;
}
}
画图表示:
3.1 打印链表
-
怎么从第一个节点走到第二个节点?
答:
head = head.next
-
什么时候算是把节点都遍历完成?
答:
head == null
代码实现:
/***
* 打印链表
*/
@Override
public void display() {
ListNode cur = head;
while (cur != null) {
System.out.print(cur.val + " ");
cur = cur.next;// 让cur这个节点 可以从一个节点走到下一个节点
}
System.out.println();
}
3.2 头插法
在链表的第一个位置插入元素。
思路:
- 插入元素的
next
指向head
head
指向插入元素
代码实现:
/**
* 头插法
* @param data
*/
@Override
public void addFirst(int data) {
ListNode node = new ListNode(data); // 定义一个节点
node.next = head;
head = node;
}
3.3 尾插法
在链表的最后个位置插入元素
思路:
- 判断链表中是否有元素。
- 如果没有元素,直接添加头结点即可。
- 如果有元素,将原链表最后一个元素
next
指向插入的元素。
代码实现:
/**
* 尾插法
* @param data
*/
@Override
public void addLast(int data) {
ListNode node = new ListNode(data); // 定义一个节点
if (head == null) { // 链表一个元素都没有
head = node;
} else {
ListNode cur = head;
while (cur.next != null) {
cur = cur.next;
}
cur.next = node;
}
}
3.4 任意位置插入元素
思路:
- 判断
index
是否合法(index < 0 或者 index 大于链表长度
),如果不合法则抛出异常。 - 判断
index
等于0或者index
等于链表长度,则使用头插法或尾插法 cur
找到index - 1
位置- 插入元素的
next
指向cur
的next
cur
的next
指向插入的元素
代码实现:
/**
* 在index位置 插入data
* @param index
* @param data
*/
@Override
public void addIndex(int index, int data) throws IndexException{
if (index < 0 || index > size()) {
throw new IndexException("index不合法:" + index);
}
ListNode node = new ListNode(data); // 定义一个节点
if (head == null) {
head = node;
return;
}
if (index == 0) {
addFirst(data);
return;
}
if (index == size()) {
addLast(data);
return;
}
ListNode cur = searchPrevIndex(index);
node.next = cur.next;
cur.next = node;
}
/**
* 找到index-1的位置
* @param index
* @return
*/
private ListNode searchPrevIndex(int index) {
ListNode cur = head;
int count = 0;
while (count != index - 1) {
cur = cur.next;
count++;
}
return cur;
}
异常类:
public class IndexException extends RuntimeException{
public IndexException() {
}
public IndexException(String msg) {
super(msg);
}
}
3.5 查找元素
代码实现:
/***
* 求当前链表 是否存在key
* @param key
* @return
*/
@Override
public boolean contains(int key) {
ListNode cur = head;
while (cur != null) {
if (cur.val == key) {
return true;
}
cur = cur.next;
}
return false;
}
3.6 链表节点个数
代码实现:
/**
* 求当前链表 有多少个节点
* @return
*/
@Override
public int size() {
ListNode cur = head;
int count = 0;
while (cur != null) {
count++;
cur = cur.next;
}
return count;
}
3.7 删除元素
思路:
- 判断链表是否为空,如果为空直接返回
- 判断删除元素是否为头节点,如果是则
head
指向head
的next
- 定义指针找到要删除节点的前一个节点
- 前一个节点的
next
指向删除节点的next
代码实现:
/**
*
* @param key
*/
@Override
public void remove(int key) {
if (head == null) {
return;
}
if (head.val == key) {
head = head.next;
return;
}
ListNode cur = findPrevKey(key);
if (cur == null) {
return;// 链表里要没有删除的数字
}
ListNode del = cur.next;
cur.next = del.next;
}
/**
* 找到删除节点的前一个节点
* @param key
* @return
*/
private ListNode findPrevKey(int key) {
ListNode cur = head;
while (cur.next != null) {
if (cur.next.val == key) {
return cur;
} else {
cur = cur.next;
}
}
return null;
}
3.8 [删除链表中指定的所有元素](203. 移除链表元素 - 力扣(LeetCode))
思路:
- 判断链表是否为空,如果是空直接返回
- 定义指针
cur
:可能要删除的节点 - 定义指针
prev
:可能要删除的节点的前驱 - 判断
cur
的val
是不是要删除的元素,如果是prev
的next
指向cur
的next
,cur
指向cur
的next
;否则prev
指向cur
,cur
指向cur
的next
- 判断头节点的
val
是否为的元素,如果是头节点指向头节点的neext
代码实现:
/**
* 删除链表中所有的key
* @param key
*/
@Override
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;
}
}
LeetCode运行结果:
3.9 清空链表
当一个对象,没有被引用的时候,就会被回收掉
/**
* 清空链表
*/
@Override
public void clear() {
head = null;
}