【数据结构】虽然很难很抽象,但是你还是得努力弄懂的数据结构——链表,基本上你每一段代码都可能会用到

news2024/12/23 5:47:33

链表解决了顺序表插入或删除元素麻烦的问题,链表的存储结构是用一组任意的存储单元来存放线性表的数据元素,这组存储单元可以是连续的,也可以是不连续的。

对每个数据元素ai,除了存储其本身的信息之外,还需存储一个指示其直接后继存放位置的指针。这两部分信息组成数据元素ai的存储映像,称为结点(Node)。如下图所示:
在这里插入图片描述

结点类的泛型定义如下,数据域为data,指针域为next。构造器有两个,二者的区别是参数个数不同。有一个参数的构造器,用参数n来初始化next指针域,数据域不存储有效的用户数据。有两个参数的构造器,根据形参obj和n分别初始化数据域data和指针域next。

public class Node<T> {
   //数据
    T data;
    //指针
    Node<T> next;

    public Node(Node<T> next) {
        this.next = next;
    }

    public Node(T data, Node<T> next) {
        this.data = data;
        this.next = next;
    }
}

线性表链式存储结构如下图所示:
在这里插入图片描述
其泛型类的定义如下,

public class SingLinkList<T> {
     // 头指针
    private  Node<T> head;

    //单链表的长度
    private  int length;

    /**
     * 单链表初始化
     */
    public SingLinkList() {
        length=0;
        head = new Node<T>(null);
    }
    ...
}

在SingLinkList类中有两个成员变量。一个是指向头结点的指针head,习惯称head为头指针;另一个是length,用来存放单链表的长度。类中的基本方法和顺序表中的基本方法实现的功能是一样的,但具体实现有所区别。

在无参构造方法中,设置length值为0即初始化线性表为空。通过new创建的结点对象调用了Node(Node<T>n)构造方法,它的作用是将next指针域初始化为null,数据域data未进行初始化,所以head所引用的这个结点称之为头结点。通过头结点的next指针域可以找到链表中的第一个结点。

单链表的插入是指在单链表的第pos-1个结点和第pos个结点之间插入一个新的结点,要实现结点的插入,需修改插入位置之前的结点和当前插入结点的指针指向,过程如下图:
在这里插入图片描述

具体实现代码如下:

/**
 * 单链表的插入,需要把插入位置的上一个节点和下一个节点先找出来,然后插入 时间复杂度为O(n)
 * @param obj 需要插入的元素
 * @param pos 需要插入的位置
 * @return
 */
public  boolean add(T obj,int pos){

    if (pos < 1 || pos > length+1) {
        throw new LinkException("pos值不合法");
    }
    int num =1;
    // 当前节点, 需要从头开始查找
    Node<T> p = head;
    // 下一个结点
    Node<T> q = head.next;
    // 从头结点开始一个个找到需要删除的结点
    while (num<pos){
        // 保留下一个结点
        p =q;
        //获取下一个结点
        q=q.next;
        num++;
    }
    // 插入一个新节点
    p.next=new Node<>(obj,q);
    length ++;
    return true;
}

从代码可知,链表的插入操作首先需要检查参数pos的合法性,当1≤pos≤length时,初始化num为1,变量引用单链表中头结点之后的第一个结点,之后q每后移一个位置q=q.next,num就加1,循环num-1次后,就可以找到第pos个结点。要删除的第pos个结点,通过q引用,被删除结点的前一个结点,通过变量p引用。要把元素x插入到链表中,首先应该构建一个Node对象,数据域data的值为x,定义Node<T>类型变量s用来引用它。插入操作的第一步是s.next=q,对应要算法中的语句是Node<T> s= new Node<T>(obj,q);第二步是p所引用结点的指针域指向s所引用结点,语句是p.next=s;插入操作完成后链表长度加1。

由于链表不具有随机访问的特点,所以插入操作之前,要从单链表的头结点开始,顺序扫描每个结点并计数,从而找到插入位置,即第pos个结点,时间主要用来查找插入位置,该算法的时间复杂度为O(n)。

单链表的删除是指删除单链表的第pos个结点,要实现该结点的删除,可将删除位置之前的结点,即第pos-1个结点的指针域指向第pos+1个结点。删除过程如下;
在这里插入图片描述

