【数据结构与算法】5.详解双向链表的基本操作(Java语言实现)

在这里插入图片描述
📚博客主页:爱敲代码的小杨.

✨专栏:《Java SE语法》

❤️感谢大家点赞👍🏻收藏⭐评论✍🏻,您的三连就是我持续更新的动力❤️

🙏小杨水平有限,欢迎各位大佬指点,相互学习进步!

文章目录

  • 0. 前言
  • 1. 双链表的定义
  • 2. LinkedList 模拟实现
    • 2.1 接口
    • 2.2 定义双向链表类
    • 2.3 定义两个指针,分别指向头节点和尾节点
    • 2.4 头插法
    • 2.5 尾插法
    • 2.6 指定位置插入元素
    • 2.7 查找指定元素
    • 2.8 删除指定元素
    • 2.9 删除链表中所有指定元素
    • 2.10 统计链表元素个数
    • 2.11 清空链表
    • 2.12 打印链表
    • 2.13 测试
  • 3.代码
  • 4. ArrayList和LinkedList的区别

0. 前言

上一篇【数据结构与算法】4.自主实现单链表的增删查改 我们自主实现了单链表的操作,在Java的集合类中LinkedList底层实现是无头双向循环链表。所以今天我们模拟LinkedList的实现。

1. 双链表的定义

学习双链表之前,做个回顾。

单链表的特点:

  1. 我们可以轻松的到达下一个节点,但是回到前一节点是很难的。
  2. 只能从头遍历到尾或者从尾遍历到头(一般是从头到尾)

双链表的特点:

  1. 每次在插入或删除某个节点时, 需要处理四个节点的引用, 而不是两个. 实现起来要困难一些
  2. 相对于单向链表, 必然占用内存空间更大一些.
  3. 既可以从头遍历到尾, 又可以从尾遍历到头

双链表的定义:

双向链表也叫双链表,是链表的一种,它的每个数据结点中都有两个指针,分别指向直接后继和直接前驱。所以,从双向链表中的任意一个结点开始,都可以很方便地访问它的前驱结点和后继结点。

image-20240125171130806

指针域(prev):用于指向当前节点的直接前驱节点;

数据域(data):用于存储数据元素;

指针域(next):用于指向当前节点的直接后继节点。

2. LinkedList 模拟实现

2.1 接口

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();
}

2.2 定义双向链表类

static class ListNode {public int val; // 数值域 - 存放当前节点的值public ListNode next; // next域 指向下一个节点public ListNode prev; // prev域 指向上一个节点public ListNode(int val) {this.val = val;}
}

2.3 定义两个指针,分别指向头节点和尾节点

// 链表的属性 链表的头节点
public ListNode head; 
// 链表的属性 链表的尾节点
public ListNode last;

2.4 头插法

  1. 判断链表是否为空,如果为空,将新节点的node设置为头节点,将新节点的node设置为尾节点

    head = node;
    last = node;
    
  2. 如果链表不为空,将新节点的nodenext域设置为头节点,将当前头节点的prev设置为新节点的node,更新头节点为新节点的node

    node.next = head;
    head.prev = node;
    head = node;
    

动画演示:

1706271497900

代码:

/**
* 头插法
* @param data
*/
@Override
public void addFirst(int data) {ListNode node = new ListNode(data);if (head == null) {head = node;last = node;}else {node.next = head;head.prev = node;head = node;}
}

2.5 尾插法

  1. 判断链表是否为空,如果为空,将新节点的node设置为头节点,将新节点的node设置为尾节点

    head = node;
    last = node;
    
  2. 如果链表不为空,将最后一个节点lastnext域指向新节点,新节点的prev域指向最后一个节点,更新尾节点为新节点

    last.next = node;
    node.prev = last;
    last = node;
    

动画演示:

1706271820869

代码:

    /**** 尾插法* @param data*/@Overridepublic void addLast(int data) {ListNode node = new ListNode(data);if (head == null) {head = node;last = node;} else {last.next = node;node.prev = last;last = node;}}

