数据结构与算法—单链表

目录

一、链表

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,一经查实,立即删除!

相关文章

spring中事务相关面试题(自用)

1 什么是spring事务 Spring事务管理的实现原理是基于AOP&#xff08;面向切面编程&#xff09;和代理模式。Spring提供了两种主要的方式来管理事务&#xff1a;编程式事务管理和声明式事务管理。 声明式事务管理&#xff1a; Spring的声明式事务管理是通过使用注解或XML配置来…

LocalDateTime与时间戳

众所周知&#xff0c;如果想把 LocalDateTime 转为时间戳&#xff0c;需要先指定时区&#xff0c;然后才能转为时间戳&#xff0c;例如&#xff1a; LocalDateTime localDateTime LocalDateTime.now(); ZonedDateTime zonedDateTime localDateTime.atZone(ZoneId.systemDe…

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;那么又没有一种折中的…

design compiler中的drc规则详解

design compiler中的drc规则详解 DRC是什么&#xff1f;DRC分类各个DRC的含义写在最后 DRC是什么&#xff1f; 本文讨论的DRC即是Design Rule Constraint,而不是Design Rule Check&#xff0c;后者是物理端或者后端的一个关键步骤。 DRC分类 DRC为DC中的一个约束大类&#x…

设计模式——21. 中介者模式

1. 说明 中介者模式(Mediator Pattern)是一种行为设计模式,它允许对象之间通过一个中介者对象进行通信,而不是直接相互引用。这种模式有助于减少对象之间的直接关联,从而提高系统的可维护性和松耦合性。中介者模式将对象之间的交互集中在一个中介者对象中,该对象负责协调…

windows DOM 命令手册

Windows 打开windows中特定程序 win R > cmd > Enter # 打开 dos 窗口 win R > devmgmt.msc > Enter # 打开设备管理器 win R > services.msc > Enter # 打开服务管理器 基础命令 help-查看某个命令帮助信息 # 查看…

自动化办公篇之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;但…

php 解析json字符串

在PHP中解析JSON字符串通常使用内置的json_decode函数。json_decode函数将一个JSON格式的字符串转换为PHP对象&#xff08;如果设置第二个参数为true&#xff0c;则转换为关联数组&#xff09;。 以下是一个示例&#xff1a; $json_string {"name":"John"…

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

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

SQL Server创建数据库

简单创建写法 默认初始大小为5MB,增长速度为2MB create database DBTEST自定义 用户创建的数据库都被存放在sys.database中&#xff0c;每个数据库在表中占一行&#xff0c;name字段存放的数据库的名称&#xff0c;具体字段可以看此博客sys.database系统表详细说明 所以判断…

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())报错描述如…

LCR 174.寻找二叉搜索树中的目标节点

​​题目来源&#xff1a; leetcode题目&#xff0c;网址&#xff1a;LCR 174. 寻找二叉搜索树中的目标节点 - 力扣&#xff08;LeetCode&#xff09; 解题思路&#xff1a; 首先用栈保存到二叉搜索树最大节点的链条上的各个指针&#xff08;栈顶为最大元素指针&#xff09;&a…