【数据结构】第三节:单链表
前言
本篇要求掌握的C语言基础知识:指针、结构体
目录
前言
单链表
概念
对比链表和顺序表
创建链表
实现单链表
准备工作
打印链表
创建节点并初始化
尾插
二级指针的调用
尾插代码
头插
尾删
头删
查找(返回节点)
在指定位置(pos)之前插入数据
在指定位置(pos)之后插入数据
删除pos节点
删除pos之后的节点
销毁链表
单链表
概念
链表是⼀种物理存储结构上⾮连续、⾮顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的 。
对比链表和顺序表
顺序表:
1) 占用一大片连续内存空间
2) 不需要额外空间存储逻辑关系,总空间需求最少
4) 可顺序访问,支持随机访问
5) 在C语言中,通过数组实现
6) 数据元素的插入和删除操作通过移动元素完成
链表:
1) 不要求占用连续内存空间
2) 不仅要存储数据,还要存储数据之间的关系,故总空间需求较大
3) 通过指针反映逻辑关系
4) 逻辑连续,物理可不连续
5) 只可顺序访问,不支持随机访问
6) 存在标记:头指针
7) 数据元素的插入和删除操作通过修改指针完成:定位插入点/删除点的直接前驱/后
从上文可以得知与顺序表不同的是,链表⾥的每节"⻋厢"都是独⽴申请下来的空间,我们称之为“结点/节点” ,节点的组成主要有两个部分:当前节点要保存的数据和保存下⼀个节点的地址(指针变量)。

创建链表
//创建节点
typedef int SLTDataType;typedef struct SLNode
{SLTDataType data;//数据域struct SLNode* next;//指针域
}SLTNode;//创建节点
SLTNode* node1 = (SLTNode*)malloc(sizeof(SLTNode));
node1->data = 1;SLTNode* node2 = (SLTNode*)malloc(sizeof(SLTNode));
node2->data = 2;SLTNode* node3 = (SLTNode*)malloc(sizeof(SLTNode));
node3->data = 3;SLTNode* node4 = (SLTNode*)malloc(sizeof(SLTNode));
node4->data = 4;//链接节点
node1->next = node2;
node2->next = node3;
node3->next = node4;
node4->next = NULL;//尾指针置空
其中数据域用于存放数据,指针域用于存放下一个结点的地址。上面的链表是手动创建节点,只是为了展示链表的形成,后续创建和链接单链表可以通过函数实现。
实现单链表
准备工作
在工程中一共包含三个文件
- 定义文件SLNode.h:定义函数和结构体,头文件:stdio.h、stdlib.h、assert.h
- 实现文件SLNode.c:实现函数具体功能,头文件:SLNode.h
- 测试文件test.c:测试每一部分代码的正确性,头文件:SLNode.h
在开始之前我们需要定义一个指向为空的结构体类型的节点(SLNode*)plist,作为链表的头节点。
SLNode* plist = NULL;
打印链表
//打印 void SLTprint(SLTNode* phead) {SLNode* pcur = phead;while (pcur != NULL){printf("%d ", pcur->data);pcur = pcur->next;}printf("\n"); }
创建节点并初始化
//创建节点并初始化 SLNode* SLTbuyNode(SLTDataType x) {SLNode* newnode = (SLNode*)malloc(sizeof(SLNode));//创建新节点if (newnode == NULL){perror("malloc fail!");exit(1);//表示非正常退出}newnode->data = x;newnode->next = NULL;return newnode; }
尾插
二级指针的调用
从这一部分开始就涉及到了二级指针传参的问题,在对单链表进行尾插时,如果此时头节点plist指向为空(即该单链表为空),就需要在函数内部改变头指针的指向,指向新插入的节点。
这里举一个简单的例子,假如我要实现一个交换两个整形数据的函数,应该如何实现?
void Exchange(int a,int b) {int tmp=a;a=b;tmp=b; }如果仅仅将两个整形作为参数是无法成功的,因为在主函数中调用Exchange时在栈帧中又开辟了一块地址不同于主函数的函数栈帧,以上"传值调用"仅仅将形参里的内容进行交换,在函数执行结束时所占据的空间会被释放,同时形参也会因为被销毁而无法对实参产生影响。
如果想要"形参影响实参",就要把"传值调用"改为"传址调用",即将变量的地址作为参数传给函数,对应的函数参数应为指针类型。
void Exchange(int* a,int* b) {int* tmp=*a;*a=*b;*tmp=*b; }这样就实现了交换两个数据的操作。
同理,想要在函数内部改变一级头指针plist的指向,应该把plist的地址传入,用二级指针接收,也就是"传址调用",如果只传递一级指针(即链表的头指针),无法直接修改它所指向的地址,因为在函数内部对指针的修改不会影响到函数外部,最终只是将形参指针的指向改变而无法对实参造成影响。为了实现对链表头指针的修改,需要传递指向指针的指针,这样在函数内部就可以修改指针所指向的地址,从而改变链表的头指针。
来一张图解释二级指针

