java实现不带哨兵节点的双向链表(二)

概述

todo 扩展:实现链表节点的删除(根据索引删除节点、删除第一个元素)、添加的元素为数组最后一个元素(addLast)等

实现上一篇博客中提到的

  1. 根据索引删除节点。
  2. 删除第一个元素。
  3. 添加的元素为数组最后一个元素。

根据索引删除节点

实现

	/**
     * 根据遍历过程中的索引删除节点 思路:
     * 核心思路是 维护待删除节点的上一个节点和下一个节点的next和pre的指向
     * <p>
     * 查找目标索引上一个节点preNode,preNode的next即目标节点targetNode,targetNode的next即nextNode
     * preNode的next直接指向nextNode,nextNode的pre直接指向preNode
     * targetNode的next设置为null,pre也设置为null 即可
     *
     * @param index
     */
    public void removeByIndex(int index) {
        Node preNode = getNodeByIndex(index - 1);
        if (preNode == null) {
            throw new RuntimeException(String.format("索引%s不正确", index));
        }
        Node targetNode = preNode.next;
        Node nextNode = targetNode.next;

        preNode.next = nextNode;
        // 由于是不带尾哨兵的双向链表 所以nextNode可能为空
        // 如 链表为 3->2->1 但要删除索引为2的元素 此时 nextNode为null
        if (nextNode != null) {

            nextNode.pre = preNode;
        }

        targetNode.next = null;
        targetNode.pre = null;
    }

测试用例

	public static void main(String[] args) {
        DoubleLinkedList list = new DoubleLinkedList();
        list.addFirst(1);
        list.addFirst(2);
        list.addFirst(3);
        list.print();
        /**
         *  输出为 list:3->1
         */
//        list.removeByIndex(1);
//        list.print();

        /**
         *  输出为 list:2->1
         */
//        list.removeByIndex(0);
//        list.print();

        /**
         *  输出为 list:3->2
         */
        list.removeByIndex(2);
        list.print();
    }

测试用例输出

测试用例输出

删除第一个元素

实现

	/**
     *  删除第一个元素
     */
    public void removeFirst(){
        // 调用removeByIndex 传index为0即可
        removeByIndex(0);
    }

测试用例

	public static void main(String[] args) {
        DoubleLinkedList list = new DoubleLinkedList();
        list.addFirst(1);
        list.addFirst(2);
        list.addFirst(3);
        list.print();
        // 测试 删除第一个元素
        list.removeFirst();
        list.print();
    }

测试用例输出

测试用例输出

添加的元素为数组最后一个元素

实现

	/**
     *  添加的元素为最后一个元素 思路:
     *      找到链表当前的最后一个元素 让这个元素的next指向欲添加的元素
     *      欲添加的元素的pre指向原来的lastNode
     *      由于本链表实现不是一个带尾哨兵的双向链表 所以需要将欲添加的元素的next设置为null(欲添加的元素是最后一个元素)
     * @param value 欲添加元素的值
     */
    public void addLast(int value){
        Node lastNode = getLastNode();
        lastNode.next=new Node(value,lastNode,null);
    }

测试用例

	public static void main(String[] args) {
        DoubleLinkedList list = new DoubleLinkedList();
        list.addFirst(1);
        list.addFirst(2);
        list.addFirst(3);
        list.print();
        // 测试 添加的元素为最后一个元素
        list.addLast(100);
        list.print(); // 输出为 list:3->2->1->100
    }

测试用例输出

测试用例输出

完整代码

package com.lovehena.datastructure;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.extern.slf4j.Slf4j;

/*
 *   不带哨兵节点的双向链表:
 *       链表中每个元素都是一个节点。每个节点的属性有当前节点的值、上一个节点的地址(变量引用)pre、下一个节点的地址next
 *       含有一个头节点。初始时,头节点指向的元素为null。
 *       当往链表中某个索引处添加节点时,思路:
 *           先找到目标索引的上一个节点preNode,记录当前preNode的next的值nextNode。
 *           构建新节点,并将新节点的pre设置为preNode,next设置为nextNode
 *           再将preNode的next值设置为新节点
 *           nextNode的pre值设置为新节点
 *       比较抽象 可以看图
 * */
@Slf4j
public class DoubleLinkedList {

    // 初始化头节点 头节点的value值无所谓
    private Node head = new Node(999, null, null);

