408考研-数据结构算法-单链表

单链表

线性表的顺序存储结构。它是有缺点的,最大的缺点就是插入和删除时需要移动大量元素,这显然就需要耗费时间。能不能想办法解决呢?

要解决这个问题,我们就得考虑一下导致这个问题的原因。

为什么当插入和删除时,就要移动大量元素,仔细分析后,发现原因就在于相邻两元素的存储位置也具有邻居关系。它们编号是1,2,3,…,n,它们在内存中的位置也是挨着的,中间没有空隙,当然就无法快速介入,而删除后,当中就会留出空隙,自然需要弥补。问题就出在这里。

我们反正也是要让相邻元素间留有足够余地,那干脆所有的元素都不要考虑相邻位置了,哪有空位就到哪里,而只是让每个元素知道它下一个元素的位置在哪里,这样,我们可以在第一个元素时,就知道第二个元素的位置(内存地址),而找到它;在第二个元素时,再找到第三个元素的位置(内存地址)。这样所有的元素我们就都可以通过遍历而找到。

以前在顺序结构中,每个数据元素只需要存数据元素信息就可以了。现在链式结构中,除了要存数据元素信息外,还要存储它的后继元素的存储地址。

为了表示每个数据元素ai与其直接后继数据元素ai+1之间的逻辑关系,对数据元素ai来说,除了存储其本身的信息之外,还需存储一个指示其直接后继的信息(即直接后继的存储位置)。

我们把存储数据元素信息的域称为数据域,把存储直接后继位置的域称为指针域。指针域中存储的信息称做指针或链。这两部分信息,称为结点(Node)。

n个结点链结成一个链表,即为线性表(a1,a2,…,an)的链式存储结构,因为此链表的每个结点中只包含一个指针域,所以叫做单链表。

单链表正是通过每个结点的指针域将线性表的数据元素按其逻辑次序链接在一起

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
我们的链表包含4项数据:“a”、“b”、“c"和"d”。因为每个结点都需要2个格子,头一格用作数据存储,后一格用作指向下一结点的链(最后一个结点的链是null,因为它是终点),所以整体占用了8个格子。

若想使用链表,你只需知道第一个结点在内存的什么位置。因为每个结点都有指向下一结点的链,所以只要有给定的第一个结点,就可以用结点1的链找到结点2,再用结点2的链找到结点3……如此遍历链表的剩余部分。

链表相对于数组的一个好处就是,它可以将数据分散到内存各处,无须事先寻找连续的空格子。

对于线性表来说,总得有个头有个尾,链表也不例外。我们把链表中第一个结点的存储位置叫做头指针,那么整个链表的存取就必须是从头指针开始进行了。之后的每一个结点,其实就是上一个的后继指针指向的位置。

类似像一个铁链,一环扣一环地连在一起
在这里插入图片描述

想象一下,最后一个结点,它的指针指向哪里?最后一个,当然就意味着直接后继不存在了,所以我们规定,线性链表的最后一个结点指针为“空”(通常用NULL或“^”符号表示)。

在这里插入图片描述
有时,我们为了更加方便地对链表进行操作,会在单链表的第一个结点前附设一个结点,称为头结点。头结点的数据域可以不存储任何信息。也可以存储如线性表的长度等附加信息, 头结点的指针域存储指向第一个结点的指针

在这里插入图片描述
带有头节点的链表就像是给铁链加了钥匙扣
在这里插入图片描述

Node{
 int data;
 Node next;
}

单链表的基本操作

下面讲解单链表的初始化、创建、取值、查找、插入、删除等基本操作。

初始化

单链表的初始化是指构建一个空表。先创建一个头节点,不存储数据,然后令其指针域为空

在这里插入图片描述
创建

创建单链表分为头插法和尾插法两种,头插法是指每次把新节点插到头节点之后,其创建的单链表和数据输入顺序正好相反,因此也称为逆序建表。尾插法是指每次把新节点链接到链表的尾部,其创建的单链表和数据输入顺序一致,因此也称为正序建表。

