一.链表
1.链表概念
链表是一种 物理存储结构上非连续 的存储结构 , 数据元素的逻辑顺序是通过 链表中的引用链接次序实现的

注意:
- 从上图中可以看出 , 链表结构在逻辑上是连续的 , 但是在物理上是不连续的
- 现实中的结点一般都是从 堆上申请出来的
- 从堆上申请的空间 , 是按照一定的策略来分配的 , 两次申请的 空间可能连续 , 也可能不连续
2.链表分类(8种)
实际中的链表的结构非常多样 , 以下情况组合起来就有8种
- 单向或双向
- 带头结点或者不带头结点
- 循环或非循环
类型 | 结构特点 | 核心引用域 |
---|---|---|
单向链表 | 每个节点只指向后一个节点,尾节点next 为null ,只能从头部遍历 |
next |
双向链表 | 每个节点同时指向前后节点,支持双向遍历,尾节点next 为null |
prev + next |
循环链表 | 尾节点的next 指向头节点(单向循环)或头节点的prev 指向尾节点(双向循环),可循环遍历 |
同单向 / 双向 |
头结点可以存放数据 , 但是无任何意义
3.链表的实现
- 无头单项非循环链表的实现
- 头插法 , 尾插法 , 任意位置插入 , 查找 , 删除 , 长度 , 清空 , 打印等功能
①接口的实现
java
public interface IList {
//头插法
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();
public void display();
}
②功能的实现
java
//无头单项非循环链表的实现
public class MyLinkedList implements IList {
static class ListNode {
public int val;
public ListNode next;
public ListNode(int val) {
this.val = val;
}
}
public ListNode head;
//求链表的长度
@Override
public int size() {
if(head == null) {
return 0;
}else {
int len = 0;
ListNode cur = head;
while (cur.next != null){
cur = cur.next;
len++;
}
return len;
}
}
//头插法
@Override
public void addFirst(int data) {
ListNode node = new ListNode(data);
if(head == null){
head = node;
}else {
node.next = head;
head = node;
}
}
//尾插法
@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;
}
}
//在下标 index 位置 , 插入 data
@Override
public void addIndex(int index, int data) {
ListNode node = new ListNode(data);
int len = size();
if(index<0||index>len){
throw new IndexOutOfBoundsException("下标越界");
}
if(index == 0){
addFirst(data);
}
if(index == len){
addLast(data);
}
ListNode cur = head;
for (int i = 0; i < index; i++) {
cur = cur.next;
}
node.next = cur.next;
cur.next = node;
}
//查找是否包含关键字key是否在单链表当中
@Override
public boolean contains(int key) {
int len = size();
if(len == 0) {
return false;
}
ListNode cur = head;
while (cur.next!=null)
if(cur.val == key) {
return true;
}
cur = cur.next;
return false;
}
//删除第一次出现关键字为key的节点
@Override
public void remove(int key) {
// 空链表直接返回
if(head == null) {
return;
}
// 处理头节点是目标节点的情况
if(head.val == key) {
head = head.next;
return;
}
// 查找并删除中间/尾部的目标节点
ListNode cur = head;
while (cur.next != null) {
if (cur.next.val == key) {
// 找到目标节点,进行删除
cur.next = cur.next.next;
return; // 只删除第一个匹配的节点
}
cur = cur.next;
}
// 循环结束仍未找到,说明链表中没有该节点,无需操作
}
//删除所有值为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;
}
}
//清空链表
@Override
public void clear() {
ListNode cur = head;
while (cur!=null){
ListNode curN = cur.next;
cur = null;
cur = curN;
}
head =null;
}
//打印链表
@Override
public void display() {
ListNode cur = head;
while (cur!=null){
System.out.println(cur.val+" ");
cur = cur.next;
}
}
}
4.链表与数组的对比
- 内存分配:链表动态分配内存,数组需连续内存。
- 访问效率:数组随机访问O(1),链表需遍历O(n)。
- 插入/删除:链表在已知位置操作更高效(O(1)或O(n)),数组需移动元素(O(n))。
对比维度 | 链表(LinkedList) | 数组(ArrayList) |
---|---|---|
内存存储 | 非连续内存,通过引用连接 | 连续内存,元素按索引存储 |
增删效率 | 头部 / 尾部增删 O (1),中间增删 O (n)(需遍历) | 尾部增删 O (1),头部 / 中间增删 O (n)(需移动元素) |
查询效率 | 按索引查询 O (n)(需遍历) | 按索引查询 O (1)(直接通过地址偏移获取) |
动态扩容 | 无需扩容(节点可动态创建) | 需扩容(默认扩容为原容量 1.5 倍,浪费内存) |
线程安全 | 非线程安全 | 非线程安全 |
适用场景 | 频繁增删(尤其是头部 / 尾部)、不确定长度 | 频繁查询、已知大致长度 |
5.总结
- Java 中链表分为自定义链表和内置
LinkedList
,LinkedList
是双向循环链表,功能强大; - 链表的核心是节点和引用,增删灵活但查询效率低,适合频繁增删的场景;
- 需掌握链表的基本操作(增、删、反转)和经典算法问题,理解指针(引用)的移动逻辑。
例1:
- 给你单链表的头结点
head
,请你找出并返回链表的中间结点。 - 如果有两个中间结点,则返回第二个中间结点。
java
class Solution {
public ListNode middleNode(ListNode head) {
ListNode cur1 = head;//快指针
ListNode cur2 = head;//慢指针
while(cur1!=null&&cur1.next!=null){//
cur1 = cur1.next.next;
cur2 = cur2.next;
}
return cur2;
}
}
例2:
- 链表的逆置
java
//链表的逆置
public ListNode reverseList() {
if(head == null||head.next == null) {
return head;
}
ListNode cur = head.next;
head.next = null;
while(cur != null) {
ListNode curN = cur.next;
cur.next = head;
head = cur;
cur = curN;
}
return head;
}
例3:
- 获取倒数第 k 个结点的数据
java
//获取倒数第 k 个结点的数据
public int kthToLast( int k) {
if(head == null) return -1;
if(k <= 0) {
return -1;
}
ListNode fast = head;
ListNode slow = head;
int count = 0;
while(count != k-1) {
fast = fast.next;
if(fast == null) {
return -1;
}
count++;
}
while(fast.next != null) {
fast = fast.next;
slow = slow.next;
}
return slow.val;
}