    // 定义节点类
    @Data
    @AllArgsConstructor
    @NoArgsConstructor
    private static class Node { // 该类的对象中无需访问外部类DoubleLinkedList对象的任何成员 所以将Node类设置为static的
        int value;
        Node pre; // 上一个节点
        Node next; // 下一个节点
    }

    /**
     * 往索引为0处添加一个元素
     *
     * @param value
     */
    public void addFirst(int value) {
        // 调用insert方法即可
        insert(0, value);

    }

    /**
     *  添加的元素为最后一个元素 思路:
     *      找到链表当前的最后一个元素 让这个元素的next指向欲添加的元素
     *      欲添加的元素的pre指向原来的lastNode
     *      由于本链表实现不是一个带尾哨兵的双向链表 所以需要将欲添加的元素的next设置为null(欲添加的元素是最后一个元素)
     * @param value 欲添加元素的值
     */
    public void addLast(int value){
        Node lastNode = getLastNode();
        lastNode.next=new Node(value,lastNode,null);
    }

    /**
     * 实现往链表中插入一个元素 核心:维护各个节点的指向
     *
     * @param index 目标索引 索引是便利过程中得到的索引 链表的实现中不在链表的节点发生增删时维护索引(因为实现较为繁琐)
     * @param value 待插入元素的值
     */
    public void insert(int index, int value) {
        // 目标索引处上一节点
        Node preNode = getNodeByIndex(index - 1);
        // 目标索引原节点
        Node next = preNode.next;
        // 构建新节点 新节点的next指向目标索引原节点 pre指向目标索引处上一节点
        Node inserted = new Node(value, preNode, next);
        // 目标索引处上一节点的next指向新节点
        preNode.next = inserted;
        if (next != null) {
            // 目标索引原节点的pre指向新节点
            next.pre = inserted;
        }
    }

    /**
     * 根据索引获取节点
     *
     * @param index
     * @return
     */
    private Node getNodeByIndex(int index) {
        Node temp = head;
        int i = -1; // 头节点的索引为-1 第一个节点的索引为0
        while (temp != null) {
            if (index == i) {
                break;
            }
            temp = temp.next;
            i++;
        }
        return temp;
    }

    /**
     * 获取最后一个节点 由于这是一个不包含尾节点(哨兵节点)的链表 所以需单独实现
     *
     * @return
     */
    private Node getLastNode() {
        Node temp = head.next;
        Node lastNode = temp;
        while (temp != null) {
            lastNode = temp;
            temp = temp.next;
        }
        return lastNode;
    }

    /**
     *  删除第一个元素
     */
    public void removeFirst(){
        // 调用removeByIndex 传index为0即可
        removeByIndex(0);
    }

    /**
     * 根据遍历过程中的索引删除节点 思路:
     * 核心思路是 维护待删除节点的上一个节点和下一个节点的next和pre的指向
     * <p>
     * 查找目标索引上一个节点preNode,preNode的next即目标节点targetNode,targetNode的next即nextNode
     * preNode的next直接指向nextNode,nextNode的pre直接指向preNode
     * targetNode的next设置为null,pre也设置为null 即可
     *
     * @param index
     */
    public void removeByIndex(int index) {
        Node preNode = getNodeByIndex(index - 1);
        if (preNode == null) {
            throw new RuntimeException(String.format("索引%s不正确", index));
        }
        Node targetNode = preNode.next;
        Node nextNode = targetNode.next;

        preNode.next = nextNode;
        // 由于是不带尾哨兵的双向链表 所以nextNode可能为空
        // 如 链表为 3->2->1 但要删除索引为2的元素 此时 nextNode为null
        if (nextNode != null) {

            nextNode.pre = preNode;
        }

        targetNode.next = null;
        targetNode.pre = null;
    }

    /**
     * 遍历打印链表 如3->2->1
     */
    public void print() {
        if (head.next == null) { // 只有头节点
            log.info("双向链表为空");
            return;
        }
        StringBuilder sb = new StringBuilder();
        // 从第一个节点开始遍历 不打印头节点的值 直到最后一个元素(包含最后一个元素)
        Node temp = head.next;
        while (temp != null) {
            int value = temp.value;
            if (temp.next == null) { // 最后一个节点
                sb.append(value);
            } else {
                sb.append(value).append("->");
            }
            temp = temp.next;
        }
        log.info("list:{}", sb.toString());
    }