头插

我们先讲头插法建表。头插法每次把新节点插入到头节点之后,创建的单链表和数据输入顺序相反。

1)初始状态。初始状态是指初始化后的空表,只有一个头节点

在这里插入图片描述
2)输入数据元素1,创建新节点,把元素1放入新节点数据域

在这里插入图片描述
3)头插操作,插入头节点的后面

在这里插入图片描述
4)输入数据元素2,创建新节点,把元素2放入新节点数据域

在这里插入图片描述
5)头插操作,插入头节点的后面

在这里插入图片描述
赋值解释

假设赋值之前节点的地址及指针

在这里插入图片描述
赋值语句两端,等号的右侧是节点的地址,等号的左侧是节点的指针域。

① s->next=L->next : L->next存储的是下一个节点地址“9630”,将该地址赋值给s->next指针域,即s节点的next指针指向1节点

在这里插入图片描述

② L->next=s:将s节点的地址“2046”赋值给L->next指针域,即L节点的next指针指向s节点

在这里插入图片描述

注意修改指针顺序

要先修改后面那个指针

因为一旦修改了L节点的指针域指向s,那么原来L节点后面的节点就找不到了,因此修改指针是有顺序的。修改指针的顺序原则:先修改没有指针标记的那一端

在这里插入图片描述
如果要插入节点的两端都有标记,例如,再定义一个指针q指向L节点后面的节点,那么先修改哪个指针都无所谓了

在这里插入图片描述
6)拉直链表之后,如图

在这里插入图片描述
7)继续依次输入数据元素3、4、5、6、7、8、9、10,头插法创建的单链表如图所示。可以看出,头插法创建的单链表与数据输入顺序正好相反。

在这里插入图片描述

尾插法

尾插法每次把新节点链接到链表的尾部,其创建的单链表和数据输入顺序一致。

尾插法每次把新节点链接到链表的尾部,因此需要一个尾指针永远指向链表的尾节点。

1)初始状态。初始状态是指初始化后的空表,只有一个头节点,设置一个尾指针r指向该节点,如图

在这里插入图片描述
2)输入数据元素1,创建新节点,把元素1放入新节点数据域,如图
在这里插入图片描述
3)完成尾插操作,插入尾节点的后面,如图
在这里插入图片描述
赋值解释

① s->next=NULL:s节点的指针域置空。

② r->next=s:将s节点的地址赋值给r节点的指针域,即将新节点s插入尾节点r之后。

③ r=s:将s节点的地址赋值给r,即r指向新的尾节点s。

4)输入数据元素2,创建新节点,把元素2放入新节点数据域,如图

在这里插入图片描述
5)完成尾插操作,插入尾节点的后面,如图
在这里插入图片描述
6)继续依次输入数据元素3、4、5、6、7、8、9、10,尾插法创建的单链表如图所示。可以看出,尾插法创建的单链表与数据输入顺序一致。

在这里插入图片描述

创建遍历节点

package com.maweiqi.linked;

/**

 * 链表节点
 */
public class Node {
    //数据域
    public Integer data;

    //指针域,指向下一个节点
    public Node next;

    public Node() {
    }

    public Node(Integer data, Node next) {
        this.data = data;
        this.next = next;
    }

    public Node(Integer data) {
        this.data = data;
    }
}

/**

 *
 * SingleLinkedList 定义的链表
 */
public class SingleLinkedList {
    //定义一个head头结点
    private static Node head = new Node();

    //向链表添加数据
    public static void addData(int value){
        //初始化要添加的节点
        Node newNode = new Node(value);

        //设置临时节点
        Node temp = head;

        //找到尾节点
        while (temp.next != null){
            temp = temp.next;
        }

        //已经包括了头结点.next为null的情况了
        temp.next = newNode;

    }
 //遍历链表
    public static void traverse(Node head){

        //定义临时节点,从首节点开始
        Node temp = head.next;

        while (temp != null){
            if(temp.data != null){
                System.out.println("当前节点的data为: "+temp.data);
            }
            //继续找下一个节点
            temp = temp.next;
        }
    }

