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

【数据结构】链表详解

大家好,今天为大家分享一下第二个数据结构——单链表
先打个广告:这里是博主写道顺序表,大家也可以查看:顺序表详解
首先:

我们学完顺序表的时候,我们发现有以下问题:
中间/头部的插入删除,时间复杂度为O(N)
增容需要申请新空间,拷贝数据,释放旧空间、消耗大量资源。
增容一般是2倍的增长,势必会有一定的空间浪费。例如当前容量为300,满了以后增容到600,我们再继续插入了5个数据,后面没有数据插入了,那么就浪费了295个数据空间。

链表就能够很好的解决这些问题!

链表是一种物理存储单元上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的。链表由一系列结点(链表中每一个元素称为结点)组成,结点可以在运行时动态生成。每个结点包括两个部分:一个是存储数据元素的数据域,另一个是存储下一个结点地址的指针域。由于不必须按顺序存储,链表在插入的时候可以达到O(1)的复杂度,比另一种线性表顺序表快得多,但是查找一个节点或者访问特定编号的节点则需要O(n)的时间,而线性表和顺序表相应的时间复杂度分别是O(logn)和O(1)。

单链表模样如下:
在这里插入图片描述

博主分享的链表接口如下:

typedef int SLTDataType;//存储的数据类型
typedef struct SListNode {SLTDataType data;struct SListNode* next;
}SLTNode;//打印链表
void SListPrint(SLTNode* phead);
//创建节点
SLTNode* BuySListNode(SLTDataType x);
//尾插
void SListPushBack(SLTNode** phead, SLTDataType x);
//头插
void SListPushFront(SLTNode** phead, SLTDataType x);
//尾删
void SListPopBack(SLTNode** phead);
//头删
void SListPopFront(SLTNode** phead);
//查找
SLTNode* SListFind(SLTNode* phead, SLTDataType x);
//在pos位置之前插入
void SListInsert(SLTNode** phead, SLTNode* pos, SLTDataType x);
//删除pos位置
void SListErase(SLTNode** phead, SLTNode* pos);
//在pos位置之后插入
void SListInsertAfter(SLTNode* pos, SLTDataType x);
//删除pos之后的位置
void SListEraseAfter(SLTNode* pos);

下面对每个接口的实现进行说明:

1、创建新节点的接口函数

SLTNode* BuySListNode(SLTDataType x)
{SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));//出这个函数newnode被销毁,但是它保存的地址空间是malloc出来的,不会销毁assert(newnode);newnode->data = x;newnode->next = NULL;return newnode;
}

assert断言检查一下,如果申请失败就报错提示,申请成功就返回创建的节点的地址

2、打印数据函数的实现

void SListPrint(SLTNode* phead)
{SLTNode* cur = phead;while (cur!= NULL){printf("%d->", cur->data);cur = cur->next;}printf("NULL\n");
}

定义一个指针cur指向头结点,使用这个指针循环遍历,这个指针指向的不为空的话就继续循环,在循环中打印cur->data,对应的操作是printf(“%d->”, cur->data);打印%d后面加->是为了方便观察;然后将cur指针移动到下一个结点的位置对应操作是cur = cur->next;,继续打印,直到cur为空打印完毕!

3、尾插函数的实现

void SListPushBack(SLTNode** phead, SLTDataType x)
{//需要传入头节点,以供寻找尾结点从而进行尾插SLTNode* newnode = BuySListNode(x);//创造新结点assert(phead);//就算链表为空,但是他的地址不能为空呀if (*phead == NULL){*phead = newnode;}else {SLTNode* tail = *phead;//循环找尾节点while (tail->next != NULL){tail = tail->next;}//找到当下最后一个节点后让它指向插入的这个节点tail->next = newnode;}
}

在这里插入图片描述

在这里插入图片描述

4、头插函数的实现

void SListPushFront(SLTNode** phead, SLTDataType x)
{assert(phead);//就算链表为空,但是他的地址不能为空呀SLTNode* newnode = BuySListNode(x);//创造新结点newnode->next = *phead;*phead = newnode;
}

