当前位置: 首页 > 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…...

微信小程序之bind和catch

这两个呢&#xff0c;都是绑定事件用的&#xff0c;具体使用有些小区别。 官方文档&#xff1a; 事件冒泡处理不同 bind&#xff1a;绑定的事件会向上冒泡&#xff0c;即触发当前组件的事件后&#xff0c;还会继续触发父组件的相同事件。例如&#xff0c;有一个子视图绑定了b…...

前端倒计时误差!

提示:记录工作中遇到的需求及解决办法 文章目录 前言一、误差从何而来?二、五大解决方案1. 动态校准法(基础版)2. Web Worker 计时3. 服务器时间同步4. Performance API 高精度计时5. 页面可见性API优化三、生产环境最佳实践四、终极解决方案架构前言 前几天听说公司某个项…...

《从零掌握MIPI CSI-2: 协议精解与FPGA摄像头开发实战》-- CSI-2 协议详细解析 (一)

CSI-2 协议详细解析 (一&#xff09; 1. CSI-2层定义&#xff08;CSI-2 Layer Definitions&#xff09; 分层结构 &#xff1a;CSI-2协议分为6层&#xff1a; 物理层&#xff08;PHY Layer&#xff09; &#xff1a; 定义电气特性、时钟机制和传输介质&#xff08;导线&#…...

关于nvm与node.js

1 安装nvm 安装过程中手动修改 nvm的安装路径&#xff0c; 以及修改 通过nvm安装node后正在使用的node的存放目录【这句话可能难以理解&#xff0c;但接着往下看你就了然了】 2 修改nvm中settings.txt文件配置 nvm安装成功后&#xff0c;通常在该文件中会出现以下配置&…...

Qt Http Server模块功能及架构

Qt Http Server 是 Qt 6.0 中引入的一个新模块&#xff0c;它提供了一个轻量级的 HTTP 服务器实现&#xff0c;主要用于构建基于 HTTP 的应用程序和服务。 功能介绍&#xff1a; 主要功能 HTTP服务器功能&#xff1a; 支持 HTTP/1.1 协议 简单的请求/响应处理模型 支持 GET…...

微信小程序云开发平台MySQL的连接方式

注&#xff1a;微信小程序云开发平台指的是腾讯云开发 先给结论&#xff1a;微信小程序云开发平台的MySQL&#xff0c;无法通过获取数据库连接信息的方式进行连接&#xff0c;连接只能通过云开发的SDK连接&#xff0c;具体要参考官方文档&#xff1a; 为什么&#xff1f; 因为…...

tree 树组件大数据卡顿问题优化

问题背景 项目中有用到树组件用来做文件目录&#xff0c;但是由于这个树组件的节点越来越多&#xff0c;导致页面在滚动这个树组件的时候浏览器就很容易卡死。这种问题基本上都是因为dom节点太多&#xff0c;导致的浏览器卡顿&#xff0c;这里很明显就需要用到虚拟列表的技术&…...

JS设计模式(4):观察者模式

JS设计模式(4):观察者模式 一、引入 在开发中&#xff0c;我们经常会遇到这样的场景&#xff1a;一个对象的状态变化需要自动通知其他对象&#xff0c;比如&#xff1a; 电商平台中&#xff0c;商品库存变化时需要通知所有订阅该商品的用户&#xff1b;新闻网站中&#xff0…...

音视频——I2S 协议详解

I2S 协议详解 I2S (Inter-IC Sound) 协议是一种串行总线协议&#xff0c;专门用于在数字音频设备之间传输数字音频数据。它由飞利浦&#xff08;Philips&#xff09;公司开发&#xff0c;以其简单、高效和广泛的兼容性而闻名。 1. 信号线 I2S 协议通常使用三根或四根信号线&a…...

CSS | transition 和 transform的用处和区别

省流总结&#xff1a; transform用于变换/变形&#xff0c;transition是动画控制器 transform 用来对元素进行变形&#xff0c;常见的操作如下&#xff0c;它是立即生效的样式变形属性。 旋转 rotate(角度deg)、平移 translateX(像素px)、缩放 scale(倍数)、倾斜 skewX(角度…...