    public static void main(String[] args) {
        addData(11);
        addData(22);
        addData(33);
        addData(44);

        traverse(head);

    }
}

输出

当前节点的data为: 11
当前节点的data为: 22
当前节点的data为: 33
当前节点的data为: 44

指定任意位置插入

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

在这里插入图片描述
插入操作,首先检查参数pos的合法性,插入操作的第二步是s.next=q,对应要算法中的语句是Node s= new Node;第三步是p所引用结点的指针域指向s所引用结点,语句是p.next=s;插入操作完成后链表长度加1

package com.maweiqi.linked;

/**

 *
 * SingleLinkedList 定义的链表
 */
public class SingleLinkedList {
    //定义一个head头结点
    private static Node head = new Node();

    //向链表添加数据
    public static void addData(int value){
        //初始化要添加的节点
        Node newNode = new Node(value);

        //设置临时节点
        Node temp = head;

        //找到尾节点
        while (temp.next != null){
            temp = temp.next;
        }

        //已经包括了头结点.next为null的情况了
        temp.next = newNode;

    }

    //遍历链表
    public static void traverse(Node head){

        //定义临时节点,从首节点开始
        Node temp = head.next;

        while (temp != null){
            if(temp.data != null){
                System.out.println("当前节点的data为: "+temp.data);
            }
            //继续找下一个节点
            temp = temp.next;
        }
    }


    /**
     * 插入节点
     * @param head 头指针
     * @param index 要插入元素的位置
     * @param value 要插入元素的值
     */
    public static void insertNode(Node head, int index, int value){
        //首先判断指定的位置是否合法
        if(index < 1 || index > linkListLength(head) + 1){
            System.out.println("插入位置不合法");
            return;
        }

        //设置临时节点,从头结点开始
        Node temp = head;

        //记录遍历当前的位置
        int currentPos = 0;

        //初始化插入的节点
        Node insertNode = new Node(value);

        //循环进行操作
        while(temp.next != null){
            //找到上一个节点的位置
            if((index -1) == currentPos){
                //temp 就是代表上一个元素,将原本上一个节点的指向交给由插入元素节点来指向
                insertNode.next = temp.next;
                //将上一个节点的指针指向要插入的节点
                temp.next = insertNode;
                return;
            }
            currentPos++;
            temp = temp.next;
        }
    }

    /**
     * 获取链表长度的方法
     * @param head
     * @return
     */
    private static int linkListLength(Node head) {
        int length = 0;

        //设置临时节点,从首节点开始
        Node temp = head.next;

        //找尾节点,累加length
        while (temp != null){
            length++;
            temp = temp.next;
        }
        return length;
    }

   


    public static void main(String[] args) {
        addData(11);
        addData(22);
        addData(33);
        addData(44);

        insertNode(head,3,88);
        traverse(head);
    }
}

输出

当前节点的data为: 11
当前节点的data为: 22
当前节点的data为: 88
当前节点的data为: 33
当前节点的data为: 44

删除

删除一个节点,实际上是把这个节点跳过去。根据单向链表向后操作的特性,要想跳过第i个节点,就必须先找到第i-1个节点,否则是无法跳过去的。删除操作如图

在这里插入图片描述
赋值解释

p->next=q->next的含义是将q节点的下一个节点地址赋值给p节点的指针域。

对这些有关指针的赋值语句,很多读者不理解,容易混淆。在此说明一下,等号的右侧是节点的地址,等号的左侧是节点的指针域,如图