单链表进行插入操作的前提是链表不为空,条件满足之后,分别找到要删除的pos个结点,通过q引用,和被删除结点的前一个结点(通过变量p引用),修改p引用结点指针域的值即可完成删除操作,语句为p.next=q.next,删除操作完成后,链表长度减1,并返回被删除结点数据域的值。删除代码如下:

/**
 * 单链表的删除某个结点,需要把插入位置的上一个节点和下一个节点先找出来,然后删除 时间复杂度为O(n)
 * @param pos 需要删除的结点
 * @return
 */
public  T remove(int pos){
    if (isEmpty()){
        throw new LinkException("链表为空");
    }else {
        if (pos < 1 || pos > length) {
            throw new LinkException("pos值不合法");
        }
        int num =1;
        // 当前节点, 需要从头开始查找
        Node<T> p = head;
        // 下一个结点
        Node<T> q = head.next;
       // 从头结点开始一个个找到需要删除的结点
        while (num<pos){
            // 保留下一个结点
            p =q;
            //获取下一个结点
            q=q.next;
            num++;
        }
        // 把q节点删除
        p.next =q.next;
        // 链表长度减少1
        length--;
        return q.data;
    }
}

单链表的查找思路和顺序表类似,值得注意的是:由于构建的是带有头结点的单链表,所以首先变量p引用的是头结点之后的结点,当该结点不存在时链表即为空。通过调用方法equals来判断两个对象的值是否相等,查找成功返回对象obj在单链表中的位序,查找失败返回-1,代码实现如下:

/**
 * 单链表的查找 时间复杂度为O(n)
 * @param obj  需要查找的值
 * @return num 需要查找的值对应的节点位置. -1 表示未找到,
 */
public  int find(T obj){
    if (isEmpty()){
        throw new LinkException("链表为空");
    }
    int num=1;
    // p引用的是头结点之后的节点
    Node<T>  p=head.next;
   //如果单链表不为空
    while (p!=null){
        //如当前节点的data不等需要查找的值,就继续查找下一个节点
        if (!p.data.equals(obj)){
            p = p.next;
            num++;
        }else {
            break;
        }
    }
    if (p == null){
        return -1;
    }
    return  num;
}

单链表的完整实现如下:

public class SingLinkList<T> {
     // 头指针
    private  Node<T> head;

    //单链表的长度
    private  int length;

    /**
     * 单链表初始化
     */
    public SingLinkList() {
        length=0;
        head = new Node<T>(null);
    }

    /**
     * 获取单链表头结点的地址
     * @return
     */
    public  Node<T> getHead(){
        return  head;
    }

    /**
     * 单链表的插入,需要把插入位置的上一个节点和下一个节点先找出来,然后插入 时间复杂度为O(n)
     * @param obj 需要插入的元素
     * @param pos 需要插入的位置
     * @return
     */
    public  boolean add(T obj,int pos){

        if (pos < 1 || pos > length+1) {
            throw new LinkException("pos值不合法");
        }
        int num =1;
        // 当前节点, 需要从头开始查找
        Node<T> p = head;
        // 下一个结点
        Node<T> q = head.next;
        // 从头结点开始一个个找到需要删除的结点
        while (num<pos){
            // 保留下一个结点
            p =q;
            //获取下一个结点
            q=q.next;
            num++;
        }
        // 插入一个新节点
        p.next=new Node<>(obj,q);
        length ++;
        return true;
    }

    /**
     * 单链表的删除某个结点,需要把插入位置的上一个节点和下一个节点先找出来,然后删除 时间复杂度为O(n)
     * @param pos 需要删除的结点
     * @return
     */
    public  T remove(int pos){
        if (isEmpty()){
            throw new LinkException("链表为空");
        }else {
            if (pos < 1 || pos > length) {
                throw new LinkException("pos值不合法");
            }
            int num =1;
            // 当前节点, 需要从头开始查找
            Node<T> p = head;
            // 下一个结点
            Node<T> q = head.next;
           // 从头结点开始一个个找到需要删除的结点
            while (num<pos){
                // 保留下一个结点
                p =q;
                //获取下一个结点
                q=q.next;
                num++;
            }
            // 把q节点删除
            p.next =q.next;
            // 链表长度减少1
            length--;
            return q.data;
        }
    }

