【数据结构】双链表实现

news2024/9/24 19:21:57

双链表实现

  • 双链表
  • LinkedList的使用
  • ArrayList 和 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 display() {
    ListNode cur = head;
    while (cur != null){
        System.out.print(cur.val+" ");
        cur = cur.next;
    }
    System.out.println();
}

//求双链表的长度
public int size() {
    int len = 0;
    ListNode cur = head;
    while (cur != null){
        len++;
        cur = cur.next;
    }
    return len;
}

//查找是否包含关键字key是否在单链表当中
public boolean contains(int key) {
    ListNode cur = head;
    while (cur != null){
        if (cur.val == key){
            return true;
        }
        cur = cur.next;
    }
    return false;
}

头插法:

在链表头部插入数据时,需要考虑链表中是否有节点,没有结点只需将 head 和 last 都指向这个新结点即可,有节点就需要改动三个地方了(原来 head 的 prev,新结点的 next,head 的指向)。

头插实现过程

头插代码:

//头插法
public void addFirst(int data) {
    ListNode node = new ListNode(data);
    if (head == null){
        head = node;
        last = node;
    }else {
        //将新结点的next指向head
        node.next = head;
        //head的prev存储新结点的地址
        head.prev = node;
        //head重新指向新结点
        head = node;
    }
}

实现效果:

头插效果

尾插法:

在单链表中,尾插需要找到最后一个节点,而在双链表中,last 记录了链表中的最后一个节点,不需要遍历查找最后一个节点。

尾插法实现过程

尾插代码:

//尾插法
public void addLast(int data) {
    ListNode node = new ListNode(data);
    if (head == null){
        head = node;
        last = node;
    }else {
    	//原来last的next指向新结点node
        last.next = node;
        //新结点的prev指向原来的last
        node.prev = last;
        //last指向新结点node
        last = node;
    }
}

实现效果:

尾插实现效果

任意位置插:

任意位置插也要注意位置是否合法。
任意位置插

存储 cur 的地址并修改新结点 node 的 next:

存储下一结点地址

存储下标1节点地址:

存储前一节点地址
修改 cur 节点的 prev 和下标1节点的 next:

修改前一节点和cur节点

尾插代码:

//任意位置插入,第一个数据节点为0号下标
public void addIndex(int index, int data){
//插入位置不合法,抛出异常
if (index < 0 || index > size()){
    throw new ListIndexOutOfException();
}
//在0位置插入为头插
if (index == 0){
    addFirst(data);
    return;
}
//最后位置插入为尾插
if (index == size()){
    addLast(data);
    return;
}
//其他位置插入,则需要找到这个位置
ListNode cur = head;
while(index != 0){
    cur = cur.next;
    index--;
}
//找到插入位置后,修改指向
ListNode node  = new ListNode(data);
node.next =cur;
node.prev = cur.prev;
cur.prev = node;
node.prev.next = node;

尾插效果:

尾插效果

删除第一次关键字为 key :

删除关键字需要考虑多种情况,避免造成错误。

remove

删除第一次关键字为 key 的代码:

//删除第一次出现关键字为key的节点
public void remove(int key) {
    ListNode cur = head;
    while (cur != null) {
        //如果结点的值等于key就删除
        if (cur.val == key) {
            //先判断删除的结点是否为第一个节点
            if (cur == head) {
                //是头就采用删除头的方式
                head = head.next;
                //head等于空,说明只有一个节点并且被删了,如果不判断就会空指针异常
                if (head != null) {
                    //head不等于空才能使用
                    head.prev = null;
                }
            } else {
                //不是头就是中间和结尾的结点,再根据next判断一下是中间的还是尾巴
                if (cur.next == null) {
                    //这里是尾巴,使用删除尾巴的方式
                    last = last.prev;
                    last.next = null;
                } else {
                    //这里就是中间结点
                    cur.prev.next = cur.next;
                    cur.next.prev = cur.prev;
                }
            }
            //删除后返回
            return;
        }
        //不等于就指向下一个
        cur = cur.next;
    }
}

删除效果:

remove效果

删除所有值为 key 的节点

我们只需将上面方法中的 return 语句删除,cur 就会继续向链表后面移动并删除。

代码:

public void removeAllKey(int key) {
    //上面的删除关键字后会返回,只需取消返回,cur就会继续向后走,并删除所有关键字
    ListNode cur = head;
    while (cur != null) {
        //如果结点的值等于key就删除
        if (cur.val == key) {
            //先判断删除的结点是否为第一个节点
            if (cur == head) {
                //是头就采用删除头的方式
                head = head.next;
                //head等于空,说明只有一个节点并且被删了,如果不判断就会空指针异常
                if (head != null) {
                    //head不等于空才能使用
                    head.prev = null;
                }
            } else {
                //不是头就是中间和结尾的结点,再根据next判断一下是中间的还是尾巴
                if (cur.next == null) {
                    //这里是尾巴,使用删除尾巴的方式
                    last = last.prev;
                    last.next = null;
                } else {
                    //这里就是中间结点
                    cur.prev.next = cur.next;
                    cur.next.prev = cur.prev;
                }
            }
            //取消return后,cur就会继续向后走
			//return;
        }
        //不等于就指向下一个
        cur = cur.next;
    }
}

实现效果:

删除所有关键字

清空链表:

双链表中清空链表方法就不像单链表那样,只需将 head 和 last 置为 null,因为在双链表中,head 的下一个结点还可以通过 prev 找到 head 结点。

我们只需在遍历链表的同时,将链表中的 next 和 prev 都置为 null,最后再让 head 和 last 都置为 null 即可。

代码:

public void clear() {
    ListNode cur = head;
    while (cur != null) {
        //记录下一结点
        ListNode curNext = cur.next;
        cur.next = null;
        cur.prev = null;
        cur = curNext;
    }
    head = null;
    last = null;
}

LinkedList的使用

LinkedList 的底层是双向链表结构,由于链表没有将元素存储在连续的空间中,元素存储在单独的节点中,然后通过引用将节点连接起来了,因此在任意位置插入或删除元素时,不需要移动元素,只需要修改指向,效率较高。LinkedList 的官方文档。

LinkedList 的构造:
LinkedList 既可以无参构造,也可以使用其他集合容器中元素构造,例如:

linkedlist构造

LinkedList 的常用方法:

添加:
添加

删除:
删除

其他常用方法:
other
other

遍历:
遍历

ArrayList 和 LinkedList的区别

存储上空间上:ArrayList 物理上连续。LinkedList 逻辑上连续,但物理上不一定连续。

头插法:ArrayList 头插涉及元素的移动,效率较低。LinkedList 只需修改引用的指向,不用移动元素,效率高。

插入时:ArrayList 空间不够时,需要扩容。而 LinkedList 则没有这个说法。

总的来说就是,只需要对元素进行大量查询但不需要插入或删除时,建议使用 ArrayList,如果需要对元素进行大量插入或删除时,建议使用 LinkedList。

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

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

相关文章

【经典算法】双指针(尺取法):爱,是双向奔赴,还是你追我赶?

&#x1f451;专栏内容&#xff1a;算法学习随笔⛪个人主页&#xff1a;子夜的星的主页&#x1f495;座右铭&#xff1a;日拱一卒&#xff0c;功不唐捐 目录一、前言二、左右指针&#xff08;双向奔赴&#xff09;1、定义2、回文检查三、快慢指针&#xff08;你追我赶&#xff…

将字符串代码编译为字节代码对象 compile()

【小白从小学Python、C、Java】【计算机等级考试500强双证书】【Python-数据分析】将字符串代码编译为字节代码对象compile()[太阳]选择题关于以下python代码表述错误的一项是?sx1y2print("xy",xy)print("【显示】s&#xff1a;")print(s)print("【执…

fpga实操训练(一个典型的fpga系统)

【 声明:版权所有,欢迎转载,请勿用于商业用途。 联系信箱:feixiaoxing @163.com】 对于刚学习fpga的同学来说,很多人可能一开始并不了解,一个典型的fpga系统应该是什么样子的。今天正好来熟悉一下。此外,我们也可以通过这个系统,从另外一个角度学习下,为什么要…

【蓝桥杯】简单数论——快速幂矩阵快速幂

1、快速幂 1.1运算模 定义&#xff1a;模运算为a除以m的余数&#xff0c;记为a mod m&#xff0c;有a mod m a % m。 模运算是大数运算中的常用操作&#xff1a;如果一个数太大&#xff0c;无法直接输出&#xff0c;或者不需要直接输出&#xff0c;可以把它取模后&#xff…

2.4总线操作和定时

文章目录一、引子二、介绍1.总线周期2.总线定时规范三、同步定时方式1.过程2.特点3.优缺点①优点②缺点四、异步定时方式1.介绍2.三种方式&#xff08;1&#xff09;不互锁方式&#xff08;2&#xff09;半互锁方式&#xff08;3&#xff09;全互锁方式3.优缺点①优点②缺点五、…

Allegro如何统计包含过孔长度的网络长度操作指导

Allegro如何统计包含过孔长度的网络长度操作指导 当需要统计网络长度的时候,可以通过element选择nets看到网络的长度,但是当网络换层了,并且需要统计到过孔的长度,类似下图 Allegro可以快速的统计网络的长度,并且包含过孔的长度 具体操作如下 选择Setup选择Constraint –…

设计模式 - 六大设计原则之SRP(单一职责)

文章目录概述CaseBad ImplBetter Impl1. 定义接口2. 职责分离-多种实现类3. 单元测试小结概述 单一职责原则&#xff08;Single Responsibility Principle&#xff0c; SRP&#xff09;又称单一功能原则&#xff0c;是面向对象的五个基本原则&#xff08;SOLID&#xff09;之一…

2022这一年

前言 一年过得好快啊&#xff0c;这个年终总结不知道该写点啥&#xff0c;所以一直到现在也没动笔。 但如果不写吧&#xff0c;总感觉少了点什么。就像过年守夜&#xff0c;反正我是每年都要等到凌晨12点放完鞭炮后才睡。 前些天也看到不少博主发布了2022年终总结&#xff0c;…

【ARM体系结构】之相关概念与公司简介

1、ARM相关的概念 机器码&#xff1a;计算机可以识别的0和1的组合。即高低电平的信号&#xff0c;1高电平信号&#xff0c;0低电平信号 汇编指令&#xff1a;编译器可以将汇编指令&#xff08;存在代码段&#xff09;编译成为机器码&#xff0c;执行汇编指令可以完成相应的汇编…

【进击的算法】基础算法——动态规划

&#x1f37f;本文主题&#xff1a;动态规划 &#x1f388;更多算法&#xff1a;回溯算法 &#x1f495;我的主页&#xff1a;蓝色学者的主页 文章目录一、前言二、概念2.1概念一&#xff1a;状态转移2.2概念二&#xff1a;Dp数组三、例题3.1斐波那契数列3.1.1题目描述3.1.2状态…

JQUERY总结(四)

对象拷贝&#xff1a; <script src"jQuery.min.js"></script> <script>$(function(){// var targetObj{};// var obj{// id:0,// name:"xinyi",// location:"henan"// };// //覆盖以前的相同key值对应的数据// $.…

【自然语言处理】基于NLP的电影评论情感分析模型比较

基于NLP的电影评论情感分析模型比较一段时间以来&#xff0c;使用机器学习的 NLP 任务借助 BERT&#xff08;Bidirectional Encoder Representations from Transformers&#xff09;模型被认为是当前的黄金标准。这些模型通常用于我们日常的许多语言处理任务&#xff0c;比如谷…

Java面试题,线程安全问题

线程安全问题一、对线程安全的理解&#xff08;实际上是内存安全&#xff09;二、Thread类的继承、Runable接口的重写三、守护线程四、ThreadLocal原理和使用场景五、sleep、wait、join、yield六、线程池、解释线程池参数一、对线程安全的理解&#xff08;实际上是内存安全&…

JVM面试一

5. JVM 5.1 JVM包含哪几部分? 参考答案 JVM 主要由四大部分组成:ClassLoader(类加载器),Runtime Data Area(运行时数据区,内存分区),Execution Engine(执行引擎),Native Interface(本地库接口),下图可以大致描述 JVM 的结构。 JVM 是执行 Java 程序的虚拟计算…

【计算机组成原理】第一章 计算机系统概述

文章目录第一章 知识体系1.1 计算机发展历程1.1.1 计算机硬件的发展1.1.2 计算机软件的发展1.2 计算机系统层次结构1.2.1 计算机系统的组成1.2.2 计算机硬件1.2.3 计算机软件1.2.4 计算机的层次结构1.2.5 计算机系统的工作原理1.3 计算机的性能指标第一章 知识体系 1.1 计算机发…

35.Isaac教程--机械臂取放物体示例应用程序

机械臂取放物体示例应用程序 ISAAC教程合集地址文章目录机械臂取放物体示例应用程序使用 Omniverse 套件模拟驱动的机器人手臂启动取放示例应用程序该包为拾取和放置场景提供了一个应用程序脚手架。 它具有执行拾取和放置任务所需的高级步骤&#xff0c;并与两种类型的机器人操…

Java面试题,Spring与SpringBoot相关问题

Spring与SpringBoot相关问题1、BeanFactory和ApplicationContext有什么区别&#xff1f;2、描述一下Spring Bean的生命周期3、Spring的几种Bean的作用域4、单例Bean是线程安全的吗&#xff1f;5、Spring框架用到了哪些设计模式6、Spring事务的实现方式、隔离级别、传播行为7、S…

Lesson4--栈和队列

目录 1.栈 1.1栈的概念及结构 1.2栈的实现 初始化栈 销毁栈 栈的扩容 入栈 出栈 获取栈顶元素 获取栈中有效元素个数 判空 程序代码如下 Stack.h Stack.c test.c 2.队列 2.1队列的概念及结构 ​2.2队列的实现 初始化队列 队尾入队列 队头出队列 获取队列头部元素 获取…

二、pyhon基础语法篇(黑马程序猿-python学习记录)

黑马程序猿的python学习视频&#xff1a;https://www.bilibili.com/video/BV1qW4y1a7fU/ 目录 一 、print 1. end 2. \t对齐 二、字面量 1. 字面量的含义 2. 常见的字面量类型 3. 如何基于print语句完成各类字面量的输出 三、 注释的分类 1. 单行注释 2. 多行注释 3. 注释的…

多进程|基于非阻塞调用的轮询检测方案|进程等待|重新理解挂起|Linux OS

说在前面 今天给大家带来操作系统中进程等待的概念&#xff0c;我们学习的操作系统是Linux操作系统。 我们今天主要的目标就是认识wait和waitpid这两个系统调用。 前言 那么这里博主先安利一下一些干货满满的专栏啦&#xff01; 手撕数据结构https://blog.csdn.net/yu_cbl…