Collection与数据结构 链表与LinkedList (一):链表概述与单向无头非循环链表实现

1.ArrayList的缺点

上篇文章我们已经对顺序表进行了实现,并且对ArrayList进行了使用,我们知道ArrayList底层是使用数组实现的.
由于其底层是一段连续空间,当在ArrayList任意位置插入或者删除元素时,就需要将后序元素整体往前或者往后搬移,时间复杂度为O(n),效率比较低,因此ArrayList不适合做任意位置插入和删除比较多的场景。因此:java集合中又引入了LinkedList,即链表结构。

2.链表

2.1 链表的概念与结构

链表是一种物理存储结构上非连续存储结构,数据元素的逻辑顺序是通过链表中的引用链接次序实现的.就像一列动车一样.
在这里插入图片描述
在这里插入图片描述
通过上图我们可以看到,一个链表中有一节一节的"车厢",还有中间的"链条".我们称每一节"车厢"为结点,我们可以看到每一个结点中有下一个结点的地址,链表就是通过存储节点的地址来连接起来的,还有该结点的值.上面就是我们需要重点掌握的一种链表类型,叫做单向无头非循环链表.其实链表还有好多类型,下面我们来展示.

2.2 链表的类型

链表有以下几种性质:有头/无头,单向/双向,循环/非循环

  1. 有头/无头
    在这里插入图片描述
    在这里插入图片描述
    它们的区别就是,有头链表的head永远指向一个固定的结点,而无头链表的head永远在改变.
  2. 单向/双向
    在这里插入图片描述

在这里插入图片描述
3. 循环/非循环
在这里插入图片描述
在这里插入图片描述
上面几种性质排列组合可以得到许多中链表的类型,这里我们重点掌握2中即可,一种是单向无头非循环,它在笔面试中经常出现,另一种是是无头双向循环链表,Java中的LinkedList底层就是通过它来实现的.

3. 单向无头非循环链表实现

下面是要实现的接口,即链表中的常用方法:

public interface ILinkedList {
    void addFirst(int data);
    //尾插法
    void addLast(int data);
    //任意位置插入,第一个数据节点为0号下标
    void addIndex(int index,int data);
    //查找是否包含关键字key是否在单链表当中
    boolean contains(int key);
    //删除第一次出现关键字为key的节点
    void remove(int key);
    //删除所有值为key的节点
    void removeAllKey(int key);
    //得到单链表的长度
    int size();
    //清空链表
    void clear() ;
    //打印链表
    void display();

}

下面我们来实现这些方法:

public class MyLinkedList implements ILinkedList {
    static class Node{
        public int val;
        public Node next = null;
        public Node(int val) {
            this.val = val;
        }
    }
    public Node head = null;

    /**
     * 创建默认链表
     */
    public void createDeafultLinkedList(){
        Node node = new Node(23);
        this.head = node;
        Node node1 = new Node(34);
        node.next = node1;
        Node node2 = new Node(45);
        node1.next = node2;
        Node node3 = new Node(56);
        node2.next = node3;
        Node node4 = new Node(67);
        node3.next = node4;
    }

    /**
     * 在链表头部添加新的结点
     * @param data
     */
    @Override
    public void addFirst(int data) {
        Node node = new Node(data);
//        if(head == null){
//            head = node;
//        }else {
//            node.next = head;
//            head = node;
//        }
        //上面的代码其实没必要验证链表是否为空,head为null赋值过去还是null
        node.next = this.head;
        this.head = node;
    }

    /**
     * 在尾部添加新的结点
     * @param data
     */
    @Override
    public void addLast(int data) {
        Node node = new Node(data);
        Node cur = this.head;
        if (this.head == null){
            this.head = node;
        }else {
            while (cur.next != null){
                cur = cur.next;
            }
            cur.next = node;
        }
    }

    /**
     * 在指定位置添加结点
     * @param index
     * @param data
     */
    @Override
    public void addIndex(int index, int data) {
        if (index > size() || index < 0){
            throw new IndexExeption("下标有误");
        }
        Node node = new Node(data);
        if (this.head == null){
            this.head = node;
            return;//记得返回,不然会执行中间插入的代码
        }
        if (index == 0){//在链表头添加
            addFirst(node.val);
            return;
        }
        if (index == size()){//在链表尾部添加
            addLast(node.val);
            return;
        }
        //在中部添加
        Node cur = this.head;
        int count = 0;
        while (count != index-1){//寻找cur的前一个结点
            cur = cur.next;
            count++;
        }
        node.next = cur.next;
        cur.next = node;
    }

