数据结构与算法-双向链表

1.简介

在使用带头节点的单向链表查找时查找的方向只能是一个方向,而双向链表可以向前或向后查找。例如单向链表只能查到一个节点的下一个节点,但是双向链表不仅可以查到下一个节点,还可以查到上一个节点。

在删除节点时,单向链表不能自我删除,需要借助辅助节点temp,因为单向链表就能找到下一个节点,把这个节点删了就不能直接找到下一个节点了。而双向链表可以自我删除,反正可以找到前一个节点,也可以找到后一个节点,这个节点自己就可以实现删除自我,让自己上一个节点和下一个节点连起来。

比如单向链表长这样:一个next可以找到下一个节点

双向链表可以长这样:一个pre指向前一个节点,一个next指向后一个节点

它可以从前往后查找,也可以从后往前查找

2.思路

①遍历:在遍历时遍历方式和单链表一样,只是它可以向前查找,也可以向后查找

②添加:在添加节点时,默认添加到双向链表最后的情况,先找到双向链表最后的节点,找到以后先使temp.next=newHeroNode,再使newHeroNode.pre=temp。就是原来最后一个节点的下一个节点是新节点,新节点的前一个是原来最后一个节点。

③修改:和单向链表一样

④删除:比如删除编号为5的节点,先找到要删除的这个节点temp,然后使temp.pre.next=temp.next,再使temp.next.pre=temp.pre。这样遍历的时候就跳过要删除的节点了。

要注意:在删除最后一个节点时如果要找temp.next.pre就会找不到。所以如果temp.next==null时就不执行temp.next.pre=temp.pre这行代码。

这两行代码可以记成:等号前面是更长的,有next有pre,等号后面是前面的头和尾,没有中间的next或者pre。

temp.next.pre=temp.pre

temp.pre.next=temp.next

⑤考虑编号顺序添加节点:找到要添加位置的前一个节点temp,找到以后新节点.next=temp.next,判断temp.next是否为空,如果不为空就temp.next.pre=新节点。然后temp.next=新节点,新节点.pre=temp。

3.代码

package com.xjj.linkedlist;

public class DoubleLinkedListDemo {
    public static void main(String[] args) {
        //测试
        System.out.println("双向链表");
        //创建节点
        HeroNode2 hero1 = new HeroNode2(1, "宋江", "及时雨");
        HeroNode2 hero2 = new HeroNode2(2, "卢俊义", "玉麒麟");
        HeroNode2 hero3 = new HeroNode2(3, "吴用", "智多星");
        HeroNode2 hero4 = new HeroNode2(4, "林冲", "豹子头");
        //创建双向链表
        DoubleLinkedList doubleLinkedList = new DoubleLinkedList();
        doubleLinkedList.list();
        doubleLinkedList.add(hero1);
        doubleLinkedList.add(hero2);
        doubleLinkedList.add(hero3);
        doubleLinkedList.add(hero4);
        doubleLinkedList.list();
        //修改双向链表
        System.out.println("修改双向链表");
        HeroNode2 newHeroNode = new HeroNode2(2, "卢", "玉麒麟--");
        doubleLinkedList.update(newHeroNode);
        doubleLinkedList.list();
        //删除
        System.out.println("删除节点1");
        doubleLinkedList.delete(1);//删除第一个节点
        doubleLinkedList.list();
        System.out.println("删除节点4");
        doubleLinkedList.delete(4);//删除第四个节点
        doubleLinkedList.list();
        //按编号顺序添加
        System.out.println("按编号顺序添加");
        DoubleLinkedList doubleLinkedList1 = new DoubleLinkedList();
        doubleLinkedList1.addByOrder(hero2);
        doubleLinkedList1.addByOrder(hero4);
        doubleLinkedList1.addByOrder(hero3);
        doubleLinkedList1.addByOrder(hero1);
        doubleLinkedList1.list();
    }
}

class HeroNode2 {
    public int no;
    public String name;
    public String nickname;
    public HeroNode2 next;//指向下一个节点
    public HeroNode2 pre;//指向上一个节点

    //构造器
    public HeroNode2(int hNo, String hName, String hNickname) {
        this.no = hNo;
        this.name = hName;
        this.nickname = hNickname;
    }

