【数据结构】Java实现双向链表
创始人
2025-05-31 04:11:32

目录

1. 接口的实现

2. 动手实现双链表

2.1 重写SeqList接口方法

2.2 在当前链表尾部添加节点(尾插)

2.3 在当前链表头部添加节点(头插)

2.4 检验index是否合法

2.5 在 第index位置添加节点(任意位置)

2.6  删除第index个节点

2.7 删除第一个值element的节点

2.8 删除所有值element的节点

2.9 修改第index个节点的值为element

2.10 获取第index个节点的值

2.11 判断链表中是否存在element

2.12  获取element在链表中的位置

2.13 打印链表

2.14  获取链表长度以及清空链表

3. DoubleLinkedList整体实现

3.1 DoubleLinkedList类

3.2 Test类

3.3 测试结果


LinkedList的底层是双向链表结构,由于链表没有将元素存储在连续的空间中,元素存储在单独的节点中,然后通过引用将节点连接起来了,因此在在任意位置插入或者删除元素时,不需要搬移元素,效率比较高。

1. 接口的实现

(这个接口在前面的单链表,动态数组中也可以用到)

public interface SeqList {//    尾插void add(E element);//    将 element 插入到 index 位置void add(int index,E element);//    删除 index 位置元素<返回删除的值E removeByIndex(int index);//    删除第一个值element的元素void removeByValue(E element);//    删除所有值element的元素void removeAllValue(E element);//    将下标 index 位置元素设置为 element,返回替换前的值E set(int index,E element);E get(int index);//    判断 o 是否在其中boolean contains(E element);int indexOf(E element);int size();void clear();
}

2. 动手实现双链表

2.1 重写SeqList接口方法

定义双向链表类,实现SeqList方法,重写同String方法。

(Alt + insert 快速实现方法重写)

package seqlist.link;import seqlist.SeqList;public class DoubleLinkedList implements SeqList {private DoubleNode head;//头节点private DoubleNode tail;//尾节点private int size; // 车厢节点个数,保存的元素个数//车厢类的定义,车厢作为火车的内部类,对外部完全隐藏private class DoubleNode {E val;//保存的元素DoubleNode prev;DoubleNode next;DoubleNode(E val) {this.val = val;}public DoubleNode(E val, DoubleNode prev, DoubleNode next) {this.val = val;this.prev = prev;this.next = next;}}public void addFrist(E val){ }@Overridepublic void add(E element) { }@Overridepublic void add(int index, E element) { }@Overridepublic E removeByIndex(int index) { }@Overridepublic void removeByValue(E element) { }@Overridepublic void removeAllValue(E element) { }@Overridepublic E set(int index, E element) { }public boolean rangeCheck(int index){ }@Overridepublic E get(int index) { }@Overridepublic boolean contains(E element) { }@Overridepublic int indexOf(E element) { }@Overridepublic int size() { }@Overridepublic void clear() { }@Overridepublic String toString() { }
}

2.2 在当前链表尾部添加节点(尾插)

(1)size++

(2)如果链表为空,这个新插入的节点就是头节点

(3)链表不为空,将当前链表的尾节点的next指向新节点,新节点的前驱prev指向尾节点

(4)更新当新节点为尾节点

public void add(E element) {DoubleNode node = new DoubleNode(element);size ++;if (head == null){head = node;}else {node.prev = tail;tail.next = node;}tail = node;}

2.3 在当前链表头部添加节点(头插)

这里头插不是重写方法!(因为前面的动态数组没又头插这一说法,所以这个方法就不放在接口里了)

(1)size++

(2)如果链表尾空,那么新节点就是头节点和尾节点

(3)链表不为空,将新节点的next指向head,head的前驱prev指向新节点

(4)更新新节点为头节点head

public void addFirst(E element) {DoubleNode node = new DoubleNode(element);size ++;if(head == null){tail = node;}else {node.next = head;head.prev = node;}head = node;}

2.4 检验index是否合法

 private boolean rangeCheck(int index) {if (index < 0 ||index >= size) {return false;}return true;}

2.5 在 第index位置添加节点(任意位置)

