单链表的头插,尾插,头删,尾删等操作

news2024/11/29 12:35:50

前言

  1. 顺序表要求是具有连续的物理空间,并且数据的话是在这些空间当中是连续的存储。但这样会带来很多问题,比如说在头部或者说中间插入的话,效率不是很高;并且申请空间可能需要扩容,并且越往后一般来说都是异地扩容,虽然看起来的话是简单的调用了一下realloc,但是从底层来看的话,这个代价很大。而且扩容的话也会存在一定程度的浪费,因此就需要链表的存在。

  1. 链表的话是一个数据存一个内存块。这些内存块之间并不一定要求是物理上连续的,这些内存块之间用指针进行相关的链接,链表实际上具有八种结构。

  1. 链表其实就是用指针这么链接起来的一些个内存块。

链表当中各个节点在物理上不一定是连续的,这个节点之间也并没有任何的顺序关系(都是各自随意malloc出来的),根本就不需要去考虑顺序,孤岛之间只需要有一根桥梁即可

单链表就关注这三个有机组成部分:

  1. 节点 SLTNode

  1. 节点地址(指向节点的指针) SLTNode*

  1. 一级指针(指向链表第一个节点) phead

  1. 二级指针(指向上面的一级指针) pphead


逻辑结构与物理结构

  1. 虽然有时候平时画图的时候,画链表的时候可能会带一些箭头之类的,但是真正在内存里面是不可能有箭头这些东西的。内存里面是不可能存着箭头→这些东西的。我们把这些箭头叫做逻辑结构,是我们想象出来的,因为这些箭头比指针更为直观。

  1. 在内存里面实实在在怎么存的被称为物理结构。

  1. 我们画图的时候看起来好像指针啊什么呀都是在动的,但实际上在内存里面的话,一切都是死的,没有动,无非就是不断的给指针变量进行赋值。然后在我们脑海中直观的形象表现就是:该指针变量不断的指来指去。

  1. 逻辑结构就是我们为了方便理解,形象化画出来的;但是在计算机内存里面是十分枯燥的,实实在在数据在内存中的变化是物理结构。


单链表节点(结构体类型)的创建

  1. 从语言的角度来讲,凡是有多个数据,都需要存到结构体里面去。链表的每一个节点就是通过结构体来表示。结构体里面有当前的数值data,并且还需要一个指针。因为上一个节点需要存下一个节点的地址,这样我才有寻找的依据,这样的话,各个内存块之间才可以像链条一样这么串起来。

  1. 并且我们已经知道每一个节点实际上就是一个结构体。既然上一个节点需要存下一个节点的地址。那么因此显而易见,无非就是一个结构体指针罢了,我们叫做next。

  1. 之前在c语言当中我们也讲过,结构体里面是不能够嵌套结构体的,就是无穷套娃了。但我们刚才结构体里面并没有结构体,是一个结构体指针,大小是确定的,就是四个字节。

//节点结构体的创建
typedef int SLTDataType;
typedef struct SLTNode
{
    SLTDataType data;
    struct SLTNode* next;
}SLTNode;

单链表的打印

1. 空的顺序表可以打印,当然一样道理,空的链表也可以打印,给我显示为空不就可以了

2. phead就是链表第一个节点的地址,如果这个链表是空链表,那么phead就是NULL

3. 当我为空链表的时候,phead就为NULL,在打印链表的时候不能够进行断言,不然的话空链表我就打印不出来了,直接把我程序终止了。

4. 链表的话在物理上并不是连续的,它的每一个节点都是malloc出来的

5. 每一个节点(说白了就是结构体)都会存放下一个节点的地址,因此就需要这一很关键的一步:cur = cur -> next

6. 0就是假,非0就是真,NULL本质就是0。

//单链表的打印
void SLTPrint(SLTNode* phead)
{
    SLTNode* cur = phead;
    while (cur != NULL)
    {
        printf("%d->",cur->data);
        cur = cur->next;
    }
    printf("NULL\n");
}

单链表malloc一个新节点并赋值

//单链表malloc一个新节点并赋值
SLTNode* BuySLTNode(SLTDataType x)
{
    SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));
    if (newnode == NULL)
    {
        perror("BuySLTNode::Malloc");
        return NULL;
    }
    newnode->data = x;;
    newnode->next = NULL;
    return newnode;
}

单链表的尾插

1. 由于之前一样的,在这个地方是不能够进行空指针断言的,因为如果是断言的话,我在空链表的情形下想要去尾插,但是断言了的话,给我程序提前终止了,这还玩个屁啊。