    /**
     * 单链表的查找 时间复杂度为O(n)
     * @param obj  需要查找的值
     * @return num 需要查找的值对应的节点位置. -1 表示未找到,
     */
    public  int find(T obj){
        if (isEmpty()){
            throw new LinkException("链表为空");
        }
        int num=1;
        // p引用的是头结点之后的节点
        Node<T>  p=head.next;
       //如果单链表不为空
        while (p!=null){
            //如当前节点的data不等需要查找的值,就继续查找下一个节点
            if (!p.data.equals(obj)){
                p = p.next;
                num++;
            }else {
                break;
            }
        }
        if (p == null){
            return -1;
        }
        return  num;
    }

    /**
     * 获取单链表第pos个结点的值, 从头节点开始往下查找, 时间复杂度为O(n)
     * @param pos  需要查找的结点位置
     * @return 需要查找的结点位置对应的值data
     */
    public  T value(int pos){
        if (isEmpty()){
            throw new LinkException("链表为空");
        }else{
            if (pos<1 || pos>length){
                throw new LinkException("pos值不合法");
            }
            //当前节点的位置
            int num =1;
            Node<T> q =head.next;
            while (num<pos){
                q= q.next;
                num++;
            }
            return  q.data;
        }
    }

    /**
     * 更新单链表第pos个结点的值 ,时间复杂度为O(N) ,需要从头节点开始查找
     * @param obj 需要更新的值
     * @param pos 需要更新的结点的位置
     * @return
     */
    public  boolean modify(T obj,int pos){
        if (isEmpty()){
            throw new LinkException("链表为空");
        }else {
            if (pos < 1 || pos > length) {
                throw new LinkException("pos值不合法");
            }
            //当前节点的位置
            int num =1;
            Node<T> q =head.next;
            while (num<pos){
                q= q.next;
                num++;
            }
            q.data=obj;
            return  true;
        }
    }

    /**
     * 获取单链表的长度
     * @return 单链表的长度
     */
    public  int size(){
        return  length;
    }

    /**
     * 清空单链表
     */
    public  void clear(){
        length=0;
        head.next= null;
    }

    /**
     * 打印节点的元素
     */
    public  void show(){
        System.out.println("");
        System.out.print("打印节点元素:");
        //获取头节点的下一个节点
        Node<T> p= head.next;
        while (p!=null){
            System.out.print(p.data+"-->");
            //获取下一个节点
            p =p.next;
        }
    }
    /**
     * 判断单链表是否为空
     * @return
     */
    public  boolean isEmpty(){
        return  length==0;
    }

    public static void main(String[] args) {
        SingLinkList<Integer>  singLinkList = new SingLinkList<>();
        int l,i;
        int[] a ={10,12,3,8,6,4,9};
        l=a.length;
        for (i=0;i<l;i++){
            singLinkList.add(a[i],i+1);
        }
        singLinkList.size();
        singLinkList.show();

        singLinkList.remove(4);
        singLinkList.size();
        singLinkList.show();
    }
}

执行结果如下:
在这里插入图片描述

循环链表是另一种形式的链表,它的特点是表中最后一个结点的指针域不再为空,而是指向表头结点,整个链表形成一个环。

双向链表也是一种形式的链表,相比单链表多了一个指向前驱的指针,找其前驱节点的时间复杂度从O(n)提升到O(1)。双向链表的结点结构如下图所示:
在这里插入图片描述

结点描述代码如下:

public class Node<T> {
   //数据
    T data;
    //前指针
    Node<T> prior;

    //后指针
    Node<T> next;

    public Node(T data) {
        this.data = data;
        prior=null;
       // next=null;
    }

    public Node(T data, Node<T> prior, Node<T> next) {
        this.data = data;
        this.prior = prior;
        this.next = next;
    }
}

双向链表图示如下:
在这里插入图片描述

和单链表类似,双向链表也可以有循环表,循环双向链表图示如下:
在这里插入图片描述

