文章目录
一:链表的概念及结构
1.1链表是⼀种物理存储结构上⾮连续 存储结构,数据元素的逻辑顺序 是通过链表中的引⽤链接 次序实现
的。
(1))物理非连续 :链表的节点在内存中不一定连续,每个节点包含数据域(val)和引用域(next)(指向其他节点)。
(2)逻辑连续 :通过引用域,节点之间形成链式关系,保证了数据在逻辑上的连续。
(3))节点来源 :现实中,链表的节点一般从堆内存中申请,两次申请的空间可能连续也可能不连续。
1.2 链表的常见结构
链表的结构多样,通过以下三个维度组合,可形成 8 种不同的链表结构:
(1))单向 / 双向 :单向链表节点只有一个引用域,指向后继节点;双向链表节点有两个引用域,分别指向前驱和后继节点。
(2))带头 / 不带头 :带头链表有一个头节点(不存储实际数据),用于简化操作;不带头链表直接从存储数据的节点开始。
(3))循环 / 非循环 :循环链表的尾节点引用指向头节点(或头节点相关节点),形成闭环;非循环链表的尾节点引用为 null。
示例:
(1)单向不带头非循环
(2)单向带头非循环
(3)单向不带头循环
(4)单向带头循环
在实际应用中,我们重点掌握两种核心结构:
无头单向非循环链表 :结构简单,一般不单独用于存储数据,常作为其他数据结构(如哈希桶、图的邻接表)的子结构,也是笔试面试中的高频考点。
无头双向非循环链表:Java 中 LinkedList 的底层实现就是这种结构,能兼顾插入、删除和查询操作的性能。
二:单向链表
我们自己实现一个⽆头单向⾮循环链表
(1)基础结构定义
java
public class MySingleList {
// 内部静态类:链表节点
static class ListNode{
public int val; // 数据域
public ListNode next; // 引用域(下一个节点引用)
// 初始化数据域
public ListNode(int val) {
this.val = val;
}
}
ListNode head; // 链表的头引用,是访问链表的入口
}
(2)手动创建链表
java
public void createList(){
ListNode node1 = new ListNode(12);
ListNode node2 = new ListNode(23);
ListNode node3 = new ListNode(34);
ListNode node4 = new ListNode(45);
ListNode node5 = new ListNode(56);
node1.next = node2;
node2.next = node3;
node3.next = node4;
node4.next = node5;
this.head = node1;
}

(3)打印链表
java
public void display() {
//不要让head动
ListNode cur = this.head;
while (cur != null) {
System.out.print(cur.val + " ");
cur = cur.next;
}
System.out.println();
}
测试:
java
public class Test {
public static void main(String[] args) {
MySingleList mySingleList = new MySingleList();
mySingleList.createList();
mySingleList.display();
}
}

在这里我们会思考如果我们把循环条件改为cur.next !=null会怎么样
根据上面的链表图,我们可以推出不会打印最后一个val;
我们来修改一下代码来看结果
确实少打印了56 ,说明我们的推断是正确的
所以我们可以总结出一个结论:
如果要停在最后一个节点,那么cur.next !=null,如果要遍历完所有节点,那么cur !=null
(4)得到单链表的⻓度
java
public int size(){
int count = 0;
ListNode cur = this.head;
while(cur !=null){
count++;
cur = cur.next;
}
return count;
}
(5)查找是否包含关键字key是否在单链表当中
java
public boolean contains(int key){
ListNode cur = this.head;
while(cur != null){
if(cur.val == key){
return true;
}
cur = cur.next;
}
return false;
}
(6)头插法
1实例化一个节点对象node
2.修改指向
java
//时间复杂度为O(1)
public void addFirst(int data){
ListNode node = new ListNode(data);//实例化一个节点对象node
node.next = this.head;//修改指向
this.head = node;//修改指向
}
注意:修改指向这两步不能调换,因为调换之后相当于自己指向自己
结论:所有插入的时候先绑定后面
这时我们会有一个疑问,如果我们在一个空链表里使用头插法要不要特殊处理?
答案是不用!
因为你一开始节点的引用域本来就是null,你的head也是null,第一步修改指向没什么变化,第二步修改指向,head就存node的地址了,也就是正常插入了!
(7)尾插法
1看一下链表是不是空链表(一个节点都没有)head==null
2.找尾巴
java
//时间复杂度O(N)
public void addLast(int data){
ListNode node = new ListNode(data);
if(this.head == null){
this.head = node;
return;
}
//找尾巴
ListNode cur = this.head;
while(cur.next != null){
cur = cur.next;
}
cur.next = node;
}
(8)下标不合法异常
java
public class illegalIndexException extends RuntimeException {
public illegalIndexException() {
}
public illegalIndexException(String message) {
super(message);
}
}
(9)任意位置前面插⼊,第一个数据节点为0号下标
假如插入2位置,那就是插入2之前,所以要找到index位置的前一个节点

java
//任意位置前面插⼊,第一个数据节点为0号下标
public void addIndex(int index,int data){
int len = size();
if(index < 0 || index > len){
throw new illegalIndexException("下标不合法");
}
//头插法
if(index == 0){
addFirst(data);
return;
}
//尾插法
if(index == len){
addLast(data);
return;
}
//中间位置
ListNode cur = searchIndex(index);
if(cur == null){
return;
}
ListNode node = new ListNode(data);
node.next = cur.next;
cur.next = node;
}
/**
* 找到index位置的前一个节点
* @param index
* @return
*/
private ListNode searchIndex(int index){
int len = size();
if(index < 0 || index >len){
return null;
}
ListNode cur = this.head;
int count = 0;
while(count !=index-1){
cur = cur.next;
count++;
}
return cur;
}

(10)删除第⼀次出现关键字为key的节点
java
/**
* 查找关键字key的前一个节点,找到返回地址
* 找不到返回null
* @param key
* @return
*/
private ListNode findNode(int key){
if(this.head == null){
return null;
}
ListNode prev = this.head;
while(prev.next != null){
if(prev.next.val == key){
return prev;
}
prev = prev.next;
}
return null;
}
//删除第⼀次出现关键字为key的节点
public void remove(int key){
if(this.head == null){
return;
}
if(this.head.val == key){
this.head = this.head.next;
return;
}
//走到这里 第一个节点如果是要删除的节点 此时已经删除完毕
ListNode prev = findNode(key);
if(prev == null){
return;
}
ListNode del = prev.next;
prev.next = del.next;
}
(11)删除所有值为key的结点
java
public ListNode removeAllKey(int key) {
if(this.head == null) {
return null;
}
ListNode prev = this.head;
ListNode cur = this.head.next;
while(cur != null) {
if(cur.val == key) {
prev.next = cur.next;
cur = cur.next;
} else {
prev = cur;
cur = cur.next;
}
}
//最后处理头
if(this.head.val == key) {
this.head = this.head.next;
}
return this.head;
}
(12)清空链表中所有元素:
java
public void clear() {
while (this.head != null) {
ListNode curNext = head.next;
head.next = null;
head.prev = null;
head = curNext;
}
last = null;
}