在这里插入图片描述
假设q节点的下一个节点地址为1013,该地址存储在q->next里面,因此等号右侧的q->next的值为1013。把该地址赋值给p节点的next指针域,把原来的值2046覆盖掉,这样p->next也为1013,相当于把q节点跳过去了。赋值之后,如图2-58所示,然后用delete q释放被删除节点的空间。
在这里插入图片描述
在单链表中,每个节点除存储自身数据之外,还存储了下一个节点的地址,因此可以轻松访问下一个节点,以及后面的所有后继节点。但是,如果想访问前面的节点就不行了,再也回不去了。例如,删除节点q时,要先找到它的前一个节点p,然后才能删掉q节点,单向链表只能向后操作,不可以向前操作。如果需要向前操作,该怎么办呢?

还有另外一种链表——双向链表。

package com.maweiqi.linked;

/**

 *
 * SingleLinkedList 定义的链表
 */
public class SingleLinkedList {
    //定义一个head头结点
    private static Node head = new Node();

    //向链表添加数据
    public static void addData(int value){
        //初始化要添加的节点
        Node newNode = new Node(value);

        //设置临时节点
        Node temp = head;

        //找到尾节点
        while (temp.next != null){
            temp = temp.next;
        }

        //已经包括了头结点.next为null的情况了
        temp.next = newNode;

    }

    //遍历链表
    public static void traverse(Node head){

        //定义临时节点,从首节点开始
        Node temp = head.next;

        while (temp != null){
            if(temp.data != null){
                System.out.println("当前节点的data为: "+temp.data);
            }
            //继续找下一个节点
            temp = temp.next;
        }
    }


    /**
     * 插入节点
     * @param head 头指针
     * @param index 要插入元素的位置
     * @param value 要插入元素的值
     */
    public static void insertNode(Node head, int index, int value){
        //首先判断指定的位置是否合法
        if(index < 1 || index > linkListLength(head) + 1){
            System.out.println("插入位置不合法");
            return;
        }

        //设置临时节点,从头结点开始
        Node temp = head;

        //记录遍历当前的位置
        int currentPos = 0;

        //初始化插入的节点
        Node insertNode = new Node(value);

        //循环进行操作
        while(temp.next != null){
            //找到上一个节点的位置
            if((index -1) == currentPos){
                //temp 就是代表上一个元素,将原本上一个节点的指向交给由插入元素节点来指向
                insertNode.next = temp.next;
                //将上一个节点的指针指向要插入的节点
                temp.next = insertNode;
                return;
            }
            currentPos++;
            temp = temp.next;
        }
    }

    /**
     * 获取链表长度的方法
     * @param head
     * @return
     */
    private static int linkListLength(Node head) {
        int length = 0;

        //设置临时节点,从首节点开始
        Node temp = head.next;

        //找尾节点,累加length
        while (temp != null){
            length++;
            temp = temp.next;
        }
        return length;
    }

    /**
     * 删除节点
     */
    public static void deleteNode(Node head ,int index){
        //首先判断指定的位置是否合法
        if(index < 1 || index > linkListLength(head) + 1){
            System.out.println("插入位置不合法");
            return;
        }

        //设置临时节点
        Node temp = head;

        //记录遍历当前的位置
        int currentPos = 0 ;

        while (temp.next != null){
            //先找到上一个节点的位置
            if((index - 1) == currentPos){
                //temp表示上一个节点,我们想删除的是temp.next节点
                //将想要删除的节点进行存储
                Node deleteNode = temp.next;
                //想要删除: 节点的下一个节点交友上一个节点来指向
                temp.next = deleteNode.next;

                deleteNode = null;
                return;
            }

            currentPos++;
            temp = temp.next;
        }
    }


    public static void main(String[] args) {
        addData(11);
        addData(22);
        addData(33);
        addData(44);

        deleteNode(head,2);
        traverse(head);
    }
}

输出

当前节点的data为: 11
当前节点的data为: 33
当前节点的data为: 44

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

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

相关文章

探讨uniapp的页面问题

1 新建页面 uni-app中的页面&#xff0c;默认保存在工程根目录下的pages目录下。 每次新建页面&#xff0c;均需在pages.json中配置pages列表&#xff1b; 未在pages.json -> pages 中注册的页面&#xff0c;uni-app会在编译阶段进行忽略。pages.json的完整配置参考&am…

