【数据结构】模拟实现双向链表

news2025/2/25 12:18:36

你必须非常努力,才能显得毫不费劲 


目录

1.模拟实现双向链表 

1.1 DLinkedList的内部类

1.2 DLinkedList的成员属性

1.3 DLinkedList的成员方法

1.3.1 在链表开头插入一个新结点

1.3.2 在链表结尾插入一个新的结点 

1.3.3 计算结点个数

1.3.4 在链表任意位置插入一个新结点

1.3.5 查找链表中是否有指定的数据

1.3.6 删除第一次出现的指定数据的结点

1.3.7 删除所有指定数据的结点

 1.3.8 打印链表

1.3.9 清除链表


1.模拟实现双向链表 

LinkedList 底层就是一个双向链表,那我们就来实现一个双向链表

我们首先需要创建一个类来实现这样一个链表:

public class DLinkedList {

}

 接下来我们就需要将这个实现链表的过程全部放在 DLinkedList 这个类中

1.1 DLinkedList的内部类

我们知道链表是用结点来存储数据的,并且还是用结点来实现结点与结点之间的链接

那我们现在就需要在 DLinkedList类 中创建一个内部类当做结点类 

//结点
private class Node {
    private int val;//数据
    private Node prev;//指向前一个结点
    private Node next;//指向后一个结点

    public Node(int val) {
        this.val = val;
    }
}
  • data:用来存放数据
  • prev:用来存储上一个结点的地址,从而达到结点与结点之间的双向的链接
  • next:用来存储下一个结点的地址,从而达到结点与结点之间的双向的链接
  • 构造方法:每实例化一个结点类时,都需要调用构造方法,来把数据放入数据域

1.2 DLinkedList的成员属性

定义一个结点类型的 head 变量用来记录头结点,还定义了一个结点类型的 tail 变量用来记录尾结点 

private Node head;//头结点
private Node tail;//尾结点

1.3 DLinkedList的成员方法

1.3.1 在链表开头插入一个新结点

在链表开头插入一个结点,首先需要根据 data 数据实例化一个结点,然后在判断这个链表是否是空链表,如果是空链表那么这个结点即是第一个结点又是最后结点。如果不是空链表直接让这个结点的后指针域存放 head 结点的地址,让 head 前指针域存放这个结点的地址,然后让 head 等于这个结点,因为这个结点变成了第一个结点

//头插法
public void addFirst(int data) {
    Node tmpNode = new Node(data);
    if (this.head == null) {
        this.head = tmpNode;
        this.tail = tmpNode;
    } else {
        tmpNode.next = this.head;
        this.head.prev = tmpNode;
        this.head = tmpNode;
    }
}

1.3.2 在链表结尾插入一个新的结点 

在链表开头插入一个结点,首先需要根据 data 数据实例化一个结点,然后在判断这个链表是否是空链表,如果是空链表那么这个结点即是第一个结点又是最后结点。如果不是空链表直接让这个结点的前指针域存放 tail 的地址,让 tail后指针域存放这个结点地址,然后让 tail 等于这个结点,因为这个结点变成了最后一个结点

//尾插法
public void addLast(int data) {
    Node tmpNode = new Node(data);
    if (this.head == null) {
        this.head = tmpNode;
        this.tail = tmpNode;
    } else {
        tmpNode.prev = this.tail;
        this.tail.next = tmpNode;
        this.tail = tmpNode;
    }
}

1.3.3 计算结点个数

首先判断这个链表是否为空链表,如果为空直接返回0。否则不为空就遍历这个链表计数有多少个结点,遍历结束后直接返回结点的个数 

注:tail.next 是最后一个结点的下一个结点,当 cur == tail.next 说明链表已经遍历结束了

//结点个数
public int size() {
    if (this.head == null) {
        return 0;
    }
    Node cur = this.head;
    int count = 0;
    while (cur != this.tail.next) {
        count++;
        cur = cur.next;
    }
    return count;
}

1.3.4 在链表任意位置插入一个新结点

首先判断插入的位置是否合法,如果不合法直接返回 false。否则合法在判断插入的位置是否为第一个结点,如果是则为在链表开头插入一个新结点。否则判断插入的位置是否为最后一个结点,如果是则为在链表结尾插入一个新结点。否则就为在链表的中间插入一个新结点,首先需要根据 data 数据实例化一个结点,然后遍历链表找到 index 的位置,让新结点的前指针域存储 index 位置上的前一个结点的地址,让前一个结点的后指针域存储这个新结点的地址,让新结点的后指针域存储 index 位置上结点的地址,index位置上结点的前指针域存储新结点的地址

