揭开链表的真面目

简介: 揭开链表的真面目

链表是一种常见的数据结构,链表是由一连串的结点组成,这个节点就是链结点,每个链结点都由数据域指针域两部分组成。

使用链表结构可以克服数组结构需要预先知道数据大小的缺点,链表结构可以充分利用计算机内存空间,实现灵活的内存动态管理。但是链表失去了数组随机读取的优点,同时链表由于增加了结点的指针域,空间开销比较大。

链表比较好的一种理解是:将链表看成一个火车,每个车厢之间都是相互连接的,只要找到火车头,就可以找到具体的车身。链表也是,我们只关心它的头。

一 单向链表

1.1 单向链表原理图

单向链表的一个链结点包含数据域和下一个链结点的指针。头结点也包含数据域和指针域,但是一般为了方便查找,头节点不写数据,最后一个结点的指针指向空。

1.2 实现单向链表的存储等操作

创建一个链结点的实体类

public class Node {

    // 数据域
    public long data;
    // 指针域
    public Node next;

    public Node(long value){
        this.data = value;
    }
}

1.2.1 插入一个节点

在头节点后插入一个结点,第一步需要将新插入的结点指向头结点指向的结点,第二部将头结点指向新插入的结点。插入结点只需要改变一个引用,所以复杂度为O(1)。

public class LinkList {

    private Node head;
    /**
     * 在头节点之后插入一个节点
     */
    public void insertFirst(long value){
        Node node = new Node(value);
        node.next = head;
        head = node;
    }
}

1.2.2 头结点后删除一个结点

在头结点后删除一个结点,就是让头结点指向这个结点的下一个结点。复杂度也是O(1)。

public Node deleteFirst(){
    Node tmp = head;
    head = tmp.next;
    return tmp;
}

1.2.3 根据数据域查找结点

查找需要比对每个结点的数据,理论上查找一个结点平均需要N/2次,所以复杂度为O(N)。

public Node find(long value){

    Node current = head;
    while (current.data != value){
        if(current.next == null){
            return null;
        }
        current = current.next;
    }
    return current;
}

1.2.4 根据数据与删除结点

查找需要比对每个结点的数据,理论上删除一个结点平均需要N/2次,所以复杂度为O(N)。

public Node delete(int value){
    Node current = head;
    // 当前结点的前一个结点
    Node pre = head;
    while (current.data != value){
        if(current.next == null){
            return null;
        }
        pre = current;
        current = current.next;
    }
    if(current == head){
        head = head.next;
    }else{
        pre.next = current.next;
    }
    return current;
}

二 双端链表

2.1 双端链表原理图

双端链表是在单向链表的基础上,头结点增加了一个尾结点的引用。

2.2 实现双端链表的存储等操作

2.2.1 从头部插入结点

如果链表为空,则设置尾结点就是新添加的结点。复杂度为O(1)。

public class FirstLastLinkList {

    private Node first;
    private Node last;
    /**
     * 在头结点之后插入一个节点
     */
    public void insertFirst(long value){
        Node node = new Node(value);
        if(first == null){
            last = node;
        }
        node.next = first;
        first = node;
    }
}

2.2.2 从尾部插入结点

如果链表为空,则设置头结点为新添加的结点,否则设置尾结点的后一个结点为新添加的结点。复杂度为O(1)。

public void insertLast(long value){
    Node node = new Node(value);
    if(first == null){
        first = node;
    }else{
        last.next = node;
    }
    last = node;
}

2.2.3 从头部进行删除

判断头结点是否有下一个结点,如果没有则设置尾结点为null,复杂度为O(1)。

public Node deleteFirst(){

    Node tmp = first;
    if(first.next == null){
        last = null;
    }
    first = tmp.next;
    return tmp;
}

三 双向链表

3.1 双向链表原理图

每个结点除了保存对后一个结点的引用外,还保存着对前一个结点的引用。

3.2 实现双向链表的存储等操作

链结点实体类

