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

Vuex的简介以及入门案例

&#x1f3c5;我是默&#xff0c;一个在CSDN分享笔记的博主。&#x1f4da;&#x1f4da; &#x1f31f;在这里&#xff0c;我要推荐给大家我的专栏《Vue》。&#x1f3af;&#x1f3af; &#x1f680;无论你是编程小白&#xff0c;还是有一定基础的程序员&#xff0c;这个专栏…...

上海亚商投顾:沪指探底回升 华为汽车概念股集体大涨

上海亚商投顾前言&#xff1a;无惧大盘涨跌&#xff0c;解密龙虎榜资金&#xff0c;跟踪一线游资和机构资金动向&#xff0c;识别短期热点和强势个股。 一.市场情绪 三大指数昨日探底回升&#xff0c;早盘一度集体跌超1%&#xff0c;随后震荡回暖&#xff0c;深成指、创业板指…...

Android网络监听

1.通过注册BroadCastReceiver进行网络监听。 1) 添加网络权限 <uses-permission android:name"android.permission.INTERNET"/><uses-permission android:name"android.permission.ACCESS_NETWORK_STATE" /> 2&#xff09;定义BroadCastRe…...

Kubernetes 常用命令 持续更新

1、进入指定namespace pod kubectl exec -it --namespacekube-system g-lsb-proxy-nginx-r7zfl-2522744936-11rld /bin/sh kubectl exec -it g-lsb-proxy-nginx-r7zfl-2522744936-9tz5k -n kube-system /bin/bash2、查看k8s pod详情 kubectl describe pods -n jiankunking …...

达梦数据库常用命令行

导出dmp文件&#xff08;迁移用&#xff09; 管理工具在dmdbms下的tool文件夹下 使用tool目录下的manage程序&#xff0c;导出dmp文件 导入dmp文件 切到tool目录下 ./dimp 用户id/密码ip:5236 file"导入的文件路径(包括文件名)" 导入的模式&#xff08;一般与库名…...

【通信系列 6 -- AT 命令介绍】

文章目录 1. 背景介绍1.2 AT的命令格式1.3 AT指令用法1.3.1 指令执行结果 1.2 CP 常用AT指令1.2.1 CP 模式设置1.2.2 网络相关1.2.3 IP获取1.2.4 Band 设置1.2.5 电话相关1.2.6 SIM卡检测1.2.7 cmwap 和cmnet1.2.8 AT 写 IMEI 1. 背景介绍 AT 命令一般分为三种&#xff1a; C…...

flask捕获@app.errorhandler/@app.after_request全局异常总结

捕获处理全局异常的方法有两种&#xff1a;app.errorhandler、app.after_request1、第一种的使用&#xff0c;需要将flask的debug开关打开才能生效&#xff08;自动捕获异常&#xff09;&#xff0c;在config里面将DEBUG TRUE就可以&#xff08;默认是False&#xff09;。 但是…...

智能晾衣架丨以科技解放双手

以往的晾衣架大多是平放式、手摇式居多&#xff0c;为衣物的晾晒提供了一个“栖身之所。”随着科技的日新月异&#xff0c;智能家居的产品越来越多。晾衣架也不例外&#xff0c;一款带有语音控制升降、同时具备照明和消毒的多功能衣架也已深入生活&#xff0c;正被人们所接受。…...

asp.net饭店订餐管理系统VS开发sqlserver数据库web结构c#编程Microsoft Visual Studio计算机设计定制

一、源码特点 asp.net 饭店订餐管理系统 是一套完善的web设计管理系统&#xff0c;系统具有完整的源代码和数据库&#xff0c;系统主要采用B/S模式开发。开发环境为vs2010&#xff0c;数据库为sqlserver2008&#xff0c;使用c#语 言开发 asp.net饭店订餐系统 二、功能介…...

Pushgateway的场景使用

1,Pushgateway简介 Pushgateway为Prometheus整体监控方案的功能组件之一,并做为一个独立的工具存在。它主要用于Prometheus无法直接拿到监控指标的场景,如监控源位于防火墙之后,Prometheus无法穿透防火墙;目标服务没有可抓取监控数据的端点等多种情况。在类似场景中,可通…...