数据结构与算法—单链表

目录

一、链表

1、链表的概念及结构

2、分类

二、实现单向链表

1、声明链表结构体 

2、输出

3、头插&尾插

4、头删尾删

5、查找

6、指定位置插入

7、删除指定节点

8、删除指定节点的后一个节点

9、单链表的销毁

完整版

LList.h

LList.c

text.c


一、链表

 1、链表的概念及结构

概念:链表是一种物理存储结构上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的 。

这张图生动形象地呈现了链表的结构。 

如同高铁一般,从头到尾一个连着一个。

2、分类

主要有两种类型的链表:单向链表和双向链表。在 单向链表中,每个节点包含一个数据元素和一个指向下一个节点的引用。而在 双向链表中,每个节点有两个引用,一个指向前一个节点,另一个指向后一个节点。

 

  • 从上图可看出,链式结构在逻辑上是连续的,但是在物理上不一定连续。
  • 现实中的结点一般都是从堆上申请出来的。
  • 从堆上申请的空间,是按照一定的策略来分配的,两次申请的空间可能连续,也可能不连续

本次讲解基础的单向链表。

二、实现单向链表

 我们创建三个文件:

  • 头文件LList.h用于调用库函数、声明结构体和函数。
  • 源文件LList.c存储函数。
  • 源文件text.c进行测试。

每个源文件都必须包含LList.h。

1、声明链表结构体 

#include <stdio.h>typedef int SLTDataType;
typedef struct SListNode
{SLTDataType data;struct SListNode* next;
}SLTNode;
  • 将链表的数据类型用SLTDatatype这个别名代TT替int,以后程序中使用到元素数据类型时都替换成SLTDatatype,方便日后修改顺序表数据类型。
  • 将结构体struct SListNode定义别名为SLTNode。
  • 结构体成员data为链表节点数据,数据类型是 SLTDataType。
  • next表示一个指向同类型结构体的指针,它指向单向链表下一个结点。

2、输出

void SLTPrint(SLTNode* phead)
{SLTNode* cur = phead;while (cur != NULL) {printf("%d->", cur->data);cur = cur->next;}printf("NULL\n");
}
  •  接收传入参数为结构体的地址
  • 结构体指针cur指向头结点phead
  • 循环遍历链表,当cur不指向尾节点,则打印输出当前节点数据,cur指向下一个节点。
  • cur指向尾节点打印NULL。

3、头插&尾插

头插 

void SLPushFront(SLTNode** pphead, SLTDataType x)
{assert(pphead);SLTNode* newnode = BuyLTNode(x);newnode->next = *pphead;*pphead = newnode;
}
  •  插入数据会修改头结点,所以传入头结点指针的地址,使用二级指针接收。
  • assert判断传入头节点指针的地址是否合法,为空则报错。(需要包含头文件<assert.h>)
  • *pphead 不需要断言,如果传入的链表为空,也可以进行插入数据。
  •  为新节点newnode开辟空间并将x储存其中,因为后续经常用到开辟空间,所以将这部分操作放入函数中。
  • 新节点newnode的next指针指向头结点*pphead
  • 头结点更新为newnode。

接下来讲解为新节点开辟空间的函数 BuyLTNode

新节点开辟空间  

SLTNode* BuyLTNode(SLTDataType x)
{SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));if (newnode == NULL) {perror("malloc fall");return;}newnode->data = x;newnode->next = NULL;return newnode;
}
  • 为新节点开辟空间,返回值为新节点的地址,所以函数类型为 SLTNode* 结构体指针类型。
  • malloc函数为newnode开辟结构体大小个字节。
  • 判断是否开辟成功,失败则打印错误信息,结束函数运行。
  • 将新节点的数据data赋值为传入参数 x。
  • next赋值为空。

尾插

