List——顺序表与链表(二)

news2024/11/14 14:29:48

文章目录

  • 前言
  • 一、链表概念及结构
  • 二、LinkedList与链表
    • 1.什么是LinkedList
    • 2.LinkedList的常用方法
    • 3.链表的遍历
  • 三.实现自己的LinkedList
  • 四.ArrayList和LinkedList的区别与==优缺点==
  • 总结


前言

上一篇文章中,介绍了List接口以及ArrayList的使用,并且进行了简单的模拟实现,通过源码知道,ArrayList底层使用数组元素来存储元素。但是由于其底层是一段连续空间,当在ArrayList任意位置插入或者删除元素时,就需要将后序元素整体往前或者往后搬移,时间复杂度为O(n),效率比较低,且频繁的扩容会导致不必要的内存空间浪费,因此ArrayList不适合做任意位置插入和删除比较多的场景。因此:java集合中又引入了LinkedList,即链表结构。


一、链表概念及结构

链表是一种物理存储结构上非连续存储结构,数据元素的逻辑顺序是通过链表中的引用链接次序实现的 。
在这里插入图片描述

链表的结构非常多样,单向双向、带头或不带头、循环或非循环。这几种情况组合起来就有八种链表结构:

1.单向或者双向

在这里插入图片描述

2.带头或不带头

在这里插入图片描述
3.循环或非循环

在这里插入图片描述
无头单向非循环链表:结构简单,一般不会单独用来存数据。实际中更多是作为其他数据结构的子结构,如哈希桶、图的邻接表等等。另外这种结构在笔试面试中出现很多

无头双向链表:在Java的集合框架库中LinkedList底层实现就是无头双向循环链表。

二、LinkedList与链表

1.什么是LinkedList

LinkedList的底层是双向链表结构,由于链表没有将元素存储在连续的空间中,元素存储在单独的节点中,然后通过引用将节点连接起来了,因此在在任意位置插入或者删除元素时,不需要搬移元素,效率比较高。但是当需要查询时,就需要按图索骥按部就班的一个一个查,查询效率不高,这个后面总结对比时再详细说明。

在这里插入图片描述
说明

  1. LinkedList实现了List接口
  2. LinkedList的底层使用了双向链表
  3. LinkedList没有实现RandomAccess接口,因此LinkedList不支持随机访问
  4. LinkedList的任意位置插入和删除元素时效率比较高,时间复杂度为O(1)
  5. LinkedList比较适合任意位置插入的场景

2.LinkedList的常用方法

