[数据结构] 链表

一.链表

1.链表概念

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

注意:

  • 从上图中可以看出 , 链表结构在逻辑上是连续的 , 但是在物理上是不连续的
  • 现实中的结点一般都是从 堆上申请出来的
  • 从堆上申请的空间 , 是按照一定的策略来分配的 , 两次申请的 空间可能连续 , 也可能不连续

2.链表分类(8种)

实际中的链表的结构非常多样 , 以下情况组合起来就有8种

  • 单向或双向
  • 带头结点或者不带头结点
  • 循环或非循环
类型 结构特点 核心引用域
单向链表 每个节点只指向后一个节点,尾节点nextnull,只能从头部遍历 next
双向链表 每个节点同时指向前后节点,支持双向遍历,尾节点nextnull 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 中链表分为自定义链表和内置LinkedListLinkedList是双向循环链表,功能强大;
  • 链表的核心是节点和引用,增删灵活但查询效率低,适合频繁增删的场景;
  • 需掌握链表的基本操作(增、删、反转)和经典算法问题,理解指针(引用)的移动逻辑。

例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;
    }

相关推荐
闪电麦坤953 小时前
数据结构:图的表示 (Representation of Graphs)
数据结构·算法·图论
胡萝卜3.03 小时前
【LeetCode&数据结构】设计循环队列
数据结构·算法·leetcode·队列·循环队列
闻缺陷则喜何志丹4 小时前
【线段树 懒删除堆】P12372 [蓝桥杯 2022 省 Python B] 最优清零方案|普及+
数据结构·c++·线段树·懒删除堆
闻缺陷则喜何志丹4 小时前
【 线段树】P12347 [蓝桥杯 2025 省 A 第二场] 栈与乘积|普及+
数据结构·c++·蓝桥杯·线段树·洛谷
徐归阳4 小时前
数组本身的深入解析
数据结构·c++·算法
睡不醒的kun5 小时前
leetcode算法刷题的第二十六天
数据结构·c++·算法·leetcode·职场和发展·贪心算法
fangzelin56 小时前
基础排序--冒泡--选择--插入
数据结构·c++·算法
ZLRRLZ6 小时前
【数据结构】并查集
数据结构
没有bug.的程序员6 小时前
Redis 数据结构全面解析:从底层编码到实战应用
java·数据结构·redis·wpf