Java数据结构-单链表

news2024/10/10 22:20:57

目录

  • 1. 链表相关概念
  • 2. 功能实现
    • 2.1 整体框架
    • 2.2 增加
      • addFirst
      • addLast
      • addIndex
    • 2.3 删除
      • 2.3.1 remove
      • 2.3.2 removeall
    • 2.4 查找
      • contains
    • 2.4 修改
      • 2.4.1 modify
      • 2.4.2 modifyAll
  • 3. 全部代码

1. 链表相关概念

老铁们好!今天我们要学习一种新的数据结构:链表。链表是一种逻辑上连续,物理上不连续的一种数据结构,它由一个一个的结点构成,每个结点包含数据和下一个结点的地址,如图。由于最后一个结点没有后续的结点,所以next为null
在这里插入图片描述
链表有很多种结构:单向、双向、循环、非循环、有头、无头。链表结构有很多,但是我们只学习两种:无头单向非循环链表(最简单的)和无头双向非循环链表(Java集合框架中的LinkedList的底层),今天我们先实现最简单的无头单向非循环链表。

2. 功能实现

2.1 整体框架

定义MySignalList泛型类,表示一个链表,定义静态内部类ListNode表示链表的结点,内部类中val表示数据,next表示下一个结点的地址,同时提供ListNode构造方法,初始化val,head表示链表的第一个结点,此外,MySignalList类中还有若干的增删查改等功能的方法

public class MySignalList<T> {

    static class ListNode {
        public Object val;
        public ListNode next;

        public ListNode(Object val) {
            this.val = val;
        }
        
    }
    public ListNode head;
    //........
    //若干方法
}  

接下来我们介绍相关的功能的实现

2.2 增加

addFirst

头插,将申请的新的结点的next指向head,再更新head的值即可

    public void addFirst(T val) {
        ListNode newNode = new ListNode(val);
        newNode.next = head;
        head = newNode;
    }

addLast

尾插,如果链表没有元素,head为空,则将申请的新结点赋值给head即可,如果链表不为空,需要找到最后一个结点(尾结点),将尾结点的next指向新的结点即可

    public void addLast(T val) {
        ListNode newNode = new ListNode(val);
        if (head == null) {
        	//如果链表为空
            head = newNode;
            return;
        }
        //找尾
        ListNode tail = head;
        while (tail.next != null) {
            tail = tail.next;
        }
        tail.next = newNode;
    }

addIndex

在Index位置插入数据,首先需要判断Index的合法性,我们定义一个异常类IndexNotLegalException:

public class IndexNotLegalException extends RuntimeException {
    public IndexNotLegalException() {
        super();
    }
    public IndexNotLegalException(String s) {
        super(s);
    }
}

如果Index小于0或者Index大于链表的长度size,抛出异常,如果Index等于0,表示头插;如果Index等于链表长度size,表示尾插,头插、尾插直接调用之前写的方法即可
获取链表长度:

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

排查了Index的合法性后,要在Index位置插入数据,先要找到Index前一个位置(定义临时结点cur,循环往后走Index-1步),找到之后,让新的结点绑定后面的链表,然后将Index前一个结点的next指向新的结点,如图
在这里插入图片描述

    public void addIndex(int Index, T val) {
        //判断Index的合法性
        try {
            if (Index < 0 || Index > size()) {
                throw new IndexNotLegalException("Index不合法");
            }
        } catch (IndexNotLegalException e) {
            e.printStackTrace();
        }
        if (Index == 0) {
            addFirst(val);
            return;
        }
        if (Index == size()) {
            addLast(val);
            return;
        }
        ListNode newNode = new ListNode(val);
        ListNode cur = head;
        for (int i = 0; i < Index - 1; i++) {
            cur = cur.next;
        }
        //cur在index前面
        newNode.next = cur.next;
        cur.next = newNode;
    }

2.3 删除

2.3.1 remove

删除第一个值为key的结点,如果链表为空直接return。
删除的逻辑:循环遍历链表,使用equals方法比较结点的值是否与key相等,定义前驱结点prev记录前一个结点的位置,定义当前结点cur用于遍历链表。如果找到了,让prev结点的next指向当前结点的下一个结点
在这里插入图片描述

    //删除第一次出现的key
    public void remove(T key) {
        if (head == null) {
            return;
        }
        ListNode cur = head;
        ListNode prev = null;
        while (cur != null) {
            if (cur.val.equals(key)) {
                if (prev == null) {
                	//如果第一个结点的值是key,让head往后走,然后return
                    head = head.next;
                    return;
                }
                prev.next = cur.next;
                return;
            }
            prev = cur;
            cur = cur.next;
        }
    }

2.3.2 removeall