    //重写toString来显示方法
    @Override
    public String toString() {
        return "HeroNode [no=" + no + ", name=" + name + ", nickname=" + nickname + "]";
    }
}

class DoubleLinkedList {
    private HeroNode2 head = new HeroNode2(0, "", "");

    //返回头节点
    public HeroNode2 getHead() {
        return head;
    }

    //添加节点。不考虑编号顺序时,先找到当前链表的最后节点,再将这个节点的next指向新节点
    public void add(HeroNode2 heroNode) {
        //辅助遍历temp
        HeroNode2 temp = head;
        //遍历链表找到最后
        while (true) {
            if (temp.next == null) {//找到最后了就退出
                break;
            }
            //没到最后就后移到下一个节点
            temp = temp.next;
        }
        //退出while循环时temp就指向链表最后
        //我们要添加新节点,就将这个最后节点的next指向节点,将节点的前一个指向temp
        temp.next = heroNode;
        heroNode.pre = temp;
    }

    //添加节点,考虑编号顺序。也就是插入到链表非末尾位置
    public void addByOrder(HeroNode2 heroNode) {
        //辅助变量位于添加位置的前一个节点
        HeroNode2 temp = head;
        boolean flag = false;//标识添加的编号是否存在。如果存在就不能添加了
        while (true) {
            if (temp.next == null) {
                //已经在链表最后,要添加的节点可能已经在链表最后
                break;
            }
            if (temp.next.no > heroNode.no) {
                //temp下一个节点的编号大于要插入的节点编号,就是找到了,要在这个temp后面插入
                break;
            } else if (temp.next.no == heroNode.no) {
                //编号已存在
                flag = true;//编号存在
            }
            temp = temp.next;//后移
        }
        //判断编号是否已存在
        if (flag) {
            System.out.printf("准备插入的编号 %d 已经存在,无法添加\n", heroNode.no);
        } else {
            //新节点插入链表中temp的后面
            heroNode.next = temp.next;//新节点下一个指向插入节点
            if (temp.next != null) {
                temp.next.pre = heroNode;
            }
            temp.next = heroNode;//插入节点后一个的前一个指向新节点
            heroNode.pre = temp;//新节点前一个指向插入节点前一个
        }
    }

    //根据编号修改节点信息,即编号不能修改
    public void update(HeroNode2 newHeroNode) {
        //判断链表是否为空
        if (head.next == null) {
            //如果链表为空就不遍历了
            System.out.println("链表为空");
            return;
        }
        //找到需要修改的节点
        //辅助变量
        HeroNode2 temp = head.next;
        boolean flag = false;//判断是否找到该节点
        while (true) {
            if (temp == null) {
                break;//已经遍历完链表
            }
            if (temp.no == newHeroNode.no) {
                //找到
                flag = true;
                break;
            }
            temp = temp.next;
        }
        //判断是否找到节点
        if (flag) {
            temp.name = newHeroNode.name;
            temp.nickname = newHeroNode.nickname;
        } else {
            System.out.printf("没有找到编号为 %d 的节点\n", newHeroNode.no);
        }
    }

    //删除节点
    //直接找到要删除的节点
    public void delete(int no) {
        //判断链表是否为空
        if (head.next == null) {
            //如果链表为空就不遍历了
            System.out.println("链表为空");
            return;
        }
        //找到需要修改的节点
        //辅助变量
        HeroNode2 temp = head.next;//直接指向head.next
        boolean flag = false;//判断是否找到该节点
        while (true) {
            if (temp == null) {
                break;//已经遍历完链表
            }
            if (temp.no == no) {
                //找到待删除节点
                flag = true;
                break;
            }
            temp = temp.next;
        }
        //判断是否找到节点
        if (flag) {
            temp.pre.next = temp.next;//前一个的下一个直接变成下一个
            //temp.next.pre=temp.pre;//下一个的前一个变成前一个。但是有风险
            //如果删除最后一个节点,temp就没有next了。所以是删最后一个节点就不用执行下面的代码
            if (temp.next != null) {
                temp.next.pre = temp.pre;
            }
        } else {
            System.out.printf("没有找到编号为 %d 的节点\n", no);
        }
    }