由于循环单链表、双向链表、循环双向链表不是本文的重点,具体不在赘述。有兴趣的的小伙伴可自行阅读相关数据结构与算法的书籍。

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

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

相关文章

【Vue】详解Vue生命周期

Vue实例的生命周期全过程&#xff08;图&#xff09; &#xff08;这里的红边圆角矩形内的都是对应的Vue实例的钩子函数&#xff09; 在beforeCreate和created钩子函数间的生命周期 在beforeCreate和created之间&#xff0c;进行数据观测(data observer) &#xff0c;也就是在这…

港联证券投资前瞻:新能源汽车再迎助力 科技巨头持续加注机器人领域

上周五&#xff0c;A股主要股指全线走高&#xff0c;沪指涨近1%&#xff0c;上证50指数涨近2%。截至收盘&#xff0c;沪指涨0.79%报3230.07点&#xff0c;深成指涨1.5%报10998.08点&#xff0c;创业板指涨1.22%报2233.27点&#xff0c;上证50指数涨1.73%&#xff1b;两市合计成…

圆梦,终于进阿里了,分享面试题

前面我说过&#xff1a;能去大厂就去大厂&#xff0c;有机会就去争取&#xff0c;年纪轻轻的&#xff0c;多努力就完事了。 总有黑粉怼我&#xff1a;进大厂哪有你说的那么简单&#xff0c;呵呵…… 我笑而不语&#xff0c;你自己都不相信自己&#xff0c;还怎么进&#xff1…

在 Python 中为对象添加属性

我们将介绍如何在 Python 中为对象添加属性。 我们还将通过示例介绍如何在 Python 中更改对象的属性。 在 Python 中为对象添加属性 在 Python 中&#xff0c;我们时常使用对象&#xff0c;因为 Python 是一种面向对象的语言。 对象使我们的代码可重用并易于实现复杂的结构。 …

《MySQL(二):基础篇- SQL》

文章目录 2. SQL2.1 SQL通用语法2.2 SQL分类2.3 DDL2.3.1 数据库操作2.3.2 表操作2.3.2.1 表操作-查询创建2.3.2.2 表操作-数据类型2.3.2.3 表操作-案例2.3.2.4 表操作-修改2.3.2.5 表操作-删除 2.4 图形化界面工具2.4.1 安装2.4.2 使用 2.5 DML2.5.1 添加数据2.5.2 修改数据2.…

MySQL数据同步到ES的4种解决方案

一、背景 大家应该都在各种电商网站检索过商品&#xff0c;检索商品一般都是通过什么实现呢&#xff1f;搜索引擎Elasticsearch。那么问题来了&#xff0c;商品上架&#xff0c;数据一般写入到MySQL的数据库中&#xff0c;那么用于检索的数据又是怎么同步到Elasticsearch的呢&…

[2.0快速体验]Apache Doris 2.0 弹性计算节点快速体验

​1. 概览 我们都知道Doris 目前是一个典型的Share-Nothing的架构&#xff0c;Doris 通过绑定数据和计算资源在同一个节点获得非常好的性能表现. 但随着Doris 计算引擎性能持续提高, 越来越多的用户也开始选择使用Doris直接查询数据湖数据. 这类场景是一种Share-Disk场景, 数据…

Vue.js 中的模板编译原理是什么?

Vue.js 中的模板编译原理是什么&#xff1f; Vue.js是一种流行的前端框架&#xff0c;它使用了一种称为“模板”的技术来实现视图的渲染和更新。在Vue.js中&#xff0c;模板是一种类似HTML的语言&#xff0c;用于描述视图的结构和内容。但是&#xff0c;Vue.js并不直接将模板转…

【web框架】——Django01——如桃花来

目录索引 web框架介绍&#xff1a;常见软件的架构&#xff1a;*CS架构&#xff1a;**BS架构&#xff1a;* 网络通信&#xff1a;socket知识复习&#xff1a;*服务端代码逻辑&#xff1a;**客户端代码逻辑&#xff1a;* socket代码演示&#xff1a;*服务端代码演示&#xff1a;*…

【学习记录】win10 + ubuntu 22.04双系统安装

