文章目录

  • 什么是双向链表
    • 属性及方法
      • 节点Node
      • size
      • 数据插入
        • 头插法 addFirst(T value)
        • 尾插法 addLast(T value)
        • 插入到指定下标位置add(int index)
      • 数据删除(返回被删除节点存储的值)
        • 删除头结点 removeFirst
        • 删除尾结点 removeLast
        • 删除指定下标节点remove(int index)
      • 获取指定下标位置节点的数据 getData(int index)
      • 获取链表长度
      • 遍历输出print()
      • 详细遍历输出
      • 清空链表
  • 实现细节

上一节说到了单链表,这一节我们来手写一个双向链表,在这之前,需要先补充一下关于双链表的概念。

什么是双向链表

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

属性及方法

节点Node

存储的数据 T data、直接前驱节点 Node pri、直接后继节点 Node next

private static class Node<T> {private T data;private Node<T> pri;private Node<T> next;
}

size

链表中节点的数量

数据插入

头插法 addFirst(T value)

插入数据首先要对链表进行判空

如果链表为空

头结点 = 插入的节点 = 尾结点

不为空则:

1、将插入节点的next指向头结点
2、调整头结点的前驱为新节点
3、将新节点设置为头结点
4、size++

实现细节:
Node<T> node = new Node<>(value);
if (size == 0) {first = node;last = first;
}else {node.next = first;first.pri = node;first = node;
}
size++;
尾插法 addLast(T value)

依旧要对链表进行判空

为空时:

等价于头插,直接调用addFirst

不为空:

1、调整尾结点的后继next指向新节点
2、调整新节点的前驱pri指向尾结点
3、更新尾结点为新的节点
4、size++

实现细节

if (size == 0){return addFirst(value);}else {Node<T> node = new Node<>(value);last.next = node;node.pri = last;last = node;}
size++;
插入到指定下标位置add(int index)

由于是根据下标插入的,所以首先要判断下标

注意,此时的下标最大值应该是size-1,但是在插入的时候是可以取到size的,因为此时的下标代表的是插入的位置(注意和删除时进行区分)
下标不合法:

这里我选择了抛出异常,也可以进行其他处理

下标合法【0 - size】:

1、判断下标取值

为0 即进行头插法 addFirst(T value)
为size 即进行尾插法 addLast(T value)
为其他值:

遍历到指定节点的前一个节点-> left
设置新节点的next值为left的next值
将left的next值设置为新节点
调整新节点的pri值为left
调整新节点的next节点的pri值为新节点

​ 2、size++

实现细节

add(int index,T value){if (index < 0 || index > size-1){throw new IndexOutOfBoundsException("数据下标越界 Index:" + index + "\tsize:" + size);} else if(index == 0){return addFirst(value);}else if (index == size){return addLast(value);}else {Node<T> node = new Node<>(value);Node<T> head = first;for (int i = 0; i < index-1; i++) {head = head.getNext();}node.next = head.next;head.next = node;node.pri = head;node.next.pri = node;}size++;
}

数据删除(返回被删除节点存储的值)

删除头结点 removeFirst

实现思想

1、判断链表是否为空
2、存储头结点存储的数据
3、将头结点更新为头结点的next节点
4、设置新的头结点的前驱节点为空
5、返回原始头结点存储的数据
6、size–

实现细节

public T removeFirst(){if (size == 0){throw new RuntimeException("链表为空!");}T data = first.getData();Node<T> node = first.next;node.setPri(null);first = node;return data;
}
删除尾结点 removeLast

实现思想

1、判断链表是否为空
2、存储尾结点存储的数据
3、更新尾结点为尾结点的前驱节点
4、设置新的尾结点的后继节点为空
5、返回原始尾结点存储的数据
6、size–

实现细节

public T removeLast(){if (size == 0){throw new RuntimeException("链表为空!");}T data = last.getData();Node<T> node = last.pri;node.setNext(null);last = node;return data;
}
删除指定下标节点remove(int index)

