当前位置: 首页 > news >正文

数据结构与算法-单链表

Java高级系列文章前言

本文章涉及到数据结构与算法的知识,该知识属于Java高级阶段,通常为学习的二阶段,本系列文章涉及到的内容如下(橙色框选内容):
在这里插入图片描述
本文章核心是教学视频,所以属于个人笔记,非商用。

文章结构

标识

本系列文章中记录的数据结构与算法都会多多少少用到一些Java的API,这些API只会一笔带过,主要还是看逻辑思路。

大标题
	小标题
		文本内容和图片内容... 70%/30%
			图片说明(加重)
				逻辑思路和次要说明
			文本说明(普通文本)
				主要说明和补充

内容结构

大标题
	介绍
	英文单词关键字陈列(有时会省略)
		代码块...
	思路引入
		(概率出现小标题)
	思路解析
		(概率出现小标题)
	源码
		代码展示
		图文说明

单链表

介绍

线性表中顺序存储的优缺点非常明显,优点是无序因为表中元素之间的逻辑关系而增加额外的存储空间,可以快速地取表中任一位置的元素。缺点是插入和删除操作需要移动大量元素,当线性表长度变化较大时,难以确定存储空间的容量,造成存储空间的“碎片”。
而链式存储则是让所有元素都不考虑是否相邻,哪里有位置就到哪里,让每个元素知道它下一个元素的位置在哪里即可,这样我们每次通过该元素的下一个元素位置就可以找到第二个元素(内存地址),依此类推…这样所有的元素我们就都可以通过遍历而找到。

英文单词关键字陈列

书写习惯:XxxYyyZzz(Java类驼峰语法)
格式:英文单词 -> 中文翻译 -> 空耳(个人习惯)


思路引入

链表是由一个一个的节点(Node)组成,该节点有data数据域和next指针域,我们把存储数据元素信息的域称为数据域,把存储直接后继位置的域称为指针域。指针域中存储的信息称为指针或链。这两部分信息组成数据元素a…i的存储映像,称为节点(Node)。
对于线性表来说,总得有个头有个尾,链表也不例外。我们把链表中第一个节点的存储位置叫做头指针,那么整个链表的存取就必须是从头指针开始进行了。之后的每一个节点,其实就是商一个的后继指针指向的位置。最后一个指针是没有后继位置的,所以我们通常让最后一个节点的next指针设置为空,通常用NULL或^符号表示。
有时,我们为了更加方便地对链表进行操作,会在单链表的第一个节点前附设一个节点,称为头节点,头结点的数据域可以不存储任何信息,也可以存储如线性表的长度等附加信息,头节点的指针域存储指向第一个节点的指针。也就是说如果有头节点,则头指针就在头结点的指针域里,表示第一个节点的内存地址!
在这里插入图片描述
下面用图片演示无头节点和有头节点和空链表的存储示意图。
无头节点的单链表
在这里插入图片描述
带有头节点的单链表
在这里插入图片描述
空链表
在这里插入图片描述

思路解析

有了上面的思路后,我们来看本文章的实现解析,该单链表我们采用英雄排名的设计思路,比如水浒传,我们此处设计了3个属性:no -> 英雄编号;name -> 英雄名字; nickName -> 外号/昵称。

  • no:该属性是链表中实现顺序存储和修改删除的重要属性。
  • name:属于数据域,没有特殊含义。
  • nickName:属于数据域,没有特殊含义。

在这里插入图片描述
我们在节点类中定义一个本类实例,该实例代表下一个节点,该属性就相当于指针域,通过该属性进行遍历、添加和删除等等…

源码

代码展示

package linkedlist;

