java 数据结构 链表

本文介绍了链表的基本概念、实现与应用。首先对比了链表与顺序表的特性差异:链表插入删除O(1)但访问O(n),空间不连续;顺序表访问O(1)但插入删除O(n),空间连续。重点演示了单链表的Java实现,包括节点结构、头插尾插、任意位置插入、删除等核心操作,并给出力扣203题(删除指定节点)和206题(反转链表)的解题思路。随后介绍了双向链表的优势(双向遍历、O(1)头尾操作)及其实现,最后对比LinkedList的特性与遍历方式,强调根据实际场景选择合适的数据结构(频繁增删选链表,随机访问选顺序表)。

链表的引入

我们在学链表之前学过顺序表

顺序表在随机访问快捷 o(1)时间复杂度 而删除 修改 是o(n)复杂度

于是链表在物理上不连续 在空间连续 它的修改 删除 就是o(1) 不用整个移动

引出来了链表

链表划分

头 有头 没头

方向 有单向和双向

循环 循环 不循环

几个组合 等 具有不同特性

一般链表构成

节点 节点里有数据 和存储下一个位置的引用

由一个个节点连接成链表

从堆上申请出来的节点

链表的模拟实现

package Book3;

/**

* Created with IntelliJ IDEA.

* Description:

* User: 28931

* Date: 2026-07-15

* Time: 13:12

*/

// 假设shxiain是自定义接口,无实现暂时保留

public class lianbiao implements shxiain {

// 静态内部节点类

static class aa {

public int b;

public aa d;

public aa(int b) {

this.b = b;

}

}

// 链表头节点,定义在外部类,每条链表独立,去掉static

private aa head;

// 构造方法:传入头节点初始化链表

public void chuang() {

aa a1 = new aa(1);

aa a2 = new aa(2);

aa a3 = new aa(3);

aa a4 = new aa(4);

a1.d = a2;

a2.d = a3;

a3.d = a4;

head = a1;

}

// 正确头插法:头部新增节点

public void addFirst(int data) {

aa newNode = new aa(data);

// 新节点的后继 = 原来的头节点

newNode.d = this.head;

// 更新头节点为新节点

this.head = newNode;

}

// 打印链表(属于lianbiao,外部实例可直接调用)

public void display() {

aa cur = head;

while (cur != null) {

System.out.print(cur.b + " ");

cur = cur.d;

}

System.out.println();

}

//尾插法

public void addLast(int data) {

aa b = new aa(data);

aa cur = head;

while (cur.d != null) {

cur = cur.d;

}

cur.d = b;

}

//任意位置插入,第一个数据节点为0号下标

public void addIndex(int index, int data) {

aa d = new aa(data);

aa e = head;

if (index == 0) {

d.d = head;

head = d;

return;

}

for (int i = 0; i < index - 1; i++) {

e = e.d;

}

d.d = e.d;

e.d = d;

}

//查找是否包含关键字key是否在单链表当中

public boolean contains(int key) {

aa cur = head;

while (cur != null) {

if (cur.b == key) {

return true;

}

cur = cur.d;

}

return false;

}

//删除第一次出现关键字为key的节点

public void remove(int key) {

aa cur = head;

while (head != null && head.b == key) {

head = head.d;

}

if (head == null) {

return;

}

while (cur.d != null) {

if (cur.d.b == key) {

cur.d = cur.d.d;

return;

}

cur = cur.d;

}

}

public int size() {

aa cur = head;

int a = 0;

while (cur != null) {

a++;

cur = cur.d;

}

return a;

}

//删除所有值为key的节点

public void removeAllKey(int key) {

aa cur = head;

while (head != null && head.b == key) {

head = head.d;

}

if (head == null) {

return;

}

while (cur.d != null) {

if (cur.d.b == key) {

cur.d = cur.d.d;

return;

}

cur = cur.d;

}

}

public void clear() {

head=null;

}

public static void main(String\[\] args) {

// 手动构建链表 1→2→3→4

// 创建链表对象,传入头节点a1

lianbiao list = new lianbiao();

list.chuang();

list.addLast(1);

list.remove(1);

System.out.println(list.size());

list.clear();

list.display();

}

}