一、背景 因为家里的台式&#xff08;Windows 10&#xff09;最近一直频繁蓝屏&#xff0c;再加上Win10之前经常性的资源管理器未响应&#xff0c;对Windows系统逐渐失去了信心&#xff0c;于是想着安装稳定性较好的Linux。以前抵触Linux是因为其人机交互界面没Windows那么直观…

今天来当一下数据库,看一下sql到底在里面如何执行的?

今天来当一下数据库&#xff0c;看一下sql到底在里面如何执行的&#xff1f; 一、引子 不管是开发&#xff0c;还是运维&#xff0c;亦或者是产品。 多多少少会写sql&#xff0c;只不过有的人写得多&#xff0c;有的人写得少罢了。 但是你有想过&#xff0c;在数据库中&…

ASCII 码对照表

1. ASCII码表&#xff08;控制字符&#xff09; 2. ASCII码表&#xff08;打印字符&#xff09; 3. ASCII码表&#xff08;扩展字符&#xff09;

不要藏着掖着了,600万用户已经使用“Excel新版本”,统统拿走

600万用户正在切换 已经2023年&#xff0c;就不用再藏着掖着了&#xff0c;目前已经有600万用户开始使用了。 没错&#xff0c;“Excel新版本”已经开始普及了&#xff0c;正在大面积替代切换&#xff0c;不仅Excel用户能用&#xff0c;Access用户照样可以轻松玩转&#xff0…

VBA中如何调用自定义函数

一、问题提出 在VBA中我要把B列中所有的非空单元格的值都判断一遍&#xff0c;如果大于60就在其旁边的单元格写入"及格"&#xff0c;反之就写入不及格。如下图所示&#xff1a; 由于B列的非空单元格数量无法确定&#xff0c;所以我们就要定义一个自定义的函数来获取…

【Qt】delegate的自定义实现函数createEditor进不去【2023.05.07】

摘要 妈卖批&#xff0c;因为这个函数进不去&#xff0c;emo了一下午。实际上就是因为函数声明和定义的地方漏了个const关键字。 1.正确✔&#xff1a; QWidget *createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) cons…

DCASE挑战赛中的声音事件检测与定位(SELD)子任务介绍

DCASE&#xff08;Detection and Classification of Acoustic Scenes and Events&#xff09;系列挑战赛包含多个与声音计算相关的子任务&#xff0c;以DCASE2023为例&#xff0c;其包含如下七个与声音计算相关的任务。 (source&#xff1a;DCASE challenge website) 声音事件…

ChatGPT配合两款神器,1分钟生成流程图

流程图&#xff0c;工作上再正常不过的一种图形&#xff0c;常见制图方法对比&#xff1a; 传统手动制图&#xff1a;耗时耗力&#xff0c;迁移性差AI 辅助制图&#xff1a;使用自然语言提出需求&#xff0c;零基础快速制图 几款常见的我在用的在线绘图工具推荐&#xff1a; Pr…

火山引擎DataLeap的Catalog系统搜索实践(三):Learning to rank与后续工作

Learning to rank Learning to rank主要分为数据收集&#xff0c;离线训练和在线预测三个部分。搜索系统是一个Data-driven system&#xff0c;因此火山引擎DataLeap的Catalog系统设计之初就需要考虑数据收集。收集的数据可以用来评估和提升搜索的效果。数据收集和在线预测前面…

基于国民技术N32G435的FLASH读写测试

一、测试工具&#xff1a; 1.国民技术N32G43XCL-STB开发板----主控为N32G435CB 2.创芯工坊PW200加密离线烧录器 3.PowerWriter上位机&#xff0c;配合PW200查看FLASH数据。 4.keil5 二、测试背景 现在很多的应用中都需要保存离线数据&#xff0c;例如一些传感器的校正数…

基于C#制作一个鼠标连点器

秒杀抢券、压枪换弹都是网上冲浪的基本操作,制作一个鼠标连点器,从此在互联网所向披靡。 一、项目搭建1.1、创建1.2、界面设计 二、功能实现2.1、类型库调用2.2、窗口句柄定义2.3、线程处理2.4、快捷键 一、项目搭建 1.1、创建 打开Visual Studio&#xff0c;右侧选择创建新项…