public class SingleLinkedListDemo {
    public static void main(String[] args) {
        //进行测试
        //先创建节点
        HeroNode hero1 = new HeroNode(1, "宋江", "及时雨");
        HeroNode hero2 = new HeroNode(2, "卢俊义", "玉麒麟");
        HeroNode hero3 = new HeroNode(3, "吴用", "智多星");
        HeroNode hero4 = new HeroNode(4, "林冲", "豹子头");
        //创建一个链表
        SingleLinkedList singleLinkedList = new SingleLinkedList();
        //加入
//        singleLinkedList.add(hero1);
//        singleLinkedList.add(hero2);
//        singleLinkedList.add(hero3);
//        singleLinkedList.add(hero4);
        //加入按照编号的顺序
        singleLinkedList.addByOrder(hero1);
        singleLinkedList.addByOrder(hero4);
        singleLinkedList.addByOrder(hero2);
        singleLinkedList.addByOrder(hero3);
        //显示一把
        singleLinkedList.list();
        //测试修改节点的代码
        HeroNode newHeroNode = new HeroNode(2,"修改节点","修改");
        singleLinkedList.update(newHeroNode);
        System.out.println("=====链表发生了修改!=====");
        //显示一把
        singleLinkedList.list();
        //删除一个节点
        singleLinkedList.del(1);
        System.out.println("=====链表发生了删除!=====");
        //显示一把
        singleLinkedList.list();
    }
}
//定义一个SingleLinkedList 管理我们的英雄人物
@SuppressWarnings({"all"})
class SingleLinkedList{
    //先初始化一个头节点,头节点的数据域不存放数据,头指针指向头节点
    private HeroNode head = new HeroNode(0,"","");
    //添加节点到单向链表
    //思路:当不考虑编号的顺序时
    //1.找到当前链表的最后节点
    //2.将最后节点的next域指向新的节点即可
    public void add(HeroNode heroNode){
        //因为头节点不能动(头结点是一个不存储数据的标记节点,也可以附加链表信息)
        //因此我们需要一个辅助变量 -> temp
        HeroNode temp = head;//有个临时节点指向了head
        //遍历链表,找到最后
        while(true){
            //找到链表的最后了
            if(temp.next == null){//说明已经找到最后了
                break;
            }
            //如果没有找到最后,将temp后移为它的下一个节点,依次往后走
            temp = temp.next;
        }
        //当退出while循环时,temp就指向了链表的最后
        temp.next = heroNode;
    }
    //第二种方式在添加英雄时,根据排名将英雄插入到指定位置
    //(如果有这个排名,则添加失败,并给出提示)
    public void addByOrder(HeroNode heroNode){
        //因为头节点不能动,因此我们仍然通过一个辅助变量来帮助找到添加的位置
        //因为单链表,因为我们找的temp是位于添加位置的前一个节点,否则加入不进去
        HeroNode temp = head;
        boolean flag = false;//flag标识添加的编号是否存在,默认为false
        while(true){
            if(temp.next == null){//说明temp已经在链表的最后了
                break;
            }
            if(temp.next.no > heroNode.no){//位置找到,就在temp的后面插入
                break;
            }else if(temp.next.no==heroNode.no){//说明希望添加的heroNode的编号依然存在
                flag = true;//说明编号存在!
                break;
            }
            temp = temp.next;//后移,遍历当前链表
        }
        //判断flag的值
        if(flag){//不能添加,说明编号存在
            System.out.printf("准备插入的英雄的编程%d已经存在了,不能加入\n",heroNode.no);
        }else{
            //插入到链表中,temp的后面
            heroNode.next = temp.next;
            temp.next = heroNode;
        }
    }
    //修改节点的信息,根据no编号来修改,即no编号不能改
    //说明
    //1.根据newHeroNode的no来修改即可
    public void update(HeroNode newHeroNode){
        //判断是否为空
        if(head.next==null){
            System.out.println("链表为空!");
            return;
        }
        //找到需要修改的节点,根据no编号
        //先定义一个辅助变量
        HeroNode temp = head.next;
        boolean flag = false;//表示是否找到该节点
        while(true){
            if(temp == null){
                break;//到链表的最后
            }
            if(temp.no == newHeroNode.no){
                //找到
                flag = true;
                break;
            }
            temp = temp.next;
        }
        //根据flag判断是否找到要修改的节点
        if(flag){
            temp.name = newHeroNode.name;
            temp.nickname = newHeroNode.nickname;
        }else{
            System.out.printf("没有找到编号%d的节点,不能修改\n",newHeroNode.no);
        }
    }
    //删除节点
    //思路
    //1.head不能动,因此我们需要一个temp辅助节点找到待删除节点的前一个节点
    //2.说明:我们在比较时,是temp.next.no和需要删除的节点的no进行比较
    public void del(int delNo){
        HeroNode temp = head;
        boolean flag = false;//标志是否找到待删除节点的前一个节点
        while(true){
            if(temp.next == null){//已到链表最后
                break;
            }
            if(temp.next.no == delNo){
                //找到了待删除节点的前一个节点 temp ,此时temp是要删除节点的前一个节点!
                flag = true;
                break;
            }
            temp = temp.next;//temp后移才能实现遍历
        }
        //判断flag
        if(flag){//说明找到
            //可以删除
            temp.next = temp.next.next;//没有引用的temp.next会被垃圾回收机制回收
        }else{
            System.out.printf("要删除的 %d 节点不存在!\n",delNo);
        }
    }
    //显示链表[遍历]
    public void list(){
        //先判断链表是否为空
        if(head.next==null){
            System.out.println("链表为空!");
            return;
        }
        //因为头节点不能动,因此我们需要一个辅助变量来遍历
        HeroNode temp = head.next;
        while(true){
            //判断是否到链表最后
            if(temp == null){
                break;
            }
            //输出节点的信息
            System.out.println(temp);
            //将next后移
            temp = temp.next;
        }
    }
}
//定义HeroNode,每个HeroNode对象就是一个节点
class HeroNode{
    public int no;//编号,No
    public String name;//名字
    public String nickname;//昵称
    public HeroNode next;//指向下一个节点
    //构造器
    public HeroNode(int heroNo,String heroName,String heroNickName){
        this.no = heroNo;
        this.name = heroName;
        this.nickname = heroNickName;
    }
    //为了显示方便,我们重写toString()方法
    @Override
    public String toString() {
        return "HeroNode{" +
                "no=" + no +
                ", name='" + name + '\'' +
                ", nickname='" + nickname + '\'' +
                '}';
    }
}

