【数据结构】--- 单链表的增删查改

前言:

经过了几个月的漫长岁月,回头时年迈的小编发现,数据结构的内容还没有写博客,于是小编赶紧停下手头的活动,补上博客以洗清身上的罪孽


目录

前言

           概念:

单链表的结构

我们设定一个哨兵位头节点给链表

单链表的基本操作

插入:

单链表的头插:

单链表的尾插:

单链表的遍历打印:从头节点开始,逐个访问链表中的每个节点,直到达到链表的末尾。

单链表的删除:删除链表中的指定节点,需要修改前一个节点的指针域以绕过被删除的节点。

单链表的头删:

单链表的尾删:

单链表元素的查找:根据给定的值或位置查找链表中的节点。

单链表的链表销毁:

验证:                                                                             

完整代码:

slist.h

slist.c

test.c

总结:


概念:

       单链表是一种链式存取的数据结构,用一组地址任意的存储单元存放线性表中的数据元素。链表中的数据是以结点来表示的,每个结点的构成:元素 (数据元素的映象) + 指针 (指示后继元素存储位置),元素就是存储数据的存储单元,指针就是连接每个结点的地址数据。单链表不要求逻辑上相邻的两个元素在物理位置上也相邻,因此不需要连续的存储空间。单链表是非随机的存储结构,即不能直接找到表中某个特定的结点。查找某个特定的结点时,需要从表头开始遍历,依次查找。 

单链表的结构

       在单链表中,每个节点由一个数据元素和一个指针构成。数据元素可以是任何类型,而指针则指向链表中的下一个节点。如果一个节点的指针为空(NULL),则表示它是链表的最后一个节点。单链表通常通过一个头指针来访问,头指针指向链表的第一个节点。

一个典型的单链表节点的结构可以用以下C语言代码表示:

typedef struct SListNode
{SLTDateType data;//数据域struct SListNode* next;// 指针域,指向下一个节点
}SListNode;
我们设定一个哨兵位头节点给链表
 SListNode* head = NULL; 
单链表的基本操作

单链表的基本操作包括初始化、遍历、插入、删除和查找等。

单链表的初始化:创建一个空链表,通常是通过创建一个头节点,其指针域为空。

插入:

在链表的指定位置插入一个新节点,需要修改前一个节点的指针域。

单链表的头插:

        断言确保头指针地址有效,先去动态申请一个新节点,然后将新节点的下一个节点指向头节点,将该新节点变为头节点,(不需要判断是否为空)