微信小程序校园生活小助手+后台管理系统|前后分离VUE

《微信小程序校园生活小助手后台管理系统|前后分离VUE》该项目含有源码、文档等资料、配套开发软件、软件安装教程、项目发布教程等 本系统包含微信小程序前台和Java做的后台管理系统&#xff0c;该后台采用前后台前后分离的形式使用JavaVUE 微信小程序——前台涉及技术&#…

使用Fiddler模拟网络

Fiddler已经预置提供了模拟Modem速度的选项&#xff0c;其位置位于&#xff1a; Rules->Performances->Simulate Modem Speeds 勾选该选项后&#xff0c;所有通过Fiddler代理的流量都会变得用56k modem上网一般。 要直观观察限速后的效果&#xff0c;最好使用运行在浏览…

微服务之架构演变

随着互联网的发展&#xff0c;网站应用规模不断扩大&#xff0c;网站架构随之不断演变&#xff0c;演变历史大致分为单体应用架构-垂直应用架构-分布式架构-SOA架构-微服务架构-云原生架构 架构演变 单体应用架构 以前网站流量小&#xff0c;只需要一个应用就可以把所有功能…

高等动物的多倍体细胞原来这么有用!从伤口愈合到物种生存,多面手的细胞机制~~~...

2008年&#xff0c;当Vicki Losick获得博士学位并加入卡内基科学研究所&#xff08;Carnegie Institute for Science&#xff09;的果蝇实验室时&#xff0c;该实验室的负责人宣布&#xff0c;他希望他的博士后能够推出新的研究领域。她选择了一个当时流行的焦点&#xff1a;干…

SAP MM学习笔记26- SAP中 振替转记(转移过账)和 在库转送(库存转储)5 - 总结

SAP 中在库移动 不仅有入库&#xff08;GR&#xff09;&#xff0c;出库&#xff08;GI&#xff09;&#xff0c;也可以是单纯内部的转记或转送。 1&#xff0c;振替转记&#xff08;转移过账&#xff09; 具体查看我之前的文章。 SAP MM学习笔记26- SAP中 振替转记&#xff…

HBase集群环境搭建与测试

&#x1f947;&#x1f947;【大数据学习记录篇】-持续更新中~&#x1f947;&#x1f947; 个人主页&#xff1a;beixi 本文章收录于专栏&#xff08;点击传送&#xff09;&#xff1a;【大数据学习】 &#x1f493;&#x1f493;持续更新中&#xff0c;感谢各位前辈朋友们支持…

为何反射探针关闭Mipmap后变成了白图

1&#xff09;为何反射探针关闭Mipmap后变成了白图 2&#xff09;2021.3 Android从AssetBundle中加载视频播放失败问题 3&#xff09;SBP是否可以解决打包时FBX等模型文件中额外的GameObject 4&#xff09;Addressables加载已打包过的Prefab后Mono脚本丢失 这是第349篇UWA技术知…

深入浅出:手把手教你实现单链表

一、什么是链表 链表是一种链状数据结构。简单来说&#xff0c;要存储的数据在内存中分别独立存放&#xff0c;它们之间通过某种方式相互关联。 如果我们使用C语言来实现链表&#xff0c;需要声明一个结构体作为链表的结点&#xff0c;结点之间使用指针关联。 二、单向链表的结…

打击儿童性虐待,遭多家机构反对,苹果宣布停止开发CSAM检测计划

据报道&#xff0c;苹果公司曾计划在其iCloud云服务中引入一项儿童性虐待资料&#xff08;CSAM&#xff09;检测计划&#xff0c;但由于反对声浪日益高涨&#xff0c;该计划最终宣布停止开发。CSAM检测计划的原本目的是为了帮助阻止儿童性虐待资料的传播&#xff0c;保护儿童的…

C#里Bitmap转Halocn的HObject