    //显示链表,看有没有成功
    public void list() {
        //判断链表是否为空,如果为空就不遍历了
        if (head.next == null) {
            System.out.println("链表为空");
            return;
        }
        //不为空就遍历
        //使用辅助变量来遍历
        HeroNode2 temp = head.next;//这里指向的是下一个
        while (true) {
            //看是否到链表最后
            if (temp == null) {
                break;
            }
            //不为空就输出节点信息
            System.out.println(temp);
            //后移
            temp = temp.next;
        }
    }
}

4.运行结果

5.提炼一下

①直接在链表末尾添加节点就是找到末尾节点temp,然后使temp.next=新节点,再使新节点.pre=temp

②在链表中间按编号顺序添加就是找到添加位置的前一个节点temp,找到以后新节点.next=temp.next,判断temp.next是否为空,如果不为空就temp.next.pre=新节点。然后temp.next=新节点,新节点.pre=temp。

②删除节点就遍历找到该节点temp后temp.next.pre=temp.pre,temp.pre.next=temp.next

③遍历以及修改节点与单链表的一样


加油加油^_^

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

C语言 | Leetcode C语言题解之第66题加一

题目: 题解: /*** Note: The returned array must be malloced, assume caller calls free().*/ int* plusOne(int* digits, int digitsSize, int* returnSize){for(int i digitsSize - 1; i > 0; --i){digits[i] digits[i] 1;//最后元素1判断是不…

怎么用CAPL与Python交互

怎么用CAPL与其他应用程序交互 怎么用CAPL与Python交互 怎么用CAPL与Python交互 怎么用CAPL与其他应用程序交互前言1、CAPL怎么调Python?1.1CAPL调Python的命令1.2CAPL调用Python实例 2、怎么把python运行的结果返回给CAPL2.1通过环境变量 3、CAPL调Python的输入参…

linux进入单用户模式指引

文章目录 引言I 通过GRUB进入单用户模式1.1 倒计时界面的操作1.2 GRUB1.3 内核参数编辑界面1.4 更多内核参数编辑界面II 预备知识:Linux用户模式引言 应用场景: root密码重置: 用passwd命令修改root修复登录相关的配置:/etc/pam.d/login 和 /etc/pam.d/sshd 案例:Centos6进…

Qt QImageReader类介绍

1.简介 QImageReader 是用于读取图像文件的类。它提供了读取不同图像格式的功能,包括但不限于 PNG、JPEG、BMP 等。QImageReader 可以用于文件,也可以用于任何 QIODevice,如 QByteArray ,这使得它非常灵活。 QImageReader 是一个…

奥尔良

目录 一,核心规则 1,游戏回合 2,公共主版面 3,公共副版面 4,个人版面 二,规则细节 1,七种随从 2,渡船、马车、公会 3,得分 4,其他规则 奥尔良是一个…

【大模型学习】私有大模型部署(基础知识)

私有大模型 优点 保护内部隐私 缺点 成本昂贵 难以共享 难以更新 大模型底座 基础知识点 知识库 知识库是什么? 知识库的作用是什么? 微调 增强大模型的推理能力 AI Agent 代理,与内部大模型进行交互 开源 and 闭源 是否可以查…

[蓝桥杯2024]-PWN:fd解析(命令符转义,标准输出重定向,利用system(‘$0‘)获取shell权限)

查看保护 查看ida 这里有一次栈溢出,并且题目给了我们system函数。 这里的知识点没有那么复杂 方法一(命令转义): 完整exp: from pwn import* pprocess(./pwn) pop_rdi0x400933 info0x601090 system0x400778payloa…

Redis教程——管道

在上篇文章我们学习了Redis教程——事务,这篇文章我们学习Redis教程——管道。 客户端向服务端发送命令分四步(发送、排队、执行和返回结果),并监听Socket返回,通常以阻塞模式等待服务端响应,如下图所示&a…

探索和构建 LLaMA 3 架构:深入探讨组件、编码和推理技术(三)KV缓存

探索和构建 LLaMA 3 架构:深入探讨组件、编码和推理技术(三) KV缓存 在推理的每一步中,只对模型输出的最后一个标记感兴趣,因为已经有了之前的标记。然而,模型需要访问所有先前的标记来决定输出哪个标记&…

【算法】【单调栈】【leetcode】1019. 链表中的下一个更大节点

刷这题之前先看: 【算法】【OD算法】【单调栈】找朋友-CSDN博客 【算法】【单调栈】【leetcode】1475. 商品折扣后的最终价格-CSDN博客 【算法】【单调栈】【leetcode】901. 股票价格跨度-CSDN博客 【算法】【单调栈】每日温度-CSDN博客 题目地址&#xff1…

Linux MQTT智能家居(Linux下运行MQTT)

文章目录 前言一、下载源码编译1.编译出64位的库文件2.编译出ARM平台下的库文件 二、将lib库文件和include文件加入自己的工程1.ubuntu下测试2.ARM平台测试 总结 前言 本篇文章将带大家在Linux下运行MQTT库,我们首先会将MQTT库下载下来,然后进行编译&am…

3.4 无关、基和维度

这一节是关于子空间的真实大小。对于 m n m\times n mn 的矩阵,它有 n n n 个列,但是它真正的维数不一定为 n n n,维数可以由无关列的个数来得到。列空间的实际维度就是秩 r r r。 无关的概念是用于向量空间中的任意向量 v 1 , . . . ,…

匿名函数和箭头函数的使用场景

箭头函数和匿名函数其实是相同的使用场景 匿名函数通常在以下情况下使用: 作为回调函数: 当你需要将函数作为参数传递给另一个函数时,可以使用匿名函数。 array.map(item > item * 2);事件处理程序: 在事件处理程序中&#xf…

如何配置Jupyter Lab以允许远程访问和设置密码保护

如何配置Jupyter Lab以允许远程访问和设置密码保护 当陪你的人要下车时,即使不舍,也该心存感激,然后挥手道别。——宫崎骏《千与千寻》 在数据科学和机器学习工作流中,Jupyter Lab是一个不可或缺的工具,但是默认情况下…

【C++】深入剖析C++11中右值引用和左值引用

目录 一、左值引用 && 右值引用 二、左值引用于右值引用的比较 三、 右值引用使用场景和意义 1、函数返回值 ①移动赋值 ②移动构造 2、STL容器插入接口 ​3、完美转发 一、左值引用 && 右值引用 传统的C语法中就有引用的语法,而C11中新增了…

[基础] Unity Shader:顶点着色器(vert)函数

顶点着色器(Vertex Shader)是图形渲染的第一个阶段,它的输入来自于CPU。顶点着色器的处理单位是顶点,CPU输入进来的每个顶点都会调用一次顶点着色器函数,也就是我们在Shader代码里所定义的vert函数。本篇我们将会通过顶…

全球知名哲学家思想家颜廷利:唯物须防危屋,唯心不及为醒…

‘唯物’须防‘危屋’ ‘唯心’不及‘为醒’…(升命学说) 21世纪东方哲学家思想家、科学家、当代中国教育界知名教授、专业周易起名改名字、易经姓名学专家、目前比较有影响力的人物、现代国学大师泰斗杰出代表颜廷利教授在《升命学说》‘净化论’里面如…

Python中如何调用其他文件的类或函数

Python中如何调用其他文件的类或函数 在Python编程中,随着项目的扩大,代码通常会被分解为多个模块,以提高可读性和可维护性。模块通常是包含Python定义和声明的文件。了解如何从一个文件调用另一个文件中的类或函数是非常重要的,…

Linux学习之路 -- 文件 -- 文件操作

在学习C语言时&#xff0c;我们就学习过文件相关的内容&#xff0c;但是由于知识储备尚且不足&#xff0c;无法深入的了解文件&#xff0c;下面我们就要重新认识一下文件。 <1> 简单介绍(铺垫) 1.前面我们说过&#xff0c;文件 内容 属性&#xff0c;所以我们对文件的…

Spring Boot中使用Redis和Lua脚本实现延时队列

码到三十五 &#xff1a; 个人主页 延时队列是一种常见的需求。延时队列允许我们延迟处理某些任务&#xff0c;这在处理需要等待一段时间后才能执行的操作时特别有用&#xff0c;如发送提醒、定时任务等。文中&#xff0c;将介绍如何在Spring Boot环境下使用Redis和Lua脚本来实…