//任意位置插入,第一个数据节点为0号下标
public boolean addIndex(int index,int data){
    //判断位置是否合法
    if (index < 0 || index >= size()) {
        return false;
    }
    //判断是否为头插
    if (index == 0) {
        addFirst(data);
        return true;
    }
    //判断是否为尾插
    if (index == size() - 1) {
        addLast(data);
        return true;
    }
    //中间插
    Node tmpNode = new Node(data);
    Node cur = this.head;
    while (index != 0) {
        cur = cur.next;
        index--;
    }
    tmpNode.prev = cur.prev;
    cur.prev.next = tmpNode;
    tmpNode.next = cur;
    cur.prev = tmpNode;
    return true;
}

1.3.5 查找链表中是否有指定的数据

直接将这个链表遍历一遍,如果有直接返回 true,否则返回 false 

//查找是否包含关键字key是否在单链表当中
public boolean contains(int key) {
    Node cur = this.head;
    while (cur != this.tail.next) {
        if (cur.val != key) {
            cur = cur.next;
        } else {
            return true;
        }
    }
    return false;
}

1.3.6 删除第一次出现的指定数据的结点

首先判断这个链表是否为 null,否则就判断指定数据的结点是否为链表的第一个结点,如果是直接让 head 等于 head.next 。否则就判断指定数据的结点是否为链表的最后一个结点,如果是直接让tail 等于 tail.next。否则就遍历链表找到要删除的结点,让这个要删除的结点前一个结点的后指针域存储这个要删除的结点后一个结点地址,让这个要删除的结点后一个结点的前指针域存储这个要删除结点的前一个结点的地址即可,然后直接 return,因为只删除第一次出现的。否则就没有这个结点

//删除第一次出现关键字为key的节点
public void remove(int key) {
    if (this.head == null) {
        return;
    }
    if (this.head.val == key) {
        this.head = this.head.next;
        return;
    }
    if (this.tail.val == key) {
        this.tail = this.tail.prev;
        return;
    }
    Node cur = this.head;
    while (cur != this.tail.next) {
        if (cur.val == key) {
            cur.prev.next = cur.next;
            cur.next.prev = cur.prev;
            return;
        } else {
            cur = cur.next;
        }
    }
}

1.3.7 删除所有指定数据的结点

首先判断这个链表是否为 null,如果为空直接 return。否则判断链表是否只有一个结点且这个结点是要删除的结点,如果是则 head tail 都是指向的这个结点的,那么直接让 headtail 指向null,即可删除。否则从第二个结点开始遍历,如果遍历到指定数据的结点,让这个要删除的结点前一个结点后指针域存储这个要删除的结点后一个结点地址,让这个要删除的结点后一个结点的前指针域存储这个要删除结点的前一个结点地址即可,遍历到这个链表倒数第二个结点才会跳出循环,跳出循环后再去判断第一个结点是否是要删除的结点,如果是直接让 head 等于 head.next,然后再去判断最后一个结点是否是要删除的结点,如果是直接让 tail 等于 tail.next

注:如果链表是多个结点,则先删除中间指定数据的结点,然后再去判断第一个结点是否要删除和最后的结点是否要删除 

//删除所有值为key的节点
public void removeAllKey(int key) {
    if (this.head == null) {
        return;
    }
    //判断链表只要一个结点,且这个结点的值等于key
    if (this.head.val == key && this.head.next == null) {
        this.head = null;
        this.tail = null;
        return;
    }
    //删除中间等于key的结点
    Node cur = this.head.next;
    while (cur != this.tail) {
        if (cur.val == key) {
            cur.prev.next = cur.next;
            cur.next.prev = cur.prev;
        }
        cur = cur.next;
    }
    //第一个结点等于key删除
    if (this.head.val == key) {
        this.head = this.head.next;
    }
    //最后一个结点等于key删除
    if (this.tail.val == key) {
        this.tail = this.tail.prev;
    }
}

 1.3.8 打印链表

首先判断这个链表是否为 null,如果是直接打印 null,然后 return。否则把链表遍历一遍,依次打印结点数据域中的数据 