2.6 指定位置插入元素

  1. 判断索引idnex是否合法,如果不合法则抛出异常。

    if (index < 0 || index > size()) {throw new IndexException("index不合法:" + index);
    }
    
  2. 判断链表是否为空,如果为空则将新节点设置为头节点和尾节点

    if (head == null) {head = node;last = node;return;
    }
    
  3. 如果索引index == 0,则使用头插法,如果索引index = 链表长度,则使用尾插法

    if (index == 0) {addFirst(data);return;
    }
    if (index == size()) {addLast(data);return;
    }
    
  4. 找到索引节点(当前节点)

        private ListNode findIndex(int index) {ListNode cur = head;while (index != 0) {cur = cur.next;index--;}return cur;}
    
  5. 将新节点的next域指向当前节点,新节点的prev域指向当前节点的前一个节点,当前节点的prev域指向新节点,更新新节点的上一个节点的next域指向当前节点。

    ListNode cur = findIndex(index);
    node.next = cur;
    node.prev = cur.prev;
    cur.prev = node;
    node.prev.next = node;
    

动画演示:

1706272997346

2.7 查找指定元素

  1. 从头节点开始遍历链表,如果当前节点的值与要查找的key相等,则返回ture,如果不相等则移动下一个节点继续查找。如果遍历完链表都没有找到key则返回false.

代码:

    @Overridepublic boolean contains(int key) {ListNode cur = head;while (cur != null) {if (cur.val == key) {return true;}cur = cur.next;}return false;}

2.8 删除指定元素

  1. 从头节点开始遍历链表,找到要删除的节点

  2. 情况一:删除的节点为头节点,更新头节点为下一个节点,更新下一个节点的prev域置为空。

    1706277210409

  3. 情况二:链表中只有一个元素,且正好要删除这个元素。

  4. 情况三:删除的节点为尾节点,更新尾节点为当前节点的上一个节点,上一个节点的next域置为空

    1706277783850

  5. 情况四:删除中间节点,当前节点的上一个节点的next域指向当前节点的下一个节点,更新下一个节点的prev域指向当前节点的上一个节点

    1706278104977

  6. 删除了节点就结束方法的执行