    /**
     * 检测链表中是否含有指定的值
     * @param key
     * @return
     */
    @Override
    public boolean contains(int key) {
        Node cur = this.head;
        while (cur != null){//先遍历链表
            if(cur.val == key){//在遍历的过程中如果等于,返回true
                return true;
            }
            cur = cur.next;
        }
        return false;
    }

    /**
     * 移除遇到的第一个值为key的元素
     * @param key
     */
    @Override
    public void remove(int key) {
        if (this.head == null){
            return;
        }
        if (head.val == key){
            head = head.next;
            return;
        }
        Node cur = findPreNode(key);//需要找到前一个结点才可以删除
        if (cur == null){//没找到要删除的结点
            return;
        }
        cur.next = cur.next.next;
    }
    private Node findPreNode(int key){//找到要删除结点的前一个结点
        Node cur = this.head;
        while (cur.next != null){//这里必须写成next不等于null,否则下面可能会出现空指针异常
            if (cur.next.val == key){
                return cur;
            }
            cur = cur.next;
        }
        return null;
    }

    /**
     * 移除所有符合值为key的结点
     * @param key
     */

    @Override
    public void removeAllKey(int key) {
        if (this.head == null){
            return;
        }
        Node pre = this.head;
        Node cur = this.head.next;
        //先不要管头结点,先删中间的
        while(cur != null){
            if (cur.val == key){
                pre.next = cur.next;
            }else {
                pre = pre.next;//若该结点不是要删除的结点,pre往后走
            }
            cur = cur.next;
        }
        //所有的都删完了,删除头结点
        if (head.val == key){
            head = head.next;
        }
    }

    /**
     * 计算链表大小
     * @return
     */
    @Override
    public int size() {
        int count = 0;
        Node cur = this.head;
        while (cur != null){
            count++;
            cur = cur.next;
        }
        return count;
    }

    /**
     * 清空链表
     */
    @Override
    public void clear() {
        head = null;//为被引用的结点会被JWM回收
    }

    /**
     * 打印链表
     */
    @Override
    public void display() {
        Node cur = this.head;
        while (cur != null){
            System.out.print(cur.val+" ");
            cur = cur.next;
        }
        System.out.println();
    }

    /**
     * 从指定位置开始打印链表
     * @param node
     */
    public void display(Node node) {
        Node cur = node;
        while (cur != null){
            System.out.print(cur.val+" ");
            cur = cur.next;
        }
        System.out.println();
    }

}

上面有几个问题需要注意

  1. 在中间插入元素的时候先要进行后链接,在进行前链接,如果先进行前链接,cur的下一个结点的地址就会丢失
  2. 在删除所有值为key的结点的时候,先删除head后符合条件的结点最后再处理head.

下面是对上述问题的动态演示:

[插入结点错误演示]

插入中间节点(错误)

[插入结点正确演示]

插入中间结点(正确)

[删除所有key结点]

删除所有key结点

下面我们对上述实现的方法进行测试:

**
 * 开始测试
 */
public class Test {
    public static void main(String[] args) {
        MyLinkedList myLinkedList = new MyLinkedList();
        myLinkedList.createDeafultLinkedList();
        myLinkedList.addFirst(11);
        myLinkedList.addLast(88);
        myLinkedList.display();
        System.out.println(myLinkedList.size());
        myLinkedList.addIndex(2,33);
        myLinkedList.display();
        System.out.println(myLinkedList.contains(11));
        System.out.println(myLinkedList.contains(12));
        myLinkedList.addIndex(1,23);
        myLinkedList.addIndex(1,23);
        myLinkedList.addIndex(1,23);
        myLinkedList.display();
        myLinkedList.remove(23);
        myLinkedList.display();
        myLinkedList.removeAllKey(23);
        myLinkedList.display();
        myLinkedList.clear();
        myLinkedList.display();
    }
}

测试结果:
在这里插入图片描述

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

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

相关文章