删除所有值为key的结点,与remove逻辑一样,remove删除之后就return了,removeall删除之后不需要return,继续执行之前的操作即可

    //删除所有的key
    public void removeAll(T key) {
        if (head == null) {
            return;
        }
        //如果前半部分都是key,直接让head往后走,如果head为空了就return
        while (head.val.equals(key)) {
            head = head.next;
            if (head == null) {
                return;
            }
        }
        ListNode cur = head.next;
        ListNode prev = head;
        while (cur != null) {
            if (cur.val.equals(key)) {
                prev.next = cur.next;
            } else {
                prev = cur;
            }
            cur = cur.next;
        }
    }

在这里插入图片描述

2.4 查找

contains

判断链表中是否包含某个值,如果包含返回true,否则返回false
循环遍历链表,使用equals方法比较值是否相等,如果有相等的结点返回true

    //是否包含val
    public boolean contains(T val) {
        if (head == null) {
        	//链表为空,返回false
            return false;
        }
        ListNode cur = head;
        while (cur != null) {
            if (cur.val.equals(val)) {
                return true;
            }
            cur = cur.next;
        }
        return false;
    }

2.4 修改

2.4.1 modify

修改第一个值为oldVal的结点,将其改为newVal
循环遍历链表,找到了该结点就将val修改

    //修改第一次出现的oldVal,将它改为newVal
    public void modify(T oldVal, T newVal) {
        if (head == null) {
            return;
        }
        ListNode cur = head;
        while (cur != null) {
            if (cur.val.equals(oldVal)) {
                cur.val = newVal;
                return;
            }
            cur = cur.next;
        }
    }

2.4.2 modifyAll

修改所有值为结点oldVal,将其改为newVal
循环链表,直到修改完所有的结点为止

    //修改所有的oldVal,将它改为newVal
    public void modifyAll(T oldVal, T newVal) {
        if (head == null) {
            return;
        }
        ListNode cur = head;
        while (cur != null) {
            if (cur.val.equals(oldVal)) {
                cur.val = newVal;
            }
            cur = cur.next;
        }
    }

3. 全部代码

//单链表
public class MySignalList<T> {

    static class ListNode {
        public Object val;
        public ListNode next;

        public ListNode(Object val) {
            this.val = val;
        }
    }

    public ListNode head;

    //头插
    public void addFirst(T val) {
        ListNode newNode = buyNode(val);
        newNode.next = head;
        head = newNode;
    }

    //尾插
    public void addLast(T val) {
        ListNode newNode = buyNode(val);
        if (head == null) {
            head = newNode;
            return;
        }
        //找尾
        ListNode tail = head;
        while (tail.next != null) {
            tail = tail.next;
        }
        tail.next = newNode;
    }

    //在Index位置插入
    public void addIndex(int Index, T val) {
        //判断Index的合法性
        try {
            if (Index < 0 || Index > size()) {
                throw new IndexNotLegalException("Index不合法");
            }
        } catch (IndexNotLegalException e) {
            e.printStackTrace();
        }
        if (Index == 0) {
            addFirst(val);
            return;
        }
        if (Index == size()) {
            addLast(val);
            return;
        }
        ListNode newNode = buyNode(val);
        ListNode cur = head;
        for (int i = 0; i < Index - 1; i++) {
            cur = cur.next;
        }
        //cur在index前面
        newNode.next = cur.next;
        cur.next = newNode;
    }

    //删除第一次出现的key
    public void remove(T key) {
        if (head == null) {
            return;
        }
        ListNode cur = head;
        ListNode prev = null;
        while (cur != null) {
            if (cur.val.equals(key)) {
                if (prev == null) {
                    head = head.next;
                    return;
                }
                prev.next = cur.next;
                return;
            }
            prev = cur;
            cur = cur.next;
        }
    }

    //删除所有的key
    public void removeAll(T key) {
        if (head == null) {
            return;
        }
        //如果前半部分都是key
        while (head.val.equals(key)) {
            head = head.next;
            if (head == null) {
                return;
            }
        }
        ListNode cur = head.next;
        ListNode prev = head;
        while (cur != null) {
            if (cur.val.equals(key)) {
                prev.next = cur.next;
            } else {
                prev = cur;
            }
            cur = cur.next;
        }
    }

    //是否包含val
    public boolean contains(T val) {
        if (head == null) {
            return false;
        }
        ListNode cur = head;
        while (cur != null) {
            if (cur.val.equals(val)) {
                return true;
            }
            cur = cur.next;
        }
        return false;
    }

    //返回结点
    public ListNode find(T findVal) {
        if (head == null) {
            return null;
        }
        ListNode ret = head;
        while (ret != null) {
            if (ret.val.equals(findVal)) {
                return ret;
            }
            ret = ret.next;
        }
        return null;
    }