图文说明

本程序分三块:节点类、链表类、main方法测试。
我们主要介绍链表类中的实现方法,节点类和main方法并不重要。
节点类
在这里插入图片描述
链表类
1.链表类的逻辑
在这里插入图片描述
2.无排序添加节点方法
在这里插入图片描述
3.根据no属性排序的添加节点方法
方法的逻辑是这样的,如果新插入的节点它的no在链表中遍历后与某一个节点的no一致,则判断为该链表中已存在该节点,就不添加新节点。否则根据no大小的排序进行添加,下面示例图演示了如果中间插入节点的流程图。
3.1节点指针域的逻辑
在这里插入图片描述
在这里插入图片描述
3.2方法实现
其实很简单,假设temp是自己不认识的小朋友,比自己no大的小朋友是自己的朋友,这个朋友叫宝马,自己就是新节点。
假设自己看到不认识的小朋友和朋友宝马拉着手,那么自己肯定要插入到中间来前者陌生人和朋友的手,那么你看到了朋友宝马,那么就要知道拉着宝马手的人是谁(temp.next.no>newNode.no){ break}。这样你直接拉着陌生人拉着的手(你朋友宝马的手,让陌生人往左边移动先),然后再让陌生人拉着你的手(陌生人再拉住你的手)。这样子你就插入到中间了。
在这里插入图片描述
补充,如果while循环没有符合三个if就会进行temp = temp.next;这样的话就完成了一次节点后移,这样就可以遍历所有的节点,只有遍历完所有的节点才会触发temp.next==null的判断,所以循环一定不会死循环!
4.修改节点数据域的数据
该案例我们通过节点的no属性来定位节点,然后进行修改,我们仍然会定义一个boolean类型的flag,如果该属性是false则表明没有找到该no属性相同的节点,则直接提示没找到。
在这里插入图片描述
5.删除节点方法
在这里插入图片描述
6.显示单链表[遍历]
在这里插入图片描述

相关文章:

  • 记一次git误操作, 合并冲突别人新增文件显示成“自己新增“绿色文件
  • Dubbo----------------------------配置信息整合SpringBoot的三种方式
  • 基于视觉的车道线识别技术在智能车导航中的应用研究
  • bleu-mp 多进程bleu评估工具
  • webpack多进程打包
  • 索尼IMX316 标定_ToF模块相机校准
  • 【Proteus仿真】【51单片机】智能鱼缸系统设计
  • 瑞吉外卖2.0 Redis 项目优化 Spring Cache MySQL主从复制 sharding-JDBC Nginx
  • 2023-02-04 Elasticsearch 倒排索引的理解 Trie前缀树原理
  • 【DIY小记】VMWare设置主机连接到的Ubuntu虚拟机的网络端口
  • Spring Boot 集成Quartz
  • 【Java学习】JUC并发编程
  • 【入门AUTOSAR网络管理测试】CANoe测试T_STARTx_AppFrame时间
  • Apache Shiro身份验证绕过(CVE-2023-22602)
  • Cadence PCB仿真 使用 Allegro PCB SI 为电源网络分配电压并选择仿真的电源网络的方法图文教程
  • (考研湖科大教书匠计算机网络)第三章数据链路层-第六节媒体接入控制3:载波监听多址接入-碰撞避免(CSMA-CA)协议
  • ocs系统介绍
  • JVM运行时数据区
  • PMP考试答题技巧及注意事项
  • SSRF盲打 Collaborator everywhere
  • 电加热油锅炉工作原理_电加热导油
  • 大型电蒸汽锅炉_工业电阻炉
  • 燃气蒸汽锅炉的分类_大连生物质蒸汽锅炉
  • 天津市维修锅炉_锅炉汽化处理方法
  • 蒸汽汽锅炉厂家_延安锅炉厂家
  • 山西热水锅炉厂家_酒店热水 锅炉
  • 蒸汽锅炉生产厂家_燃油蒸汽发生器
  • 燃煤锅炉烧热水_张家口 淘汰取缔燃煤锅炉
  • 生物质锅炉_炉
  • 锅炉天然气_天燃气热风炉