2. 尾插的第一步是先得搞一个节点出来,这个地方就不像顺序表一样了,要考虑什么空间够不够啊,要不要扩容啊之类的就不需要考虑了。在这边的话就自己去malloc新创建一个节点。

3. 然后对于这个新节点newnode的值给进去,next指针为NULL。

4. 然后就是找到原先的尾巴,原尾节点的next成员要存储新尾节点的地址。

5. 刚才讲的情况都是链表不为空的情况,如果链表是空的话,这种情况需要额外处理。这时候只需要把phead指向新节点newnode就可以了。

//单链表的尾插
void SLTPushBack(SLTNode** pphead, SLTDataType x)
{
    SLTNode* newnode = BuySLTNode(x);
    if (*pphead == NULL)
    {
        *pphead = newnode;
    }
    else
    {
        SLTNode* tail = *pphead;
        while (tail->next != NULL)
        {
            tail = tail->next;
        }
        tail->next = newnode;
    }
}

单链表的头插

1. 这个也需要分两种情况,以总是链表为空的情况,第二种就是链表非空。但是发现仅有的三行代码可以完全解决空的情况。

2. 然后发现在这个函数内部也需要创建一个新的节点。那么就是说可以把创建新的节点这个过程给他单独的提取出来。

//单链表的头插
void SLTPushFront(SLTNode** pphead, SLTDataType x)
{
    SLTNode* newnode = BuySLTNode(x);
    newnode->next = *pphead;
    *pphead = newnode;
}

单链表的尾删

1. 删除的时候也是需要用二级指针。因为万一你要把指针置空的时候,如果你传一级指针的话,就完蛋了。那么原先那个指针就变成野指针了。

2. 链表在尾删的时候要注意分几种特殊情况,第一种情况是当前只有一个节点,第二种特殊情况就是整个链表都是空的。对于第一种情况直接free,然后把指针置空就完事儿了。对于第二种情况,直接暴力assert检查就可以。

//单链表的尾删
void SLTPopBack(SLTNode** pphead)
{
    assert(*pphead);
    if ((*pphead)->next == NULL)
    {
        free(*pphead);
        *pphead = NULL;
    }
    else
    {
        SLTNode* prev = NULL;
        SLTNode* tail = *pphead;
        while (tail->next != NULL)
        {
            prev = tail;
            tail = tail->next;
        }
        prev->next = NULL;
        free(tail);
        tail = NULL;
    }
}

单链表的头删

1. 首先也可以暴力检查一下这个链表是不是空的。但头删不需要特判链表只有一个节点的情况。

//单链表的头删
void SLTPopFront(SLTNode** pphead)
{
    assert(*pphead);
    SLTNode* first = *pphead;
    *pphead = (*pphead)->next;
    free(first);
    first = NULL;
}

单链表的初始化

这个非常简单,直接初始化就可以,因为只有一个值。直接创建一个结构体指针赋为NULL就可以

SLTNode* plist = NULL;

关于单链表函数操作中的有关于assert断言的问题

不是在参数里面碰到一个指针就assert一下,不是这么一根筋的。像这边单链表的话,当phead为NULL时我们都知道此时此刻表示一个空链表,但是空链表我就不能头插,尾插插入了吗?我照样可以这么插入;空链表难道我就不能打印了吗?我照样可以打印,只是什么都没有而已。但是对于删除而言,如果此时此刻你已经是一个空链表了,那么你还删个毛线啊?所以此时此刻呢又需要用assert断言一下,这种东西都是具体问题具体分析。

关于参数为二级指针的问题

1. 尤其要注意形参的改变不会影响实参,这个其实底层要理解的话,就是得通过函数栈帧。当传址调用的时候,实际上地址也是在拷贝,但由于你是在函数里面对指针进行解引用操作,因此产生的影响是持久性的,即使调用的函数退出回来了。

2. malloc向堆区动态申请内存的时候,是随机在内存里面划分一块区域的。

3. 当你的实参是一个指针的时候,虽然这时候函数传参看起来好像也是传址调用,但实际上调用函数内部执行的各种操作,对于函数外头的这个参数指针不会发生任何影响。因为实际上无论如何都会有拷贝这个环节会发生。