题目

删除链表中等于给定值 val的所有节点。

203. 移除链表元素 - 力扣(LeetCode)
考虑头节点和尾节点 空节点
if(head==null){

return null;

}

if(head!=null&&head.val==val){

head=head.next;

}

ListNode cur=head;

while(cur!=null){

//避免访问空节点 导致空指针异常

if(cur.next.val==val){

cur.next=cur.next.next;

}else{

cur=cur.next;

}

}

return head;

反转一个单链表

206. 反转链表 - 力扣(LeetCode)
public ListNode reverseList(ListNode head) {

//判断是否为空

if(head==null){

return null;

}

将第一个节点的下一个地址为null设置为最后一个节点 用头插法

ListNode cur=head.next;

head.next=null;

while(cur!=null){

//记录下一个节点的地址

ListNode cn=cur.next;

//更新坐标和位置

cur.next=head;

head=cur;

cur=cn;

}

return head;

}

其中链表的题目大量运用快慢指针的做法

双向链表的模拟实现

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 display (){}
public void clear (){}

复制代码
package Book3;

import java.util.LinkedList;
import java.util.ListIterator;

public class shaungxianglianbiao {
    public int val;
    public shaungxianglianbiao p;
    public shaungxianglianbiao s;
    public shaungxianglianbiao head;
    public shaungxianglianbiao last;

    public shaungxianglianbiao(int val) {
        this.val = val;
    }

    public int size() {
        int a = 0;

        for(shaungxianglianbiao cur = this.head; cur != null; cur = cur.s) {
            ++a;
        }

        return a;
    }

    public void display() {
        for(shaungxianglianbiao cur = this.head; cur != null; cur = cur.s) {
            System.out.println(cur.val);
        }

    }

    public void clear() {
        this.head = this.last = null;
    }

    public void addFirst(int data) {
        shaungxianglianbiao datas = new shaungxianglianbiao(data);
        if (this.head == null) {
            this.last = this.head = datas;
        } else {
            this.head.p = datas;
            datas.s = this.head;
            this.head = datas;
        }

    }

    public void addLast(int data) {
        shaungxianglianbiao a = new shaungxianglianbiao(data);
        if (this.last == null) {
            this.head = a;
            this.last = a;
        } else {
            this.last.s = a;
            a.p = this.last;
            this.last = a;
        }

    }

    public void addIndex(int index, int data) {
        if (index >= 0 && index <= this.size()) {
            if (index == 0) {
                this.addFirst(data);
            } else if (index == this.size()) {
                this.addLast(data);
            } else {
                shaungxianglianbiao cur = this.head;
                shaungxianglianbiao a = new shaungxianglianbiao(data);

                for(int i = 0; i < index; ++i) {
                    cur = cur.s;
                }

                a.p = cur;
                cur.s.p = a;
                a.s = cur.s;
                cur.s = a;
            }
        }
    }

    public boolean contains(int key) {
        for(shaungxianglianbiao cur = this.head; cur != null; cur = cur.s) {
            if (cur.val == key) {
                return true;
            }
        }

        return false;
    }

    public void remove(int key) {
        if (this.head != null) {
            shaungxianglianbiao cur;
            for(cur = this.head; cur != null && cur.val != key; cur = cur.s) {
            }

            if (cur != null) {
                shaungxianglianbiao p = cur.p;
                shaungxianglianbiao s = cur.s;
                if (cur == this.head) {
                    this.head = s;
                    if (this.head != null) {
                        p = null;
                    }
                }

                if (s == null) {
                    this.last = p;
                } else {
                    s.p = p;
                    p.s = s;
                }
            }
        }
    }