    public void reversePrint() {
        if (head.next == null) { // 只有头节点
            log.info("双向链表为空");
            return;
        }
        StringBuilder sb = new StringBuilder();
        Node temp = head.next;
        while (temp != null) {
            int value = temp.value;
            if (temp.next == null) { // 最后一个节点
                sb.append(value);
            } else {
                sb.append(value).append("<-");
            }
            temp = temp.next;
        }
        log.info("list:{}", sb.toString());
    }

    //todo 扩展:如何实现带头哨兵、尾哨兵的双向链表?
    //todo 扩展:如何打印双向链表的形式?
}

扩展

如何实现带头哨兵、尾哨兵的双向链表?
如何打印双向链表的形式?

最后

好了,如果对你有帮助的话,欢迎点个免费的赞哦。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

AcWing 798. 差分矩阵

题目来源&#xff1a; 找不到页面 - AcWing 题目内容&#xff1a; 输入一个 n 行 m 列的整数矩阵&#xff0c;再输入 q 个操作&#xff0c;每个操作包含五个整数 x1,y1,x2,y2,c&#xff0c;其中 (x1,y1) 和 (x2,y2)表示一个子矩阵的左上角坐标和右下角坐标。 每个操作都要将…

【kafka系列】消费者

目录 获取消息 1. 消费者获取消息的流程逻辑分析 阶段一&#xff1a;消费者初始化 阶段二&#xff1a;分区分配与重平衡&#xff08;Rebalance&#xff09; 阶段三&#xff1a;消息拉取与处理 阶段四&#xff1a;偏移量提交 核心设计思想 2. 流程 关键点总结 常见参数…

游戏引擎学习第105天

仓库:https://gitee.com/mrxiao_com/2d_game_2 查看当前进度 今天的工作重点是继续进行渲染系统的清理。昨天已经完成了一次渲染清理&#xff0c;现在还有一些内容需要继续处理。首先&#xff0c;已经解决了坐标系统的问题&#xff0c;其中世界坐标基本上是正确的&#xff0c…

重新求职刷题力扣DAY15