4. 在这边想要着重说明的是:我们在进行函数传参的时候,这时候就必须传二级指针。我们在函数体外已经先有一个指针,我们的函数内部操作都离不开要改变该指针的指向位置,要实现这个操作,就不能传一级指针,因为这样只有我这个一级指针自己在捣鼓;如果我传指针的地址(也就是二级指针),这样子我才能在函数体内去改变函数体外这个指针所指向的位置。

5. 我这个phead是一个实实在在的结构体指针,各种有关于单链表的操作,其中都需要phead指向的位置发生变化,如果在函数里面传一级指针的话,函数里面的各种乱七八糟的运转,跟我函数外面的phead指针一点关系都没有,因为你在函数内部自己有一个参数指针(形参),也许这个形参与phead指向的地址是一样的,但是当你传一级指针的时候,在函数里面各种捣鼓运算改变的都是你形参指向的位置。如果想要改变我这个phead指针指向的位置,就必须把phead的地址当成参数传给函数,因此这个参数也就是指针的地址,显而易见即为二级指针。

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

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

相关文章

优思学院|精益生产中的“单件流”真的能够做到吗?

精益生产中提到的“一个流”(One Piece Flow)是一种生产方式,它的核心理念是通过合理配置作业场地、人员和设备,使产品从投入到成品产出的整个制造加工过程中始终处于不停滞、不堆积、不超越,按节拍一个一个地流动。 …

Idea+maven+spring-cloud项目搭建系列--11 整合dubbo

前言: 微服务之间通信框架dubbo,使用netty (NIO 模型)完成RPC 接口调用; 1 dubbo 介绍: Apache Dubbo 是一款 RPC 服务开发框架,用于解决微服务架构下的服务治理与通信问题,官方提…

渲染十万条数据就把你难住了?不存在的!

虚拟列表的使用场景如果我想要在网页中放大量的列表项,纯渲染的话,对于浏览器性能将会是个极大的挑战,会造成滚动卡顿,整体体验非常不好,主要有以下问题:页面等待时间极长,用户体验差CPU计算能力…

pyqt5(二) 标签(QLabel)组件的属性说明及示例