(1)判断index是否合法,不合法退出 (2)判断是不是头插或者尾插,调用相应的方法添加后退出 (3)调用node方法index位置节点的找到前驱prev,将prev.next作为后继节点node方法判断index是比较靠近head就从前往后遍历,比较靠近tail就从后往前遍历,使代码效率更高 (4)节点链接,先连左边区域,再连右边区域 (5)size++ 
DoubleNode node = new DoubleNode(element,prev,next); 看前面的构造函数,这时候已经将node.prev = prev,node.next = next
    public void add(int index, E element) {if (index < 0 || index > size){throw new IllegalArgumentException("add index illegal");}if(head == null){addFirst(element);return;}if(index == size){add(element);return;}DoubleNode prev = node(index - 1);DoubleNode next = prev.next;DoubleNode node = new DoubleNode(element,prev,next);// 先处理左边区域prev.next = node;// 再处理右半区域next.prev = node;size ++;}// 根据传入索引与中间位置的关系,决定到底从前向后寻找节点还是从后向前寻找节点// 内部使用的工具方法private DoubleNode node(int index){if (index < (size>>1)){DoubleNode ret = head;for (int i = 0; i < index; i++) {ret = ret.next;}return ret;}else {DoubleNode ret = tail;for (int i = size -1; i > index; i--) {ret = ret.prev;}return ret;}}

2.6  删除第index个节点

(1)判断index是否合法,不合法退出

(2)调用node方法找到待删除节点

(3)调用unlink(node)方法进行删除,将node的前驱prev和后继next连接,将node的prev和next置空null,再size--。(前驱prev为空时,node是头节点,将新的头节点设为node的下一个节点next;后继next为空时node为尾节点,将node的前驱prev设尾节点)

(4)返回被删除节点的值

public E removeByIndex(int index) {if (!rangeCheck(index)){throw new IllegalArgumentException("removeByIndex index illegal");}DoubleNode node = node(index);unlink(node);return node.val;}private void unlink(DoubleNode node){DoubleNode prev =node.prev;DoubleNode next = node.next;// 先处理左半区域if(prev == null){this.head = next;}else {node.next = null;prev.next = next;}// 在处理右半区域if(next == null){this.tail = prev;}else {node.next = null;next.prev = prev;}size--;}

2.7 删除第一个值element的节点

遍历链表,在链表中找到与element值相等的节点,调用unlink(node)方法进行删除
这里的图和2.6 中的图一致
    public void removeByValue(E element) {DoubleNode node = head;for (int i = 0; i < size; i++) {if (node.val.equals(element)){unlink(node);return;}node = node.next;}}

2.8 删除所有值element的节点

(1)遍历链表,在链表中找到与element值相等的节点,调用unlink(node)方法进行删除

(2)链表有多长就要遍历几次,以防有的节点没有被遍历(此时,每当进行一次删除,size就会减一,直接用size遍历可能导致某些节点漏掉了,因此用length保存初始的size值)

public void removeAllValue(E element) {DoubleNode node = head;// 因为每次unlink之后都会修改size的值,但是删除所有元素,// 要把所有链表节点全部遍历一遍int length = this.size;for (int i = 0; i < length; i++) {DoubleNode next = node.next;if (node.val.equals(element)) {unlink(node);}node = next;}}

2.9 修改第index个节点的值为element

(1)判断index是否合法,不合法退出

(2)调用node方法找到该节点

(3)保存原来节点的值

(4)修改该节点的值

(5)返回原来节点的值

    public E set(int index, E element) {if(!rangeCheck(index)){throw new IllegalArgumentException("set index illeagal");}DoubleNode node = node(index);E oldVal = node.val;node.val = element;return oldVal;}

2.10 获取第index个节点的值

(1)判断index是否合法,不合法退出

(2)调用node方法找到该节点并返回

public E get(int index) {if (!rangeCheck(index)) {throw new IllegalArgumentException("get index illegal!");}return node(index).val;}

2.11 判断链表中是否存在element

public boolean contains(E element) {DoubleNode node = head;while (node.next != null){if (node.val.equals(element)){return true;}node = node.next;}return false;}

2.12  获取element在链表中的位置

public int indexOf(E element) {DoubleNode node = head;int i = 0;while (node.next != null){if (node.val.equals(element)){return i;}i ++;node = node.next;}return -1;}

2.13 打印链表

public String toString() {StringBuilder sb = new StringBuilder();for(DoubleNode x = head; x != null; x = x.next){sb.append(x.val);sb.append("->");if(x.next == null){// 此时temp走到了尾结点sb.append("NULL");}}return sb.toString();}

2.14  获取链表长度以及清空链表

    public int size() {return size;}@Overridepublic void clear() {while (head.next != null){DoubleNode node = head.next;head.next =null;head.prev = null;head = node;}head = null;size = 0;}

3. DoubleLinkedList整体实现

3.1 DoubleLinkedList类

public class DoubleLinkedList implements SeqList {private DoubleNode head;//头节点private DoubleNode tail;//尾节点private int size; // 车厢节点个数,保存的元素个数//车厢类的定义,车厢作为火车的内部类,对外部完全隐藏private class DoubleNode {E val;//保存的元素DoubleNode prev;DoubleNode next;DoubleNode(E val) {this.val = val;}public DoubleNode(E val, DoubleNode prev, DoubleNode next) {this.val = val;this.prev = prev;this.next = next;}}//    w尾插@Overridepublic void add(E element) {DoubleNode node = new DoubleNode(element);size ++;if (head == null){head = node;}else {node.prev = tail;tail.next = node;}tail = node;}public void addFirst(E element) {DoubleNode node = new DoubleNode(element);size ++;if(head == null){tail = node;}else {node.next = head;head.prev = node;}head = node;}@Overridepublic void add(int index, E element) {if (index < 0 || index > size){throw new IllegalArgumentException("add index illegal");}if(head == null){addFirst(element);return;}if(index == size){add(element);return;}DoubleNode prev = node(index - 1);DoubleNode next = prev.next;DoubleNode node = new DoubleNode(element,prev,next);// 先处理左边区域prev.next = node;// 再处理右半区域next.prev = node;size ++;}// 根据传入索引与中间位置的关系,决定到底从前向后寻找节点还是从后向前寻找节点// 内部使用的工具方法private DoubleNode node(int index){if (index < (size>>1)){DoubleNode ret = head;for (int i = 0; i < index; i++) {ret = ret.next;}return ret;}else {DoubleNode ret = tail;for (int i = size -1; i > index; i--) {ret = ret.prev;}return ret;}}@Overridepublic E removeByIndex(int index) {if (!rangeCheck(index)){throw new IllegalArgumentException("removeByIndex index illegal");}DoubleNode node = node(index);unlink(node);return node.val;}@Overridepublic void removeByValue(E element) {DoubleNode node = head;for (int i = 0; i < size; i++) {if (node.val.equals(element)){unlink(node);return;}node = node.next;}}@Overridepublic void removeAllValue(E element) {DoubleNode node = head;// 因为每次unlink之后都会修改size的值,但是删除所有元素,// 要把所有链表节点全部遍历一遍int length = this.size;for (int i = 0; i < length; i++) {DoubleNode next = node.next;if (node.val.equals(element)) {unlink(node);}node = next;}}private void unlink(DoubleNode node){DoubleNode prev =node.prev;DoubleNode next = node.next;// 先处理左半区域if(prev == null){this.head = next;}else {node.next = null;prev.next = next;}// 在处理右半区域if(next == null){this.tail = prev;}else {node.next = null;next.prev = prev;}size--;}private boolean rangeCheck(int index) {if (index < 0 ||index >= size) {return false;}return true;}@Overridepublic E set(int index, E element) {if(!rangeCheck(index)){throw new IllegalArgumentException("set index illeagal");}DoubleNode node = node(index);E oldVal = node.val;node.val = element;return oldVal;}@Overridepublic E get(int index) {if (!rangeCheck(index)) {throw new IllegalArgumentException("get index illegal!");}return node(index).val;}@Overridepublic boolean contains(E element) {DoubleNode node = head;while (node.next != null){if (node.val.equals(element)){return true;}node = node.next;}return false;}@Overridepublic int indexOf(E element) {DoubleNode node = head;int i = 0;while (node.next != null){if (node.val.equals(element)){return i;}i ++;node = node.next;}return -1;}@Overridepublic int size() {return size;}@Overridepublic void clear() {while (head.next != null){DoubleNode node = head.next;head.next =null;head.prev = null;head = node;}head = null;size = 0;}@Overridepublic String toString() {StringBuilder sb = new StringBuilder();for(DoubleNode x = head; x != null; x = x.next){sb.append(x.val);sb.append("->");if(x.next == null){// 此时temp走到了尾结点sb.append("NULL");}}return sb.toString();}
}

3.2 Test类

public class DoubleNodeTest {public static void main(String[] args) {DoubleLinkedList list = new DoubleLinkedList<>();list.add(1);list.add(3);list.add(5);list.add(9);list.add(3);list.add(3);System.out.println(list);System.out.println("清空链表");list.clear();System.out.println(list);list.add(6);list.add(10);list.add(5);list.add(7);list.add(10);list.add(10);System.out.println(list);System.out.println("------------添加测试-----------");System.out.println("从链表尾部添加99,头部添加99999");list.add(99);list.addFirst(99999);System.out.println(list.size());System.out.println("添加index为2,element为88");list.add(2,88);System.out.println(list);System.out.println(list.size());System.out.println("-----------删除测试------------");System.out.println("删除index为0");list.removeByIndex(0);System.out.println("删除元素为6");list.removeByValue(6);System.out.println("删除所有10");list.removeAllValue(10);System.out.println(list);System.out.println("-----------其他------------");System.out.println("查看是否包含10这个元素");System.out.println(list.contains(10));System.out.println("修改index为3,element为19");list.set(3,19);System.out.println("获取index为3的元素");System.out.println(list.get(3));System.out.println(list);System.out.println("获取element为88的index");System.out.println(list.indexOf(88));System.out.println("获取链表长度");System.out.println(list.size());System.out.println("清空链表");list.clear();System.out.println(list + "...");}
}

3.3 测试结果

相关内容

热门资讯

【实验报告】实验一 图像的... 实验目的熟悉Matlab图像运算的基础——矩阵运算;熟悉图像矩阵的显示方法࿰...
MATLAB | 全网最详细网... 一篇超超超长,超超超全面网络图绘制教程,本篇基本能讲清楚所有绘制要点&#...
大模型落地比趋势更重要,NLP... 全球很多人都开始相信,以ChatGPT为代表的大模型,将带来一场NLP领...
Linux学习之端口、网络协议... 端口:设备与外界通讯交流的出口 网络协议:   网络协议是指计算机通信网...
kuernetes 资源对象分... 文章目录1. pod 状态1.1 容器启动错误类型1.2 ImagePullBackOff 错误1....
STM32实战项目-数码管 程序实现功能: 1、上电后,数码管间隔50ms计数; 2、...
TM1638和TM1639差异... TM1638和TM1639差异说明 ✨本文不涉及具体的单片机代码驱动内容,值针对芯...
Qt+MySql开发笔记:Qt... 若该文为原创文章,转载请注明原文出处 本文章博客地址:https://h...
Java内存模型中的happe... 第29讲 | Java内存模型中的happen-before是什么? Java 语言...
《扬帆优配》算力概念股大爆发,... 3月22日,9股封单金额超亿元,工业富联、鸿博股份、鹏鼎控股分别为3.0...
CF1763D Valid B... CF1763D Valid Bitonic Permutations 题目大意 拱形排列࿰...
SQL语法 DDL、DML、D... 文章目录1 SQL通用语法2 SQL分类3 DDL 数据定义语言3.1 数据库操作3.2 表操作3....
文心一言 VS ChatGPT... 3月16号,百度正式发布了『文心一言』,这是国内公司第一次发布类Chat...
CentOS8提高篇5:磁盘分...        首先需要在虚拟机中模拟添加一块新的硬盘设备,然后进行分区、格式化、挂载等...
Linux防火墙——SNAT、... 目录 NAT 一、SNAT策略及作用 1、概述 SNAT应用环境 SNAT原理 SNAT转换前提条...
部署+使用集群的算力跑CPU密... 我先在开头做一个总结,表达我最终要做的事情和最终环境是如何的,然后我会一...
Uploadifive 批量文... Uploadifive 批量文件上传_uploadifive 多个上传按钮_asing1elife的...
C++入门语法基础 文章目录:1. 什么是C++2. 命名空间2.1 域的概念2.2 命名...
2023年全国DAMA-CDG... DAMA认证为数据管理专业人士提供职业目标晋升规划,彰显了职业发展里程碑及发展阶梯定义...
php实现助记词转TRX,ET... TRX助记词转地址网上都是Java,js或其他语言开发的示例,一个简单的...
【分割数据集操作集锦】毕设记录 1. 按要求将CSV文件转成json文件 有时候一些网络模型的源码会有data.json这样的文件里...
Postman接口测试之断言 如果你看文字部分还是不太理解的话,可以看看这个视频,详细介绍postma...
前端学习第三阶段-第4章 jQ... 4-1 jQuery介绍及常用API导读 01-jQuery入门导读 02-JavaScri...
4、linux初级——Linu... 目录 一、用CRT连接开发板 1、安装CRT调试工具 2、连接开发板 3、开机后ctrl+c...
Urban Radiance ... Urban Radiance Fields:城市辐射场 摘要:这项工作的目标是根据扫描...
天干地支(Java) 题目描述 古代中国使用天干地支来记录当前的年份。 天干一共有十个,分别为:...
SpringBoot雪花ID长... Long类型精度丢失 最近项目中使用雪花ID作为主键,雪花ID是19位Long类型数...
对JSP文件的理解 JSP是java程序。(JSP本质还是一个Servlet) JSP是&#...
【03173】2021年4月高... 一、单向填空题1、大量应用软件开发工具,开始于A、20世纪70年代B、20世纪 80年...
LeetCode5.最长回文子... 目录题目链接题目分析解题思路暴力中心向两边拓展搜索 题目链接 链接 题目分析 简单来说࿰...