链表拓展之双向链表

news2024/11/27 21:43:11

前言

在前面已经总结了单链表,有了单链表的基础会很好理解双链表的实现,忘记了可以跳转——>http://t.csdnimg.cn/GFPk9

接下来就由我带着各位看官来认识今天的主角吧~

什么是双向链表 

在单链表的基础上,它有两个方向的链接,一个链接指向前一个节点,另一个链接指向下一个节点。每个节点通常包含两个部分:数据(val)部分和两个指针部分,即前向指针(prev)和后向指针(next)。这使得在双向链表中,可以双向遍历数据,搭配下面图片更好理解~

因为是双向链表,为了分清头尾,定义头结点head和尾结点last,同时别忘在各自的末尾置null~这样一个完整的双向链表就表示出来了。 

双向链表的基本方法

这里就用图来表示

boolean add(E e)尾插 e
void add(int index, E element)将 e 插入到 index 位置
boolean contains(Object o)判断元素o是否在表中
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()

清空

这里就引用了LinkedList类,观察代码演示就能理解这些方法的使用。

import java.util.LinkedList;

public class Test3 {
    public static void main(String[] args) {
        LinkedList<Integer> list=new LinkedList<>();
        list.add(1);//尾插法
        list.add(2);
        list.add(3);
        list.add(4);
        System.out.println(list.size());//获取链表长度
        System.out.println(list);//打印链表

        System.out.println("插入");
        list.add(0,5);//指定位置插入
        System.out.println(list);

        boolean a= list.contains(4);//判断指定元素是否在list当中
        System.out.println(a);

        list.set(0,6);//替换指定位置的元素
        System.out.println(list);

        System.out.println("移除");
        list.remove(0);//移除指定位置
        list.removeFirst();//头删
        list.removeLast();//尾删
        System.out.println(list);

        list.clear();
        System.out.println(list);//清空链表
}
}

运行结果如图(搭配代码就能理解通透了):

双向链表的模拟实现

首先初始化一个双向链表,对于一个单节点,存储的val值,前驱和后继,然后定义头尾节点表示顺序。对比这张图,便好理解了~

1.添加元素 

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) {
           LinkedList node= new LinkedList(data);
           if(head==null){
               head=node;
               last=node;
           }
           node.next=head;
           head.prev=node;
           head=node;
       }

尾插法 

尾插法的原理是一样的,头插反着来就是了

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

任意位置插入 

 修改节点的指向的就可以实现这个效果了,注意改变的指向次序

    // 任意位置插入,第一个数据节点为0号下标
    public boolean addIndex(int index, int data) {
        if (index < 0 || index > size()) {
            return false;
        }
        if (index == 0) {//头插
            addFirst(data);
            return true;
        }
        if (index == size()) {//尾插
            addLast(data);
            return true;
        }
        ListNode cur = findIndex(index);
        ListNode node = new ListNode(data);

        node.next = cur;
        cur.prev.next=node;//cur.prev.next表示的是cur的前一个节点的next
        node.prev = cur.prev;//cur.prev表示的是cur的前一个节点
        cur.prev=node;
        return true;
    }

    //找到要删除元素的下标
    public ListNode findIndex(int index) {
        ListNode cur = head;
        while (index != 0) {
            cur = cur.next;
            index--;
        }
        return cur;
    }

2.删除元素 

 删除该节点就是没有节点指向它了,直接跨过它了

删除第一次出现的指定元素 

    // 删除第一次出现关键字为key的节点
    public void remove(int key) {
        ListNode cur = head;
        while (cur != null) {
            if (cur.val == key) {
                if (cur == head) { //头删
                    head = cur.next;
                    if (head != null) {
                        head.prev = null; //如果头节点不为空,将新的头节点的prev设置为null
                    }
                } else {
                    cur.prev.next = cur.next;//将cur的前一个节点的next指向cur的下一个节点
                    if (cur.next != null) {
                        cur.next.prev = cur.prev;//将cur的下一个节点的prev指向cur的前一个节点
                    }
                }
                break;//找到就退出循环
            }
            cur = cur.next;
        }
    }

删除所有指定元素 

 两者是很相似的,区别在于有无break跳出循环

    // 删除第一次出现关键字为key的节点
    public void remove(int key) {
        ListNode cur = head;
        while (cur != null) {
            if (cur.val == key) {
                if (cur == head) { //头删
                    head = cur.next;
                    if (head != null) {
                        head.prev = null; //如果头节点不为空,将新的头节点的prev设置为null
                    }
                } else {
                    cur.prev.next = cur.next;//将cur的前一个节点的next指向cur的下一个节点
                    if (cur.next != null) {
                        cur.next.prev = cur.prev;//将cur的下一个节点的prev指向cur的前一个节点
                    }
                }
            
            }
            cur = cur.next;
        }
    }

