单链表详解

个人主页:不爱学英文的码字机器-CSDN博客

收录合集:《数据结构》

在本篇博客中,我们将深入探讨单链表的定义、实现和应用。

 本篇博客将用C语言实现的单链表进行讲解,通过一段代码一段讲解来逐个详细讲解,深入了解单链表的实现。


什么是单链表?

单链表是由一系列节点组成的数据结构,每个节点包含两部分:数据域和指针域。数据域用于存储数据元素,指针域用于指向下一个节点。单链表的最后一个节点指向NULL,表示链表的结束。

 不同于顺序表,顺序表的链接是物理上的空间连续,而单链表是用指针将第一个数据的尾和下一个数据的头相接(指向同一地址),具体如下图:

单链表的结构定义

typedef int SLTDataType;
struct SListNode
{SLTDataType data;struct SListNode* next;
};
typedef struct SListNode SLTNode;

首先通过typedef设置SLTDataType为int型,之后通过改变int的类型可以更轻松的改变单链表中的数据的类型。

在结构中再定义结构体指针,相当于逐个深入嵌套,在第一个结构中用next连接下一个结构,下一个结构中储存数据和连接下一个结构的结构体指针next,逐一递推,图示如下:

单链表的基本操作

  1. 创建链表:动态分配内存创建节点,通过指针连接节点形成链表。
  2. 插入节点:在指定位置插入新节点,调整指针连接关系。
  3. 删除节点:删除指定节点,调整指针连接关系并释放内存。
  4. 遍历链表:通过循环遍历链表中的所有节点,访问节点的数据域。
  5. 查找节点:根据数据值或位置查找节点。
  6. 反转链表:将链表的指针方向反转,实现链表的逆序。

单链表的代码实现

· 新节点的创建

SLTNode* BuySListNode(SLTDataType x)
{SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));newnode->data = x;newnode->next = NULL;return newnode;
}

· 表尾插入数据

void SListPushBack(SLTNode** pphead, SLTDataType x)
{SLTNode* newnode = BuySListNode(x);if (*pphead == NULL){*pphead = newnode;}else{// 找尾节点的指针SLTNode* tail = *pphead;while (tail->next != NULL){tail = tail->next;}// 尾节点,链接新节点tail->next = newnode;}
}

开文创建新节点,检查当前是否存在数据,若不存在即表头直接指向创建的newcode作为表头结构。创建结构体指针tail,若存在数据即不断递推寻找目前单链表的最后一个数据(直到找到NULL),然后再将找到最后的next地址与newcode相连,完成单链表尾部的插入。

当tail ->next为NULL时表明在当前的结构中的next指向的是NULL而不是下一个结构的地址,所以可以理解为让next指向newcode,以此完成链接。 

· 表头插入数据

void SListPushFront(SLTNode** pphead, SLTDataType x)
{SLTNode* newnode = BuySListNode(x);newnode->next = *pphead;*pphead = newnode;
}

创建newcode,newcode的next指向现在的表头地址即可完成链接。

· 表头删除数据

void SListPopFront(SLTNode** pphead)
{SLTNode* next = (*pphead)->next;free(*pphead);*pphead = next;
}

(*pphead)-> next 表示未添加前的表头的next,我们在刚开始创建结构体指针指向当前表头的next,这样就相当于先将表头设置成当前表头next所连接的下一个数据的地址,然后再将刚开始的表头给free掉,这样新的表头就是刚才的结构体指针next指向的地址了。在成功转移表头后就可以将原表头空间释放,达到从表头删除数据的操作。

· 表尾删除数据

void SListPopBack(SLTNode** pphead)
{// 1、空if (*pphead == NULL){return;}// 2、一个节点else if ((*pphead)->next == NULL){free(*pphead);*pphead = NULL;}// 3、一个以上的节点else{SLTNode* prev = NULL;SLTNode* tail = *pphead;while (tail->next != NULL){prev = tail;tail = tail->next;}free(tail);prev->next = NULL;}
}

在表尾删除数据时有三种情况,当单链表为空的时候return结束函数,当单链表只有一个数据时直接释放表头指向的空间,当有多个数据的时候才开始正式执行逻辑。我们再创建两个结构体指针prev和tail,用tail来寻找tail当前所在结构的next是不是NULL,因为prev永远指向的都比tail指向的结构前一位,所以当tail位置不再递推就表明已经到了最后一个数据位置。找到最后一个结构之后free掉tail,即释放了最后一个数据的空间,使它和链表切除联系。释放空间后prev的next指向的地址就变成了野指针(定义在下文讲解),所以将prev的next设置为NULL,完成了删除最后一个数据的最后步骤。

野指针:

  1. 指针变量未初始化:如果指针变量没有被初始化,它会包含一个随机的值,可能是一个未知的内存地址。
  2. 指针变量指向已经释放的内存:如果指针变量指向的内存已经被释放(通过free或delete操作),那么该指针就会变成野指针。
  3. 指针操作超出作用域:如果一个指针变量在其所指向的对象被销毁之后仍然被使用,那么该指针就会成为野指针。

· 查找数据

SLTNode* SListFind(SLTNode* phead, SLTDataType x)
{SListNode* cur = phead;//while (cur != NULL)while (cur){if (cur->data == x){return cur;}cur = cur->next;}return NULL;
}
  • 参数phead是一个指向SLTNode类型的指针,这是一个自定义的数据结构,表示单链表的头节点。
  • 参数x是一个SLTDataType类型的变量,它表示要查找的值。

函数内部使用了一个指针cur来遍历单链表。首先,将cur指向头节点phead。然后,使用一个while循环来遍历整个链表。在循环中,每次检查当前节点cur的值是否等于要查找的值x。如果相等,就返回当前节点的指针;如果不相等,就将cur指向下一个节点。如果遍历完整个链表都没有找到要查找的值,函数返回NULL。如此便完成查找数据的操作,返回数据所在地址。

· 指定位置前插入数据

// 在pos的前面插入x
void SListInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x)
{if (pos == *pphead){SListPushFront(pphead, x);}else{SLTNode* newnode = BuySListNode(x);SLTNode* prev = *pphead;while (prev->next != pos){prev = prev->next;}prev->next = newnode;newnode->next = pos;}
}
  • 参数pphead是一个指向SLTNode类型的指针的指针,这是一个双重指针,用于间接操作链表的头节点。

  • 参数pos是一个指向SLTNode类型的指针,它表示要插入节点的位置。

  • 参数x是一个SLTDataType类型的变量,它表示要插入的值