//打印
public void display() {
    if (head == null) {
        System.out.println("null");
        return;
    }
    Node cur = this.head;
    while(cur != this.tail.next) {
        System.out.print(cur.val + " ");
        cur = cur.next;
    }
    System.out.println();
}

1.3.9 清除链表

遍历一遍,依次清空每个结点的前指针域后指针域,让它们都指向 null 即可,在让 head 等于nulltail 等于null,这样整个链表的每个结点都没有被指向,编译器会直接回收

//清除链表
public void clear() {
    if (this.head == null) {
        return;
    }
    Node cur = this.head;
    while (cur != this.tail.next) {
        Node tmp = cur.next;
        cur.next = null;
        cur.prev = null;
        cur = tmp;
    }
    this.head = null;
    this.tail = null;
}

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

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

相关文章

4.构造器,this,修饰符详解

构造器&#xff1a; 构造器也叫构造方法&#xff0c;无返回值。非构造方法必须要有返回类型 主要作用&#xff1a;完成对象的初始化&#xff0c;创造对象时&#xff0c;自动调用构造器初始化对象 即使没有显示地使用static关键字&#xff0c;构造器实际上也是静态方法 JAVA…

HTML---基础入门知识详解

1&#xff1a;标签的概念 在别人写的网页中我们会看到许多文字&#xff0c;图片排版整齐&#xff0c;让人看的赏心悦目&#xff0c;这就是用到了标签&#xff0c;或者说标签就是帮我们实现某种作用的工具&#xff0c;比如制作段落&#xff0c;换行&#xff0c;导入图片&#x…

Android App 导出APK安装包以及制作App图标讲解及实战(图文解释 简单易懂)

操作有问题请点赞关注收藏后评论区留言~~~ 一、导出APK安装包 之前在运行App的时候&#xff0c;都是先由数据线连接手机和电脑&#xff0c;再通过Android Studio的Run菜单把App安装到手机上&#xff0c;这种方式只能在自己手机上调试应用&#xff0c;如果想在别人手机上安装应…

Python画爱心——谁能拒绝用代码敲出会跳动的爱心呢~

还不快把这份浪漫拿走&#xff01;&#xff01;节日就快到来了&#xff0c;给Ta一个惊喜吧~ 今天给大家分享一个浪漫小技巧&#xff0c;利用Python中的 HTML 制作一个立体会动的心动小爱心 成千上百个爱心汇成一个大爱心&#xff0c;从里到外形成一个立体状&#xff0c;给人视…

FITC标记SPG,FITC-SPG,荧光素标记链球菌G蛋白

产品名称&#xff1a;FITC标记SPG&#xff0c;荧光素标记链球菌G蛋白 英文名称&#xff1a;FITC-SPG 纯度&#xff1a;98% 规格&#xff1a;1mg 5mg 10mg 产地&#xff1a;西安 说明&#xff1a;提供使用说明&#xff0c;核磁图谱&#xff0c;包装&#xff0c;价格&#xff0…

ipv6地址概述——带你了解ipv6与ipv4的不同

作者简介&#xff1a;一名在校云计算网络运维学生、每天分享网络运维的学习经验、和学习笔记。 座右铭&#xff1a;低头赶路&#xff0c;敬事如仪 个人主页&#xff1a;网络豆的主页​​​​​​ 目录 前言 一.ipv4与ipv6 1.ipv4与ipv6概述 在开始Pv6的学习以前&#xf…

Tomcat的安装与Maven依赖Servlet的使用

Tomcat的安装与Maven依赖Servlet的使用&#x1f34e;一.Tomcat应用&#x1f352;1.1什么是Tomcat&#x1f352;1.2Tomcat下载安装&#x1f352;1.3 IDEA插件Smart Tomcat&#x1f34e;二.Maven依赖Servlet应用&#x1f352;2.1什么是Servlet&#x1f352;2.2创建Servlet简单实现…

《Java》图书管理系统

这是一个对于Java中知识点的类&#xff0c;抽象类&#xff0c;封装&#xff0c;继承&#xff0c;多态&#xff0c;接口等进行的一个简单的代码练习&#xff0c;对于实际的图书管理系统还需要一点的优化 目录 前言 效果展示 功能模块 书架 定义书的类 创建书架 用户 User用…

黑马点评--优惠卷秒杀

