6. LinkedList与链表

news2024/11/15 10:57:42

一、ArrayList的缺陷

通过源码知道,ArrayList底层使用数组来存储元素,由于其底层是一段连续空间,当在ArrayList任意位置插入或者删除元素时,就需要将后序元素整体往前或者往后搬移,时间复杂度为O(n),效率比较低,因此ArrayList不适合做任意位置插入和删除比较多的场景。所以,java集合中又引入了LinkedList,即链表结构。

二、链表

1. 链表的概念及结构

链表是一种物理存储结构上非连续存储结构,数据元素的逻辑顺序是通过链表中的引用链接次序实现的。

实际中链表的结构非常多样,以下情况组合起来就有8种链表结构:

1.1 单向或者双向 

1.2 带头或者不带头

1.3 循环或者非循环

重点掌握两种:  

  • 无头单向非循环链表:结构简单,一般不会单独用来存数据。实际中更多是作为其他数据结构的子结构,如哈希桶、图的邻接表等等。另外这种结构在笔试面试中出现很多。
  • 无头双向链表:在Java的集合框架库中LinkedList底层实现就是无头双向循环链表。

2. 链表的实现

import java.util.List;

public class MySingleLinkedList {

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

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

    public ListNode head;//代表链表的头节点

    public void createList() {
        ListNode node1 = new ListNode(12);
        ListNode node2 = new ListNode(23);
        ListNode node3 = new ListNode(34);
        ListNode node4 = new ListNode(45);
        ListNode node5 = new ListNode(56);

        node1.next = node2;
        node2.next = node3;
        node3.next = node4;
        node4.next = node5;

        this.head = node1;
    }

    public void display() {
        ListNode cur = head;
        while (cur != null) {
            System.out.print(cur.val+" ");
            cur = cur.next;
        }
        System.out.println();
    }

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

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

    public void addFirst(int val) {
        ListNode node = new ListNode(val);
        node.next = head;
        head = node;
    }

    public void addLast(int val) {
        ListNode node = new ListNode(val);
        if(head == null) {
            head = node;
            return;
        }
        ListNode cur = head;
        while (cur.next != null) {
            cur = cur.next;
        }
        cur.next = node;
    }

    public void addIndex(int index,int val) {
        //1.判断index的合法性
        try {
            checkIndex(index);
        }catch (IndexNotLegalException e) {
            e.printStackTrace();
        }
        //2.index == 0  || index == size()
        if(index == 0) {
            addFirst(val);
            return;
        }
        if(index == size()) {
            addLast(val);
            return;
        }
        //3. 找到index的前一个位置
        ListNode cur = findIndexSubOne(index);
        //4. 进行连接
        ListNode node = new ListNode(val);
        node.next = cur.next;
        cur.next = node;
    }
    private ListNode findIndexSubOne(int index) {
        int count = 0;
        ListNode cur = head;
        while (count != index-1) {
            cur = cur.next;
            count++;
        }
        return cur;
    }

    private void checkIndex(int index) throws IndexNotLegalException{
        if(index < 0 || index > size()) {
            throw new IndexNotLegalException("index不合法");
        }
    }

    public boolean contains(int val) {
        ListNode cur = head;
        while (cur != null) {
            if(cur.val == val) {
                return true;
            }
            cur = cur.next;
        }
        return false;
    }
    public void remove(int val) {
        if(head == null) {
            return;
        }
        if(head.val == val) {
            head = head.next;
            return;
        }
        ListNode cur = head;
        while (cur.next != null) {
            if(cur.next.val == val) {
                ListNode del = cur.next;
                cur.next = del.next;
                return;
            }
            cur = cur.next;
        }
    }

    public void removeAllKey(int val) {
        //1. 判空
        if(this.head == null) {
            return;
        }
        //2. 定义prev 和 cur
        ListNode prev = head;
        ListNode cur = head.next;
        //3.开始判断并且删除
        while(cur != null) {
            if(cur.val == val) {
                prev.next = cur.next;
                //cur = cur.next;
            }else {
                prev = cur;//prev = prev.next;
                //cur = cur.next;
            }
            cur = cur.next;
        }
        //4.处理头节点
        if(head.val == val) {
            head = head.next;
        }
    }

    public void clear() {
        //head = null;
        ListNode cur = head;
        while (cur != null) {
            ListNode curN = cur.next;
            //cur.val = null;
            cur.next = null;
            cur = curN;
        }
        head = null;
    }
}

三、链表面试题