该函数分为两个情况,一种是在表头插入,一种是在其他地方插入。表头的话我们可以直接用上面的函数SListPushFront,其他地方的话先创建一个结构体指针指向新数据的空间,再通过创建的结构体指针prev来寻找到pos对应的前一个数据,然后用找到的prev的next指向插入新数据的地址,再将新数据的next指向pos的地址,完成连接。

· 删除指定位置的数据

// 删除pos位置的值
void SListErase(SLTNode** pphead, SLTNode* pos)
{if (pos == *pphead){SListPopFront(pphead);}else{SLTNode* prev = *pphead;while (prev->next != pos){prev = prev->next;}prev->next = pos->next;free(pos);}
}

当pos为当前头结点的时候用头删函数SListPopFront直接操作达到目的。其他情况下与插入数据中的方法相同,用prev寻找pos前的数据,然后用prev的next指向pos的next,也就是指向了pos的下一个数据,然后将pos空间释放掉,完成操作。

整体示例代码呈现

#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->data = x;newnode->next = NULL;return newnode;
}void SListPushBack(SLTNode** pphead, SLTDataType x)
{SLTNode* newnode = BuySListNode(x);if (*pphead == NULL){*pphead = newnode;}else{// 找尾节点的指针SLTNode* tail = *pphead;while (tail->next != NULL){tail = tail->next;}// 尾节点,链接新节点tail->next = newnode;}
}void SListPushFront(SLTNode** pphead, SLTDataType x)
{SLTNode* newnode = BuySListNode(x);newnode->next = *pphead;*pphead = newnode;
}void SListPopFront(SLTNode** pphead)
{SLTNode* next = (*pphead)->next;free(*pphead);*pphead = next;
}void SListPopBack(SLTNode** pphead)
{// 1、空// 2、一个节点// 3、一个以上的节点if (*pphead == NULL){return;}else if ((*pphead)->next == NULL){free(*pphead);*pphead = NULL;}else{SLTNode* prev = NULL;SLTNode* tail = *pphead;while (tail->next != NULL){prev = tail;tail = tail->next;}free(tail);prev->next = NULL;}
}SLTNode* SListFind(SLTNode* phead, SLTDataType x)
{SListNode* cur = phead;//while (cur != NULL)while (cur){if (cur->data == x){return cur;}cur = cur->next;}return NULL;
}// 在pos的前面插入x
void SListInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x)
{if (pos == *pphead){SListPushFront(pphead, x);}else{SLTNode* newnode = BuySListNode(x);SLTNode* prev = *pphead;while (prev->next != pos){prev = prev->next;}prev->next = newnode;newnode->next = pos;}
}// 删除pos位置的值
void SListErase(SLTNode** pphead, SLTNode* pos)
{if (pos == *pphead){SListPopFront(pphead);}else{SLTNode* prev = *pphead;while (prev->next != pos){prev = prev->next;}prev->next = pos->next;free(pos);}
}