面试八股——数据库——慢查询

什么是慢查询 查询速度很慢的查询。 慢查询定位策略 在MYsql开启慢查询日志并配置慢查询的查询时间阈值。以后所有超过该阈值的慢查询都会被记录&#xff1a; 面试总结 慢查询一般会发生在多表查询、聚合查询和表数据量较大的查询操作中。 可通过Mysql自带的慢日志定位慢查询…

基于Pytorch的验证码识别模型应用

前言 在做OCR文字识别的时候&#xff0c;或多或少会接触一些验证码图片&#xff0c;这里收集了一些验证码图片&#xff0c;可以对验证码进行识别&#xff0c;可以识别4到6位&#xff0c;纯数字型、数字字母型和纯字母型的一些验证码&#xff0c;准确率还是相当高&#xff0c;需…

基于单片机的自动浇灌系统的设计

本文设计了一款由单片机控制的自动浇灌系统。本设计的硬件电路采用AT89C51单片机作为主控芯片,采用YL-69土壤湿度传感器检测植物的湿度。通过单片机将采集湿度值与设定值分析处理后,控制报警电路和水泵浇灌电路的开启,从而实现植物的自动浇灌。 1 设计目的 随着生活水平的…

【C++】C到C++的入门知识

目录 1、C关键字 2、命名空间 2.1 命名空间的定义 2.2 命名空间的使用 2.2.1 加命名空间名称及作用域限定符 2.2.2 使用using将命名空间中某个成员引入 2.2.3 使用using namespace 命名空间名称引入 3、C输入&输出 4、缺省参数 4.1 缺省参数的概念 4.2 缺省参数的…

深入理解计算机系统 家庭作业2.59

#include <stdio.h> int main(void) { int x 0x89ABCDEF; int y 0x76543210; int z (x&0xFF)|((y>>8)<<8); printf("z%x\n",z); } //x的最低有效字节形成一个掩码(x&0xFF) //书右上角页码29页,寻址和字节顺序中提到…

Dynamic Declaration Language (DDL) 百练平台

题目链接: OpenJudge - 1684:Dynamic Declaration Language (DDL) 题目描述: 评价: 一道单纯的大模拟题&#xff0c;算法难度不大&#xff0c;需要非常细心&#xff0c;锻炼我们的耐心和能力&#xff01;值得一做 分析: 总结一下题目的条件&#xff0c;这种语言&#xff0…

YOLOv9改进策略 :主干篇 | 南开大学提出LSKNet,遥感旋转目标检测新SOTA ,ICCV 2023

💡💡💡本文改进内容: 动态调整特征提取骨干的感受野,以便更有效地处理被检测大小物体的不同的检测能力,也就是说可以有效提升检测数据集当中存在大小目标的检测能力 改进结构图如下: 《YOLOv9魔术师专栏》将从以下各个方向进行创新: 【原创自研模块】【多组合点优…

基于随机森林的信用卡满意度模型预测

基于随机森林的信用卡满意度模型预测 本文介绍了如何利用机器学习技术构建信用评分模型&#xff0c;以帮助金融机构评估借款人的信用风险并做出贷款决策。文章首先从数据预处理开始&#xff0c;包括数据读取、清洗和特征工程&#xff0c;以确保数据质量和适用性。接着&#xf…

实施阶段(2024年3月)

【项目活动1】需求分析 学生&#xff1a;在系统中可以账号登陆&#xff0c;查看今日菜谱&#xff0c;点餐反馈。 食堂管理人员&#xff1a;对原始数据整合&#xff0c;显示菜品结果统计&#xff0c;并根据统计结果对菜品供应量进行调整反馈&#xff0c;避免浪费。 【项目活动…

微信公众号互阅平台有哪些?阅读量互刷互粉工具软件

微信公众号的互助平台有哪些&#xff1f;微信公众号互阅平台与阅读量互刷互粉工具软件的相关介绍 微信公众号近期已成为信息传播的重要渠道&#xff0c;对于企业和个人而言具有重大意义。为了提升公众号的表现力和文章的影响力&#xff0c;互阅平台以及阅读量互刷互粉工具软件…

vue echarts 记录一个带tab切换的echarts页面 切换的时候如果有一个tab里的echarts没有数据 该如何清空echarts图的数据的问题