1. 删除链表中等于给定值 val 的所有节点。

2. 反转一个单链表。

3. 给定一个带有头结点 head 的非空单链表,返回链表的中间结点。如果有两个中间结点,则返回第二个中间结点。

4. 输入一个链表,返回该链表中倒数第k个结点。

5. 将两个有序链表合并为一个新的有序链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。

6. 编写代码,以给定值x为基准将链表分割成两部分,所有小于x的结点排在大于或等于x的结点之前。

7. 链表的回文结构。

8. 输入两个链表,找出它们的第一个公共结点。

9. 给定一个链表,判断链表中是否有环。

10. 给定一个链表,返回链表开始入环的第一个节点。 如果链表无环,则返回 NULL。

四、LinkedList的模拟实现

public class MyLinkedList {

    static class ListNode {
        public int val;
        public ListNode prev;
        public ListNode next;

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

    public ListNode head;
    public ListNode last;

    //头插法
    public void addFirst(int data){
        ListNode node = new ListNode(data);
        if(head == null) {
            head = node;
            last = node;
            //return;
        }else {
            node.next = head;
            head.prev = node;
            head = node;
        }
    }
    //尾插法
    public void addLast(int data){
        ListNode node = new ListNode(data);
        if(head == null) {
            head = last = node;
        }else {
            last.next = node;
            node.prev = last;
            last = node;
        }
    }

    //任意位置插入,第一个数据节点为0号下标
    public void addIndex(int index,int data){
        if(index < 0 || index > size()) {
            return;
        }
        if(index == 0) {
            addFirst(data);
            return;
        }
        if(index == size()) {
            addLast(data);
            return;
        }
        ListNode cur = searchIndex(index);
        ListNode node = new ListNode(data);
        node.next = cur;
        cur.prev.next = node;
        node.prev = cur.prev;
        cur.prev = node;
    }

    private ListNode searchIndex(int index) {
        ListNode cur = head;
        int count = 0;
        while (count != index) {
            cur = cur.next;
            count++;
        }
        return cur;
    }

    //查找是否包含关键字key是否在单链表当中
    public boolean contains(int key){
        ListNode cur = head;
        while (cur != null) {
            if(cur.val == key) {
                return true;
            }
            cur =cur.next;
        }
        return false;
    }
    //得到单链表的长度
    public int size(){
        int count = 0;
        ListNode cur = head;
        while (cur != null) {
            count++;
            cur =cur.next;
        }
        return count;
    }
    public void display(){
        ListNode cur = head;
        while (cur != null) {
            System.out.print(cur.val+" ");
            cur =cur.next;
        }
        System.out.println();
    }


    //删除第一次出现关键字为key的节点
    public void remove(int key){
        ListNode cur = head;
        while (cur != null) {
            if(cur.val == key) {
                if(cur == head) {
                    head = head.next;
                    //判断 头节点是不是1个
                    if(head != null) {
                        head.prev = null;
                    }else {
                        last = null;
                    }
                }else {
                    cur.prev.next = cur.next;
                    if(cur.next == null) {
                       //cur.prev.next = cur.next;
                        last = cur.prev;
                    }else {
                        //cur.prev.next = cur.next;
                        cur.next.prev = cur.prev;
                    }
                }
                return;
            }else {
                cur = cur.next;
            }
        }
    }
    //删除所有值为key的节点
    public void removeAllKey(int key){
        ListNode cur = head;
        while (cur != null) {
            if(cur.val == key) {
                if(cur == head) {
                    head = head.next;
                    //判断 头节点是不是1个
                    if(head != null) {
                        head.prev = null;
                    }else {
                        last = null;
                    }
                }else {
                    cur.prev.next = cur.next;
                    if(cur.next == null) {
                        //cur.prev.next = cur.next;
                        last = cur.prev;
                    }else {
                        //cur.prev.next = cur.next;
                        cur.next.prev = cur.prev;
                    }
                }

            }
            cur = cur.next;
        }
    }