方法解释
boolean add(E e尾插 e
void add(int index, E element将e插入到index位置
boolean addAll(Collection<? extends E> c)尾插c中的元素
E remove(int index)删除index位置的元素
boolean remove(Object o)删除遇到的第一个o
E get(int index)获取下标index位置的元素
E set(int index, E element)将下标index位置的元素修改为element并返回修改之前的元素
void clear()清空
boolean contains(Object o)判断o是否在链表中
int indexOf(Object o)返回第一个遇到的o的下标
int lastIndexOf(Object o)返回最后一个o的下标
List subList(int fromIndex, int toIndex)截取部分list
void display()打印链表

3.链表的遍历

遍历链表的几种方式:

public static void main(String[] args) {
LinkedList<Integer> list = new LinkedList<>();
list.add(1); // add(elem): 表示尾插
list.add(2);
list.add(3);
list.add(4);
list.add(5);
list.add(6);
list.add(7);
System.out.println(list.size());
// foreach遍历
for (int e:list) {
System.out.print(e + " ");
}
System.out.println();
// 使用迭代器遍历---正向遍历
ListIterator<Integer> it = list.listIterator();
while(it.hasNext()){
System.out.print(it.next()+ " ");
}
System.out.println();
// 使用反向迭代器---反向遍历
ListIterator<Integer> rit = list.listIterator(list.size());
while (rit.hasPrevious()){
System.out.print(rit.previous() +" ");
}
System.out.println();
}

三.实现自己的LinkedList

首先需要定义自己的MyList接口,接口中声明一系列方法后在MyLinkedList中实现,我们还需要定义一个结点类,这里我们实现的是带有前驱和后继的双向结点,以实现双向链表。具体实现如下:

MyList接口:

public interface MyList {
    /**
     * 返回链表中元素个数
     * @return
     */
    int size();

    /**
     * 尾插
     * @param e  将元素e尾插到线性表
     * @return  返回true
     */
    boolean add(Long e);

    /**
     * 将元素e插入到指定节点处
     * @param index
     * @param e
     */
    void add (int index,Long e);

    Long remove(int index);

    /**
     * 删除值为e的元素
     * @param e 需要删除的元素
     * @return 成功返回true  失败返回false
     */
    boolean remove(Long e);

    /**
     * 获取index处元素的值
     * @param index 数组下标
     * @return 该下标处的元素值
     */
    Long get(int index);

    /**
     * 修改元素
     * @param index [0,size)
     * @param e  修改后的元素
     * @return 原来index处的元素
     */
    Long set(int index,Long e);


    /**
     * 从前往后,返回第一次遇到e的下标
     * @param e 目标元素
     * @return 返回在第几个
     */
    int indexOf(Long e);

    /**
     * 从后向前
     * @param e
     * @return
     */
    int lastIndexOf(Long e);

    /**
     * 线性表中是否包含e
     * @param e
     * @return
     */
    boolean contains(Long e);

    /**
     * 打印顺序表
     */
    void display();


    /**
     * 判空
     * @return
     */
    boolean isEmpty();

    /**
     * 清空
     */
    void clear();

}

定义结点类

public class MyNode {
    Long val;
    MyNode next;  //指向后继节点,尾节点的后继是null

    MyNode prev;  //指向前驱节点,头节点的前驱是null

    public MyNode() {
        //无参构造方法
    }
    public MyNode(Long val) {
        //需传入值的有参构造
        this.val = val;
        this.next = null;
        this.prev = null;
    }
}

MyLinkedList实现类:

public class MyLinkedList implements MyList {
    //需要维护三个属性:链表的头节点、链表的尾节点、链表中元素个数
    private MyNode head;
    private MyNode last;
    private int size;

    //构造方法,构造一个空的链表
    public MyLinkedList() {
        this.head = this.last = null;
        this.size = 0;
    }

    @Override
    public int size() {
        return size;
    }

    @Override
    //链表的尾插
    public boolean add(Long e) {
        MyNode node = new MyNode(e);  //将元素e放入节点中
        node.next = null;
        //分情况讨论
        if (size > 0) {
            this.last.next = node;  //这一步将尾节点的后继指向新节点    (链接)
            node.prev = this.last;  //这一步让新节点的前驱指向之前的尾节点尾    (链接完成)
            this.last = node;    //这一步让新节点成为新的尾节点   (善后)
        } else {
            node.prev = null;
            this.last = this.head = node;
        }
        this.size++;    //涉及的增删的操作必须修改size!
        return true;
    }

    //链表的头插
    private boolean addFirst(Long e) {
        MyNode node = new MyNode(e);  //将元素e放入节点中
        node.next = null;
        if (size > 0) {
            this.head.prev = node;
            node.next = this.head;
            this.head = node;
        } else {
            this.head = this.last = node;
        }

        return true;
    }

    @Override
    public void add(int index, Long e) {
        if (index < 0 || index > size) {
            throw new ArrayIndexOutOfBoundsException("下标越界");
        }
        if (size == 0) {
            add(e);
            return;
        }
        if (size == 1) {
            if (index == 0) {
                addFirst(e);
            } else {
                add(e);
            }
            return;
        }
        if (index == 0) {
            addFirst(e);
            return;
        } else if (index == size) {
            add(e);
            return;
        }
        MyNode prevNode = this.head;
        for (int i = 0; i < index - 1; i++) {
            prevNode = prevNode.next;
        }
        MyNode curnode = prevNode.next;

        MyNode node = new MyNode(e);
        prevNode.next = node;
        curnode.prev = node;
        node.prev = prevNode;
        node.next = curnode;

        size++;
    }

    @Override
    public Long remove(int index) {
        if (index < 0 || index >= size) {
            throw new ArrayIndexOutOfBoundsException("下标异常");
        }
        if (size == 1) {
            Long e = this.head.val;
            this.head = this.last = null;
            this.size = 0;
            return e;
        }
        if (index == 0) {
            Long e = this.head.val;
            this.head = this.head.next;
            this.head.prev = null;
            size--;
            return e;
        }
        if (index == size - 1) {
            Long e = this.head.val;
            this.last = this.last.prev;
            this.last.next = null;
            size--;
            return e;
        }
        //走到这儿  说明链表中至少有两个元素
        MyNode curNode = this.head;
        for (int i = 0; i < index; i++) {
            curNode = curNode.next;
        }
        Long e = curNode.val;
//        MyNode prevNode = curNode.prev;
//        MyNode nextNode = curNode.next;
//
//        // 修改引用关系,删除 curNode
//        prevNode.next = nextNode;
//        nextNode.prev = prevNode;
        curNode.prev.next = curNode.next;
        curNode.next.prev = curNode.prev;

        size--;
        return e;

    }


    @Override
    public boolean remove(Long e) {
        MyNode curNode = this.head;
        for (int i = 0; i < size; i++) {
            if (curNode.val.equals(e)) {
                //找到元素e后判断其所在位置
                if (i == 0) {
                    this.head = this.head.next;
                    if (this.head != null) {
                        this.head.prev = null;
                    } else {
                        this.last = null;
                    }

                    size--;
                    return true;
                }
                if (i == size - 1) {
                    this.last = this.last.prev;
                    this.last.next = null;
                    size--;
                    return true;
                }

                //走到这儿说明在中间位置,既不是头删,也不是尾删
                MyNode prevNode = curNode.prev;
                MyNode nextNode = curNode.next;
                prevNode.next = nextNode;
                nextNode.prev = prevNode;
                size--;
                return true;
            }

            curNode = curNode.next;
        }
        return false;
    }

    @Override
    public Long get(int index) {
        if(index<0||index>size-1){
            throw new ArrayIndexOutOfBoundsException("下标有误");
        }
        MyNode cur=this.head;
        for (int i = 0; i <index ; i++) {
            cur=cur.next;
        }

        return cur.val;
    }

    @Override
    public Long set(int index, Long e) {
        if(index<0||index>size-1){
            throw new ArrayIndexOutOfBoundsException("下标有误");
        }
        MyNode cur=this.head;
        for (int i = 0; i <index ; i++) {
            cur=cur.next;
        }
        Long olde=cur.val;
        cur.val=e;
        return olde;
    }

    @Override
    public int indexOf(Long e) {
        int i = 0;
        MyNode cur = this.head;
        while (cur != null) {
            if (cur.val.equals(e)) {
                return i;
            }

            i++;
            cur = cur.next;
        }

        return -1;
    }

    @Override
    public int lastIndexOf(Long e) {
        int i = size-1;
        MyNode cur = this.last;
        while (cur != null) {
            if (cur.val.equals(e)) {
                return i;
            }

            i--;
            cur = cur.prev;
        }

        return -1;
    }

    @Override
    public boolean contains(Long e) {
//        MyNode cur=this.head;
//        for (int i = 0; i < size; i++) {
//            if(cur.val.equals(e)){
//                return true;
//            }
//            cur=cur.next;
//        }
//        return false;
        return indexOf(e)!=-1;
    }

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

    }

    @Override
    public boolean isEmpty() {
        return size==0;
    }

    @Override
    public void clear() {
        this.head=this.last=null;
        this.size=0;
    }

四.ArrayList和LinkedList的区别与优缺点

不同点ArrayListLinkedList
存储空间上物理上一定连续逻辑上连续,物理上不一定连续
随机访问(查询)支持O(1)不支持O(N)
插入需要搬移元素,效率低O(N)只需要修改引用指向的方向,时间复杂度O(1)
多次插入空间不够时需要扩容没有容量的概念
应用场景元素高效存储+频繁访问(改查)任意位置的频繁插入删除(增删)

总结

以上两篇内容将ArrayList以及LinkedList通过自己的代码实现了简单的实现,也将其中常用的方法进行了罗列以及讲解,并对两者进行了归纳总结,总的来说就是增删频繁用链表,改查频繁用顺序表两者各有优缺点。后续将会更新与其相关的力扣题。

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

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

相关文章

ByteTrack多目标追踪论文阅读

paper:ByteTrack: Multi-Object Tracking by Associating Every Detection Box code:ByteTrack 一.摘要 多目标追踪的目的是识别视频中物体或对象的位置和身份&#xff0c;也就是说&#xff0c;不同于目标检测的是&#xff0c;追踪问题可以分为两个任务&#xff1a;1&#x…

(附源码)springboot平衡膳食小程序 毕业设计 250859

基于springboot平衡膳食小程序 摘 要 随着我国经济迅速发展&#xff0c;人们对手机的需求越来越大&#xff0c;各种手机软件也都在被广泛应用&#xff0c;但是对于手机进行数据信息管理&#xff0c;对于手机的各种软件也是备受用户的喜爱&#xff0c;平衡膳食小程序被用户普遍使…

物联网设备WIFI模块实现

问题 如何在设备上进行 Wifi 编程&#xff1f; LwIp (Light Weight IP) 简介 LwIp 是轻量化的 TCP/IP&#xff0c;是一个小型开源的 TCP/IP 协议栈 LwIp 的设计目标是用较少的资源实现较完整的 TCP/IP 协议栈 LwIp 能在操作系统中运行&#xff0c;也能在无操作系统的情况下…

Java—异常体系

文章目录异常和错误java异常的分类&#xff1a;非运行时异常运行时异常受检异常&#xff08;非运行时异常&#xff09;如何处理&#xff1f;1、try catch finally为什么要用try catch finally2、throwsThrow和Throws的区别JVM是如何处理异常的try-catch-finally中哪个部分可以省…

项目管理逻辑:项目经理如何掌控项目生命周期, 才能避免身心俱疲?

目录 1.项目生命周期 2.预测型项目周期 3.迭代型项目周期 3.1.初始阶段 3.2.精化阶段 3.3.构建阶段 3.4.交付阶段 4.增量型生命周期 5.敏捷开发 5.根据具体项目使用合理的开发方式 1.项目生命周期 2.预测型项目周期 预测型项目周期就是软件开发领域的瀑布流模型&…

【Python自然语言处理】概率上下文无关文法(PCFG)及神经网络句法分析讲解(图文解释 超详细)

觉得有帮助或有疑问麻烦点赞关注收藏后评论区私信留言~~~ 一、句法分析 句法分析&#xff08;syntactic parsing或者parsing&#xff09;是识别句子包含的句法成分要素以及成分之间的内在关系&#xff0c;一般以句法树来表示句法分析的结果。实现该过程的应用称作句法分析器&a…

三维模型的简化算法研究(任务书+lunwen+外文翻译+源码+查重报告)

目 录 第1章 绪论 1 1.1 研究背景 1 1.2 内存网格简化算法 1 1.2.1 顶点聚类 1 1.2.2 区域合并 2 1.2.3 迭代式消除 4 1.2.4 随机重采样 5 1.3 三维模型简化算法 6 1.3.1 分片简化 6 1.3.2 使用外部数据结构 7 1.3.3 网格批处理 9 1.3.4 流式简化 10 1.3.5 小结 11 1.4 自适应等…

【前沿技术RPA】 一文了解UiPath Orchestrator的触发器和监听器

&#x1f40b;作者简介&#xff1a;博主是一位.Net开发者&#xff0c;同时也是RPA和低代码平台的践行者。 &#x1f42c;个人主页&#xff1a;会敲键盘的肘子 &#x1f430;系列专栏&#xff1a;UiPath &#x1f980;专栏简介&#xff1a;UiPath在传统的RPA&#xff08;Robotic…

公众号接口免费调用

公众号接口免费调用 本平台优点&#xff1a; 多题库查题、独立后台、响应速度快、全网平台可查、功能最全&#xff01; 1.想要给自己的公众号获得查题接口&#xff0c;只需要两步&#xff01; 2.题库&#xff1a; 题库&#xff1a;题库后台&#xff08;点击跳转&#xff09;…

Express:CORS 跨域资源共享

CORS 跨域资源共享 Staticfile CDN 1. 接口的跨域问题 刚才编写的 GET 和 POST接口&#xff0c;存在一个很严重的问题&#xff1a;不支持跨域请求。 解决接口跨域问题的方案主要有两种&#xff1a; 1.CORS&#xff08;主流的解决方案&#xff0c;推荐使用&#xff09; 2.J…

Excel - 选择性粘贴和单元格引用规则

最基本的功能&#xff0c;才是最重要的功能&#xff0c;一定好好好理解。 最常用的复制、粘贴功能&#xff0c;在Excel里赋予了更多的选项&#xff0c;也变得更加强大。Excel里一般可复制的内容都是只单元格区域&#xff0c;其组成包括数据(文本或数值)、格式、公式、有效性验证…

FileZilla Server.xml 如何配置

要从xp.cn说起&#xff0c;因为它自带了一个ftp服务器。我点击配置后&#xff0c;就会直接用记事本打开FileZilla Server.xml让配置。我就很懵。不知道如何下手。 弹出的配置界面如下&#xff1a; 如何配置FileZilla Server.xml 我一开始想到去xp.cn找文档&#xff0c;可惜…

初探基因组组装——生信原理第四次实验报告

初探基因组组装——生信原理第四次实验报告 文章目录初探基因组组装——生信原理第四次实验报告实验目的实验内容实验题目第一题题目用SOAPdenovo 进行基因组组装评估组装质量第二题题目Canu组装Hifiasm组装基于nucmer的基因组比对过滤比对结果转换为可读性强的tab键分隔的文件…

期末论文LaTeX模板

简介 这学期的其中一门课程结束了&#xff0c;考核形式是写一篇中文的课程论文。于是&#xff0c;我使用了Elegant LaTeX 系列的模板。 小编已经把最新版本的三份模板放到公众号&#xff0c;后台回复[课程论文模板]即可获取。也欢迎大家去 GitHub 给贡献者点 star&#xff01;…

【从零开始玩量化13】quantstats:分析你的量化策略

背景 之前总结了一些获取量化数据的途径&#xff0c;数据是一个量化策略的“原材料”&#xff0c;接下来要考虑的问题就是如何使用这些数据。 本文&#xff0c;介绍一个量化指标分析工具quantstats&#xff0c;利用它可以很方便的分析你的策略。 Github地址&#xff1a;https…

[附源码]计算机毕业设计校园帮平台管理系统Springboot程序

项目运行 环境配置&#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…

【5G MAC】随机接入流程中的 Msg3 —— Scheduled UL (PUSCH) Transmission

博主未授权任何人或组织机构转载博主任何原创文章&#xff0c;感谢各位对原创的支持&#xff01; 博主链接 本人就职于国际知名终端厂商&#xff0c;负责modem芯片研发。 在5G早期负责终端数据业务层、核心网相关的开发工作&#xff0c;目前牵头6G算力网络技术标准研究。 博客…

机器学习數據降維之主成分分析(PCA)

文章目录前言数据降维是什么&#xff1f;维度灾难与降维作用主成分分析PCA原理PCA算法小例實戰總結前言 例如&#xff1a;随着人工智能的不断发展&#xff0c;机器学习这门技术也越来越重要&#xff0c;很多人都开启了学习机器学习&#xff0c;本文就介绍了机器学习的基础内容…

cubeIDE开发,结合汉字取模工具,在LCD输出各种字体

一、汉字取模工具 嵌入式LCD屏显示无非就是不间断刷新LCD宽度*LCD高度的像素矩阵&#xff0c;并为每个像素指定特定颜色。对于LCD屏幕显示汉字&#xff0c;无非就是将字体形状转换为字体宽度*字体高度的像素矩阵&#xff0c;及指定每个字体像素的颜色&#xff0c;然后在LCD屏幕…

点击试剂Methyltetrazine-PEG4-NHS ester,甲基四嗪-PEG4-琥珀酰亚胺酯,CAS:1802907-9

An English name&#xff1a;Methyltetrazine-PEG4-NHS ester Chinese name&#xff1a;甲基四嗪-四聚乙二醇-琥珀酰亚胺酯 Item no&#xff1a;X-CL-1328 CAS&#xff1a;1802907-92-1 Formula&#xff1a;C24H31N5O9 MW&#xff1a;533.54 Purity&#xff1a;95% Avai…