使用语法 widget QLable() widget.function(parameter) widget:实例化QLablefunction:QLable里的函数parameter:函数需要用到的参数 参数说明: 参数说明参数解释 setText() 配置文本内容 setPixmap() 添加图片 setFixedSize(…

蓝桥杯--等差素数列

等差素数列 技巧 这里的等差数列–首项需要枚举列出 公差也需要枚举列出 在公差为1开始&#xff0c;对n-1也进行枚举 //重要代码段 判断一个数是否为素数 int check(int n) { for(int i2;i<n;i){if(n%i0){return 0 } return 1; } }这道题不是很简单 本题为填空题&#xff0…

Webstorm使用、nginx启动、FinalShell使用

文章目录 主题设置FinalShellFinalShell nginx 启动历史命令Nginx页面发布配置Webstorm的一些常用快捷键代码生成字体大小修改Webstorm - gitCode 代码拉取webstorm 汉化webstorm导致CPU占用率高方法一 【忽略node_modules】方法二 【设置 - 代码编辑 - 快速预览文档 - 关闭】主…

Linux 练习七 (IPC 共享内存)

文章目录System V 共享内存机制&#xff1a;shmget shmat shmdt shmctl案例一&#xff1a;有亲缘关系的进程通信案例二&#xff1a;非亲缘关系的进程通信内存写端write1.c内存读端read1.c案例三&#xff1a;不同程序之间的进程通信程序一&#xff0c;写者shmwr.c程序二&#xf…

2022-06-14至2022-08-11 关于复现MKP算法的总结与反思

Prerequisite 自2022年6月14日至2022年8月11日的时间内&#xff0c;我致力于完成A Hybrid Approach for the 0–1 Multidimensional Knapsack problem 论文的复现工作&#xff0c;此次是我第一次进行组合优化方向的学习工作&#xff0c;下面介绍该工作内容发展过程以及该工作结…

JavaScript Array 数组对象实例集合

文章目录JavaScript Array 数组对象实例集合创建数组合并两个数组 - concat()合并三个数组 - concat()用数组的元素组成字符串 - join()删除数组的最后一个元素 - pop()数组的末尾添加新的元素 - push()反转一个数组中的元素的顺序 - reverse()删除数组的第一个元素 - shift()从…

数字化时代,企业的商业模式建设

随着新一代信息化、数字化技术的应用&#xff0c;众多领域通过科技革命和产业革命实现了深度化的数字改造&#xff0c;进入到以数据为核心驱动力的&#xff0c;全新的数据处理时代&#xff0c;并通过业务系统、商业智能BI等数字化技术和应用实现了数据价值&#xff0c;从数字经…

Vue项目打包部署总结配合nginx部署

你可能还想了解&#xff1a;https://blog.csdn.net/weixin_52901235/article/details/129437990?spm1001.2014.3001.5502使用Vue做前后端分离项目时&#xff0c;通常前端是单独部署&#xff0c;用户访问的也是前端项目地址&#xff0c;因此前端开发人员很有必要熟悉一下项目部…

C#要点技术(二) - Dictionary 底层源码剖析

Dictionary 底层代码我们知道 Dictionary 字典型数据结构&#xff0c;是以关键字Key 和 值Value 进行一一映射的。Key的类型并没有做任何的限制&#xff0c;可以是整数&#xff0c;也可以是的字符串&#xff0c;甚至可以是实例对象。关键字Key是如何映射到内存的呢&#xff1f;…

【python】如何用python写一个下拉选择框和页签?

文章目录前言ttk模块下拉选择框combobox下拉选择框2页签Notebook前言 python学习之路任重而道远&#xff0c;要想学完说容易也容易&#xff0c;说难也难。 很多人说python最好学了&#xff0c;但扪心自问&#xff0c;你会用python做什么了&#xff1f; 刚开始在大学学习c语言&…

【玩转c++】stack和queue的介绍和模拟实现

本期主题&#xff1a;list的讲解和模拟实现博客主页&#xff1a; 小峰同学分享小编的在Linux中学习到的知识和遇到的问题小编的能力有限&#xff0c;出现错误希望大家不吝赐stack的介绍和使用1.1.stack的介绍1. stack是一种容器适配器&#xff0c;专门用在具有后进先出操作的上…

论文阅读-MGTAB: A Multi-Relational Graph-Based Twitter Account DetectionBenchmark

目录 摘要 1. 引言 2. 相关工作 2.1. 立场检测 2.2.机器人检测 3.数据集预处理 3.1.数据收集和清理 3.2.专家注释 3.3. 质量评估 3.4.特征分析 4. 数据集构建 4.1.特征表示构造 4.2.关系图构建 5. 实验 5.1.实验设置 5.2.基准性能 5.3训练集大小的研究 5.4 社…

Matlab进阶绘图第6期—雷达图/蜘蛛图/星图

雷达图&#xff08;Radar Chart&#xff09;&#xff0c;又称星图、蜘蛛图、蜘蛛网图、网络图、Kiviat图等&#xff0c;是一种以从同一点开始的轴上表示的三个以上变量的二维图表的形式&#xff0c;来显示多变量数据的图形方法。 雷达图可以直观地对多维数据集目标对象的性能、…

三步搞定OOM内存溢出,记一次使用Arthas处理OOM内存溢出问题java.lang.OutOfMemoryError: Java heap space

记一次OOM内存溢出问题修复java.lang.OutOfMemoryError: Java heap spaceOutOfMemoryError1.使用article找到问题线程2.分析线程运行链路&#xff0c;找出问题代码位置3.使用堆文件确认问题Arthas 是Alibaba开源的Java诊断工具&#xff0c;功能强大&#xff0c;操作简单 Arthas…

我们为什么使用docker 优点 作用

1. 我们为什么使用Docker? 当我们在工作中&#xff0c;一款产品从开发设计到上线运行&#xff0c;其中需要开发人员和运维工程师&#xff0c;开发人员负责代码编写&#xff0c;开发产品&#xff0c;运维工程师需要测试环境&#xff0c;产品部署。这之间就会有分歧。 就好比我…

信创国产化,试试 Solon v2.2.2

Solon 是一个高效的 Java 应用开发框架&#xff1a;更快、更小、更简单。它不是 Spring、没有用 Servlet、也无关 JavaEE&#xff0c;是一个有自己接口标准的开放生态。可以为应用软件国产化提供支持&#xff0c;助力信创建设。 150来个生态插件&#xff0c;覆盖各种不同的应用…

不知道Redis?来这里可以带你快速学完Redis,干活满满!

文章目录一、NoSQL的基本介绍二、为什么要使用NoSQL&#xff0c;难道SQL不够你用吗&#xff1f;三、Redis的基本概念四、Redis基本操作命令五、Redis五大数据类型及其操作命令六、三种特殊的数据类型及其操作命令七、 Redis事务八、Redis对key的监控九、Redis数据库密码十、Jed…