    //修改第一次出现的oldVal,将它改为newVal
    public void modify(T oldVal, T newVal) {
        if (head == null) {
            return;
        }
        ListNode cur = head;
        while (cur != null) {
            if (cur.val.equals(oldVal)) {
                cur.val = newVal;
                return;
            }
            cur = cur.next;
        }
    }

    //修改所有的的oldVal,将它改为newVal
    public void modifyAll(T oldVal, T newVal) {
        if (head == null) {
            return;
        }
        ListNode cur = head;
        while (cur != null) {
            if (cur.val.equals(oldVal)) {
                cur.val = newVal;
            }
            cur = cur.next;
        }
    }

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

今天的内容就到这里,感谢老铁们的点赞、收藏、评论~❤

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

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

相关文章

vue3+ts | axios 二次封装

安装 pnpm i axios axios 二次封装 // 实用性工具文件 放于 utils文件中 // 对axios函数库进行二次封装&#xff1f; // 二次封装的目的&#xff1f;利用axios请求、响应拦截器 import axios from axios// axios.create 创建一个axios实例&#xff1a;可以设置基础路径&a…

蓝桥杯 - 小明的背包1(01背包)

解题思路&#xff1a; 本题属于01背包问题&#xff0c;使用动态规划 dp[ j ]表示容量为 j 的背包的最大价值 注意&#xff1a; 需要时刻提醒自己dp[ j ]代表的含义&#xff0c;不然容易晕头转向 注意越界问题&#xff0c;且 j 需要倒序遍历 如果正序遍历 dp[1] dp[1 - vo…

公平锁和非公平锁,为什么要“非公平”?

公平锁和非公平锁&#xff0c;为什么要“非公平”&#xff1f; 主要讲一讲公平锁和非公平锁&#xff0c;以及为什么要“非公平”&#xff1f; 什么是公平和非公平 首先&#xff0c;我们来看下什么是公平锁和非公平锁&#xff0c;公平锁指的是按照线程请求的顺序&#xff0c;…

对话式 AI 的天花板来了,Hume AI 再拿 5000 万美金融资

Hume AI 刚刚推出了世界上第一款共情 AI 语音接口 EVI&#xff0c;它可以让开发人员能够通过几行代码将情感智能人工智能语音集成到健康和保健、AR/VR、客户服务呼叫中心、医疗保健等领域的应用程序中。 之前我介绍过好几个对话式 AI 产品&#xff0c;或者也可以将它们称为 AI…

WebPack的使用及属性配、打包资源

WebPack(静态模块打包工具)(webpack默认只识别js和json内容) WebPack的作用 把静态模块内容压缩、整合、转译等&#xff08;前端工程化&#xff09; 1️⃣把less/sass转成css代码 2️⃣把ES6降级成ES5 3️⃣支持多种模块文件类型&#xff0c;多种模块标准语法 export、export…

STM32之HAL开发——串口配置(CubeMX)

串口引脚初始化&#xff08;CubeMX&#xff09; 选择RCC时钟来源 选择时钟频率&#xff0c;配置为最高频率72MHZ 将单片机调试模式打开 SW模式 选择窗口一配置为异步通信模式 点击IO口设置页面&#xff0c;可以看到当前使用的串口一的引脚。如果想使用复用功能&#xff0c;只需…

状态压缩dp[详解 + 例题]

1 . 题目 2 . 分析 可以发现 : 横放的方案数 总方案数 ; 剩下的都是竖放去填补空缺 ; 关于状态定义 : 考虑按列拜访 &#xff0c; 某列的隔行用0/1表示摆放状态 ; 某行为1 : 表示横放 , 0 : 表示竖放 ; 状态表示 : f[i][j] : 表示拜访第i列&#xff0c;状态为j的方案数…

6_springboot_shiro_jwt_多端认证鉴权_过滤器链

1. Shiro过滤器链 ​ Shiro中可以配置多个Filter&#xff0c;那么Shiro是如何管理这些过滤器的&#xff1f;主要是靠ShiroFilterFactoryBean 它是一个Spring Bean&#xff0c;用于在Spring应用中配置Shiro的Web过滤器链。过滤器链是一系列按照特定顺序排列的过滤器&#xff0c…

关于Devc++调试的问题以及解决STL变量无法查看

目前Devc的调试主要有以下几点&#xff1a; 1.调试不能直接查看stl变量&#xff0c;会卡死不动 2.目前单步进入只能用鼠标键按 3.若想按下一步进入函数体内&#xff0c;要在函数体内打上断点才行 4.调试到return 0 ;上一句就停了&#xff0c;不会结束程序 5.目前F2跳至断点…

代码随想录训练营Day35:● 860.柠檬水找零 ● 406.根据身高重建队列 ● 452. 用最少数量的箭引爆气球

860.柠檬水找零 题目链接 https://leetcode.cn/problems/lemonade-change/description/ 题目描述 思路 class Solution {public boolean lemonadeChange(int[] bills) {int five 0,ten 0,twenty 0;for (int i 0; i < bills.length; i) {if(bills[i] 5){five;}else i…

记录在项目中引用本地的npm包

1、先把需要的包下载下来&#xff0c;以Photo Sphere Viewer 为引用的npm包、项目以shpereRepo为例子 git clone https://github.com/mistic100/Photo-Sphere-Viewer2、拉下代码后修改之后执行 ./build.sh build.sh #!/usr/bin/env bashyarn run build targetDir"../sh…

SpringBoot 登录认证(二)

HTTP是无状态协议 HTTP协议是无状态协议。什么又是无状态的协议&#xff1f; 所谓无状态&#xff0c;指的是每一次请求都是独立的&#xff0c;下一次请求并不会携带上一次请求的数据。而浏览器与服务器之间进行交互&#xff0c;基于HTTP协议也就意味着现在我们通过浏览器来访…

jmeter中参数加密

加密接口常用的方式有&#xff1a; MD5&#xff0c;SHA&#xff0c;HmacSHA RSA AES&#xff0c;DES&#xff0c;Base64 压测中有些参数需要进行加密&#xff0c;加密方式已接口文档为主。 MD5加密 比如MD5加密的接口文档&#xff1a; 请求URL&#xff1a;http://101.34.221…

Unity AI Navigation自动寻路

目录 前言一、Unity中AI Navigation是什么&#xff1f;二、使用步骤1.安装AI Navigation2.创建模型和材质3.编写向目标移动的脚本4.NavMeshLink桥接组件5.NavMeshObstacle组件6.NavMeshModifler组件 三、效果总结 前言 Unity是一款强大的游戏开发引擎&#xff0c;而人工智能&a…

基于单片机病房呼叫系统数码管显示房号设计

**单片机设计介绍&#xff0c;基于单片机病房呼叫系统数码管显示房号设计 文章目录 一 概要二、功能设计设计思路 三、 软件设计原理图 五、 程序六、 文章目录 一 概要 基于单片机病房呼叫系统数码管显示房号设计概要主要涵盖了利用单片机技术实现病房呼叫系统&#xff0c;并…

基于springboot实现校园周边美食探索及分享平台系统项目【项目源码+论文说明】计算机毕业设计

基于springboot实现园周边美食探索及分享平台系统演示 摘要 美食一直是与人们日常生活息息相关的产业。传统的电话订餐或者到店消费已经不能适应市场发展的需求。随着网络的迅速崛起&#xff0c;互联网日益成为提供信息的最佳俱渠道和逐步走向传统的流通领域&#xff0c;传统的…

【SpringCloud】Ribbon负载均衡

&#x1f3e1;浩泽学编程&#xff1a;个人主页 &#x1f525; 推荐专栏&#xff1a;《深入浅出SpringBoot》《java对AI的调用开发》 《RabbitMQ》《Spring》《SpringMVC》《项目实战》 &#x1f6f8;学无止境&#xff0c;不骄不躁&#xff0c;知行合一 文章目录 …

新家装修选中央空调如何选?认准约克VRF中央空调

在现代家居生活中,追求舒适和健康生活环境的家庭越来越倾向于选择中央空调系统。面对市场上琳琅满目的中央空调品牌,如何挑选一款合适的家用中央空调成为许多消费者的一大难题。今天,我们以约克VRF中央空调为例,深入探讨其特点和优势,为广大家庭提供一个舒适的选择答案。 首先…

玩美移动升级虚拟试妆体验: 推出3D多色腮红AR试妆解决方案

领先的AI和AR美妆和时尚技术解决方案供应商&#xff0c;以及“美力AI”解决方案开发商——玩美移动于今天宣布&#xff0c;其3D腮红虚拟试妆工具已实现进一步技术提升。用户可以通过三色腮红虚拟试妆体验&#xff0c;尝试更多腮红色号、质地以及多种颜色组合妆效。本次虚拟试妆…

应用方案 D431L可调精密基准源,可耐压35V以上

概述 D431L是一种低压三端可调稳压器&#xff0c;保证在适用温度范围内的热稳定性。输出电压可以设置为VREF(约1.24V)~16V&#xff08;接两个外部电阻&#xff09;。该装置具有典型的动态输出0.2Ω的阻抗。在很多应用中&#xff0c;可替代齐纳二极管。 D431L有TO-92和SOT23封装…