    public void clear(){
        ListNode cur = head;
        while (cur != null) {
            ListNode curN = cur.next;
            cur.next = null;
            cur.prev = null;
            cur = curN;
        }
        head = null;
        last = null;
    }

}

五、LinkedList的使用

1. 什么是LinkedList

LinkedList的底层是双向链表结构(链表后面介绍),由于链表没有将元素存储在连续的空间中,元素存储在单独的节点中,然后通过引用将节点连接起来了,因此在在任意位置插入或者删除元素时,不需要搬移元素,效率比较高。

在集合框架中,LinkedList也实现了List接口,具体如下:

【说明】

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

2. LinkedList的使用

2.1 LinkedList的构造

public static void main(String[] args) {
    // 构造一个空的LinkedList

    List<Integer> list1 = new LinkedList<>();
    
    List<String> list2 = new java.util.ArrayList<>();
    list2.add("JavaSE");
    list2.add("JavaWeb");
    list2.add("JavaEE");
    // 使用ArrayList构造LinkedList

    List<String> list3 = new LinkedList<>(list2);
}
2.2 LinkedList的其他常用方法介绍

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

六、ArrayList和LinkedList的区别

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

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

相关文章

64、Python之函数高级:装饰器实战,动态语言也能有类型检查

引言 Python作为一门动态类型语言&#xff0c;有时候&#xff0c;一个不小心的类型错误只有在实际运行中才有可能被发现。相较而言&#xff0c;静态类型语音虽然不够灵活&#xff0c;但是&#xff0c;类型错误等语法检查在编译期间就可以提前发现了。 那么我们有没有方法在Py…

Visual Studio Code 月刊 (2024-08)

文章目录 配置文件编辑器Django 单元测试支持vscode.dev 上的 IntelliSenseNotebook 差异查看器通过键盘调整列的大小源代码管理图GitHub Copilot结语 2024 年 8 月 Visual Studio Code&#xff08;简称 vscode&#xff09;发布了 version 1.93。该版本带来了许多更新&#xff…

Spring Boot Admin集成与自定义监控告警

目录 一.Spring Boot Admin集成 1.引入依赖 2.添加配置 3.监控界面 二.Spring Boot Admin告警机制 1. 基本告警机制 2. 配置告警 2.1 triggers触发器讲解 3. 自定义通知 3.1 Instance 对象 三.Spring Boot Admin支持的监控属性 1.常见的Spring Boot Admin监控属性 …

进阶SpringBoot之配置 Swagger API 框架信息

Swagger&#xff1a;API 框架 RestFul API 文档在线自动生成工具&#xff0c;API 文档与 API 定义同步更新 Swagger 官网 Maven 仓库 创建 Spring Boot 项目&#xff0c;依赖勾选 Spring Web pom.xml 导入相关依赖&#xff1a; springfox-swagger2、springfox-swagger-ui …

何为数据中台

数据中台 什么是数据中台 2014年马云正式提出“DT&#xff08;Data Technology&#xff09;”的概念&#xff0c;人类从IT时代走向了DT时代&#xff0c;阿里内部的数据平台事业部大刀阔斧的建立整个集团的数据资产&#xff0c;同年&#xff0c;阿里从芬兰Supercell公司接触到…

Canny算子 一张图看懂

对于最高值和最低值的设置&#xff0c; 1&#xff0c;high t最大值一般以一阶导数幅度图的最大值的30%-40%来定 2&#xff0c;最小值一般halcon里默认为 low theigh t/3得到 3&#xff0c;canny的优势是有极大值抑制&#xff0c;所以提取的边缘是1个像素的窄边缘。 3&#xff0…

Golang path/filepath包详解:高效路径操作与实战案例

Golang path/filepath包详解&#xff1a;高效路径操作与实战案例 引言基础用法Abs 函数Base 函数Clean 函数Dir 函数Ext 函数FromSlash 和 ToSlash 函数 基础用法Abs 函数Base 函数Clean 函数Dir 函数Ext 函数FromSlash 和 ToSlash 函数 路径操作Join 函数Split 函数Rel 函数Ma…

LabVIEW编程语言出于什么原因开发的?

LabVIEW最初由美国国家仪器公司&#xff08;NI&#xff09;于1986年开发&#xff0c;目的是为工程师和科学家提供一种图形化编程环境&#xff0c;简化数据采集、仪器控制、自动化测试和测量系统开发等工作。开发LabVIEW的主要原因包括以下几点&#xff1a; 简化复杂系统开发&am…

《数字图像处理(面向新工科的电工电子信息基础课程系列教材)》Chapter 1课件2024

每一轮备课都有新的感悟。 禹晶、肖创柏、廖庆敏《数字图像处理&#xff08;面向新工科的电工电子信息基础课程系列教材&#xff09;》 禹晶、肖创柏、廖庆敏《数字图像处理》资源二维码

Tektronix泰克MSO5204B混合信号示波器4+16通道2G

Tektronix泰克MSO5204B混合信号示波器416通道2G 2 GHz、416 通道、10/5 GS/s&#xff08;2/4 通道&#xff09;混合信号示波器&#xff0c;50 M/25 M 记录长度泰克 MSO5204B 2 GHz、416 通道、10/5 GS/s&#xff08;2/4 通道&#xff09;混合信号示波器&#xff0c;50 M/25 M …

机器学习强化学习

版权声明 本文原创作者:谷哥的小弟作者博客地址:http://blog.csdn.net/lfdfhl1. 强化学习概述 1.1 定义与核心概念 强化学习是一种目标导向的机器学习方法,它使智能体能够在环境中通过试错学习最优行为策略。这种学习过程涉及到智能体与环境之间的交互,智能体根据当前状态…

AI基础 L8 Local Search I 局部搜索

Iterative Improvement Algorithms • In many optimization problems, the path to a goal is irrelevant — the goal state itself is the solution • State space a set of goal states — find one that satisfies constraints (e.g., no two classes at same time) —…

《系统安全架构设计及其应用》写作框架,软考高级系统架构设计师

论文真题 随着社会信息化进程的加快&#xff0c;计算机及网络已经被各行各业广泛应用&#xff0c;信息安全问题也变得愈来愈重要。它具有机密性、完整性、可用性、可控性和不可抵赖性等特征。信息系统的安全保障是以风险和策略为基础&#xff0c;在信息系统的整个生命周期中提…

【审批流】基于JAVA开发的工作流审批系统(直接集成或者直接可使用)

基于Javavue开发的智能审批系统&#xff0c;低代码平台 软件资料清单列表部分文档清单&#xff1a;工作安排任务书&#xff0c;可行性分析报告&#xff0c;立项申请审批表&#xff0c;产品需求规格说明书&#xff0c;需求调研计划&#xff0c;用户需求调查单&#xff0c;用户需…

Android APK插件化:DynamicAPK技术如何改变游戏规则

在移动应用开发领域&#xff0c;尤其是Android平台&#xff0c;应用的体积和更新速度一直是开发者和用户关注的焦点。随着应用功能的不断增加&#xff0c;APK文件的大小也在逐渐膨胀&#xff0c;这不仅增加了用户的下载成本&#xff0c;也影响了应用的更新效率。DynamicAPK技术…

数学建模笔记——层次分析法

数学建模笔记——层次分析法 数学建模笔记——层次分析法1. 层次分析法的基本原理和步骤2. 层次分析法的建模过程2.1 问题的提出2.2 模型原理2.3 为该问题建立层次结构模型2.4 构造判断矩阵1. 判断矩阵的含义2. 为该问题构造判断矩阵 2.5 一致性检验1. 一致性检验方法2. 对上述…

【Linux】HTTP协议中的cookie和session

一、B站的登录和未登录——一种登录场景的演示 我们现在上的是B站大学&#xff0c;所以对于B站&#xff0c;我们是很熟悉的。当我们打开浏览器&#xff0c;并访问B站网页时&#xff08;很熟悉&#xff09;&#xff0c;会发现我们会自动登录上B站&#xff0c;为什么呢&#xff1…

解锁 macOS 剪贴板历史记录,高效复制、粘贴技巧

在Mac上&#xff0c;我们经常需要在不同文档之间复制和粘贴内容。然而&#xff0c;macOS自带的剪贴板只能保存最后一个复制项&#xff0c;这大大限制了我们的工作效率。幸运的是&#xff0c;一些第三方应用程序可以帮助我们查看和管理剪贴板的历史记录&#xff0c;从而提升我们…

基于RP2350 MCU的树莓派Pico 2开发板及MicroPython编程使用

2021年1月21日,树莓派基金会同时发布了第1代RP2040 MCU芯片和基于RP2040 MCU的第1代树莓派Pico开发板(Raspberry Pi Pico/ Raspberry Pi Pico 1)。2024年8月8日,树莓派基金会又发布了第2代RP2350 MCU芯片并推出了基于RP2350 MCU的第2代树莓派Pico开发板(Raspberry Pi Pico 2)…

pandas:一个强大的数据处理Python库

我是东哥&#xff0c;一个热衷于探索Python世界的自媒体人。今天&#xff0c;我要为大家介绍一个在Python数据分析领域中非常强大的库——Pandas。如果你对数据分析充满好奇&#xff0c;或者正在寻找一个简单易用的库来处理和分析数据&#xff0c;那么Pandas绝对是你的不二之选…