1、判断链表是否为空
2、判断下标是否合法

不合法:

抛出异常

合法【0 - size-1】:

为0 即删除头结点 removeFirst()

为size-1 即删除尾结点 removeLast()

为其他值

1、遍历到指定下标对应节点的前驱节点 left,则当前节点为left的后继节点 temp
2、存储temp存储的数据 data
3、设置left的后继节点值为temp的后继节点
4、设置left的后继节点的前驱节点值为left节点
5、返回原指定下标对应的数据(也可以在返回之前将当前节点的后继值设为空,便于GC回收)
6、size–

实现细节

public T remove(int index){if (size == 0){throw new RuntimeException("链表为空!");}//注意添加的时候,下标取不到size,但是添加的位置可以是size,但是删除的时候不行if (index < 0 || index > size-1){throw new IndexOutOfBoundsException("数据下标越界 Index:" + index + "\tsize:" + size);} else if(index == 0){return removeFirst();}else {Node<T> node = first;for (int i = 0; i < index - 1; i++) {node = node.next;}Node<T> temp = node.next;if (temp != last){T data = temp.getData();node.next = temp.next;temp.next.pri = node;temp.setNext(null);return data;}else {return removeLast();}}
}

获取指定下标位置节点的数据 getData(int index)

实现思想

1、判断下标是否合法【0 - size-1】
2、处理特殊下标 0 size-1,直接返回头、尾结点存储的数据
3、处理其他下标

遍历到当前下标对应节点,返回对应节点存储的数据

实现细节

public T getData(int index){if (index<0 || index>size-1){throw new IndexOutOfBoundsException("数据下标越界 Index:" + index + "\tsize:" + size);}else if (size == 0){throw new RuntimeException("链表为空");}else if (size == 1){return first.data;}else {Node<T> node = first;for (int i = 0; i < index; i++) {node = node.next;}return node.data;}
}

获取链表长度

getSize()

遍历输出print()

public void print(){if (size == 0) {System.out.println("该链表为空!");}Node<T> node = first;while (node != null) {System.out.print(node.getData() + "\t");node = node.next;}System.out.println();
}

详细遍历输出

依次输出每个节点的直接前驱存储的数据、当前节点存储的数据、后继节点存储的数据(不存在则输出 null)

public void detailPrint(){if (size == 0) {System.out.println("该链表为空!");}Node<T> node = first;while (node != null) {System.out.print("前驱节点值:");System.out.printf("%-5s",node.pri == null ? "null\t" : node.pri.getData()+"\t");System.out.print("当前节点值:");System.out.printf("%-6s",node.getData() + "\t");System.out.print("后继节点值:");System.out.printf("%-5s",node.next == null ? "null\t" : node.next.getData()+"\t");System.out.println();node = node.getNext();}System.out.println();
}

清空链表

public void clear(){first.next = null;last = first;
}

实现细节

为了方便大家进行测试,下面放了整体实现,欢迎大家测评