void SLPushBack(SLTNode** pphead, SLTDataType x)
{assert(pphead); SLTNode* newnode = BuyLTNode(x);if (*pphead == NULL){*pphead = newnode;}else {SLTNode* tail = *pphead;while (tail->next != NULL)tail = tail->next;tail->next = newnode;}		
}
  •  assert判断传入头节点指针的地址是否合法,为空则报错。
  • 为新节点newnode开辟空间并将x储存其中。
  • 插入时分两种情况:空链表 非空链表
  • 如果链表为空则直接将*pphead 指向新节点 newnode,使其成为新的链表的头节点。
  • 如果链表不为空,则创建变量tail指向头结点,循环遍历链表使tail指向尾节点,将新节点地址赋值给tail的next,成功将新节点添加到链表尾部。

4、头删尾删

头删

void SLPopFront(SLTNode** pphead)
{assert(pphead); assert(*pphead);SLTNode* del = *pphead;*pphead = (*pphead)->next;free(del);
}
  • 第一个assert判断传入头节点指针的地址是否合法,为空则报错。
  • 第二个assert判断链表头节点是否为空,为空无法删除,则报错。
  • 定义变量del指向头节点,以便稍后释放该节点的内存。
  • 头节点指向头节点的next,也就是指向后一个节点。
  • 使用free函数释放del指向的已删除头节点空间。(需要包含头文件<stdlib.h>)

尾删 

void SLPopBack(SLTNode** pphead)
{assert(pphead);assert(*pphead);if ((*pphead)->next == NULL) {free(*pphead);*pphead = NULL;}else {SLTNode* tail = *pphead;while (tail->next->next) {tail = tail->next;}free(tail->next);tail->next = NULL;}
}
  • 第一个assert判断传入头节点指针的地址是否合法,为空则报错。
  • 第二个assert判断链表头节点是否为空,为空则报错。
  • 链表只有一个节点时,直接释放头节点空间,然后置空。
  • 链表有多个节点使,通过循环使变量 tail->next 找到尾节点,然后释放tail后一个节点的空间,也就是尾节点的空间,同时将其置空。

5、查找

SLTNode* STFind(SLTNode* phead, SLTDataType x)
{SLTNode* cur = phead;while (cur) {if (cur->data == x)return cur;cur = cur->next;}return NULL;
}
  •  函数在单链表中查找包含特定数据值 x 的节点。
  • 变量cur通过循环找到数据data等于x的节点。
  • 找到则返回指向当前节点的指针 cur,否则返回值为空。

6、指定位置插入

指定位置之前


void SLInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x)
{assert(pphead);assert(pos);if (*pphead == pos){SLPushFront(pphead, x);}else {SLTNode* prev = *pphead;while (prev->next != pos){prev = prev->next;}SLTNode* newnode = BuyLTNode(x);prev->next = newnode;newnode->next = pos;}
}
  • 第一个assert判断传入头节点指针的地址是否合法,为空则报错。
  • 第二个assert判断传入指向链表中某个节点的指针pos是否合法,不存在则报错。
  • 如果在头节点位置之前插入,则调用头插解决。
  • 如果不是头节点位置,则创建一个指向链表头节点的指针 prev,然后使用循环找到要插入位置 pos 前面的节点。
  • 创建一个新的节点 newnode 并将数据值 x 存储在其中。

  • 修改 prev 节点的 next 指针,使其指向新节点 newnode,从而将新节点插入到 pos 前面。

 指定位置之后

void SLInsertAfter(SLTNode* pos, SLTDataType x)
{assert(pos);SLTNode* newnode = BuyLTNode(x);newnode->next = pos->next;pos->next = newnode;
}
  •  assert判断传入指向链表中某个节点的指针pos是否合法,不存在则报错。
  • 创建一个新的节点 newnode 并将数据值 x 存储在其中。
  • newnode的next指针指向pos的后一项。
  • pos的next指向新节点newnode。

7、删除指定节点