总结:只要头指针发生改变就需要用到二级指针
尾插代码
void SLTpushBack(SLTNode** pphead, SLTDataType x) {assert(pphead);SLTNode* newnode = SLTbuyNode(x);if (*pphead == NULL)//链表为空{*pphead = newnode;}else{SLNode* ptail = *pphead;while (ptail->next != NULL)//遍历链表找到尾节点{ptail = ptail->next;}ptail->next = newnode;} }
头插
与尾插同理,头指针的指向发生改变,需要借助二级指针
void SLTpushFront(SLTNode** pphead, SLTDataType x) {assert(pphead);SLTNode* newnode = SLTbuyNode(x);newnode->next = *pphead;//*pphead是指向第一个节点的指针*pphead = newnode; }
尾删
void SLTpopBack(SLTNode** pphead) {assert(pphead && *pphead);//*pphead为空说明整个链表为空if ((*pphead)->next == NULL)//链表中只有一个节点{free(*pphead);*pphead = NULL;}else{SLTNode* ptail = *pphead;SLTNode* prev = *pphead;while (ptail->next != NULL){prev = ptail;//prev指向的是尾节点的前一个节点ptail = ptail->next;}free(ptail);prev->next = NULL;//prev成为新的尾节点ptail = NULL;} }
头删
void SLTpopFront(SLTNode** pphead) {assert(pphead && *pphead);if ((*pphead) == NULL){free(*pphead);*pphead = NULL;}else{SLTNode* p = *pphead;//此时p指向的是头节点*pphead = (*pphead)->next;free(p);p = NULL;} }
查找(返回节点)
SLNode* SLTfind(SLTNode* phead, SLTDataType x) {assert(phead);SLNode* pcur = phead;while (pcur != NULL){if (pcur->data == x){return pcur;}pcur = pcur->next;}return NULL;//没有找到返回NULL }
在指定位置(pos)之前插入数据
void SLTinsert(SLTNode** pphead, SLTNode* pos, SLTDataType x) {assert(pphead && *pphead && pos);SLTNode* pcur = *pphead;SLNode* newnode = SLTbuyNode(x);if (pos == *pphead){SLTpushFront(pphead, x);}else{while (pcur->next != pos)//遍历到pos节点的前驱节点{pcur = pcur->next;}newnode->next = pos;pcur->next = newnode;} }
在指定位置(pos)之后插入数据
void SLTinsertAfter(SLTNode* pos, SLTDataType x) {assert(pos);SLNode* newnode = SLTbuyNode(x);if (pos->next == NULL)//如果pos是尾节点{pos->next = newnode;newnode->next = NULL;}else{SLNode* pafter = pos->next;//pcur是pos的后继节点newnode->next = pafter;pos->next = newnode;} }在这里不调用二级指针的原因是头指针无需改变,需要改变的时pos节点内部next指针的指向,而对于next指针来说,pos指向的时next所在的节点,所以pos可以直接访问这个黑点,从而改变next的指向,换句话pos相对于next来说就是二级指针。
删除pos节点
void SLTerase(SLTNode** pphead, SLTNode* pos) {assert(*pphead && pos && pphead);if (pos->next == NULL)//如果pos是尾节点{SLTNode* prev = *pphead;while (prev->next != pos){prev = prev->next;}prev->next = NULL;free(pos);pos = NULL;}else if (*pphead == pos)//如果pos是头节点{SLTNode* next = (*pphead)->next;free(*pphead);(*pphead) = next;}else{SLTNode* prev = *pphead;while (prev->next != pos){prev = prev->next;}prev->next = pos->next;free(pos);pos = NULL;} }
删除pos之后的节点
void SLTeraseAfter(SLTNode* pos) {assert(pos->next && pos);SLTNode* next = pos->next;pos->next = pos->next->next;free(next);next = NULL; }
销毁链表
void SLTdestroy(SLTNode** pphead) {assert(*pphead && pphead);SLTNode* pcur = *pphead;while (pcur != NULL){SLTNode* next = pcur->next;free(pcur);pcur = next;}*pphead = NULL; }
相关文章:
【数据结构】第三节:单链表
前言 本篇要求掌握的C语言基础知识:指针、结构体 目录 前言 单链表 概念 对比链表和顺序表 创建链表 实现单链表 准备工作 打印链表 创建节点并初始化 尾插 二级指针的调用 尾插代码 头插 尾删 头删 查找(返回节点) 在指定位…...
Python中操作Excel表对象并打包为脚本
一、准备工作 pip install pandas pip install openpyxl pip install pyinstaller 数据表格: 数据表下载 二、执行写入操作 import pandas as pd # pyinstaller --onefile attendance_records_score.py # 打包 # 读取源Excel文件(假设源表有列A…...
Python学习笔记23 - 目录操作
os模块操作目录相关函数 os.path模块操作目录相关函数 案例1 —— 列出指定目录下的所有.py文件 案例2 —— walk()...
今天你学langchain了吗?
langchain的重重难关 学习langchain也有一段时间了,从最初的0.0339版本到现在的稳定版本,langchain走了很长的路.在学习的路上也遇到了很多的困难. api_key难关 学习langchain最大的困难就是openai的API_KEY,国内无法申请到官方账号,申请到了也无法进行充值.好在有几美元的免…...
插值算法-代码实现
1、 import java.util.HashMap; import java.util.Map;public class Interpolation {public static void main(String[] args) {// 定义给定的 XML 字段值Map<String, double[]> xmlValues new HashMap<>();xmlValues.put("faceSize", new double[]{10…...
113.PyQt5_QtPrintSupport_打印操作
我 的 个 人 主 页:👉👉 失心疯的个人主页 👈👈 入 门 教 程 推 荐 :👉👉 Python零基础入门教程合集 👈👈 虚 拟 环 境 搭 建 :👉&…...
在vue中使用bing map 的小demo
1.注意事项(关于经纬度) 如果不转换成WGS84 标准的经纬度 bing map会报错 如果要在 Bing Maps 中使用中国地区的经纬度,需要先将其转换为 WGS84 标准的经纬度。你可以使用第三方的坐标转换服务,或者使用相关的 JavaScript 库进行…...
基于uni-app的埋点sdk设计
一、统计app激活状态 在App.vue 中 利用onShow生命周期验证 或者操作 onShow: function () { uni.showToast({ title: onShow }) }, 二、页面级别的统计 (进入页面、停留时长、手机系统信息、网络状态、页面路径、标题) 需要收集的数据 { &quo…...
Python学习笔记(三)
一、使用朴素贝叶斯制作鸢尾花数据模型 from sklearn.preprocessing import StandardScaler from sklearn.naive_bayes import MultinomialNB from sklearn.datasets import load_iris from sklearn.model_selection import train_test_split from sklearn.feature_extraction…...
Python办公自动化之Excel做表自动化:全网最全,看这一篇就够了!
0 Python Excel库对比 我们先来看一下python中能操作Excel的库对比(一共九个库): 1 Python xlrd 读取 操作Excel 1.1 xlrd模块介绍 (1)什么是xlrd模块? python操作excel主要用到xlrd和xlwt这两个库&…...
【学习笔记】R语言入门与数据分析1
数据分析 数据分析的过程: 数据采集 数据存储 数据分析 数据挖掘 数据可视化 进行决策 数据挖掘 数据量大 复杂度高,容忍一定的误差限 追求相关性而非因果性 数据可视化 直观明了 R语言介绍 R是免费的(开源软件、扩展性好)…...
MyBatis-Spring整合
引入Spring之前需要了解mybatis-spring包中的一些重要类; http://www.mybatis.org/spring/zh/index.html 什么是 MyBatis-Spring? MyBatis-Spring 会帮助你将 MyBatis 代码无缝地整合到 Spring 中。 知识基础 在开始使用 MyBatis-Spring 之前&#x…...
资深亚马逊运营实战技巧:跨境电商6大选品法
1、工具选品法 比如店雷达, 通过大数据分析工具选出来利基产品或者通过工具选出来利基的市场,然后再通过分析市场来得到产品。 以女装为例,通过大数据分析,全方位对市场需求、款式、质量等进行多维度判断,其中SKU销量…...
bugku-web-需要管理员
页面源码 <html> <head> <meta http-equiv"Content-Type" content"text/html; charsetUTF-8"> <title>404 Not Found</title> </head> <body> <div idmain><i> <h2>Something error:</h2…...
STM32之FreeRTOS移植
1.FreeRTOS的移植过程是将系统需要的文件和代码进行移植和裁剪,其移植的主要过程为: (1)官网上下载FreeRTOS源码:https://www.freertos.org/ (2)移植文件夹,在portable文件夹中只需…...
SpringBoot实用开发(十四)-- 消息(Message)的简单认识
目录 1.消息的概念 2.Java处理消息的标准规范 3.JMS 4.AMQP 5.MQTT 1.消息的概念 广义角度来说,消息其实就是信息,但是和信息又有所不同。信息通常被定义为一组数据,而消息除了具有数据的特征之外,还有...
【Spring Boot 源码学习】SpringApplication 的 run 方法核心流程介绍
《Spring Boot 源码学习系列》 SpringApplication 的 run 方法核心流程介绍 一、引言二、往期内容三、主要内容3.1 run 方法源码初识3.2 引导上下文 BootstrapContext3.3 系统属性【java.awt.headless】3.4 早期启动阶段3.5 准备和配置应用环境3.6 打印 Banner 信息3.7 新建应用…...
如何保证消息不丢失?——使用rabbitmq的死信队列!
如何保证消息不丢失?——使用rabbitmq的死信队列! 1、什么是死信 在 RabbitMQ 中充当主角的就是消息,在不同场景下,消息会有不同地表现。 死信就是消息在特定场景下的一种表现形式,这些场景包括: 消息被拒绝访问&am…...
html、css、京东移动端静态页面,资源免费分享,可作为参考,提供InsCode在线运行演示
CSDN将我上传的免费资源私自变成VIP专享资源,且作为作者的我不可修改为免费资源,不可删除,寻找客服无果,很愤怒,(我发布免费资源就是希望大家能免费一起用、一起学习),接下来继续寻找…...
头歌-机器学习 第13次实验 特征工程——共享单车之租赁需求预估
第1关:数据探索与可视化 任务描述 本关任务:编写python代码,完成一天中不同时间段的平均租赁数量的可视化功能。 相关知识 为了完成本关任务,你需要掌握: 读取数据数据探索与可视化 读取数据 数据保存在./step1/…...
关于iview组件中使用 table , 绑定序号分页后序号从1开始的解决方案
问题描述:iview使用table 中type: "index",分页之后 ,索引还是从1开始,试过绑定后台返回数据的id, 这种方法可行,就是后台返回数据的每个页面id都不完全是按照从1开始的升序,因此百度了下,找到了…...
连锁超市冷库节能解决方案:如何实现超市降本增效
在连锁超市冷库运营中,高能耗、设备损耗快、人工管理低效等问题长期困扰企业。御控冷库节能解决方案通过智能控制化霜、按需化霜、实时监控、故障诊断、自动预警、远程控制开关六大核心技术,实现年省电费15%-60%,且不改动原有装备、安装快捷、…...
系统设计 --- MongoDB亿级数据查询优化策略
系统设计 --- MongoDB亿级数据查询分表策略 背景Solution --- 分表 背景 使用audit log实现Audi Trail功能 Audit Trail范围: 六个月数据量: 每秒5-7条audi log,共计7千万 – 1亿条数据需要实现全文检索按照时间倒序因为license问题,不能使用ELK只能使用…...
Nginx server_name 配置说明
Nginx 是一个高性能的反向代理和负载均衡服务器,其核心配置之一是 server 块中的 server_name 指令。server_name 决定了 Nginx 如何根据客户端请求的 Host 头匹配对应的虚拟主机(Virtual Host)。 1. 简介 Nginx 使用 server_name 指令来确定…...
python爬虫:Newspaper3k 的详细使用(好用的新闻网站文章抓取和解析的Python库)
更多内容请见: 爬虫和逆向教程-专栏介绍和目录 文章目录 一、Newspaper3k 概述1.1 Newspaper3k 介绍1.2 主要功能1.3 典型应用场景1.4 安装二、基本用法2.2 提取单篇文章的内容2.2 处理多篇文档三、高级选项3.1 自定义配置3.2 分析文章情感四、实战案例4.1 构建新闻摘要聚合器…...
04-初识css
一、css样式引入 1.1.内部样式 <div style"width: 100px;"></div>1.2.外部样式 1.2.1.外部样式1 <style>.aa {width: 100px;} </style> <div class"aa"></div>1.2.2.外部样式2 <!-- rel内表面引入的是style样…...
在Ubuntu24上采用Wine打开SourceInsight
1. 安装wine sudo apt install wine 2. 安装32位库支持,SourceInsight是32位程序 sudo dpkg --add-architecture i386 sudo apt update sudo apt install wine32:i386 3. 验证安装 wine --version 4. 安装必要的字体和库(解决显示问题) sudo apt install fonts-wqy…...
【Go语言基础【13】】函数、闭包、方法
文章目录 零、概述一、函数基础1、函数基础概念2、参数传递机制3、返回值特性3.1. 多返回值3.2. 命名返回值3.3. 错误处理 二、函数类型与高阶函数1. 函数类型定义2. 高阶函数(函数作为参数、返回值) 三、匿名函数与闭包1. 匿名函数(Lambda函…...
C++:多态机制详解
目录 一. 多态的概念 1.静态多态(编译时多态) 二.动态多态的定义及实现 1.多态的构成条件 2.虚函数 3.虚函数的重写/覆盖 4.虚函数重写的一些其他问题 1).协变 2).析构函数的重写 5.override 和 final关键字 1&#…...
【JavaSE】多线程基础学习笔记
多线程基础 -线程相关概念 程序(Program) 是为完成特定任务、用某种语言编写的一组指令的集合简单的说:就是我们写的代码 进程 进程是指运行中的程序,比如我们使用QQ,就启动了一个进程,操作系统就会为该进程分配内存…...