将创建的新结点的地址存放在newnode指针变量中,pphead为原先头结点的地址,头插一个新结点后,应该将新结点的next存放原先头结点的地址,对应操作为newnode->next = pphead;然后保存在pphead应该为新的头结点的地址,也就是newnode所指向的地址,对应操作为pphead = newnode;

在这里插入图片描述

5、尾删函数的实现

void SListPopBack(SLTNode** phead)
{//if (*phead == NULL)//温柔的检查//{//	return;//}assert(phead);//就算链表为空,但是他的地址不能为空呀assert(*phead!=NULL);//暴力检查if ((*phead)->next == NULL)//如果只有一个节点{free(*phead);*phead = NULL;//                            要改变phead,就需要二级指针}else//如果有多个结点{SLTNode* tailPrev = *phead;SLTNode* tail = *phead;//循环找尾节点while (tail->next != NULL){tailPrev = tail;tail = tail->next;}free(tail);tailPrev->next = NULL;}}

先创建两个指针,一个tail,一个tailprev,便利一遍链表,tail保存最后一个节点,tailprev保存最后一个节点的前一个节点,free掉tail,将tailprev的next指向NULL,就完成了尾删
如果只有一个节点,则释放后要通过对二级指针解引用将其置为空
在这里插入图片描述

6、头删函数的实现

void SListPopFront(SLTNode** phead)//                    要改变phead,就需要二级指针
{assert(*phead!=NULL);//链表为空就不能删除呀assert(phead);//就算链表为空,但是他的地址不能为空呀//if (*phead == NULL)//温柔的检查//{//	return;//}SLTNode* Next = (*phead)->next;free(*phead);*phead = Next;
}

提前保存好头节点的next值,删除后将头节点变为保存的next
在这里插入图片描述

7、查找函数的实现

SLTNode* SListFind(SLTNode* phead, SLTDataType x)
{SLTNode* cur = phead;while (cur){if (cur->data == x){return cur;}cur = cur->next;}return NULL;
}

遍历一遍链表,哪个节点的数据和要求的数据相同,则返回该节点

在这里插入图片描述

8、任意位置插入节点函数的实现

void SListInsert(SLTNode** phead, SLTNode* pos, SLTDataType x)
{assert(pos);//插入位置 传 错,为空就报错assert(*phead);assert(phead);//就算链表为空,但是他的地址不能为空呀if (pos == *phead){SListPushFront(phead, x);//头插}else{SLTNode* prev = *phead;while (prev->next !=pos){prev = prev->next;}SLTNode* newnode = BuySListNode(x);prev->next = newnode;newnode->next = pos;}
}

这个函数写出来之后就可以利用他对前面所写的头插和尾插进行改写了,大佬们可以尝试一下呀

在这里插入图片描述

9、删除节点函数的实现

void SListErase(SLTNode** phead, SLTNode* pos)
{assert(pos);//插入位置 传 错,为空就报错assert(*phead);assert(phead);//就算链表为空,但是他的地址不能为空呀if (pos == *phead){SListPopFront(phead);}else{SLTNode* prev = *phead;while (prev->next!= pos){prev = prev->next;}prev->next = pos->next;free(pos);pos = NULL;//可有可无}
}

这个函数写出来之后就可以利用他对前面所写的头删和尾删进行改写了,大佬们可以尝试一下呀
在这里插入图片描述
下面是完整的代码:

#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>typedef int SLTDataType;
typedef struct SListNode {SLTDataType data;struct SListNode* next;
}SLTNode;//打印链表
void SListPrint(SLTNode* phead);
//创建节点
SLTNode* BuySListNode(SLTDataType x);
//尾插
void SListPushBack(SLTNode** phead, SLTDataType x);
//头插
void SListPushFront(SLTNode** phead, SLTDataType x);
//尾删
void SListPopBack(SLTNode** phead);
//头删
void SListPopFront(SLTNode** phead);
//查找
SLTNode* SListFind(SLTNode* phead, SLTDataType x);
//在pos位置之前插入
void SListInsert(SLTNode** phead, SLTNode* pos, SLTDataType x);
//删除pos位置
void SListErase(SLTNode** phead, SLTNode* pos);
//在pos位置之后插入
void SListInsertAfter(SLTNode* pos, SLTDataType x);
//删除pos之后的位置
void SListEraseAfter(SLTNode* pos);
#define _CRT_SECURE_NO_WARNINGS 1
#include"SList.h"void SListPrint(SLTNode* phead)
{SLTNode* cur = phead;while (cur!= NULL){printf("%d->", cur->data);cur = cur->next;}printf("NULL\n");
}SLTNode* BuySListNode(SLTDataType x)
{SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));//出这个函数newnode被销毁,但是它保存的地址空间是malloc出来的,不会销毁assert(newnode);newnode->data = x;newnode->next = NULL;return newnode;
}void SListPushBack(SLTNode** phead, SLTDataType x)
{//需要传入头节点,以供寻找尾结点从而进行尾插SLTNode* newnode = BuySListNode(x);//创造新结点assert(phead);//就算链表为空,但是他的地址不能为空呀if (*phead == NULL){*phead = newnode;}else {SLTNode* tail = *phead;//循环找尾节点while (tail->next != NULL){tail = tail->next;}//找到当下最后一个节点后让它指向插入的这个节点tail->next = newnode;}
}void SListPushFront(SLTNode** phead, SLTDataType x)
{assert(phead);//就算链表为空,但是他的地址不能为空呀SLTNode* newnode = BuySListNode(x);//创造新结点newnode->next = *phead;*phead = newnode;}void SListPopBack(SLTNode** phead)
{//if (*phead == NULL)//温柔的检查//{//	return;//}assert(phead);//就算链表为空,但是他的地址不能为空呀assert(*phead!=NULL);//暴力检查if ((*phead)->next == NULL)//如果只有一个节点{free(*phead);*phead = NULL;//                            要改变phead,就需要二级指针}else//如果有多个结点{SLTNode* tailPrev = *phead;SLTNode* tail = *phead;//循环找尾节点while (tail->next != NULL){tailPrev = tail;tail = tail->next;}free(tail);tailPrev->next = NULL;}}void SListPopFront(SLTNode** phead)//                    要改变phead,就需要二级指针
{assert(*phead!=NULL);//链表为空就不能删除呀assert(phead);//就算链表为空,但是他的地址不能为空呀//if (*phead == NULL)//温柔的检查//{//	return;//}SLTNode* Next = (*phead)->next;free(*phead);*phead = Next;
}SLTNode* SListFind(SLTNode* phead, SLTDataType x)
{SLTNode* cur = phead;while (cur){if (cur->data == x){return cur;}cur = cur->next;}return NULL;
}void SListInsert(SLTNode** phead, SLTNode* pos, SLTDataType x)
{assert(pos);//插入位置 传 错,为空就报错assert(*phead);assert(phead);//就算链表为空,但是他的地址不能为空呀if (pos == *phead){SListPushFront(phead, x);//头插}else{SLTNode* prev = *phead;while (prev->next !=pos){prev = prev->next;}SLTNode* newnode = BuySListNode(x);prev->next = newnode;newnode->next = pos;}
}void SListErase(SLTNode** phead, SLTNode* pos)
{assert(pos);//插入位置 传 错,为空就报错assert(*phead);assert(phead);//就算链表为空,但是他的地址不能为空呀if (pos == *phead){SListPopFront(phead);}else{SLTNode* prev = *phead;while (prev->next!= pos){prev = prev->next;}prev->next = pos->next;free(pos);pos = NULL;//可有可无}
}void SListInsertAfter(SLTNode* pos, SLTDataType x)
{assert(pos);SLTNode* newnode = BuySListNode(x);newnode->next = pos->next;//这两句的顺序一定要区分好pos->next = newnode;}void SListEraseAfter(SLTNode* pos)
{assert(pos);if (pos->next == NULL){return;}else{SLTNode* del = pos->next;pos->next = del->next;free(del);del = NULL;//可有可无}
}

单链表分享到这里就完了,大家可以参考,有不正确的地方还请指教,谢谢!!!

相关文章:

【数据结构】链表详解

大家好&#xff0c;今天为大家分享一下第二个数据结构——单链表 先打个广告&#xff1a;这里是博主写道顺序表&#xff0c;大家也可以查看&#xff1a;顺序表详解 首先&#xff1a; 我们学完顺序表的时候&#xff0c;我们发现有以下问题&#xff1a; 中间/头部的插入删除&…...

STM32使用HAL库驱动DS18B20

1、STM32CubeMx配置IO口 因为DS18B20是单总线&#xff0c;数据接收发送都是这根线&#xff0c;所以单片机配置为开漏上拉输出。 2、定时器配置 因为DS18B20对时序要求比较严格&#xff0c;建议用定时器延时获得微秒延时函数。 总线为48M&#xff0c;分频48&#xff0c;获得1…...

echarts折线图设置背景颜色

initChartsBox() {this.option {tooltip: {trigger: "axis",axisPointer: {// 方法一type: "shadow", // 默认为直线&#xff0c;可选为&#xff1a;line | shadowshadowStyle: {color: "rgba(41, 95, 204, 0.2)",},},borderColor: "rgba(…...

spring boot+ vue+ mysql开发的一套厘米级高精度定位系统源码

UWB室内高精度定位系统源码&#xff0c;自主版权演示 UWB技术最核心的能力就是精准的定位与测距&#xff0c;当然它还具备通信功能。不过&#xff0c;目前主流通信技术已经相当成熟&#xff0c;无需UWB兼顾去做通信传输。而且&#xff0c;如果使用UWB通信功能&#xff0c;反而会…...

【初试396分】西北工业大学827学长经验分享

这个系列会邀请往届学长学姐进行经验分享~欢迎后台回复经验分享&#xff0c;进行投稿&#xff01; 经验贴征集&#xff1a;前人栽树&#xff0c;后人乘凉&#xff0c;上岸同学也是看着经验贴一点一点过来的&#xff0c;有偿征集各位同学的经验分享&#xff0c;以此来帮助更多的…...

【Qt之信号和槽】对象多层嵌套后,高效使用信号和槽

抛出问题 Qt的信号槽机制非常牛逼&#xff0c;也是Qt的独特的核心功能之一。 有时候在很多窗体中传递信号来实现更新或者处理&#xff0c;如果窗体层级比较多&#xff0c;比如窗体A的父类是窗体B&#xff0c;窗体B的父类是窗体C&#xff0c;窗体C有个子窗体D&#xff0c;如果窗…...

搬砖日记:vue2 用require引入图片项目编译失败

代码如下&#xff1a; data中定义&#xff1a; minLogo: require(process.env.VUE_APP_BASE_MAX_LOGO) 使用&#xff1a; <img :src"minLogo" />结果&#xff1a; 试错&#xff1a; 一开始我以为是不能直接在data中require&#xff0c;修改成在created中引入…...

国内外都可以使用的【免费AI工具】,实用性满满

受到ChatGPT的影响&#xff0c;大量的AI工具涌现&#xff0c;那么真的对我们学习和生活有用的免费AI工具都有哪些呢&#xff1f; 1.ChatSider ChatSider是一款可以对话的AI写作机器人。 ①学习上 推荐学生党使用它的“阅读助手”和“写作助手”功能。 阅读助手&#xff1a;…...

银河麒麟服务器x86安装ntp客户端,并配置成功可以同步时间

脚本 # 安装ntp客户端 sudo dnf install chrony # 配置 pool 2.centos.pool.ntp.org iburst给这一行加注释 sudo sed -i s/^pool 2.centos.pool.ntp.org iburst/#&/ /etc/chrony.conf # 添加3个阿里云NTP服务器 # echo -e "server ntp1.aliyun.com iburst\nserver nt…...

vue踩的坑:属性报undefined错误问题汇总

问题 在一个组件里&#xff0c;通过props传值进去对象&#xff0c;在控制台打印报错误信息&#xff0c;提示某属性不存在。 例如&#xff1a; <div>{{data.param.aaa}}</div> 类似这种的&#xff0c;取对象子级下面的值&#xff0c;就报了undefined。 原因应该…...

Ubuntu22.04.3安装教程

虚拟机系列文章 VMware Workstation Player 17 免费下载安装教程 VMware Workstation 17 Pro 免费下载安装教程 windows server 2012安装教程 Ubuntu22.04.3安装教程 FTP服务器搭建 Ubuntu22.04.3安装教程 虚拟机系列文章前言Ubuntu22.04.3安装&#xff08;图文&#xff09; 前…...

Vue2和Vue3的emit、props、watch等知识点对比

1.props/defineProps 使用场景: 一般当父组件需要给子组件传值的时候会用到。 Vue2:props vue2在父组件里引入子组件以后需要在components里面注册后再使用&#xff1b; 父组件 <son-compnents :infoinfo></son-components>import SonCompnents from "./cp…...

HTML 笔记:初识 HTML(HTML文本标签、文本列表、嵌入图片、背景色、网页链接)

1 何为HTML 用来描述网页的一种语言超文本标记语言(Hyper Text Markup Language)不是一种编程语言&#xff0c;而是一种标记语言 (markup language) 2 HTML标签 HTML 标签是由尖括号包围的关键词&#xff0c;比如 <html> 作用是为了“标记”页面中的内容&#xff0c;使…...

使用弹性盒子flex对html进行布局和动态计算视口高度

使用弹性盒子flex对html进行布局的一个练习 height: calc(100vh - 4px); # vh表示视口高度的百分比&#xff0c;所以100vh表示整个视口的高度。 .mytxt { text-indent: 2em; /* 首航缩进2字符 */ line-height: 2; /* 2倍行高 */ padding: 8px; /* 内容与边框的距离 */ } …...

华为云云耀云服务器L实例评测|华为云耀云服务器L实例评测用例(五)

六、华为云耀云服务器L实例评测用例&#xff1a; “兵马未动&#xff0c;粮草先行”&#xff0c;随着企业业务的快速发展&#xff0c;服务器在数字化建设体系至关重要&#xff0c;为了保证服务器的稳定性、可靠性&#xff0c;需要对服务器进行评测&#xff0c;以确保服务器能够…...

uniapp-vue3微信小程序实现全局分享

uniapp-vue3微信小程序实现全局分享 文章目录 uniapp-vue3微信小程序实现全局分享微信小程序官方文档的分享说明onShareAppMessage(Object object)onShareTimeline() uniapp 官方文档的分享说明onShareAppMessage(OBJECT) 实现全局分享代码结构如下share.js文件内容main.js注意…...

Qt如何实现动态背景-视频背景

前言 需求&#xff1a;加载视频作为视频背景&#xff0c;在上层可以进行图片的动画化&#xff0c;或是进行其他操作。 几种方法&#xff1a; 1、直接将视频弄成一个QDialog&#xff0c; 然后再上层在弄一个QDialog,背景透明即可。但遇到一个问题&#xff0c;QDialog没办法局…...

vue按键全屏和F11全屏共存

以下代码可直接复制 注意此段代码 // let element document.documentElement // 当前页所有元素全屏 let element document.getElementById(‘div1’) //让某个div元素全屏 <template><div><div style"width: 300px;height: 300px;background-color: cya…...

springboot就业信息管理系统springboot32

大家好✌&#xff01;我是CZ淡陌。一名专注以理论为基础实战为主的技术博主&#xff0c;将再这里为大家分享优质的实战项目&#xff0c;本人在Java毕业设计领域有多年的经验&#xff0c;陆续会更新更多优质的Java实战项目&#xff0c;希望你能有所收获&#xff0c;少走一些弯路…...

深入探讨芯片制程设备:从原理到实践

&#x1f482; 个人网站:【工具大全】【游戏大全】【神级源码资源网】&#x1f91f; 前端学习课程&#xff1a;&#x1f449;【28个案例趣学前端】【400个JS面试题】&#x1f485; 寻找学习交流、摸鱼划水的小伙伴&#xff0c;请点击【摸鱼学习交流群】 在现代科技领域&#xf…...

多云管理“拦路虎”:深入解析网络互联、身份同步与成本可视化的技术复杂度​

一、引言&#xff1a;多云环境的技术复杂性本质​​ 企业采用多云策略已从技术选型升维至生存刚需。当业务系统分散部署在多个云平台时&#xff0c;​​基础设施的技术债呈现指数级积累​​。网络连接、身份认证、成本管理这三大核心挑战相互嵌套&#xff1a;跨云网络构建数据…...

Linux链表操作全解析

Linux C语言链表深度解析与实战技巧 一、链表基础概念与内核链表优势1.1 为什么使用链表&#xff1f;1.2 Linux 内核链表与用户态链表的区别 二、内核链表结构与宏解析常用宏/函数 三、内核链表的优点四、用户态链表示例五、双向循环链表在内核中的实现优势5.1 插入效率5.2 安全…...

多场景 OkHttpClient 管理器 - Android 网络通信解决方案

下面是一个完整的 Android 实现&#xff0c;展示如何创建和管理多个 OkHttpClient 实例&#xff0c;分别用于长连接、普通 HTTP 请求和文件下载场景。 <?xml version"1.0" encoding"utf-8"?> <LinearLayout xmlns:android"http://schemas…...

论文笔记——相干体技术在裂缝预测中的应用研究

目录 相关地震知识补充地震数据的认识地震几何属性 相干体算法定义基本原理第一代相干体技术&#xff1a;基于互相关的相干体技术&#xff08;Correlation&#xff09;第二代相干体技术&#xff1a;基于相似的相干体技术&#xff08;Semblance&#xff09;基于多道相似的相干体…...

网站指纹识别

网站指纹识别 网站的最基本组成&#xff1a;服务器&#xff08;操作系统&#xff09;、中间件&#xff08;web容器&#xff09;、脚本语言、数据厍 为什么要了解这些&#xff1f;举个例子&#xff1a;发现了一个文件读取漏洞&#xff0c;我们需要读/etc/passwd&#xff0c;如…...

算法:模拟

1.替换所有的问号 1576. 替换所有的问号 - 力扣&#xff08;LeetCode&#xff09; ​遍历字符串​&#xff1a;通过外层循环逐一检查每个字符。​遇到 ? 时处理​&#xff1a; 内层循环遍历小写字母&#xff08;a 到 z&#xff09;。对每个字母检查是否满足&#xff1a; ​与…...

云原生安全实战:API网关Kong的鉴权与限流详解

&#x1f525;「炎码工坊」技术弹药已装填&#xff01; 点击关注 → 解锁工业级干货【工具实测|项目避坑|源码燃烧指南】 一、基础概念 1. API网关&#xff08;API Gateway&#xff09; API网关是微服务架构中的核心组件&#xff0c;负责统一管理所有API的流量入口。它像一座…...

接口自动化测试:HttpRunner基础

相关文档 HttpRunner V3.x中文文档 HttpRunner 用户指南 使用HttpRunner 3.x实现接口自动化测试 HttpRunner介绍 HttpRunner 是一个开源的 API 测试工具&#xff0c;支持 HTTP(S)/HTTP2/WebSocket/RPC 等网络协议&#xff0c;涵盖接口测试、性能测试、数字体验监测等测试类型…...

如何配置一个sql server使得其它用户可以通过excel odbc获取数据

要让其他用户通过 Excel 使用 ODBC 连接到 SQL Server 获取数据&#xff0c;你需要完成以下配置步骤&#xff1a; ✅ 一、在 SQL Server 端配置&#xff08;服务器设置&#xff09; 1. 启用 TCP/IP 协议 打开 “SQL Server 配置管理器”。导航到&#xff1a;SQL Server 网络配…...

热烈祝贺埃文科技正式加入可信数据空间发展联盟

2025年4月29日&#xff0c;在福州举办的第八届数字中国建设峰会“可信数据空间分论坛”上&#xff0c;可信数据空间发展联盟正式宣告成立。国家数据局党组书记、局长刘烈宏出席并致辞&#xff0c;强调该联盟是推进全国一体化数据市场建设的关键抓手。 郑州埃文科技有限公司&am…...