    public void removeAllKey(int key) {
        if (this.head != null) {
            shaungxianglianbiao nextNode;
            for(shaungxianglianbiao cur = this.head; cur != null; cur = nextNode) {
                nextNode = cur.s;
                if (cur.val == key) {
                    shaungxianglianbiao p = cur.p;
                    shaungxianglianbiao s = cur.s;
                    if (p == null) {
                        this.head = s;
                    } else {
                        p.s = s;
                    }

                    if (s == null) {
                        this.last = p;
                    } else {
                        s.p = p;
                    }

                    cur.p = null;
                    cur.s = null;
                }
            }

        }
    }

    public static void main(String[] args) {
        shaungxianglianbiao a = new shaungxianglianbiao(-1);
        a.addFirst(1);
        a.remove(1);
        a.display();
        LinkedList<Integer> e = new LinkedList();
        e.add(1);
        e.add(2);
        e.add(3);
        ListIterator<Integer> it = e.listIterator(e.size());

        while(it.hasPrevious()) {
            System.out.print(it.previous());
        }

    }
}





特点
  1. 可双向遍历:既能从头往后走,也能从尾往前走;单向链表只能正向。
  2. 删除节点优势:拿到目标节点即可直接删,不用遍历找前驱;单向链表必须遍历找前驱。
  3. 头尾指针加持:头插、尾插、头尾删除都是 O(1),不需要遍历。
  4. 空间开销更大:每个节点多存 1 个prev前驱指针。

LinkedList

  1. LinkedList 实现了 List 接口
  2. LinkedList 的底层使用了双向链表
  3. LinkedList 没有实现 RandomAccess 接口,因此 LinkedList 不支持随机访问
  4. LinkedList 的任意位置插入和删除元素时效率比较高,时间复杂度为 O(1)
  5. LinkedList 比较适合任意位置插入的场景
    有参构造和无参构造
    LinkedList a=new LinkedList();
    LinkedList<Character> a=new LinkedList<>(LinkedList相同的类);

遍历方式

// foreach 遍历
for ( int e : list ) {
System . out . print ( e + " " );
}
System . out . println ();
// 使用迭代器遍历 --- 正向遍历
ListIterator < Integer > it = list . listIterator ();
while ( it . hasNext ()){
System . out . print ( it . next () + " " );
}
System . out . println ();
// 使用反向迭代器 --- 反向遍历
ListIterator < Integer > rit = list . listIterator ( list . size ());
while ( rit . hasPrevious ()){
System . out . print ( rit . previous () + " " );

链表和顺序表的区别

链表的插入删除 时间复杂度o(1) 但是随机访问下标是o(N) 在空间上不连续 但是在逻辑上连续
顺序表的插入删除时间复杂度 是o(N) 但是随机访问下标是o(1) 在空间上连续
所以正确运用链表和顺序表才能更好的运用

相关推荐
猫猫不是喵喵.1 小时前
认证授权【Spring Security】
java·认证授权
小罗水1 小时前
第1章 大模型、企业知识库与 RAG 平台概述
java·spring
不知疲倦的仄仄1 小时前
MySQL/Read View快照/MVCC/串行化
java·数据库·mysql
奶糖 肥晨1 小时前
mac系统中Java项目环境配置与问题解决全记录|环境准备篇:JDK与Maven安装配置
java·macos·maven
SimonKing2 小时前
OpenCode 桌面版这 10 天偷偷迭代了 5 个版本,你还在用旧版吗?
java·后端·程序员
Dontla2 小时前
冒烟测试介绍(Smoke Test、BVT构建验证测试、构建验收测试)与回归测试对比、与单元测试对比、与集成测试对比、Cypress
java·单元测试·集成测试
暖和_白开水3 小时前
数据分析agent(十):loguru日志输出优化 和es 启动流程
java·elasticsearch·数据分析
减瓦3 小时前
Gradle 版本演进全景:构建工具的进化密码
java·gradle
玛丽莲茼蒿3 小时前
很难出错的Spring5(十)—— IOC进阶
java·开发语言·jvm
减瓦3 小时前
Jackson 使用指南
java·spring boot·json