<template><div class"app-container"><el-form :model"queryParams" ref"queryForm" size"small" v-show"showSearch" label-width"85px"><el-form-item label"园所名称" prop&q…

技巧 Win10电脑打开SMB协议共享文件,手机端查看

一. 打开 SMB1.0/CIFS文件共享支持 ⏹如下图所示&#xff0c;打开SMB1.0/CIFS文件共享支持 二. 开启网络发现 ⏹开启网络发现&#xff0c;确保共享的文件能在局域网内被发现 三. 共享文件夹到局域网 ⏹根据需要勾选需要共享的文件夹&#xff0c;共享到局域网 四. 共享文件查…

数据库聚簇索引和非聚簇索引的区别

聚簇索引&#xff08;Clustered Index&#xff09;和非聚簇索引&#xff08;Non-clustered Index&#xff09;是数据库中两种不同的索引类型&#xff0c;它们的主要区别在于数据的存储方式和索引的结构&#xff1a; 数据存储方式&#xff1a; 聚簇索引&#xff1a;索引的叶子节…

CPU 密集型 和 IO密集型 的区别,如何确定线程池大小?

CPU密集型任务 是指需要大量的CPU资源进行计算的任务 CPU密集型任务通常涉及到复杂的计算&#xff0c;如算法逻辑、数学计算等&#xff0c;其特点是CPU使用率高&#xff0c;多在这种类型的任务中&#xff0c;线程数量一般与CPU的核心数相匹配就足够了。 一个简单的估算方法是…

【第十二届“泰迪杯”数据挖掘挑战赛】【2024泰迪杯】B题基于多模态特征融合的图像文本检索—解题全流程(论文更新)

【第十二届“泰迪杯”数据挖掘挑战赛】【2024泰迪杯】B题基于多模态特征融合的图像文本检索更新&#xff08;论文更新&#xff09; ​ 本节主要更新了论文、训练日志的log数据提取&#xff08;Loss、ACC、RK&#xff09;等数据可视化作图的代码 B题交流QQ群&#xff1a; 4583…

JavaSE day16笔记 - string

第十六天课堂笔记 学习任务 Comparable接口★★★★ 接口 : 功能的封装 > 一组操作规范 一个抽象方法 -> 某一个功能的封装多个抽象方法 -> 一组操作规范 接口与抽象类的区别 1本质不同 接口是功能的封装 , 具有什么功能 > 对象能干什么抽象类是事物本质的抽象 &…

stable diffusion 的 GPU 不足怎么解决

稳定扩散&#xff08;stable diffusion&#xff09;是一种用于图像处理和计算机视觉任务的图像滤波算法。 当使用Stable Diffusion过程中遇到GPU显示内存不足的问题时。解决这个问题的方法有以下几种&#xff1a; 目前&#xff0c;对我来说&#xff0c;就最后一点能够暂时解决当…

基于SpringBoot的“校园志愿者管理系统”的设计与实现(源码+数据库+文档+PPT)

基于SpringBoot的“校园志愿者管理系统”的设计与实现&#xff08;源码数据库文档PPT) 开发语言&#xff1a;Java 数据库&#xff1a;MySQL 技术&#xff1a;SpringBoot 工具&#xff1a;IDEA/Ecilpse、Navicat、Maven 系统展示 系统总体结构图 系统首页界面图 志愿者注册…

Unity-C#进阶——3.27更新中

文章目录 数据结构类ArrayListStackQueueHashtable 泛型泛型类、泛型方法、泛型接口ListDictionaryLinkedList泛型栈&#xff0c;泛型队列 委托和事件委托事件匿名函数Lambad 表达式**闭包** List 排序逆变协变多线程进程线程多线程方法&#xff1a;线程之间共享数据&#xff1…

从0开始搭建基于VUE的前端项目(一) 项目创建和配置

准备与版本 安装nodejs(v20.11.1)安装vue脚手架(@vue/cli 5.0.8) ,参考(https://cli.vuejs.org/zh/)vue版本(2.7.16),vue2的最后一个版本vue.config.js的配置详解(https://cli.vuejs.org/zh/config/)element-ui(2.15.14)(https://element.eleme.io/)初始化项目 创建一个gi…