public class Node {

    // 数据域
    public long data;
    // 后一个结点指针域
    public Node1 next;
    // 前一个结点指针域
    public Node prev;
    
    public Node(long value){
        this.data = value;
    }
}

3.2.1 从头部插入结点

如果链表为空,则设置尾结点为新添加的结点,如果不为空,还需要设置头结点的前一个结点为新添加的结点。插入结点只需要改变两个结点的引用,所以复杂度为O(1)。

public class DoubleLinkList {

    private Node first;
    private Node last;

    /**
     * 在头结点之后插入一个节点
     */
    public void insertFirst(long value){
        Node node = new Node(value);
        if(first == null){
            last = node;
        } else{
            first.prev = node;
        }
        node.next = first;
        first = node;
    }
}

3.2.2 从尾部插入结点

如果链表为空,则设置头结点为新添加的结点,否则设置尾结点的后一个结点为新添加的结点。同时设置新添加的结点的前一个结点为尾结点。插入结点只需要改变1个结点的引用,所以复杂度为O(1)。

public void insertLast(long value){
    Node node = new Node(value);
    if(first == null){
        first = node;
    }else{
        last.next = node;
        node.prev = last;
    }
    last = node;
}

3.2.3 从头部删除结点

判断头结点是否有下一个结点,如果没有则设置尾结点为null,否则设置头结点的下一个结点的prev为null。复杂度也为O(1)。

public Node deleteFirst(){

    Node tmp = first;
    if(first.next == null){
        last = null;
    }else{
        first.next.prev = null;
    }
    first = tmp.next;
    return tmp;
}

3.2.4 从尾部删除结点

如果头结点后没有其他结点,则设置头结点为null,否则设置尾结点的前一个结点的next为null,设置尾结点为前一个结点。复杂度为O(1)。

public Node deleteLast(){

    Node tmp = last; 
    if(first.next == null){
        first = null;
    }else{
        last.prev.next = null;  
    }
    last = last.prev;
    return last;
}

四 总结

链表包含一个头结点和多个结点,头结点包含一个引用,这个引用通常叫做first,它指向链表的第一个链结点。结点的next为null,则意味着这个结点时尾结点。与数组相比,链表更适合做插入、删除操作,而查找操作的复杂度更高。还有一个优势就是链表不需要初始化内存大小,不会造成内存溢出(数组中插入元素个数超过数组长度)或内存浪费(声明的数组长度比实际放的元素长)。


点关注、不迷路

如果觉得文章不错,欢迎关注点赞收藏,你们的支持是我创作的动力,感谢大家。

如果文章写的有问题,请不要吝啬,欢迎留言指出,我会及时核查修改。

如果你还想更加深入的了解我,可以微信搜索「Java旅途」进行关注。回复「1024」即可获得学习视频及精美电子书。每天7:30准时推送技术文章,让你的上班路不在孤独,而且每月还有送书活动,助你提升硬实力!

