Java链表(2)

news2024/11/24 18:51:05

🐵本篇文章将对双向链表进行讲解,模拟实现双向链表的常用方法


一、什么是双向链表

双向链表在指针域上相较于单链表,每一个节点多了一个指向前驱节点的引用prev以及多了指向最后一个节点的引用last

二、双向链表的模拟实现

首先将要模拟实现的方法写到IList接口中:

public interface IList {
    //头插法插入节点
    public void addFirst(int data);

    //尾插法插入节点
    public void addLast(int data);

    //任意位置插入,第一个数据节点为0号下标
    public void addIndex(int index,int data);

    //查找是否包含关键字key是否在链表当中
    public boolean contains(int key);

    //删除第一次出现关键字为key的节点
    public void remove(int key);

    //删除所有值为key的节点
    public void removeAllKey(int key);

    //得到链表的长度
    public int size();
    
    //清除链表
    public void clear();
    
    //显示链表
    public void display();

}

之后再创建一个MyLinkedList类实现上述接口并重写接口中所有的方法

public class MySingleList implements IList{
    static class ListNode {
        public int val;
        public ListNode next;
        public ListNode prev;

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

    public ListNode head; //指向第一个节点
    public ListNode last; //指向最后一个节点

    /*以下是要重写IList接口中的方法*/
    ...

}

2.1 模拟实现

public void addFirst(int data);

双向链表的头插法和单链表基本一致,只不过当链表为空时不仅要让head指向新节点还要让last也指向新节点

    public void addFirst(int data) {
        ListNode newNode = new ListNode(data);
        if (head == null) {
            head = newNode;
            last = newNode;
            return;
        }
        newNode.next = head;
        head.prev = newNode;
        head = newNode;
    }

public void addLast(int data);

当链表为空时要让head和last都指向新节点,当链表部不为空时要让最后一个节点的next指向新节点,之后让新节点的prev指向原来的最后一个节点

    public void addLast(int data) {
        ListNode newNode = new ListNode(data);
        if (head == null) {
            head = newNode;
            last = newNode;
            return;
        }
        last.next = newNode;
        newNode.prev = last;
        last = newNode;
    }

public void addLast(int data);

在任意位置处插入一个节点,第一个节点的索引为0

首先要判断一下index是否合法:

public class IndexException extends RuntimeException{
    public IndexException(String message) {
        super(message);
    }
}

=======================================================

public void addIndex(int index, int data) {
    if (index < 0 || index > size()) { //size()为链表长度
        throw new IndexException("下标错误");
    }
    ...
}

在任意位置插入节点,所以如果index为0或等于链表长度,就可以直接使用刚刚实现过的头插和尾插方法

public void addIndex(int index, int data) {
    if (index < 0 || index > size()) {
        throw new IndexException("下标错误");
    }
    if (index == 0) {
        addFirst(data);
    }
    if (index == size()) {
        addLast(data);
    }

    ...
}

一般情况下(在中间插入),单链表中要通过循环找到插入位置的前一个节点,但在双向链表中,直接循环到插入位置(插入位置记为cur)即可

    public void addIndex(int index, int data) {
        if (index < 0 || index > size()) {
            throw new IndexException("下标错误");
        }
        if (index == 0) {
            addFirst(data);
            return;
        }
        if (index == size()) {
            addLast(data);
            return;
        }
        ListNode newNode = new ListNode(data);
        ListNode cur = head;
        for (int i = 0; i < index; i++) {
            cur = cur.next;
        }
        
        /*一般情况下*/

        newNode.next = cur;
        cur.prev.next = newNode;
        newNode.prev = cur.prev;
        cur.prev = newNode;

    }

public void remove(int key);

删除第一次出现val = key的节点

先考虑常规情况,即通过遍历找到要删除的节点,这里记为cur

让cur的前驱节点的next指向cur的后继节点,cur的后继节点的prev指向cur的前驱节点