1.[226. 翻转二叉树](https://leetcode.cn/problems/symmetric-tree/) 给你一棵二叉树的根节点 root &#xff0c;翻转这棵二叉树&#xff0c;并返回其根节点。 示例 1&#xff1a; 输入&#xff1a;root [4,2,7,1,3,6,9] 输出&#xff1a;[4,7,2,9,6,3,1]示例 2&#xff1a…

OpenGL ES -> 投影变换矩阵完美解决绘制GLSurfaceView绘制图形拉伸问题

GLSurfaceView绘制图形拉伸问题 假如在XML文件中声明GLSurfaceView的宽高为 android:layout_width"match_parent"android:layout_height"match_parent GLSurfaceView绘制的图形在Open GL ES坐标系中&#xff0c;而Open GL ES坐标系会根据GLSurfaceView的宽高将…

Java并发编程8--线程

1.什么是线程&#xff1f; 现代操作系统调度的最小单元是线程&#xff0c;也叫轻量级进程&#xff08;Light Weight Process&#xff09;&#xff0c;在一个进程里可以创建多个线程&#xff0c;这些线程都拥有各自的计数器、堆栈和局部变量等属性&#xff0c;并且能够访问共享的…

java八股文-mysql

1. 索引 1.1 什么是索引 索引(index)是帮助Mysql高效获取数据的数据结构(有序).提高数据的检索效率,降低数据库的IO成本(不需要全表扫描).通过索引列对数据进行排序,降低数据排序成本,降低了CPU的消耗. 1.2 mysql索引使用的B树? 1. 没有使用二叉树&#xff0c;最坏情况o&…

开启蓝耘之旅:DeepSeek R1 模型在智算平台的起步教程

----------------------------------------------------------我的个人主页-------------------- 动动你的手指----------------------------------------点赞&#x1f44d; 收藏❤--------------------------------------------------------------- 引言 在深度学习的广袤领…

【设计模式】【行为型模式】访问者模式(Visitor)

&#x1f44b;hi&#xff0c;我不是一名外包公司的员工&#xff0c;也不会偷吃茶水间的零食&#xff0c;我的梦想是能写高端CRUD &#x1f525; 2025本人正在沉淀中… 博客更新速度 &#x1f44d; 欢迎点赞、收藏、关注&#xff0c;跟上我的更新节奏 &#x1f3b5; 当你的天空突…

将pyspark中的UDF提升6倍

本文亮点 调用jar中的UDF&#xff0c;减少python与JVM的交互&#xff0c;简单banchmark下对于54亿条数据集进行udf计算&#xff0c;从3小时的执行时间缩短至16分钟。 牺牲UDF部分的开发时间&#xff0c;尽量提高性能。 以接近纯python的开发成本&#xff0c;获得逼近纯scala的性…

告别第三方云存储!用File Browser在Windows上自建云盘随时随地访问

文章目录 前言1.下载安装File Browser2.启动访问File Browser3.安装cpolar内网穿透3.1 注册账号3.2 下载cpolar客户端3.3 登录cpolar web ui管理界面3.4 创建公网地址 4.固定公网地址访问 前言 无论是个人用户还是企业团队&#xff0c;都希望能够有一个高效、安全的解决方案来…

vue2老版本 npm install 安装失败_安装卡主

vue2老版本 npm install 安装失败_安装卡主 特别说明&#xff1a;vue2老版本安装慢、运行慢&#xff0c;建议升级vue3element plus vite 解决方案1&#xff1a; 第一步、修改npm 镜像为国内镜像 使用淘宝镜像&#xff1a; npm config set registry https://registry.npmmir…

Qwen2-VL 的重大省级,Qwen 发布新旗舰视觉语言模型 Qwen2.5-VL

Qwen2.5-VL 是 Qwen 的新旗舰视觉语言模型&#xff0c;也是上一代 Qwen2-VL 的重大飞跃。 Qwen2.5-VL主要特点 视觉理解事物&#xff1a;Qwen2.5-VL不仅能够熟练识别花、鸟、鱼、昆虫等常见物体&#xff0c;而且还能够分析图像中的文本、图表、图标、图形和布局。 代理性&…

[matlab优化算法-18期】基于遗传算法的模糊PID控制优化

遗传算法优化模糊PID控制器&#xff1a;原理与实践 第一节&#xff1a;背景介绍 在现代控制系统中&#xff0c;PID控制器因其结构简单、参数调整方便而被广泛应用。然而&#xff0c;传统PID控制器的参数整定依赖于经验或试错法&#xff0c;难以适应复杂系统的动态变化。模糊控…

Kotlin Lambda

Kotlin Lambda 在探索Kotlin Lambda之前&#xff0c;我们先回顾下Java中的Lambda表达式&#xff0c;Java 的 Lambda 表达式是 Java 8 引入的一项强大的功能&#xff0c;它使得函数式编程风格的代码更加简洁和易于理解。Lambda 表达式允许你以一种更简洁的方式表示实现接口&…

实现pytorch注意力机制-one demo

主要组成部分&#xff1a; 1. 定义注意力层&#xff1a; 定义一个Attention_Layer类&#xff0c;接受两个参数&#xff1a;hidden_dim&#xff08;隐藏层维度&#xff09;和is_bi_rnn&#xff08;是否是双向RNN&#xff09;。 2. 定义前向传播&#xff1a; 定义了注意力层的…

【Prometheus】prometheus结合domain_exporter实现域名监控

✨✨ 欢迎大家来到景天科技苑✨✨ 🎈🎈 养成好习惯,先赞后看哦~🎈🎈 🏆 作者简介:景天科技苑 🏆《头衔》:大厂架构师,华为云开发者社区专家博主,阿里云开发者社区专家博主,CSDN全栈领域优质创作者,掘金优秀博主,51CTO博客专家等。 🏆《博客》:Python全…

基于Java+Springboot+MySQL企业公司网站系统设计与实现

博主介绍&#xff1a;黄菊华老师《Vue.js入门与商城开发实战》《微信小程序商城开发》图书作者&#xff0c;CSDN博客专家&#xff0c;在线教育专家&#xff0c;CSDN钻石讲师&#xff1b;专注大学生毕业设计教育、辅导。 所有项目都配有从入门到精通的基础知识视频课程&#xff…

SQL复习

SQL复习 MySQL SQL介绍 SQL SQL的全拼是什么&#xff1f; SQL全拼&#xff1a;Structured Query Language&#xff0c;也叫结构化查询语言。 SQL92和SQL99有什么区别呢&#xff1f; SQL92和SQL99分别代表了92年和99年颁布的SQL标准。 在 SQL92 中采用&#xff08;&#xff…

从入门到精通:Postman 实用指南

Postman 是一款超棒的 API 开发工具&#xff0c;能用来测试、调试和管理 API&#xff0c;大大提升开发效率。下面就给大家详细讲讲它的安装、使用方法&#xff0c;再分享些实用技巧。 一、安装 Postman 你能在 Postman 官网&#xff08;https://www.postman.com &#xff09;下…