代码:

    @Overridepublic void remove(int key) {ListNode cur = head;while (cur != null) {if (cur.val == key) { // 找到要删除的元素了if (cur == head) { // 删除头节点head = head.next;if (head != null) {head.prev = null;} else { // 链表中只有一个元素,且这个正好删除这个元素last = null;}} else { // 删除中间节点cur.prev.next = cur.next;if (cur.next != null) {cur.next.prev = cur.prev;} else {// 删除尾节点last = cur.prev;}}return;// 删除了节点就结束方法}cur = cur.next;}}

2.9 删除链表中所有指定元素

从头节点遍历链表,与删除指定元素的方式一样,删除节点后继续遍历链表,直到遍历完链表,删除所有指定的元素即可。

代码:

    @Overridepublic void removeAllKey(int key) {ListNode cur = head;while (cur != null) {if (cur.val == key) { // 找到要删除的元素了if (cur == head) { // 删除头节点head = head.next;if (head != null) {head.prev = null;} else { // 链表中只有一个元素,且这个正好删除这个元素last = null;}} else { // 删除中间节点cur.prev.next = cur.next;if (cur.next != null) {cur.next.prev = cur.prev;} else {// 删除尾节点last = cur.prev;}}}cur = cur.next;}}

2.10 统计链表元素个数

代码:

    @Overridepublic int size() {int count = 0;ListNode cur = head;while (cur != null) {count++;cur = cur.next;}return count;}

2.11 清空链表

将头节点和尾节点置为空,没有引用指向直接被JVM回收

    @Overridepublic void clear() {head = null;last = null;}

2.12 打印链表

    @Overridepublic void display() {ListNode cur = head;while (cur != null) {System.out.print(cur.val + " ");cur = cur.next;}System.out.println();}

2.13 测试

public class Test {public static void main(String[] args) {MyLinkedList myLinkedList = new MyLinkedList();// 头插法myLinkedList.addFirst(1);myLinkedList.addFirst(2);myLinkedList.addFirst(3);// 打印链表myLinkedList.display();System.out.println("=========");// 尾插法myLinkedList.addLast(4);myLinkedList.addLast(5);myLinkedList.addLast(6);// 打印链表myLinkedList.display();System.out.println("=========");// 在4 位置插入7myLinkedList.addIndex(4,7);// 打印链表myLinkedList.display();System.out.println("=========");// 查找元素 7 8System.out.println(myLinkedList.contains(7));System.out.println(myLinkedList.contains(8));System.out.println("=========");// 删除3 6 4myLinkedList.remove(3);myLinkedList.display();System.out.println("=========");myLinkedList.remove(6);myLinkedList.display();System.out.println("=========");myLinkedList.remove(4);myLinkedList.display();System.out.println("=========");// 删除全部的2myLinkedList.addLast(2);myLinkedList.addLast(2);myLinkedList.addLast(2);myLinkedList.display();myLinkedList.removeAllKey(2);myLinkedList.display();System.out.println("=========");// 统计个数System.out.println(myLinkedList.size());System.out.println("=========");// 清空链表myLinkedList.clear();myLinkedList.display();System.out.println("=========");// 统计个数System.out.println(myLinkedList.size());}
}// 运行结果
3 2 1 
=========
3 2 1 4 5 6 
=========
3 2 1 4 7 5 6 
=========
true
false
=========
2 1 4 7 5 6 
=========
2 1 4 7 5 
=========
2 1 7 5 
=========
2 1 7 5 2 2 2 
1 7 5 
=========
3
==================
0

3.代码

MyLinkedList类:

public class MyLinkedList implements IList{static class ListNode {public int val; // 数值域 - 存放当前节点的值public ListNode next; // next域 指向下一个节点public ListNode prev; // prev域 指向上一个节点public ListNode(int val) {this.val = val;}}// 链表的属性 链表的头节点public ListNode head;// 链表的属性 链表的尾节点public ListNode last;/*** 头插法* @param data*/@Overridepublic void addFirst(int data) {ListNode node = new ListNode(data);if (head == null) {head = node;last = node;}else {node.next = head;head.prev = node;head = node;}}/**** 尾插法* @param data*/@Overridepublic void addLast(int data) {ListNode node = new ListNode(data);if (head == null) {head = node;last = node;} else {last.next = node;node.prev = last;last = node;}}@Overridepublic void addIndex(int index, int data) {if (index < 0 || index > size()) {throw new IndexException("index不合法:" + index);}ListNode node = new ListNode(data);if (head == null) {head = node;last = node;return;}if (index == 0) {addFirst(data);return;}if (index == size()) {addLast(data);return;}ListNode cur = findIndex(index);node.next = cur;node.prev = cur.prev;cur.prev = node;node.prev.next = node;}private ListNode findIndex(int index) {ListNode cur = head;while (index != 0) {cur = cur.next;index--;}return cur;}@Overridepublic boolean contains(int key) {ListNode cur = head;while (cur != null) {if (cur.val == key) {return true;}cur = cur.next;}return false;}@Overridepublic void remove(int key) {ListNode cur = head;while (cur != null) {if (cur.val == key) { // 找到要删除的元素了if (cur == head) { // 删除头节点head = head.next;if (head != null) {head.prev = null;} else { // 链表中只有一个元素,且这个正好删除这个元素last = null;}} else { // 删除中间节点cur.prev.next = cur.next;if (cur.next != null) {cur.next.prev = cur.prev;} else {// 删除尾节点last = cur.prev;}}return;}cur = cur.next;}}@Overridepublic void removeAllKey(int key) {ListNode cur = head;while (cur != null) {if (cur.val == key) { // 找到要删除的元素了if (cur == head) { // 删除头节点head = head.next;if (head != null) {head.prev = null;} else { // 链表中只有一个元素,且这个正好删除这个元素last = null;}} else { // 删除中间节点cur.prev.next = cur.next;if (cur.next != null) {cur.next.prev = cur.prev;} else {// 删除尾节点last = cur.prev;}}}cur = cur.next;}}@Overridepublic int size() {int count = 0;ListNode cur = head;while (cur != null) {count++;cur = cur.next;}return count;}@Overridepublic void clear() {head = null;last = null;}@Overridepublic void display() {ListNode cur = head;while (cur != null) {System.out.print(cur.val + " ");cur = cur.next;}System.out.println();}
}

接口:

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();
}

异常类:

public class IndexException extends RuntimeException{public IndexException() {}public IndexException(String msg) {super(msg);}
}

4. ArrayList和LinkedList的区别

不同点ArrayListLinkedList
存储空间上物理上一定连续逻辑上连续,但物理上不一定连续
随机访问支持O(1)不支持O(n)
头插需要搬移元素,效率低O(n)只需要修改引用的指向,时间复杂度为O(1)
插入空间不够时需要扩容没有容量的概念
应用场景元素高效存储 + 频繁访问任意位置插入和删除频繁

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/650188.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

SpringBoot之时间数据前端显示格式化

背景 在实际我们通常需要在前端显示对数据操作的时间或者最近的更新时间&#xff0c;如果我们只是简单的使用 LocalDateTime.now()来传入数据不进行任何处理那么我们就会得到非常难看的数据 解决方式&#xff1a; 1). 方式一 在属性上加上注解&#xff0c;对日期进行格式…

LeetCode.42. 接雨水

题目 题目链接 分析 读完本题以及结合题目给出的图我们可以很直观的看到&#xff0c;这道题目是让我们求形成凹槽的面积。 我们可以针对每一个数字形成凹槽的面积进行计算&#xff0c;然后相加数组每一个数字形成凹槽的面积即可。 那么问题来了&#xff0c;怎么知道一个数…

【Java与网络6】实现一个自己的HTTP浏览器

前面我们讨论了HTTP协议的基本结构和Socket编程的基本原理&#xff0c;本文我们来整个大活&#xff1a;自己实现一个简单的浏览器。 目录 1.主线程循环体 2.readHostAndPort()方法的实现 3.readHttpRequest()方法的实现 4.sendHttpRequest()方法的实现 5.readHttpRespons…

深度强化学习(王树森)笔记03

深度强化学习&#xff08;DRL&#xff09; 本文是学习笔记&#xff0c;如有侵权&#xff0c;请联系删除。本文在ChatGPT辅助下完成。 参考链接 Deep Reinforcement Learning官方链接&#xff1a;https://github.com/wangshusen/DRL 源代码链接&#xff1a;https://github.c…

STM32——创建HAL库工程

第一步 新建工程文件夹 所有文件夹存放的内容&#xff1a; 第二步 拷贝/新建工程相关文件 1、Drivers&#xff08;创建如下文件夹&#xff0c;除readme.txt&#xff09; 文件夹存放的内容&#xff1a; 2、Middlewares和output暂时不需要创建&#xff08;目前用不到&#x…

自动驾驶的决策层逻辑

作者 / 阿宝 编辑 / 阿宝 出品 / 阿宝1990 自动驾驶意味着决策责任方的转移 我国2020至2025年将会是向高级自动驾驶跨越的关键5年。自动驾驶等级提高意味着对驾驶员参与度的需求降低&#xff0c;以L3级别为界&#xff0c;低级别自动驾驶环境监测主体和决策责任方仍保留于驾驶…

华为配置在用户物理位置变化时部署业务随行示例(V200R006C00、V200R007C00、V200R008C00)

配置在用户物理位置变化时部署业务随行示例&#xff08;V200R006C00、V200R007C00、V200R008C00&#xff09; 业务随行简介配置注意事项组网需求需求分析数据规划配置思路操作步骤配置文件 组网图形 图1 组网图 业务随行简介配置注意事项组网需求需求分析数据规划配置思路操作步…

新建一个基于标准库的工程(STM32)

目录 1.新建存放工程的文件夹 2.打开KEIL5软件 3.新建一个本次工程的文件夹 4.添加工程的必要文件 4.1打开STM32的启动文件 ​编辑 4.2&#xff1a; 4.3添加内核寄存器文件 ​编辑 5.回到keil5软件&#xff0c;将刚才复制的那些文件添加到工程中 5.1添加一个启动文件&am…

浅谈WPF之样式与资源

WPF通过样式&#xff0c;不仅可以方便的设置控件元素的展示方式&#xff0c;给用户呈现多样化的体验&#xff0c;还简化配置&#xff0c;避免重复设置元素的属性&#xff0c;以达到节约成本&#xff0c;提高工作效率的目的&#xff0c;样式也是资源的一种表现形式。本文以一个简…

k8s-基础知识(Service,NodePort,CusterIP,无头服务,NameSpace,资源限制)

Node Node 是 Pod 真正运行的主机&#xff0c;可以是物理机&#xff0c;也可以是虚拟机。 Annotations 原文链接 Annotations 是 key/value 形式附加于对象的注解。不同于 Labels 用于标志和选择对象&#xff0c;Annotations 则是用来记录一些附加信息&#xff0c;用来辅助应…

x-cmd pkg | httpx - 为 Python 设计的下一代 HTTP 客户端库

目录 简介首次用户功能特点进一步探索 简介 HTTPX 是一个为 Python 设计的下一代 HTTP 客户端库&#xff0c;由 Tom Christie 创建。它提供了同步和异步的 API&#xff0c;并支持 HTTP/1.1 和 HTTP/2 协议。与 Requests 库类似&#xff0c;但增加了对异步请求的支持和 HTTP/2 …

MySql8的简单使用(1.模糊查询 2.group by 分组 having过滤 3.JSON字段的实践)

MySql8的简单使用&#xff08;1.模糊查询 2.group by 分组 having过滤 3.JSON字段的实践&#xff09; 一.like模糊查询、group by 分组 having 过滤 建表语句 create table student(id int PRIMARY KEY,name char(10),age int,sex char(5)); alter table student add height…

【TOP解刊】IEEE(trans)顶刊,国人绝对优势,同领域2个月录用,5天见刊!

工程技术类 • 顶刊解读 今天带来IEEE旗下工程技术领域顶刊&#xff0c;究竟这本高分期刊审稿情况如何呢&#xff1f;好投吗&#xff1f;一起来看看下文解析。如有投稿意向可重点关注&#xff0c;具体详情见下文&#xff1a; 01 期刊简介 IEEE Transactions on Power Electr…

FlashInternImage实战:使用 FlashInternImage实现图像分类任务(二)

文章目录 训练部分导入项目使用的库设置随机因子设置全局参数图像预处理与增强读取数据设置Loss设置模型设置优化器和学习率调整策略设置混合精度&#xff0c;DP多卡&#xff0c;EMA定义训练和验证函数训练函数验证函数调用训练和验证方法 运行以及结果查看测试完整的代码 在上…

QDockWidget : 想要 top -> left -> rigt -> bottom 的布局实现

上图红圈中的实现&#xff0c;第一次想要实现&#xff0c;总会和想的不一样。 第一种情况 第二种情况 第三种情况 有时候为了达到一种效果&#xff0c;也算是煞费苦心了&#xff0c;且不说这个demo还是找的其他CSDN博主的&#xff0c;但是功夫不负有心人。 解决办法 先让 Doc…

[UI5 常用控件] 03.Icon, Avatar,Image

文章目录 前言1. Icon2. Avatar2.1 displayShape2.2 initials2.3 backgroundColor2.4 Size2.5 fallbackIcon2.6 badgeIcon2.7 badgeValueState2.8 active 3. Image 前言 本章节记录常用控件Title,Link,Label。 其路径分别是&#xff1a; sap.m.Iconsap.m.Avatarsap.m.Image 1…

01_ESP32 MicroPython开发环境搭建

一、工作原理 Python源代码->Python解释器(MicroPython)-->二进制代码(01010)-->硬件电路(ESP32)-->高低电平输出-->其他设备 二、准备工作&#xff1a; 硬件&#xff1a;ESP32开发版&#xff0c;有很多个版本可选&#xff0c;我这里用的是ESP-32开发板&…

K8s 安装部署-Master和Minion(Node)文档

K8s 安装部署-Master和Minion(Node)文档 操作系统版本&#xff1a;CentOS 7.4 Master &#xff1a;172.20.26.167 Minion-1&#xff1a;172.20.26.198 Minion-2&#xff1a;172.20.26.210&#xff08;后增加节点&#xff09; ETCD&#xff1a;172.20.27.218 先安装部署ETC…

navicat 可以直接往 mysql导入excel表格

妈呀 还好 提前问了一下&#xff0c;不然哼哧哼哧在那里写&#xff0c;导入接口。。

05-TiDB 之 HTAP 快速上手

混合型在线事务与在线分析处理 (Hybrid Transactional and Analytical Processing, HTAP) 功能 HTAP 存储引擎&#xff1a;行存 与列存 同时存在&#xff0c;自动同步&#xff0c;保持强一致性。行存 OLTP &#xff0c;列存 OLAPHTAP 数据一致性&#xff1a;作为一个分布式事务…