    public void remove(int key) {
        
        ListNode cur = head;
        while(cur != null) {
            if (cur.val == key) {
                cur.prev.next = cur.next;
                cur.next.prev = cur.prev;
                return;
            }
            cur = cur.next;
        }
    }

之后有两种特殊情况需要考虑:1.cur为第一个节点;2.cur为最后一个节点;当cur为这两种情况时如果使用上述代码,会引发空指针异常,所以这两种情况要单独考虑

1.cur为第一个节点:此时需要让cur的后继节点prev指向空(cur.prev = null),并让head = head.next,但是这样还有一个小问题:当链表中只有一个节点时也会引发空指针异常,这个问题也要单独处理,只需要直接让head = null即可

if (cur == head) {
    head = head.next;
    if (head == null) {
        last = null;
    } else {
        head.prev = null;
    }
    return;
}

2.cur为最后一个节点:只需让cur的前驱节点的next指向空,并让last = last.prev;即可

if (cur.next == null) {
    cur.prev.next = null;
    last = last.prev;
    return;
}

remove的最终代码如下:

    public void remove(int key) {

        ListNode cur = head;
        while(cur != null) {
            if (cur.val == key) {
                if (cur == head) {
                    head = head.next;
                    if (head == null) {
                        last = null;
                    } else {
                        head.prev = null;
                    }
                    return;
                }
                if (cur.next == null) {
                    cur.prev.next = null;
                    last = last.prev;
                    return;
                }
                cur.prev.next = cur.next;
                cur.next.prev = cur.prev;
                return;
            }
            cur = cur.next;
        }
    }

void removeAllKey(int key);

删除所有val = key的节点,这里只需要将remove方法修改以下即可