3.清空链表 

清空整张链表也不难,将头尾都置null,将节点间的联系断开即可 

//清空整张链表
    public void clear() {
        ListNode cur=head;
        while(cur!=null){
            ListNode CurN=cur.next;
            cur.next=null;//逐个断开节点之间的连接
            cur.prev=null;
            cur=CurN;
        }
        head=last=null;//并将头节点和尾节点都置为null
    }

4.单双链表相同的方法 

 下面这三种方法,其实和方向无关了,所以总结在一处,,其实和单向链表是一模一样了

查找指定元素是否在链表中 

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

打印链表 

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

获取链表长度 

//得到单链表的长度
       public int size(){
           LinkedList cur=head;
           int count=0;
           while(cur!=null){
               count++;
               cur=cur.next;}
           return count;
           }

ArrayList和LinkedList的区别

在实际场景当中,这两种数据结构有着各自的优势,但区别还是要熟悉的,总结在这里了~

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

那文章的内容就总结到这里,如果觉得有帮助不妨点个免费的赞吧,您的支持是我更新的最大动力,下次见~

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

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

相关文章

SpringBlade dict-biz/list SQL 注入漏洞复现

0x01 免责声明 请勿利用文章内的相关技术从事非法测试&#xff0c;由于传播、利用此文所提供的信息而造成的任何直接或者间接的后果及损失&#xff0c;均由使用者本人负责&#xff0c;作者不为此承担任何责任。工具来自网络&#xff0c;安全性自测&#xff0c;如有侵权请联系删…

一文看懂动态IP代理API

“动态”意味着每次连接或每隔一段时间&#xff0c;用户的IP地址都会发生改变。由于IP地址的不断变化&#xff0c;用户可以避免因频繁访问同一网站而导致的IP被封锁的问题。API叫做应用程序接口&#xff0c;是一种让软件之间相互通信的接口。API允许用户通过编程方式来调用动态…

【从浅学到熟知Linux】进程控制下篇=>进程程序替换与简易Shell实现(含替换原理、execve、execvp等接口详解)

&#x1f3e0;关于专栏&#xff1a;Linux的浅学到熟知专栏用于记录Linux系统编程、网络编程等内容。 &#x1f3af;每天努力一点点&#xff0c;技术变化看得见 文章目录 进程程序替换什么是程序替换及其原理替换函数execlexeclpexecleexecvexecvpexecvpeexecve 替换函数总结实现…

转行或者跳槽入职一家新公司,应该如何快速上手工作?

不管是干测试也好或者其它任何职业&#xff0c;没有谁会在一家公司待一辈子&#xff0c;转行不一定&#xff0c;但是跳槽是每一个打工人早晚都会面临的事情&#xff0c;今天就来跟大家聊聊这件事~ 入职一家新公司&#xff0c;你应该做什么可以最快速的上手工作&#xff1f; 这…

Python编程之旅:深入探索强大的容器——列表

在Python编程的世界中&#xff0c;容器&#xff08;Containers&#xff09;是一种用于存储多个项目的数据结构。其中&#xff0c;列表&#xff08;List&#xff09;是最常用且功能强大的容器之一。无论是初学者还是资深开发者&#xff0c;掌握列表的使用方法和技巧都是提升Pyth…

判断位数、按位输出、倒序输出(C语言)

一、运行结果&#xff1b; 二、源代码&#xff1b; # define _CRT_SECURE_NO_WARNINGS # include <stdio.h>int main() {//初始化变量值&#xff1b;int number 0;int i 1;int m 0;int z 0;int z1 0, z2 0, z3 0, z4 0;//提示用户&#xff1b;printf("请输…

电动汽车退役锂电池SOC主动均衡控制MATLAB仿真

微❤关注“电气仔推送”获得资料&#xff08;专享优惠&#xff09; 仿真简介 模型选用双向反激变换器作为主动均衡拓扑电路&#xff0c;均衡策略采用基于SOC的主动均衡策略&#xff0c;旨在解决电动汽车退役锂电池的不一致性问题。模型选用双向反激变换器作为主动均衡拓扑电路…

27.8k Star,AI智能体项目GPT Pilot:第一个真正的人工智能开发者(附部署视频教程)

作者&#xff1a;Aitrainee | AI进修生 排版太难了&#xff0c;请点击这里查看原文&#xff1a;27.8k Star&#xff0c;AI智能体项目GPT Pilot&#xff1a;第一个真正的人工智能开发者&#xff08;附部署视频教程&#xff09; 今天介绍一下一个人工智能智能体的项目GPT Pilot。…

关于idea中mybatis插件,下载后,无法生成代码模板--解决方法