package com.jinhuan.chapter04.no4_4.doublyLinkedList;/*** @Author jinhuan* @Date 2022/4/19 14:43* Description:* 实现双向链表*/
public class DoublelyLinkedList<T> {private static int size;private Node<T> first;private Node<T> last;private static class Node<T> {private T data;private Node<T> pri;private Node<T> next;public Node(T data) {this.data = data;}public T getData() {return data;}public void setData(T data) {this.data = data;}public Node<T> getPri() {return pri;}public void setPri(Node<T> pri) {this.pri = pri;}public Node<T> getNext() {return next;}public void setNext(Node<T> next) {this.next = next;}}public static int getSize() {return size;}/*** 添加节点到头部* */public boolean addFirst(T value){Node<T> node = new Node<>(value);if (size == 0) {first = node;last = first;}else {node.next = first;first.pri = node;first = node;}size++;return true;}/*** 添加节点到尾部* */public boolean addLast(T value){if (size == 0){return addFirst(value);}else {Node<T> node = new Node<>(value);last.next = node;node.pri = last;last = node;}size++;return true;}/*** 元素添加到指定位置* */public boolean add(int index,T value){if (index < 0 || index > size){throw new IndexOutOfBoundsException("数据下标越界 Index:" + index + "\tsize:" + size);} else if(index == 0){return addFirst(value);}else if (index == size){return addLast(value);}else {Node<T> node = new Node<>(value);Node<T> head = first;for (int i = 0; i < index-1; i++) {head = head.getNext();}node.next = head.next;head.next = node;node.pri = head;node.next.pri = node;}size++;return true;}/*** 删除头结点* */public T removeFirst(){if (size == 0){throw new RuntimeException("链表为空!");}T data = first.getData();Node<T> node = first.next;node.setPri(null);first = node;return data;}/*** 删除尾节点* */public T removeLast(){if (size == 0){throw new RuntimeException("链表为空!");}T data = last.getData();Node<T> node = last.pri;node.setNext(null);last = node;return data;}/*** 删除指定下标结点* */public T remove(int index){if (size == 0){throw new RuntimeException("链表为空!");}//注意添加的时候,下标取不到size,但是添加的位置可以是size,但是删除的时候不行if (index < 0 || index > size-1){throw new IndexOutOfBoundsException("数据下标越界 Index:" + index + "\tsize:" + size);} else if(index == 0){return removeFirst();}else {Node<T> node = first;for (int i = 0; i < index - 1; i++) {node = node.next;}Node<T> temp = node.next;if (temp != last){T data = temp.getData();node.next = temp.next;temp.next.pri = node;temp.setNext(null);return data;}else {return removeLast();}}}/*** 获取对应下标数据* */public T getData(int index){if (index<0 || index>size-1){throw new IndexOutOfBoundsException("数据下标越界 Index:" + index + "\tsize:" + size);}else if (size == 0){throw new RuntimeException("链表为空");}else if (size == 1){return first.data;}else {Node<T> node = first;for (int i = 0; i < index; i++) {node = node.next;}return node.data;}}/*** 清空链表* */public void clear(){first.next = null;last = first;}/*** 遍历输出当前所有数据* */public void print(){if (size == 0) {System.out.println("该链表为空!");}Node<T> node = first;while (node != null) {System.out.print(node.getData() + "\t");node = node.next;}System.out.println();}/*** 详细遍历输出:*      前驱节点值*      当前节点值*      后继节点值* */public void detailPrint(){if (size == 0) {System.out.println("该链表为空!");}Node<T> node = first;while (node != null) {System.out.print("前驱节点值:");System.out.printf("%-5s",node.pri == null ? "null\t" : node.pri.getData()+"\t");System.out.print("当前节点值:");System.out.printf("%-6s",node.getData() + "\t");System.out.print("后继节点值:");System.out.printf("%-5s",node.next == null ? "null\t" : node.next.getData()+"\t");System.out.println();node = node.getNext();}System.out.println();}
}

以上均为本人个人观点,借此分享,希望能和大家一起进步。如有不慎之处,劳请各位批评指正!鄙人将不胜感激并在第一时间进行修改!

一步步带你用Java实现双向链表(超详细)相关推荐

  1. JAVA 正则表达式 (超详细,转)

    转 JAVA 正则表达式 (超详细,转) 2015年03月25日 10:27:57 阅读数:1514 在Sun的Java JDK 1.40版本中,Java自带了支持正则表达式的包,本文就抛砖引玉地介绍 ...

  2. 文件存储服务器启动不了,一步步带你用 FastDFS 搭建文件管理系统 详细的不得鸟...

    文章目录 FastDFS概述 FastDFS 中的三个角色 FastDFS 三个角色的关系 FastDFS集群 FastDFS 架构说明 FastDFS 安装 安装 FastDFS 依赖包 安装 Fa ...