    public void removeAllKey(int key) {
        ListNode cur = head;
        while(cur != null) {
            if (cur.val == key) {
                if (cur == head) {
                    head = head.next;
                    if (head == null) {
                        last = null;
                    } else {
                        head.prev = null;
                    }
                    cur = head;
                    continue;
                }
                if (cur.next == null) {
                    cur.prev.next = null;
                    last = last.prev;
                    break;
                }
                cur.prev.next = cur.next;
                cur.next.prev = cur.prev;
            }
            cur = cur.next;
        }
    }

剩下的contains() 、size()、clear()、display()方法和上篇文章的单链表实现方法一致

三、LinkedList类讲解

LinkedList类是Java提供的类,底层是一个双向链表,包含我们刚刚实现过的方法,LinkedList也实现了List接口

3.1 LinkedList构造方法

LinkedList有两个构造方法:

1.无参构造方法

public LinkedList()

2. 带参构造方法

public LinkedList(Collection<? extends E> c)

该构造方法可以将c构造为双向链表,前提是c实现了Collection接口并且其泛型必须是E或是E的子类,例如:

ArrayList<Integer> list1 = new ArrayList<>();
list1.add(1);
list1.add(2);

LinkedList<Integer> list = new LinkedList<>(list1); //list1属于ArrayList类,实现了Collection接口,泛型和list1一样都是Integer,此时顺序表list1就被构造为了双向链表list

3.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<E> subList(int fromIndex, int toIndex) //截取链表,按左闭右开的区间截取[fromIndex, toIndex)

这些方法的底层实现方式和我们上述模拟实现的方法的实现方式相同

3.3 LinkedList遍历

1. for循环

for (int i = 0; i < list.size(); i++) {
    System.out.print(list.get(i) +" ");
}

2. for-each

for (int x : list) {
    System.out.print(x +" ");
}

3.迭代器

顺向遍历

ListIterator<Integer> it = list.listIterator();
while(it.hasNext()) {
    System.out.print(it.next() +" ");
}

逆向遍历

ListIterator<Integer> it1 = list.listIterator(list.size());
while(it1.hasPrevious()) {
    System.out.print(it1.previous() +" ");
}

🙉本篇文章到此结束,下篇文章会对栈相关知识进行讲解

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

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

相关文章

深度学习:机器智能的革命性突破与未来挑战

深度学习&#xff1a;机器智能的革命性突破与未来挑战 深度学习是人工智能领域的一个重要分支&#xff0c;它利用神经网络模拟人类大脑的学习过程&#xff0c;通过大量数据训练模型&#xff0c;使其能够自动提取特征、识别模式、进行分类和预测等任务。近年来&#xff0c;深度学…

忍不了,客户让我在一个接口里兼容多种业务逻辑

分享是最有效的学习方式。 博客&#xff1a;https://blog.ktdaddy.com/ 老猫的设计模式专栏已经偷偷发车了。 不甘愿做crud boy&#xff1f;看了好几遍的设计模式还记不住&#xff1f;那就不要刻意记了&#xff0c;跟上老猫的步伐&#xff0c;在一个个有趣的职场故事中领悟设计…

一文掌握SpringBoot注解之@Component 知识文集(8)

&#x1f3c6;作者简介&#xff0c;普修罗双战士&#xff0c;一直追求不断学习和成长&#xff0c;在技术的道路上持续探索和实践。 &#x1f3c6;多年互联网行业从业经验&#xff0c;历任核心研发工程师&#xff0c;项目技术负责人。 &#x1f389;欢迎 &#x1f44d;点赞✍评论…

山体滑坡在线安全监测预警系统(解决方案)

在近年来&#xff0c;随着全球气候变化的影响&#xff0c;山体滑坡等自然灾害频发&#xff0c;给人们的生命财产安全带来了严重威胁。为了有效预防和减少山体滑坡带来的危害&#xff0c;许多地方开始在山上安装山体滑坡在线安全监测预警系统&#xff08;解决方案&#xff09;。…

【图论】【状态压缩】【树】【深度优先搜索】1617. 统计子树中城市之间最大距离

作者推荐 【动态规划】【字符串】【行程码】1531. 压缩字符串 本文涉及的知识点 图论 深度优先搜索 状态压缩 树 LeetCode1617. 统计子树中城市之间最大距离 给你 n 个城市&#xff0c;编号为从 1 到 n 。同时给你一个大小为 n-1 的数组 edges &#xff0c;其中 edges[i] …

在 Linux 中挂载新硬盘动态使用

目录 一&#xff1a;添加硬盘并且格式化 二&#xff1a;创建逻辑卷 三&#xff1a;挂载卷到目录 在 Linux 中挂载新硬盘并进行格式化的操作可以按照以下步骤进行&#xff1a; 一&#xff1a;添加硬盘并且格式化 查看现有分区状态和服务器安装的硬盘状态&#xff1a; df -…

开源项目TARZAN-NAV | 基于springboot的现代化导航网站系统

TARZAN-NAV 导航网站 一个基于 Spring Boot、MyBatis-Plus、h2database、ehcache、Docker、websocket等技术栈实现的导航网站系统&#xff0c;采用主流的互联网技术架构、全新的UI设计、支持一键源码部署&#xff0c;拥有完整的仪表板、导航管理&#xff0c;用户管理、评论管理…

操作筛选器的 1 个应用实例:自动启用事务

前言 在数据库操作过程中&#xff0c;有一个概念是绕不开的&#xff0c;那就是事务。 事务能够确保一系列数据库操作要么全部成功提交&#xff0c;要么全部失败回滚&#xff0c;保证数据的一致性和完整性。 在 Asp.Net Core Web API 中&#xff0c;我们可以使用操作筛选器给…

uni-app小程序自定义导航栏

最近在开发一个uni-app小程序&#xff0c;用到了自定义导航栏&#xff0c;在这里记录一下实现过程&#xff1a; page.json 在对应页面路由的style中设置入"navigationStyle": "custom"取消原生导航栏&#xff0c;自定义导航栏 {"path": "…

C++ //练习 3.8 分别用while循环和传统的for循环重写第一题的程序,你觉得哪种形式更好呢?为什么?

C Primer&#xff08;第5版&#xff09; 练习 3.8 练习 3.8 分别用while循环和传统的for循环重写第一题的程序&#xff0c;你觉得哪种形式更好呢&#xff1f;为什么? 环境&#xff1a;Linux Ubuntu&#xff08;云服务器&#xff09; 工具&#xff1a;vim 代码块 /********…

[晓理紫]每日论文分享(有源码或项目地址、中文摘要)--强化学习、模仿学习、机器人

专属领域论文订阅 VX 关注{晓理紫},每日更新论文,如感兴趣,请转发给有需要的同学,谢谢支持 如果你感觉对你有所帮助,请关注我,每日准时为你推送最新论文。 为了答谢各位网友的支持,从今日起免费为300名读者提供订阅主题论文服务,只需VX关注公号并回复{邮箱+论文主题}(…

深度强化学习(王树森)笔记10

深度强化学习&#xff08;DRL&#xff09; 本文是学习笔记&#xff0c;如有侵权&#xff0c;请联系删除。本文在ChatGPT辅助下完成。 参考链接 Deep Reinforcement Learning官方链接&#xff1a;https://github.com/wangshusen/DRL 源代码链接&#xff1a;https://github.c…

KAFKA高可用架构涉及常用功能整理

KAFKA高可用架构涉及常用功能整理 1. kafka的高可用系统架构和相关组件2. kafka的核心参数2.1 常规配置2.2 特殊优化配置 3. kafka常用命令3.1 常用基础命令3.1.1 创建topic3.1.2 获取集群的topic列表3.1.3 获取集群的topic详情3.1.4 删除集群的topic3.1.5 获取集群的消费组列表…

如何使用 Google 搜索引擎保姆级教程(附链接)

一、介绍 "Google语法"通常是指在 Google 搜索引擎中使用一系列特定的搜索语法和操作符来精确地定义搜索查询。这些语法和操作符允许用户过滤和调整搜索结果&#xff0c;提高搜索的准确性。 二、安装 Google 下载 Google 浏览器 Google 官网https://www.google.c…

Python||1. 使用LSTM模型进行乘客的数目预测;2.对文件rest-api-asr_python_audio_16k.m4a进行语音识别

1. 使用LSTM模型进行乘客的数目预测 数据集 international-airline-passengers.csv&#xff08;可以不在意精度和loss&#xff09; import pandas as pd import numpy as np filename rC:\Users\15002\Desktop\data1\international-airline-passengers.csv data pd.read_cs…

科技云报道:新趋势下,国产数据库或“春山可望”

科技云报道原创。 从540亿元到1286亿元——这是中国通信标准化协会大数据技术标准推进委员会针对中国数据库行业给出的一份预测报告。 报告指出&#xff0c;未来五年&#xff0c;中国数据库行业将从百亿级市场跨越成为千亿级市场。 最近两年&#xff0c;中国的数据库行业似乎…

OAuth2的四种授权方式

OAuth2的四种授权方式 OAuth2的作用OAuth2的四种授权方式OAuth2相关名词授权码模式授权码模式的步骤 简化模式简化模式的步骤 密码模式密码模式的步骤 客户端模式客户端模式的步骤 OAuth2的作用 核心作用&#xff1a;颁发token&#xff0c;也就是令牌&#xff0c;token中一般包…

【DB2 流浪之旅】 第一讲 Linux 环境安装 db2 数据库

DB2数据库是IBM开发的一种大型关系型数据库平台。它支持多用户或应用程序在同一条SQL 语句中查询不同database甚至不同DBMS中的数据。一般DB2是搭配IBM Power系列小机使用的&#xff0c;兼容性好、性能高。当然DB2也有Linux版本的&#xff0c;相对性能会差一些&#xff0c;主要…

密评机构资质和开展业务资料

一、概念 密码&#xff1a;《密码法》定义是指对信息进行加密保护、安全认证的技术、产品、服务。分为&#xff1a;核心密码、普通密码、商用密码。 商用密码&#xff1a;《密码法》定义是指对不涉及国家秘密内容的信息进行加密保护或安全认证所使用的密码技术和密码产品。 …

JVM 内存模型

1 什么是 JVM 内存模型 JVM 需要使用计算机的内存&#xff0c;Java 程序运行中所处理的对象或者算法都会使用 JVM 的内 存空间&#xff0c;JVM 将内存区划分为 5 块&#xff0c;这样的结构称之为 JVM 内存模型。 2 JVM 为什么进行内存区域划分 随着对象数量的增加&#xff…