一般情况下&#xff0c;图像的width是4的倍数的话&#xff0c;用以下代码便可将彩色bitmap转出halcon里的HObject public void Bitmap2HObject(Bitmap bmp, out HObject image){try{Rectangle rect new Rectangle(0, 0, bmp.Width, bmp.Height);BitmapData srcBmpData bmp.L…

c++11 标准模板(STL)(std::basic_stringstream)(二)

定义于头文件 <sstream> template< class CharT, class Traits std::char_traits<CharT> > class basic_stringstream;(C11 前)template< class CharT, class Traits std::char_traits<CharT>, class Allocator std::alloc…

【JAVA基础——JAVA虚拟机JVM】

JVM 文章目录 JVM一.JVM结构1.1.JVM包含两个子系统和两个组件1.2.运行时数据区1.2.1.简介1.2.2.程序计数器1.2.3.虚拟机栈1.2.4.堆1.2.5.本地方法栈1.2.6.方法区(永久代实现)java8-1.2.7.元空间(Metaspace)1.2.8.JVM字节码执行引擎1.2.9.直接内存(Direct Memory)1.2.10.垃圾收集…

为什么你懂英语但不能说流利 学习

目录 对于提升口语流畅度&#xff1a; 我们应该做到是输入和输出占比为3&#xff1a;7&#xff1b;可实际做到的是7&#xff1a;3 但是这个方法也有一个问题&#xff0c;就是没有错误反馈 最好的就是在一个开始的时候&#xff0c;就学对&#xff0c;第一次的效果很重要 另…

Nuxt3打包部署到Linux(node+pm2安装和运行步骤+nginx代理)

最近&#xff0c;我们项目组的工作接近尾声&#xff0c;需要把项目部署上线。由于前端第一次使用Nuxt3框架&#xff0c;后端也是第一次部署Nuxt3项目&#xff0c;所以刚开始出现了很多问题。在我上网搜索很多教程后&#xff0c;得到了基本的流程。 1.服务器安装node.js环境 N…

小兔鲜商03

进入可视区加载数据&#xff1a; 首页有很多模块&#xff0c;如果一次性加载所有数据&#xff0c;很卡&#xff0c;&#xff0c;当移动到要显示的地方&#xff0c;才加载数据 使用 vueuse 库中 useIntersectionObserver方法&#xff0c;&#xff0c; 传入要监听的元素 target …

RK3588开发板编译环境Ubuntu20.04编译配置增加交换内存

迅为提供的编译环境 Ubuntu20.04 默认配置了交换内存是 9G&#xff0c;如果在编译过程中&#xff0c;因内 存不够而编译报错&#xff0c;可以参考本小节进行设置。 这里举例分配 5G 交换内存。 在开始之前&#xff0c;使用命令检查一下您的 ubuntu 的 swap 分区。 sudo swa…

3.2.0 终极预告!云原生支持新增 Spark on k8S 支持

视频贡献者 | 王维饶 视频制作者 | 聂同学 编辑整理 | Debra Chen Apache DolphinScheduler 3.2.0 版本将发布&#xff0c;为了让大家提前了解到此版本更新的主要内容&#xff0c;我们已经制作了几期视频和内容做了大致介绍&#xff0c;包括《重磅预告&#xff01;Apache Dol…

MongoDb-01——Mac上安装MongoDb以及相关的简单命令

MongoDb-01——Mac上安装MongoDb以及相关的简单命令 1. 下载、安装1.1 官网下载1.2 关于安装MongoDB1.2.1 官方安装文档1.2.2 Mac安装详细步骤&#xff08;使用brew&#xff09; 2. 启动MongoDB2.1 官方说明2.2 作为macOS服务运行的相关命令2.3 访问 3. 链接并使用mongodb3.1 链…

package.json 详解

文章目录 package.json1. name2. version3. description4. homepage5. bugs6. license7. author, contributors8. funding9. files10. main11. module12. browser13. bin14. man15. directories15.1 directories.bin15.2 directories.man 16. repository17. scripts18. config1…