黑马点评–优惠卷秒杀 全局ID生成器&#xff1a; 是一种在分布式系统下用来生成全局唯一ID的工具&#xff0c;一般要满足下列特性&#xff1a; 为了增加ID的安全性&#xff0c;我们可以不直接使用Redis自增的数值&#xff0c;而是拼接一些其它信息&#xff1a; Redis自增ID策…

CentOS7安装MySQL8

文章目录一 前言二、Centos 7 安装 mysql8 步骤&#xff1a;1.下载MySQL官方的 Yum Repository2.安装方法一&#xff1a; 用wget 下载后安装方法二&#xff1a;下载 RMP 软件包将该软件包上传到 Linux 服务器&#xff0c;并安装。3.Navicate 远程连接配置一 前言 最近在自己的…

Python 入门基础

第一个Python程序之打印 Hello World! print("Hello World!")字符串定义的三种方式&#xff0c;type 用了检测数据类型 # 单引号定义法&#xff0c;使用单引号进行包围 name 测试 print(type(name)) # 双引号定义法 name "测试" print(type(name)) # 三…

vue3+ts组件练习(defineExpose defineEmits defineProps)

学习关键语句&#xff1a; vue3ts 组件写法 写在前面 进化到 vue3 ts 的时代&#xff0c;vue的不少语法发生了改变&#xff0c;尤其是选项式 API 变为了组合式 API 和 typescript 的使用使得从 vue2 过来的人需要尽快熟悉新的写法&#xff0c;毕竟大差不差嘛 文章最后有本文…

图像分割 - 阈值处理 - 全局阈值处理

目录 1. 介绍 2. 代码实现 3. 代码讲解 1. 介绍 当目标和背景像素的灰度分布非常不同的时候&#xff0c;可以对整个图像使用全局阈值 在大多数的应用中&#xff0c;图像之间通常存在足够的变化&#xff0c;全局阈值是一种合适的办法。所以&#xff0c;需要一种对图像做阈值…

生存分析的图你也要拼接 图形拼接r 不同的图形组合在一起

生存分析的图你也要拼接吗 因为都是ggplot体系的图表,很容易拼接,但是里面的生存分析是一个麻烦事情。因为它本身主要是survminer包出图,而这个survminer包出图并不是很稳定,但是学员自己解决了这个问题。 可以先用survminer包的arrange_ggsurvplots函数对多个生存分析图表…

CUDA By Example(五)——常量内存与事件

本章将介绍如何使用GPU上特殊的内存区域来加速应用程序的执行&#xff0c;以及如何通过事件来测量CUDA应用程序的性能。通过这些测量方法&#xff0c;你可以定量的分析对应用程序的某个修改是否会带来性能提升 文章目录常量内存光纤跟踪简介在GPU上实现光线跟踪通过常量内存来实…

[附源码]java毕业设计家庭医生系统

项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; SSM mybatis Maven Vue 等等组成&#xff0c;B/S模式 M…

Java代码审计——WebGoat XML外部实体注入(XXE)

目录 前言&#xff1a; 0x01 Let’s try 0x02 代码分析 2.1 安全的代码 0x03 Modern REST framework 3.1 解题&#xff1a; 3.2 改为xml格式: 3.3 源码分析&#xff1a; 3.4 参考解决方案 0x04 Blind XXE assignment 0x05XXE DOS attack 参考文章&#xff1a; 前言…

“百花齐放、百家争鸣”,数据可视化呈现我国科学文化的发展

公共财政对文化建设的支持日益加强&#xff0c;公共文化设施不断完善&#xff0c;覆盖城乡的公共文化服务网络初步建立&#xff0c;公共文化服务理念逐步深化&#xff0c;公共文化服务能力和均等化水平逐渐提高&#xff1b;文化产业投资向发展水平较低的中西部地区倾斜&#xf…

node.js+Express框架,前端自己创建接口

目录 一、安装 1、安装node.js 2、安装Express框架 3、安装nodemon 二、写接口 三、连接数据库 1、安装&#xff1a; 2、连接数据库 3、执行 四、注意事项 1、跨域 这篇文章看完如果您觉得有所收获&#xff0c;认为还行的话&#xff0c;就点个赞收藏一下呗 一、安装…

多线程详细介绍

一、分类 创建线程的四种方法&#xff1a; &#xff08;1&#xff09;继承Thread &#xff08;2&#xff09;实现Runnable &#xff08;3&#xff09;实现Callable &#xff08;4&#xff09;线程池创建一个新的线程可以通过继承Thread类或者实现Runnable接口来实现&#xff0…