一、不用相信网上其他解决方法 1.1试过&#xff0c;无效 二、解决方法 2.1【注&#xff1a;多试几次】重新下载&#xff0c;并重新启动idea 三、操作方法 3.1步骤 3.2idea重启&#xff0c;【如果没有重启】手动重启&#xff0c;必须有&#xff0c;很重要 3.3重新下载mybat…

试题 C: 质因数个数

萎了&#xff0c;整个人都萎了 快三天都没刷题了&#xff0c;想着明天就蓝桥杯了&#xff0c;就找了个真题做了下 可以看得出来这题很简单 但是没有测试点给我用&#xff0c;所以我的代码不保证正确性 代码如下&#xff1a; #include<cstdio> #include<cmath> …

2024 年10个最佳 Ruby 测试框架

QA一直在寻找最好的自动化测试框架&#xff0c;这些框架提供丰富的功能、简单的语法、更好的兼容性和更快的执行速度。如果您选择结合使用Ruby和Selenium进行Web测试&#xff0c;可能需要搜索基于Ruby的测试框架进行Web应用程序测试。 Ruby测试框架提供了广泛的功能&#xff0…

备忘录模式:恢复对象状态的智能方式

在软件开发中&#xff0c;备忘录模式是一种行为型设计模式&#xff0c;它允许捕获并外部化对象的内部状态&#xff0c;以便在未来某个时刻可以将对象恢复到此状态。这种模式是撤销操作或者回滚操作的关键实现机制。本文将详细介绍备忘录模式的定义、实现、应用场景以及优缺点。…

【产品经理修炼之道】- 产品经理如何做用户行为分析

一、为什么要做用户行为分析 观点一&#xff1a;有些功能整个平台用户都希望做&#xff0c;是没有必要耗费人力评估的&#xff0c;只要做了就可以了。用户行为分析是形式&#xff0c;不能为了分析而分析。观点二&#xff1a;我都在这个行业做了这么多年了&#xff0c;用户需要…

掼蛋小技巧(上篇)

一、一火保两单 如果手中的牌可以组成同花顺并且不会造成两张以上的单牌&#xff0c;我们就可以组成同花顺&#xff1b;如果组了同花顺后有两张以上的单张则果断放弃组同花顺。 二、十张出一对&#xff0c;九张出单张 掼蛋残局的时候&#xff0c;如果判断出下家手上只有一个四头…

pyqt的人脸识别 基于face_recognition库

参考文献&#xff1a; 1、python face_recognition实现人脸识别系统_python facerecognition检测人脸-CSDN博客 2、cv2.VideoCapture()_cv2.videocapture(0)-CSDN博客 1、camera.py文件代码如下&#xff1b;目录如下 import sys from PyQt5.QtWidgets import QApplication, …

JVM主要知识点详解

目录 1. 性能监控和调优 1.1 调优相关参数 1.2 内存泄漏排查 1.3 cpu飙⾼ 2. 内存与垃圾回收 2.1JVM的组成&#xff08;面试题&#xff09; 2.2 Java虚拟机栈的组成 2.3 本地方法栈 2.4 堆 2.5 方法区&#xff08;抽象概念&#xff09; 2.5.1 方法区和永久代以及元空…

listpack

目录 为什么有listpack? listpack结构 listpack的节点entry 长度length encoding编码方式 listpack的API 1.创建listpack 2.遍历操作 正向遍历 反向遍历 3.查找元素 4.插入/替换/删除元素 总结 为什么有listpack? ziplist是存储在连续内存空间&#xff0c;节省…

两部电话机怎样能实现对讲?直接连接能互相通话吗?门卫门房传达室岗亭电话怎么搞?

目录 两部电话机能直接连接吗&#xff1f;用三通头分出来一条电话线两部电话机用一根电话线直接连接能互相通话吗&#xff1f; 什么电话机可以直接连接两部IP电话机&#xff08;网络电话机&#xff09;可以直接连接两部普通电话机之间通过一个电话交换机也可以连接跨区域的两部…

代码随想录算法训练营第四十一天|343.整数拆分、96.不同的二叉搜索树

代码随想录算法训练营第四十一天|343.整数拆分、96.不同的二叉搜索树 343.整数拆分 给定一个正整数 n &#xff0c;将其拆分为 k 个 正整数 的和&#xff08; k > 2 &#xff09;&#xff0c;并使这些整数的乘积最大化。 返回 你可以获得的最大乘积 。 示例 1: 输入: n…

模仿银行系统的极简Java三层结构应用——存钱功能的实现

一&#xff0c;前提&#xff1a; 我们上次做了一个简易的银行系统&#xff0c;初步认识了java结构&#xff0c;目前该系统可以输入要用的数据并且输出。 二&#xff0c;目标&#xff1a; 我们这次的目标是实现一个简易的存钱功能&#xff0c;并输出存钱后的余额&#xff0c;…