目录
相关文章
|
NoSQL JavaScript 前端开发
MongoDB系列--深入理解MongoDB聚合(Aggregation )
MongoDB中聚合(aggregate) 操作将来自多个document的value组合在一起,并通过对分组数据进行各种操作处理,并返回计算后的数据结果,主要用于处理数据(诸如统计平均值,求和等)。MongoDB提供三种方式去执行聚合操作:聚合管道(aggregation pipeline)、Map-Reduce函数以及单一的聚合命令(count、distinct、group)。
2287 0
MongoDB系列--深入理解MongoDB聚合(Aggregation )
|
7天前
|
人工智能 自然语言处理 测试技术
不用写一行代码的测试时代来了:2026年AI测试智能体搭建全指南
本文探讨2026年AI测试智能体带来的范式革命:从“写脚本”迈向“说人话”。无需编码,仅凭自然语言指令即可完成端到端测试;AI自动理解意图、定位元素、执行操作并智能断言。涵盖Harness、Autonoma、qpilot等主流方案对比与实操指南,并揭示落地避坑要点与人机协同新趋势。
|
2月前
|
人工智能 运维 机器人
RAG、Agentic RAG 和 AI Memory 到底有什么区别?
它们合在一起,回答的是同一个问题:AI 怎么从“回答当前问题”,变成“参与长期工作”。RAG 让它会查资料;Agentic RAG 让它更会查资料;Memory 让它能带着过去的上下文继续工作。当这三者组合起来,AI 才更像一个长期协作的助手,而不只是一个每次都要重新介绍背景的聊天窗口。
279 0
RAG、Agentic RAG 和 AI Memory 到底有什么区别?
|
8月前
|
人工智能 搜索推荐 数据可视化
不只是作品集:用 Next.js 打造我的数字作品库
这篇文章介绍了作者使用Next.js和Shadcn UI构建的一个个人作品信息展示模板。该模板设计简洁且结构清晰,适合直接使用或作为学习参考。作者在原有基础上进行了个性化调整,增加了交互细节和动画效果。文章还强调了开发者拥有一个统一技术身份的重要性,并详细列出了项目的技术栈、特性和环境变量配置,最后提供了在线预览链接和GitHub地址。
428 1
|
5月前
|
存储 运维 搜索推荐
2026年企业网站建设全流程实施指南
本文结合数字化转型背景,从核心原则、工具选型、全流程实施三方面,给出企业建站完整方案,助力品牌传播与业务转化。
522 0
2026年企业网站建设全流程实施指南
|
6月前
|
人工智能 机器人 API
Laravel AI SDK 在 Laracon India 2026 首次亮相
Laravel AI SDK 于 Laracon India 2026 首发!由 Taylor Otwell 打造,提供统一优雅的 API,支持聊天、图像/音频生成、转录、语义搜索(embeddings)等,兼容 OpenAI、Gemini、ElevenLabs 等多服务商,开箱即用,深度集成 Laravel 生态。(239字)
513 7
|
6月前
|
监控 搜索推荐 应用服务中间件
‌301重定向完全指南:从原理到生产级实践
301重定向是HTTP永久性跳转机制,用于旧URL迁移至新地址。核心价值:①SEO权重传递(90–99%);②保障用户体验,避免404;③统一URL规范(域名/协议/路径)。适用场景包括域名更换、HTTPS升级、结构优化等。Nginx、Apache及WordPress插件均可高效实现。(239字)
2167 2
|
8月前
|
存储 Java API
Spring Boot使用slf4j进行日志记录
本文介绍了在Spring Boot项目中使用SLF4J结合Logback进行日志管理的方法。通过配置`application.yml`和`logback.xml`,实现日志级别、输出格式、文件存储与滚动策略的灵活控制,并推荐使用SLF4J门面模式替代直接调用具体日志实现,提升系统可维护性与扩展性。
|
10月前
|
机器学习/深度学习 运维 算法
Cliproxy代理与Kameleo指纹浏览器:多账号隔离的技术实现
在风控模型多维迭代下,传统代理难逃关联封禁。Cliproxy动态IP与Kameleo指纹引擎协同,实现IP与指纹异步漂移,解耦网络与设备层标识,降低贝叶斯算法置信度。基于BGP Anycast住宅IP池、Chromium内核熵注入及seccomp防护,实测唯一性降至1/28600,店铺关联封禁率由3.2%锐减至0.14%。
532 0
|
人工智能 安全 算法
2025 WAIC探展合合信息展台:AI鉴伪技术洞察“看不见”的伪造痕迹
2025 WAIC世界人工智能大会上,合合信息展台展示了多项AI鉴伪黑科技,涵盖图像、视频和文档篡改检测,能毫秒级识别AI伪造内容,广泛应用于金融、保险、社交媒体等多个领域,助力构建安全可信的AI生态。
441 0
2025 WAIC探展合合信息展台:AI鉴伪技术洞察“看不见”的伪造痕迹