// 单链表的头插
void SListPushFront(SListNode** pplist, SLTDateType x) {assert(pplist);SListNode* newnode = BuySListNode(x);//SListNode* start = *pplist;newnode->next = *pplist;*pplist = newnode;}
单链表的尾插:

      断言确保头指针地址有效,再申请一个新节点,分两种情况,当链表为空时,直接新节点就是头节点,链表不为空时,从头节点遍历到最后一个节点,将新节点设为最后一个节点的下一个节点

// 单链表尾插
void SListPushBack(SListNode** pplist, SLTDateType x) {assert(pplist);SListNode* newnode = BuySListNode(x);SListNode* end = *pplist;if (*pplist != NULL) //链表非空{while (end->next != NULL)end = end->next;end->next = newnode;}else //链表为空{*pplist = newnode;}
}

     动态创建一个SListNode大小的节点,然后看看是否创建成功,成功后将数据放入该节点,并将该节点的下一个位置,置为空;


// 动态申请一个节点
SListNode* BuySListNode(SLTDateType x) {SListNode* newnode = (SListNode*)malloc(sizeof(SListNode));if (newnode == NULL) {perror("malloc fail");return NULL;}// 初始化数据域和指针域newnode->data = x;newnode->next = NULL;return newnode;
}
单链表在pos位置前插入
// 在pos的前面插入
void SLTInsert(SListNode** pphead, SListNode* pos, SLTDateType x) {assert(pphead);assert(*pphead);assert(pos);if (*pphead == pos)SListPushFront(pphead, x);else {SListNode* ne = pos;SListNode* newnode = BuySListNode(x);SListNode* start = *pphead;while (start->next != pos) {start = start->next;if (start == NULL) {assert(0);return;}}start->next = newnode;newnode->next = ne;}}
单链表在pos位置后插入
// 单链表在pos位置之后插入x
void SListInsertAfter(SListNode* pos, SLTDateType x) {assert(pos);/*SListNode* pre = pos;*/SListNode* newnode = BuySListNode(x);SListNode* ne = pos->next;pos->next = newnode;newnode->next = ne;}
单链表的遍历打印从头节点开始,逐个访问链表中的每个节点,直到达到链表的末尾。

  创建一个头指针,遍历一遍链表,每遍历一个节点,将这个节点的数据打印出来;

// 单链表打印
void SListPrint(SListNode* plist) {SListNode* head = plist;while (head != NULL) // 遍历链表直到NULL{printf("%d->", head->data);head = head->next;}printf("NULL\n");}

单链表的删除:删除链表中的指定节点,需要修改前一个节点的指针域以绕过被删除的节点。

单链表的头删:

      断言确保头指针地址有效,以及头指针不为空,将头指针保存好,然后将头指针置为头指针的下一个位置,再将保存的指针释放,并置为空

// 单链表头删
void SListPopFront(SListNode** pplist) {assert(pplist);assert(*pplist);SListNode* start = *pplist;*pplist = (*pplist)->next;free(start);start = NULL;}
单链表的尾删:

断言确保头指针地址有效,以及头指针不为空,分两种情况,

一种是头指针下一个节点为空,直接将其释放并置为空;

另外一种情况呢,就是设两个指针,找尾和尾的前驱节点,然后将尾节点释放并置为空,再将尾的前驱节点的下一个置为空

// 单链表的尾删
void SListPopBack(SListNode** pplist) {assert(pplist);assert(*pplist);//暴力检查是否为空链表,空链表不能删数据SListNode* end = *pplist;if ((*pplist)->next== NULL) {free(*pplist);*pplist = NULL;}else{//找尾SListNode* prev = *pplist;SListNode* tail = *pplist;while (tail->next != NULL){prev = tail;tail = tail->next;}free(tail);tail = NULL;prev->next = NULL;//假如只有一个节点这里就会非法访问}}
删除pos位置
// 删除pos位置
void SLTErase(SListNode** pphead, SListNode* pos) {assert(pphead);assert(*pphead);assert(pos);if (pos == *pphead)SListPopFront(pphead);else {SListNode* start = *pphead;while (start->next != pos) {start = start->next;if (start == NULL) {assert(0);return;}}start->next = pos ->next;free(pos);pos = NULL;}
}
单链表元素的查找:根据给定的值或位置查找链表中的节点。

        创建一个节点start将头节点指针复制一个副本,然后再指针不为空时不断将其指向下一个的同时,看看该节点的值与所要查找的值是否相等,相等返回该节点的结构体指针,当遍历完链表还没有找到,返回NULL;

// 单链表查找
SListNode* SListFind(SListNode* plist, SLTDateType x) {SListNode* start = plist;while (start != NULL) {if (start->data == x)return start;start = start->next;}return NULL;
}

单链表的链表销毁:

       先判断头指针是否为空,为空直接返回,不为空时创建一个start头指针储存头节点,再通过start遍历整个链表,在遍历过程中,将每一个节点释放掉,最后再将头节点释放,置为空;

//销毁链表
void SLTDestroy(SListNode** pphead) {if (*pphead == NULL) {return;}else {SListNode* start = *pphead;while (start != NULL) {SListNode* ne = start->next;free(start);start = ne;}free(*pphead);*pphead = NULL;}
}
验证:

我们再创建一个main的测试函数测试一下我们实现的单链表的各个功能有没有问题:

完整代码:
slist.h
#pragma once
// slist.h
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<stdbool.h>
#include<assert.h>
typedef int SLTDateType;
typedef struct SListNode
{SLTDateType data;struct SListNode* next;
}SListNode;// 动态申请一个节点
SListNode* BuySListNode(SLTDateType x);
// 单链表打印
void SListPrint(SListNode* plist);
// 单链表尾插
void SListPushBack(SListNode** pplist, SLTDateType x);
// 单链表的头插
void SListPushFront(SListNode** pplist, SLTDateType x);
// 单链表的尾删
void SListPopBack(SListNode** pplist);
// 单链表头删
void SListPopFront(SListNode** pplist);
// 单链表查找
SListNode* SListFind(SListNode* plist, SLTDateType x);
// 单链表在pos位置之后插入x
// 分析思考为什么不在pos位置之前插入
void SListInsertAfter(SListNode* pos, SLTDateType x);
// 单链表删除pos位置之后的值
// 分析思考为什么不删除pos位置?
void SListEraseAfter(SListNode* pos);// 在pos的前面插入
void SLTInsert(SListNode** pphead, SListNode* pos, SLTDateType x);
// 删除pos位置
void SLTErase(SListNode** pphead, SListNode* pos);
void SLTDestroy(SListNode** pphead);
slist.c
#include "slist.h"
// 动态申请一个节点
SListNode* BuySListNode(SLTDateType x) {SListNode* newnode = (SListNode*)malloc(sizeof(SListNode));if (newnode == NULL) {perror("malloc fail");return NULL;}newnode->data = x;newnode->next = NULL;return newnode;
}
// 单链表打印
void SListPrint(SListNode* plist) {SListNode* head = plist;while (head != NULL) {printf("%d->", head->data);head = head->next;}printf("NULL\n");}
// 单链表尾插
void SListPushBack(SListNode** pplist, SLTDateType x) {assert(pplist);SListNode* newnode = BuySListNode(x);SListNode* end = *pplist;if (*pplist != NULL) {while (end->next != NULL)end = end->next;end->next = newnode;}else {*pplist = newnode;}
}
// 单链表的头插
void SListPushFront(SListNode** pplist, SLTDateType x) {assert(pplist);SListNode* newnode = BuySListNode(x);//SListNode* start = *pplist;newnode->next = *pplist;*pplist = newnode;}
// 单链表的尾删
void SListPopBack(SListNode** pplist) {assert(pplist);assert(*pplist);//暴力检查是否为空链表,空链表不能删数据SListNode* end = *pplist;if ((*pplist)->next== NULL) {free(*pplist);*pplist = NULL;}else{//找尾SListNode* prev = *pplist;SListNode* tail = *pplist;while (tail->next != NULL){prev = tail;tail = tail->next;}free(tail);tail = NULL;prev->next = NULL;//假如只有一个节点这里就会非法访问}}
// 单链表头删
void SListPopFront(SListNode** pplist) {assert(pplist);assert(*pplist);SListNode* start = *pplist;*pplist = (*pplist)->next;free(start);start = NULL;}
// 单链表查找
SListNode* SListFind(SListNode* plist, SLTDateType x) {SListNode* start = plist;while (start != NULL) {if (start->data == x)return start;start = start->next;}return NULL;
}
// 单链表在pos位置之后插入xvoid SListInsertAfter(SListNode* pos, SLTDateType x) {assert(pos);/*SListNode* pre = pos;*/SListNode* newnode = BuySListNode(x);SListNode* ne = pos->next;pos->next = newnode;newnode->next = ne;}
// 单链表删除pos位置之后的值void SListEraseAfter(SListNode* pos) {assert(pos);assert(pos->next);SListNode* pre = pos;SListNode* ne = pos->next->next;free(pos->next);pos->next = ne;
}// 在pos的前面插入
void SLTInsert(SListNode** pphead, SListNode* pos, SLTDateType x) {assert(pphead);assert(*pphead);assert(pos);if (*pphead == pos)SListPushFront(pphead, x);else {SListNode* ne = pos;SListNode* newnode = BuySListNode(x);SListNode* start = *pphead;while (start->next != pos) {start = start->next;if (start == NULL) {assert(0);return;}}start->next = newnode;newnode->next = ne;}}
// 删除pos位置
void SLTErase(SListNode** pphead, SListNode* pos) {assert(pphead);assert(*pphead);assert(pos);if (pos == *pphead)SListPopFront(pphead);else {SListNode* start = *pphead;while (start->next != pos) {start = start->next;if (start == NULL) {assert(0);return;}}start->next = pos ->next;free(pos);pos = NULL;}
}
void SLTDestroy(SListNode** pphead) {if (*pphead == NULL) {free(pphead); pphead = NULL;}else {SListNode* start = *pphead;while (start != NULL) {SListNode* ne = start->next;free(start);start = ne;}*pphead = NULL;}
}
test.c
#include "slist.h"int main() {SListNode* head = NULL; // 尾插SListPushBack(&head, 1);SListPushBack(&head, 2);SListPushBack(&head, 3);printf("链表内容(尾插入后):");SListPrint(head);// 头插SListPushFront(&head, 0);printf("链表内容(头插入后):");SListPrint(head);// 查找SListNode* found = SListFind(head, 2);if (found) {printf("找到节点:%d\n", found->data);}else {printf("未找到节点\n");}// 头删SListPopFront(&head);printf("链表内容(头删后):");SListPrint(head);// 尾删SListPopBack(&head);printf("链表内容(尾删后):");SListPrint(head);// pos插入SListInsertAfter(head, 4); printf("链表内容(插入4后):");SListPrint(head);// pos删除SLTErase(&head, head->next);printf("链表内容(删除第二个节点后):");SListPrint(head);// 销毁SLTDestroy(&head);//destroy后不能再访问// printf("链表内容(销毁后):");//SListPrint(head); return 0;
}

总结:

       本篇关于单链表的讲解到这里就结束啦,后续小编会带来更多精彩实用的内容,对你有帮助的可以点个赞,欢迎各位队列交流学习

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

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

相关文章

【JAVA】数据类型与变量:深入理解栈内存分配(4)

核心知识点详细解释 Java 的基本数据类型和引用数据类型 基本数据类型 Java 有 8 种基本数据类型&#xff0c;它们可以分为 4 类&#xff1a; 整数类型&#xff1a;byte&#xff08;1 字节&#xff09;、short&#xff08;2 字节&#xff09;、int&#xff08;4 字节&#…

ReentrantLock实现公平锁和非公平锁

在 Java 里&#xff0c;公平锁和非公平锁是多线程编程中用于同步的两种锁机制&#xff0c;它们的主要差异在于获取锁的顺序规则。下面是对二者的详细介绍&#xff1a; 公平锁 公平锁遵循 “先来先服务” 原则&#xff0c;也就是线程获取锁的顺序和请求锁的顺序一致。先请求锁…

一篇撸清 Http,SSE 与 WebSocket

HTTP,SSE 和WebSocket都是网络传输的协议,本篇快速介绍三者的概念和比较。 SSE(Server-Sent Events) 是什么? SSE(Server-Sent Events),服务器发送事件, 是一种基于 HTTP 的轻量级协议,允许服务器主动向客户端(如浏览器)推送实时数据。它设计用于单向通信(服务器到…

5个重要的财务指标讲解

1&#xff09;净资产收益率 2&#xff09;销售净利率 3&#xff09; 销售毛利率 4&#xff09;销售成本率 5&#xff09; 期间费用率 好的&#xff0c;我将通过一个假设的案例&#xff08;某公司2023年数据&#xff09;逐步解释这些财务指标&#xff0c;并用具体数字演示计算…

PISI:眼图1:眼图相关基本概念

0 英文缩写 TIE&#xff08;Time Interval Error&#xff09;时间间隔误差&#xff0c;UI&#xff08;Unit Interval&#xff09;单位间隔PDF&#xff08;Probability Density Function&#xff09;概率密度函数BER&#xff08;Bit Error Rate&#xff09;误码率TJ&#xff08…

前端八股 CSS 2 选择器

选择器功能&#xff1a;选中特定 DOM节点进行渲染 原始方法 getElementById() getElementByName() 现在方法选择器 分类&#xff1a; id选择器 类选择器 标签选择器 逻辑与选择器 其他类型选择器&#xff1a; 伪类选择器&#xff1a; :link&#xff1a;未被访问的链接…

算法竞赛进阶指南.闇の連鎖

目录 题目算法标签: 树上差分, L C A LCA LCA, 倍增思路代码 题目 352. 闇の連鎖 算法标签: 树上差分, L C A LCA LCA, 倍增 思路 对于一个无向图, 第一次切断树边, 第二次切非树边, 一共多少种方案使得图不连通, 点数和边数都很大, 时间复杂度不能是 O ( n 2 ) O(n ^ 2…

ActiveMQ 与其他 MQ 的对比分析:Kafka/RocketMQ 的选型参考(二)

ActiveMQ、Kafka 和 RocketMQ 详细对比 性能对比 在性能方面&#xff0c;Kafka 和 RocketMQ 通常在高吞吐量场景下表现出色&#xff0c;而 ActiveMQ 则相对较弱。根据相关测试数据表明&#xff0c;Kafka 在处理大规模日志数据时&#xff0c;单机吞吐量可以达到每秒数十万条甚…

Electron 从零开始:构建你的第一个桌面应用

&#x1f5a5;️ Electron 从零开始&#xff1a;构建你的第一个桌面应用 Electron 是一个可以使用 HTML、CSS 和 JavaScript 构建跨平台桌面应用的框架。它将 Chromium 和 Node.js 融合到一个环境中&#xff0c;使 Web 开发者也能轻松开发原生桌面应用。 &#x1f680; 什么是 …

相向双指针-16. 最接近的三数之和

16. 最接近的三数之和 题目描述思路讲解代码展示复杂度分析相关标签 题目描述 思路讲解 思路和 15. 三数之和 类似&#xff0c;排序后&#xff0c;枚举 nums[i] 作为第一个数&#xff0c;那么问题变成找到另外两个数&#xff0c;使得这三个数的和与 target 最接近&#xff0c;…

C 语 言 - - - 文 件 操 作

C 语 言 - - - 文 件 操 作 文 件文 件 名文 件 操 作fopenfclose 文 件 的 顺 序 读 写fputcfgetcfputsfgetsfprintffscanffwritefread 流文 件 的 随 机 读 写fseekftellrewind 总结 &#x1f4bb;作 者 简 介&#xff1a;曾 与 你 一 样 迷 茫&#xff0c;现 以 经 验 助 你…

Walrus 与 Pudgy Penguins 达成合作,为 Web3 头部 IP 引入去中心化存储

以将深受喜爱的数字藏品赋予生命而闻名的 IP 与品牌开发公司 Pudgy Penguins&#xff0c;现已集成 Walrus&#xff0c;用于存储和管理其日益增长的数字媒体资源库&#xff0c;包括在其产品和社区体验中使用的贴纸和 GIF。团队将率先通过 Tusky&#xff08;Walrus 的用户友好型文…

2019ICPC陕西省赛暨陕西邀请赛题解 BCDEF HIJKL

共111支队伍&#xff0c;获奖情况&#xff08;大概&#xff09; 铜牌66 —— 3 296 银牌33 —— 4 391 金牌 11 —— 6 808 题目难度&#xff08;过题&#xff09;L F E B C I J D K H Problem - L - Codeforces 思路&#xff1a;注意到答案是连乘&#xff0c;只要有0…

5块钱的无忧套餐卡可以变成流量卡吗

电信的 5 块钱无忧套餐卡理论上可以变成流量卡&#xff0c;但会受到一些条件限制&#xff0c;以下是具体介绍&#xff1a; 中国电信无忧卡简介 中国电信无忧卡是电信推出的低月租套餐&#xff0c;月租仅 5 元&#xff0c;包含 200M 国内流量、来电显示和 189 邮箱&#xff0c;全…

SpringBoot校园失物招领平台源码开发实现

概述 实用的​​SpringBoot校园失物招领平台​​完整项目源码&#xff0c;帮助开发者快速构建校园失物招领系统。该项目采用SpringBootVue前后端分离架构&#xff0c;包含完整的注册登录、信息发布、认领管理等模块&#xff0c;是学习企业级项目开发的优秀范例 主要内容 1. …

如何在纯C中实现类、继承和多态(小白友好版)

基本实现原理 /* 通过结构体函数指针模拟类 */ typedef struct {// 成员变量int x; // 成员方法&#xff08;函数指针&#xff09; void (*print)(void* self); } MyClass;/* 成员函数实现 */ void my_print(void* self) {MyClass* obj (MyClass*)self;p…

51单片机入门教程——每个音符对应的重装载值

前言 本教程基于B站江协科技课程进行个人学习整理&#xff0c;专为拥有C语言基础的零基础入门51单片机新手设计。既帮助解决因时间差导致的设备迭代调试难题&#xff0c;也助力新手快速掌握51单片机核心知识&#xff0c;实现从C语言理论到单片机实践应用的高效过渡 。

股票单因子的检验方法有哪些?

股票单因子的检验方法主要包括以下四类方法及相关指标&#xff1a; 一、统计指标检验 IC值分析法 定义&#xff1a;IC值&#xff08;信息系数&#xff09;衡量因子值与股票未来收益的相关性&#xff0c;包括两种计算方式&#xff1a; Normal IC&#xff1a;基于Pearson相关系数…

洛谷 P8606 [蓝桥杯 2013 国 B] 高僧斗法 博弈论

题目 传送门 P8606 [蓝桥杯 2013 国 B] 高僧斗法 - 洛谷 思路 这个题就比较考验博弈的基本题型和转换能力了&#xff1b; 这个题是nim博弈>阶梯博弈 再将小和尚的移动转化为阶梯上石子的移动&#xff1a;两个小和尚之间可以移动的距离&#xff0c;看做阶梯上的石子&…

《政治最后的日子》章节

政治与中世纪教会的类比性衰落 作者提出现代民族国家正重复中世纪教会的衰落轨迹&#xff1a; 两者均曾作为社会组织核心存在约5个世纪 晚期都成为生产力阻碍&#xff08;中世纪教会税收负担/现代国家官僚低效&#xff09; 末期均出现管理者普遍腐败与公众蔑视&#xff08;…