以上就是本篇博客的全部内容了,感谢您的阅读!

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

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

相关文章

Java编程与数据库技术:疫情居家办公的坚实后盾

✍✍计算机毕业编程指导师 ⭐⭐个人介绍:自己非常喜欢研究技术问题!专业做Java、Python、微信小程序、安卓、大数据、爬虫、Golang、大屏等实战项目。 ⛽⛽实战项目:有源码或者技术上的问题欢迎在评论区一起讨论交流! ⚡⚡ Java、…

基于自适应波束成形算法的matlab性能仿真,对比SG和RLS两种方法

目录 1.程序功能描述 2.测试软件版本以及运行结果展示 3.核心程序 4.本算法原理 5.完整程序 1.程序功能描述 基于自适应波束成形算法的matlab性能仿真,对比SG和RLS两种方法. 2.测试软件版本以及运行结果展示 MATLAB2022a版本运行 3.核心程序 ........................…

字符函数和字符串函数(C语言进阶)(三)

目录 前言 接上篇: 1.7 strtok 1.8 strerror 1.9 字符分类函数 总结 前言 C语言中对字符和字符串的处理是很频繁的,但是c语言本身是没有字符串类型的,字符串通常放在常量字符串中或着字符数组中。 字符串常量适用于那些对它不做修改的字…

某电力铁塔安全监测预警系统案例分享

项目概述 电力铁塔是承载电力供应的重要设施,它的安全性需要得到可靠的保障。但是铁塔一般安装在户外,分布广泛,且有很多安装在偏远地区,容易受到自然、人力的影响和破环。因此需要使用辅助的方法实时监控铁塔的安全状态&#xff…

使用GPT生成python图表

首先,生成一脚本,读取到所需的excel表格 import xlrddata xlrd.open_workbook(xxxx.xls) # 打开xls文件 table data.sheet_by_index(0) # 通过索引获取表格# 初始化奖项字典 awards_dict {"一等奖": 0,"二等奖": 0,"三等…

HarmonyOS—代码Code Linter检查

Code Linter代码检查 Code-Linter针对ArkTS/TS代码进行最佳实践、编程规范方面的检查,目前还会检查ArkTS语法规则。开发者可根据扫描结果中告警提示手工修复代码缺陷,或者执行一键式自动修复,在代码开发阶段,确保代码质量。 检查…

第四节:Vben Admin登录对接后端getUserInfo接口

系列文章目录 第一节:Vben Admin介绍和初次运行 第二节:Vben Admin 登录逻辑梳理和对接后端准备 第三节:Vben Admin登录对接后端login接口 第四节:Vben Admin登录对接后端getUserInfo接口 文章目录 系列文章目录前言一、回顾Vben…

C语言内存管理-栈内存

栈内存 什么东西存储在栈内存中? 环境变量命令行参数局部变量(包括形参)栈内存有什么特点? 空间有限,尤其在嵌入式环境下。因此不可以用来存储尺寸太大的变量。每当一个函数被调用,栈就会向下增长一段&…

Codeforce Monsters Attack!(B题 前缀和)

题目描述: 思路: 本人第一次的想法是先杀血量低的第二次想法是先搞坐标近的第三次想法看到数据量这么大, 我先加个和看看貌似我先打谁都行,由此综合一下, 我们可以把每一个不同的坐标当作一轮从最小的坐标开始&#x…

适合新手博主站长使用的免费响应式WordPress博客主题JianYue

这款JianYue主题之所以命名为 JianYue,意思就是简单而不简约的。是根据Blogs主题优化而成,剔除了一些不必要的功能及排版,仅保留一种博客布局,让新手站长能够快速手上WordPress。可以说这款主题比较适合新手博主站长使用&#xff…

Programming Abstractions in C阅读笔记:p293-p302

《Programming Abstractions in C》学习第73天,p293-p302总结,总计10页。 一、技术总结 1.时间复杂度 (1)quadratic time(二次时间) p293, Algorithms like selection sort that exhibit O(N^2) performance are said to run in quadratic time。 2…

新鲜出炉 | 2024年六西格玛学习路线

一、入门篇:了解六西格玛的基本概念与核心原理 六西格玛的起源与发展:了解六西格玛的历史背景和发展过程,有助于我们更好地认识这一管理方法的优势和特点。 六西格玛的核心概念:学习六西格玛中的DMAIC(定义、测量、分…