void SLErase(SLTNode** pphead, SLTNode* pos)
{assert(pphead);assert(*pphead);if (pos = *pphead){SLPopFront(pphead);}else {SLTNode* prev = *pphead;while (prev->next != pos) {prev = prev -> next;}prev->next = pos->next;free(pos);}
}
  • 第一个assert判断传入头节点指针的地址是否合法,为空则报错。
  • 第二个assert判断链表头节点是否为空,为空则报错。
  • pos节点为头节点,则调用头删解决。
  • pos不为头节点,则创建变量prev指向头节点,通过循环找到pos节点的前一个节点。
  • 将prev的next指向要删除的pos节点的下一个节点。
  • 释放pos空间

8、删除指定节点的后一个节点

void SLEraseAfter(SLTNode* pos)
{assert(pos);assert(pos->next);SLTNode* next = pos->next;pos->next = next->next;free(next);
}
  •  创建一个指向要删除的节点 pos 后面节点的指针 next,以便稍后释放该节点的内存。
  • 修改 pos 节点的 next 指针,将其指向 next 的下一个节点,从而绕过要删除的节点,使链表不再包含它。

  • 最后,使用 free 函数释放 next 指向的节点的内存,完成删除操作。

9、单链表的销毁

void SListDestroy(SLTNode* pphead)
{SLTNode* cur = pphead;SLTNode* tmp = NULL;while (cur != NULL) {tmp = cur;cur = cur->next;free(tmp);}
}
  • 定义了两个指针,cur 和 tmp,用于遍历链表并释放内存。开始时,cur 被初始化为链表的头节点指针 pphead
  • 这是一个循环,它会一直执行,直到 cur 变为 NULL,也就是遍历到链表的末尾。

  • 在循环中,首先将 cur 赋值给 tmp,以便稍后释放 cur 指向的节点的内存。

  • 然后,将 cur 移动到下一个节点,即 cur = cur->next;

  • 最后,使用 free 函数释放 tmp 指向的节点的内存,即释放链表中的一个节点,接着进行循环依次释放节点直到链表最后。

完整版

LList.h

#include <stdio.h>
#include <stdlib.h>
#include <assert.h>typedef int SLTDataType;
typedef struct SListNode
{SLTDataType data;struct SListNode* next;
}SLTNode;//打印链表
void SLTPrint(SLTNode* phead);//头插尾插
void SLPushFront(SLTNode** pphead, SLTDataType x);
void SLPushBack(SLTNode** pphead, SLTDataType x);//头删尾删
void SLPopFront(SLTNode** pphead);
void SLPopBack(SLTNode** pphead);// 单链表查找
SLTNode * STFind(SLTNode * phead, SLTDataType x);// 在pos之前插入
void SLInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x);
void SLInsertAfter(SLTNode* pos, SLTDataType x);// 删除pos位置的值
void SLErase(SLTNode** pphead, SLTNode* pos);// 删除pos位置后面的值
void SLEraseAfter(SLTNode* pos);// 单链表的销毁
void SListDestroy(SLTNode* plist);

LList.c