  3. JAVA 正则表达式 (超详细) .

    2019独角兽企业重金招聘Python工程师标准>>> 在Sun的Java JDK 1.40版本中,Java自带了支持正则表达式的包,本文就抛砖引玉地介绍了如何使用java.util ...

  4. Java正则表达式(超详细)

    学习Java的同学注意了!!!  学习过程中遇到什么问题或者想获取学习资源的话,欢迎加入Java学习交流群,群号码:183993990  我们一起学Java! 在Sun的Java JDK 1.40版本 ...

  5. Java集合(超详细-含源码)

    一 集合体系结构 集合的体系结构分为单列集合和双列集合 二 Collection单列集合 Collection是单列集合的祖宗接口,他的功能是全部单列集合都可以继承使用的. 单列集合接口下又分为Lis ...

  6. JAVA 正则表达式 (超详细)

    新网站上线 欢迎大家 网站交易中心 在这里你可以购买或者出售你的网站. 网站信息发布中心 在这里有各种交易信息的发布.同时提供 一些软件的免费使用(附有源码). 网站博客系统 这里你可以注册自己的博客 ...

  7. Java异常(超详细!)

    1.什么是异常,java提供异常处理机制有什么用? 什么是异常:程序执行过程中的不正常情况. 异常的作用:增强程序的 健壮性. eg. public class ExceptionTest01 {pu ...

  8. Java——集合(超详细超级全)

    集合 Java 集合类可以用于存储数量不等的多个对象,还可用于保存具有映射关系的关联数组. 在这里主要讲一些我们平常很常用的一些接口和一些实现类. Java 集合可分为 Collection 和 Ma ...

  9. Java反射(超详细!)

    1.反射机制有什么用? 通过java语言中的反射机制可以操作字节码文件(可以读和修改字节码文件.) 通过反射机制可以操作代码片段.(class文件.) 2.反射机制的相关类在哪个包下? java.la ...

最新文章

  1. 别忘了,明天是BCH的压力测试日
  2. 解决Vue刷新一瞬间出现样式未加载完或者出现Vue代码问题
  3. 【Java从0到架构师,mysql视频教程推荐
  4. 向页面中添加音乐或flash
  5. Py之utils:utils库的简介、安装、使用方法之详细攻略
  6. 网易云信亮相WOT, 打造“IM+连麦互动直播”云服务
  7. 设一组初始记录关键字序列为(25,50,15,35,80,85,20,40,36,70)进行一趟归并后的结果为
  8. android log.d 参数,Android log 机制 - logd 总览
  9. @EnableTransactionManagement
  10. 开源媒体标注系统cvat
  11. Python3.7 Jpype安装
  12. 关于Mysql的mysql.sock文件
  13. 虚拟机镜像克隆、移植
  14. python爬取图片失败显示404_django使用图片延时加载引起后台404错误
  15. 关于运行Unity(一些游戏)出现0xc000007b的问题
  16. vim 安装插件及常用插件
  17. Qt中disconnect断开和blockSignals阻塞的总结
  18. 和尚吃馒头c语言程序,(八十一)约瑟夫环/鲁智深吃馒头
  19. 不同的打法,相同的内核,BAT车联网谁也不比谁更强
  20. 您的鼓励,我的动力!(CSDN 2013年度博客之星评选)

热门文章

  1. c语言界面函数,C语言图形界面常用函数集锦
  2. 使用luarocks为lua5.2安装库
  3. 离散数学及其应用基本结构知识总结
  4. 大恒工业相机搭建双目相机(软件)
  5. Matlab:基于Matlab实现人工智能算法应用的简介(SVM支撑向量机GA遗传算法PSO粒子群优化算法)、案例应用之详细攻略
  6. matlab傅里叶变换 绘图
  7. 主机名修改之后VS2017出现TFS问题
  8. Logic Pro X for Mac(专业音频编辑制作工具)
  9. 查看Dell服务器硬件信息
  10. mall商城(PC端)