#define _CRT_SECURE_NO_WARNINGS 1
#include "LList.h"void SLTPrint(SLTNode* phead)
{SLTNode* cur = phead;while (cur != NULL) {printf("%d->", cur->data);cur = cur->next;}printf("NULL\n");
}SLTNode* BuyLTNode(SLTDataType x)//为新元素开辟空间
{SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));if (newnode == NULL) {perror("malloc fall");return;}newnode->data = x;newnode->next = NULL;return newnode;
}void SLPushFront(SLTNode** pphead, SLTDataType x)//头插
{assert(pphead);  // 链表为空,pphead也不为空,因为他是头指针plist的地址//assert(*pphead); // 不能断言,链表为空,也需要能插入SLTNode* newnode = BuyLTNode(x);//newnode是局部变量newnode->next = *pphead;//头插后首节点next指向原有的首节点*pphead = newnode;//将链表的头指针 *pphead 指向新插入的节点
}void SLPushBack(SLTNode** pphead, SLTDataType x)
{assert(pphead); // 链表为空,pphead也不为空,因为他是头指针plist的地址SLTNode* newnode = BuyLTNode(x);//两种情况//空链表  非空链表if (*pphead == NULL)//链表为空改变结构体指针*pphead = newnode;else {//不为空,则改变结构体的节点SLTNode* tail = *pphead;while (tail->next != NULL)tail = tail->next;tail->next = newnode;}		
}void SLPopFront(SLTNode** pphead)
{assert(pphead); // 链表为空,pphead也不为空,因为他是头指针plist的地址assert(*pphead); // 链表为空,不能头删。(当然你还可以用温柔的检查)SLTNode* del = *pphead;//指针del用于释放节点空间*pphead = (*pphead)->next;free(del);
}void SLPopBack(SLTNode** pphead)
{assert(pphead); // 链表为空,pphead也不为空,因为他是头指针plist的地址assert(*pphead); // 链表为空,不能头删。(当然你还可以用温柔的检查)//只有一个节点if ((*pphead)->next == NULL) {free(*pphead);*pphead = NULL;//修改头节点为空}else {//第一种增加前项变量//SLTNode* prev = NULL;//SLTNode* tail = *pphead;//while (tail->next) {//	prev = tail;//	tail = tail->next;//}//free(tail);//prev->next = NULL;//第二种不新增变量//改变结构体的节点SLTNode* tail = *pphead;while (tail->next->next) {tail = tail->next;}free(tail->next);//将指向的最后一个节点释放tail->next = NULL;}
}SLTNode* STFind(SLTNode* phead, SLTDataType x)//找到返回链表地址
{SLTNode* cur = phead;while (cur) {if (cur->data == x)return cur;cur = cur->next;}return NULL;
}// 在pos之前插入
void SLInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x)
{assert(pphead);assert(pos);if (*pphead == pos){//在头节点前插入等于头插SLPushFront(pphead, x);}else {SLTNode* prev = *pphead;//用于找到pos前的位置while (prev->next != pos){prev = prev->next;}SLTNode* newnode = BuyLTNode(x);prev->next = newnode;//pos前一个位置next指向新开辟节点newnode->next = pos;//新节点next指向pos}
}// 在pos之后插入
void SLInsertAfter(SLTNode* pos, SLTDataType x)
{assert(pos);SLTNode* newnode = BuyLTNode(x);//下面两行不能调换顺序,否则无法链接新节点后项节点newnode->next = pos->next;pos->next = newnode;
}void SLErase(SLTNode** pphead, SLTNode* pos)// 删除pos位置的值
{assert(pphead);assert(*pphead);//链表为空则不能删除if (pos = *pphead){SLPopFront(pphead);}else {SLTNode* prev = *pphead;while (prev->next != pos) {//找到pos前一个节点prev = prev -> next;}prev->next = pos->next;//将pos前一个节点的next指向pos后一个节点free(pos);//释放pos空间}
}void SLEraseAfter(SLTNode* pos)
{assert(pos);assert(pos->next);//后项为空则不能删除SLTNode* next = pos->next;pos->next = next->next;free(next);
}void SListDestroy(SLTNode* pphead)
{SLTNode* cur = pphead;SLTNode* tmp = NULL;while (cur != NULL) {tmp = cur;cur = cur->next;free(tmp);}
}

text.c

#define _CRT_SECURE_NO_WARNINGS 1#include "LList.h"void test1()
{SLTNode* plist = NULL;SLPushFront(&plist, 5);SLPushFront(&plist, 4); SLPushFront(&plist, 3);SLPushBack(&plist, 6);//SLPopFront(&plist);SLTNode* pos = STFind(plist, 3);SLInsert(&plist, pos, 99);//pos = STFind(plist, 2);//if (pos)//{//	SLInsertAfter(pos, 20);//}//SLPopBack(&plist);//SLPopBack(&plist);//SLPopBack(&plist);//SLPopBack(&plist);//SLPopBack(&plist);SLTPrint(plist);
}int main()
{test1();return 0;
}

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

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

相关文章

BI工具:让数据分析井然有序一望而知

BI&#xff08;Business Intelligence&#xff09;工具是一类专门用于数据分析和决策支持的软件工具。 它们能够将企业内部和外部的数据进行整合、处理和可视化&#xff0c;帮助用户从海量数据中获取有价值的见解和洞察&#xff0c;并以直观、易懂的方式展示给决策者和相关人员…

ios app开发环境搭建

Xcode是Apple iOS的应用市场app store移动应用的开发工具&#xff0c;支持不同设备、不同应用场景的开发&#xff0c;本文主要描述xcode开发工具开发环境的搭建。 如上所示&#xff0c;在macos中&#xff0c;使用app store安装xcode开发工具 如上所示&#xff0c;在macos中&…

【网络协议】聊聊ifconfig

我们知道在linux是ifconfig查看ip地址&#xff0c;但是ip addr也可以查看 IP 地址是一个网卡在网络世界的通讯地址&#xff0c;相当于我们现实世界的门牌号码。 从IP地址的划分来看&#xff0c;C类地址只可以容纳254个&#xff0c;而B类6W多&#xff0c;那么又没有一种折中的…

自动化办公篇之python

1、如果没有安装xlwings库&#xff0c;先在控制台pip install xlwings,然后点击运行&#xff0c;创建四个空excel表 。 import xlwings as xw app xw.App(visibleTrue,add_bookFalse) for dept in ["技术部","销售部","运营部","财务部&q…

74.C++ STL stack容器

目录 1.什么是stack 2.stack的构造函数 3.赋值操作 4.数据存取操作 5.大小操作 1.什么是stack stack 是 C 标准库中的容器适配器&#xff0c;它提供了一个堆栈&#xff08;栈&#xff09;数据结构的封装&#xff0c;用于管理元素的插入和移除。栈是一种后进先出的数据结构…

GaN器件的工作原理

目录 AlGaN/GaNHEMT 器件工作原理&#xff08;常开-耗尽型器件&#xff09;常关 AlGaN/GaN 功率晶体管&#xff08;增强型器件&#xff09;HD-GIT与SP-HEMT AlGaN/GaNHEMT 器件工作原理&#xff08;常开-耗尽型器件&#xff09; 来源&#xff1a;毫米波GaN基功率器件及MMIC电路…

Mybatis学习笔记注解/xml映射/动态SQL%%%Mybatis教程

介绍 Mybatis 是一款优秀的持久层框架&#xff0c;用于简化 JDBC 的开发 MyBatis中文网 Mybatis 入门 快速入门 步骤 创建 SpringBoot 工程、数据库表 user、实体类 User引入 Mybatis 相关依赖&#xff0c;配置 Mybatis&#xff08;数据库连接信息&#xff09;编写 SQL 语…

Scraping 和Crawling的区别与联系

在互联网时代&#xff0c;获取网页上的数据对于许多人来说已经成为一种常态。在这个过程中&#xff0c;我们经常会听到两个词&#xff1a;Web Scraping&#xff08;网页抓取&#xff09;和Web Crawling&#xff08;网络爬虫&#xff09;&#xff0c;它们看似相似&#xff0c;但…

大语言模型之十七-QA-LoRA

由于基座模型通常需要海量的数据和算力内存&#xff0c;这一巨大的成本往往只有巨头公司会投入&#xff0c;所以一些优秀的大语言模型要么是大公司开源的&#xff0c;要么是背后有大公司身影公司开源的&#xff0c;如何从优秀的开源基座模型针对特定场景fine-tune模型具有广大的…

Idea使用技巧——导包优化,新版idea界面取消,界面字体放大缩小

导包优化 on the fly 翻译为立刻 第一个表示&#xff0c;如果导入的包没有冲突&#xff0c;会自动帮你导入。如果输入List 无需手动altenter 第二个表示&#xff0c;没有引用的包&#xff0c;会自动删除。这个对于代码整洁尤其适用。相当于ctrlalto的功能 新版idea的界面取消 …

【RWKV】如何新增一个自定义的Tokenizer和模型到HuggingFace

0x0. 前言 RWKV社区在Huggingface上放了rwkv-4-world和rwkv-5-world相关的一系列模型&#xff0c;见&#xff1a;https://huggingface.co/BlinkDL/rwkv-4-world & https://huggingface.co/BlinkDL/rwkv-5-world &#xff0c;然而这些模型的格式是以PyTorch的格式进行保存的…

spark中使用flatmap报错:TypeError: ‘int‘ object is not subscriptable

1、背景描述 菜鸟笔者在运行下面代码时发生了报错&#xff1a; from pyspark import SparkContextsc SparkContext("local", "apple1012")rdd sc.parallelize([[1, 2], 3, [7, 5, 6]])rdd1 rdd.flatMap(lambda x: x) print(rdd1.collect())报错描述如…

【Proteus仿真】【51单片机】智能语音家居陪护机器人

文章目录 一、功能简介二、软件设计三、实验现象联系作者 一、功能简介 本项目使用Proteus8仿真51单片机控制器&#xff0c;使用OLED液晶、按键、蜂鸣器、DS18B20温度传感器、人体红外传感器、语音识别模块、继电器、风扇、LED等。 主要功能&#xff1a; 系统运行后&#xff0…

自定义jenkins镜像提示FontConfiguration.head错误

系统使用&#xff1a;Debian12&#xff0c;jdk17 提示问题&#xff1a;缺少字体 找一台jdk8的环境&#xff0c;在lib文件夹中找到fontconfig.bfc find / -name *fontconfig* 复制到jenkins目标服务器中&#xff0c;jdk目录的lib中 再次启动jenkins服务正常

docker数据卷+挂载(命令讲解+示例)

在容器中管理数据主要有两种方式&#xff1a; 数据卷&#xff08;Volumes&#xff09; 、挂载主机目录 (Bind mounts)。 一、数据卷 数据卷是一个可供一个或多个容器使用的特殊目录&#xff0c;可以在容器之间共享和重用。 特点&#xff1a; 对 数据卷 的修改会立马生效对 …

基于uniapp的商城外卖小程序

博主主页&#xff1a;猫头鹰源码 博主简介&#xff1a;Java领域优质创作者、CSDN博客专家、公司架构师、全网粉丝5万、专注Java技术领域和毕业设计项目实战 主要内容&#xff1a;毕业设计(Javaweb项目|小程序等)、简历模板、学习资料、面试题库、技术咨询 文末联系获取 项目介绍…

利用向导创建MFC

目录 1、项目的创建&#xff1a; 2、项目的管理 &#xff1a; 3、分析以及生成的项目代码 &#xff1a; &#xff08;1&#xff09;、查看CFrame中的消息映射宏 &#xff08;2&#xff09;、自动生成事件 &#xff08;3&#xff09;、在CFrame中添加对应的鼠标处理函数 …

Centos7安装Gitlab--gitlab--ee版

1 安装必要依赖 2 配置GitLab软件源镜像 3 下载安装GitLab 4 查看管理员root用户默认密码 5 登录GitLab 6 修改密码 7 gitlab相关命令 1 安装必要依赖 sudo yum install -y curl policycoreutils-python openssh-server perl sudo systemctl enable sshd sudo systemctl sta…

【Java】jvm 元空间、常量池(了解)

JDK1.8 以前的 HotSpot JVM 有方法区&#xff0c;也叫永久代&#xff08;permanent generation&#xff09;方法区用于存放已被虚拟机加载的类信息&#xff0c;常量、静态遍历&#xff0c;即编译器编译后的代码JDK1.7 开始了方法区的部分移除&#xff1a;符号引用&#xff08;S…

MySQL连接方式: Unix套接字 TCP/IP

今天连接mysql数据库使用mysql -u root -p指令的时候遇到了这个问题&#xff1a; 解决之后来总结一下mysql的连接方式 文章目录 1. Unix套接字&#xff08;或Windows命名管道&#xff09;特点&#xff1a;场景&#xff1a; 2. TCP/IP特点&#xff1a;场景&#xff1a; 3.对比总…