C语言链表设计及应用

链表

  • 链表
  • 节点
  • 设计链表项目
  • 链表中的传址调用
  • 检查申请空间
  • 链表尾插
  • 链表头插
  • 链表尾部删除
  • 链表头部删除
  • 链表的查找
  • 指定位置之前插入
  • 指定位置之后插入数据
  • 删除指定位置(节点)数据
  • 删除指定位置(节点)之后的数据
  • 链表的销毁

前面学习了顺序表,在顺序表中,虽然可以用动态内存开辟的方法来灵活改变空间大小,但顺序表本身仍然存在着一些局限性:

  • 头插/尾插中,每插入一次数据,其它数据要提前挪动以空出空间。
  • 开辟空间是以现有空间的倍数进行的,一般为2~3倍。

而随之带来空间和时间两个方向上的问题:

  • 数据频繁地挪动需要占用时间性能
  • 空间开辟后还是会不可避免的浪费。

链表

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

在这里插入图片描述


节点

节点组成部分:

  1. 数据(实际往往是运用 / 保存指针)
  2. 指向下一个节点的指针

定义链接的节点结构:

struct SlistNode     //single list node  单链表
{int data;struct SlistNode* next;
};

设计链表项目

文件有

  • 源文件:Slist.c、test.c
  • 头文件:Slist.h

链表中的传址调用

链表中,经常会出现函数传值调用的错误。
在这里插入图片描述
如图,想要改变链表plist,就要传址操作。而其本身就是地址,因此函数的参数类型应该是二级指针。

链表的创建是下面这两行代码,创建数据和指向下一个节点的指针。数据要开辟空间,所以内容使用指针接收。

SN* Node1 = (SN*)malloc(sizeof(SN));
Node1->Data = 21;

检查申请空间

在对链表进行操作(尤其是增删)的时候,要先对空间进行检查,防止内存溢出或者越界访问。
此函数,申请成功返回新节点,失败报错。

SN* Creat_newlistNode(DataType i)
{SN* newNode = (SN*)malloc(sizeof(SN));if (newNode == NULL){perror("malloc");exit(1);}newNode->Data = i;newNode->next = NULL;return newNode;
}

此函数真正使用如下:

SN* newNold = Creat_newlistNode(i);//创建一个保存数据i的链表。

链表尾插

先考虑非空链表
链表尾插,遍历链表找尾,将要插入的节点放到最后。
接着,要考虑无法遍历的情况——空链表
以上思路完成后,考虑将代码更为严谨、完善。如指针的合法等。
最后进行最终的测试。(每完成一个功能,也就应该测试一次。)

void SNPushBack(SN** pphead, DataType i)
{assert(pphead);SN* new = Creat_newlistNode(i);assert(new);//空链表和非空链表if (*pphead==NULL){*pphead = new;}else{SN* ptail = *pphead;while (ptail->next){ptail = ptail->next;}ptail->next = new;}
}

链表头插

现申请一个新的节点newnold。
将新节点和原有链表连接起来,并且新节点将作为头结点使用。

void SNPushFront(SN** pphead, DataType i)
{assert(pphead);SN* newNold = Creat_newlistNode(i);newNold->next= *pphead;*pphead = newNold;//空链表、非空链表都需要考虑(此代码两者都已通过)
}

链表尾部删除

链表的尾删,分为两个方向考虑:存在一个节点、存在多个节点
判断一个节点时,直接删除
多个节点时,循环找到最后一个(并且保留前一个),然后删除并将保留的那个指针指向置为空。

void SNPopBack(SN** pphead)
{assert(pphead && *pphead);//分为只有一个节点、多个节点if ((*pphead)->next == NULL)//  -> 优先级高于 *{free(*pphead);*pphead = NULL;}else{SN* ptail = *pphead;SN* prev = *pphead;while (ptail->next){prev = ptail;ptail = ptail->next;}free(ptail);ptail = NULL;prev->next = NULL;}}

链表头部删除

头删只需要将链表指向改为下一个(在更改指向前创建临时变量保存),
后使用临时变量释放掉第一个节点的空间。
最后一步是测试,多个节点、一个节点是否有问题。

void SNPopFront(SN** pphead)
{assert(pphead&& *pphead);SN* tmp = (*pphead)->next;free(*pphead);*pphead = tmp;
}

链表的查找

链表的修改,只需遍历寻找即可。
由于查找不涉及链表修改,因此函数参数的形参为一级指针。
往后遍历直到找到即可:

SN* FindSN(SN* phead, DataType i)
{assert(phead);SN* tmp = phead;while (tmp){if (tmp->Data == i){return tmp;}tmp = tmp->next;}return NULL;
}

在此函数的使用时,不要给参数带上 & 符号!!!(链表节名本身就是地址类型不可再乱加&)

void test3()
{SN* one = NULL;SNPushBack(&one, 99);SNPushBack(&one, 88);SNPushBack(&one, 77);SNPushBack(&one, 66);SNPushBack(&one, 55);SNPushBack(&one, 44);SNPrint(one);SN* find=FindSN(one, 66);//注意此时不涉及改变不需要使用 & 符号if (find==NULL){printf("找不到");}else{printf("可以找到%d", find->Data);}
}

指定位置:使用上面用于查找链表的函数,返回值即是这个位置


指定位置之前插入

首先考虑正常情况的多个节点。
链表中,只能由前找后,不能从后找到前。
因此确立分清三个节点:插入节点之前的节点,插入的节点,指点位置的节点。
创建插入的节点后,建立三个结点之间的联系。
再考虑特殊情况(只有一个节点的时候)
此时插入原理就是头插。
不需考虑无节点情况。

void SNInsert(SN** pphead, SN* pos, DataType i)//指定位置之前插入数据
{assert(*pphead);assert(pphead);SN* NewNold = Creat_newlistNode(i);//分为一个节点、多个节点if (*pphead == pos){SNPushFront(pphead,i);}else{SN* prev = *pphead;while (prev->next != pos){prev = prev->next;}prev->next = NewNold;NewNold->next = pos;}
}

指定位置之后插入数据

参数只需要指定的位置和数据即可。

void SNInsertAfter(SN* pos, DataType i)
{assert(pos);SN* NewNold = Creat_newlistNode(i);NewNold->next = pos->next;pos->next = NewNold;
}

需要特别提出来说明的是,这两句代码的顺序问题。

NewNold->next = pos->next;  //1
pos->next = NewNold;        //2

如果顺序颠倒,先进行

pos->next = NewNold;   //2

再进行

NewNold->next = pos->next;  //1

造成的结果等价于NewNold->next = NewNold,这个代码将会自己指向自己。


删除指定位置(节点)数据

指定位置删除数据,先考虑正常多节点情况
依旧需要弄清楚:删除位置之前的节点、删除位置的节点、删除位置之后的节点。
使用循环找到删除位置之前的节点,然后与后面的节点建立连接。
此时链表之中已不存在要删除的节点,但是空间没有释放一定要释放空间。

我们会发现,如果是一个节点,我们要指定位置删除。
代码并不适用。
所以要分情况来执行代码,使用 if 语句将两种情况分开讨论。
一个节点,指定位置删除的操作其实就是头删。拷贝或调用函数都可以。

void SNErase(SN** pphead, SN* pos)//指定位置删除数据
{assert(pphead && *pphead);assert(pos);if (pphead == pos){(*pphead)->next = NULL;      //或者头删函数也行//SNPopFront(pphead);}else{SN* prev = *pphead;while (prev->next != pos){prev = prev->next;}prev->next = pos->next;free(pos);pos = NULL;}
}

删除指定位置(节点)之后的数据

删除指定位置之后的数据,思路如下:

  1. 保存这个数据(指定位置之后的数据的位置)
  2. 链表重新建立连接(这一步完成后,链表中将不存在指定位置之后的>数据)
  3. 根据预先保存的数据找到被删除的数据,将其从内存中删除。
void SNEraseAfter(SN* pos)
{assert(pos && pos->next);   //  -> 优先级大于 &&SN* del = pos->next;pos->next = del->next;free(del);del = NULL;
}

在测试时,出现了错误,记录下来:

在这里插入图片描述

在测试指定位置删除时,使用findSN函数找到位置find,然后删除了这个位置的节点。后面又想测试指定位置之后删除,可是find这个位置的节点已经被删除,所以无法找到find,更无法找到find后面的节点了。


链表的销毁

链表是一个一个创建的,销毁也是一个一个的销毁。
思路是:创建两个指针,一个指针销毁节点使用,一个指针保存下一个节点。循环直到链表结束。

void DestorySN(SN** pphead)
{assert(pphead && *pphead);SN* pcur = *pphead;while (pcur){SN* next = pcur->next;free(pcur);pcur = next;}*pphead = NULL;
}

以上就是我对于单链表的学习记录了,单链表的理论到此结束。

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

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

相关文章

使用 YAML 自动化 Azure DevOps 管道

1. 在 Azure DevOps 中设置 YAML 管道 开始之前,您需要拥有一个 Azure DevOps 帐户和一个 git 仓库。 要创建 YAML 管道, 1. 导航至 Azure DevOps → 选择您的项目 2. 前往“管道”→ 点击“新建管道” 3. 选择您的仓库(Azure Repos、GitHub 等) 4. 选择“Starter Pipelin…

基于Spring Boot的幼儿园管理系统

基于Spring Boot的幼儿园管理系统 源码获取:https://mbd.pub/o/bread/YZWXlZtsbQ 引言 在数字化转型的浪潮中,教育行业的信息化建设显得尤为重要。幼儿园作为基础教育的重要环节,其管理系统的现代化水平直接关系到教育质量和运营效率。本文…

【NVIDIA-B200】 ‘CUDA driver version is insufficient for CUDA runtime version‘

目录 一、错误核心原因 二、排查步骤 1. 检查当前驱动版本 2. 检查 CUDA 运行时版本 3. 验证驱动与 CUDA 的兼容性 三、解决方法 1. 确保驱动正确加载 2. 重新安装匹配的驱动与 CUDA 3. 验证环境正确性 四、关键注意事项 报错日志: bash nccl.sh ------------5.安…

Android中如何实现自动化测试

目录 前言: 一、方法介绍 1、UI Automator 3、shell脚本 二、shell脚本实现自动化测试原理和步骤 1、 原理 2、步骤 三、shell自动化测试实例 前言: 在开发项目的过程中,我们将某个阶段的需求完成并且提测,通常,在测试工程师更细致的测…

绿联科技全球化突围:业财一体化如何打通全球电商全链路数字化

绿联科技专注数码配件20年,产品覆盖全球100多个国家,年销售额突破30亿。作为"连接"领域的专家,绿联深知连接的真谛不仅在于硬件产品,更在于数据的全球化连接。在全球电商竞争日益激烈的今天,绿联率先探索业财…

uv教程 虚拟环境

什么是uv 可以创建虚拟环境 安装依赖 安装uv 参见官方文档 安装 | uv-zh-cn 自定义安装目录,winr 输入powershell,输入如下命令 $env:UV_INSTALL_DIR "C:\Custom\Path";powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/inst…

绕过codex在vscode中登录403的问题

codex安装: npm i -g openai/codex codex升级: npm install -g openai/codexlatest 绕过codex在vscode中登录403的问题: https://linux.do/t/topic/924206/4 1.在windows端powelshell登陆好codex; $env:HTTP_PROXY"http://…

软件研发如何选对方法论?传统计划驱动与敏捷价值驱动的全面对比

软件项目研发中的方法论是一个核心话题,它决定了团队如何规划、执行和交付软件。下面我将对这些方法论进行一个全面的概述,从传统的到现代的,并说明它们的核心思想、适用场景和趋势。 一、 方法论的核心分类 软件研发方法论主要分为两大阵营:传统计划驱动(Plan-Driven)…

【服务器】将本地项目部署到服务器

当我们已经有了一个服务器后 如何将本地项目部署到服务器呢第一步,找到云服务器实例,查看公网IP地址第二步,推荐使用 Windows 自带的 PowerShell ssh root你的公网IP # 例如: ssh root47.98.123.45如果超时,首先检查服…

Flink中的 BinaryRowData 以及大小端

背景 本文基于 Flink 1.17.0 写此文章的目的是为了说明 Flink 堆内和堆外内存以及 内部 BinaryRowData 行处理的优化。 分析 堆内和堆外内存 跟Spark的内存管理不一样,Flink 中的堆内和堆外一直都是存在的。 堆内内存(JVM Heap)存储用户对象和…

HTTP/3.0:网络通信的技术革新与性能飞跃

🌐 HTTP/3.0:网络通信的技术革新与性能飞跃 Refer:PPP PRIVATE NETWORK™ 2 企业级虚拟以太网接入综合解决方案介绍 🚀 引言:悄然来临的网络革命 你是否曾期待视频加载卡顿成为过去?YouTube 已经迈出了重…

【golang学习笔记 gin 】1.1 路由封装和mysql 的使用封装

安装gin go get -u github.com/gin-gonic/gin go get -u github.com/go-sql-driver/mysql创建相关目录 gotest->conifg->database.go->redis.go->controller ->index.go->model->user.go->router->router.gomain.go 创建用户模型 package model imp…

SQL 层面行转列

背景:如果对一些评论、点赞、收藏等互动数据,使用了按照 type 分类存储,num 也是对应的。这样如果创建一个帖子,那么就会出现 3 行数据(type 不同,num 不同,对应评论点赞和收藏)&…

langchain4j笔记篇(阳哥)

一 概述1.1 概述langchain4j:langchain for java1.2 作用langchain4j的目标是简化将LLM集成到java应用程序中的过程。二 案例简单helloworld2.1 大模型调用三件套1.阿里百炼平台的通义模型: https://bailian.console.aliyun.com/2获取api-key&#x…

有鹿机器人的365天奇幻日记:我在景区当扫地僧

第一章 古建守护者:2cm的极致艺术琉璃瓦下的秘密记得那是个晨雾缭绕的清晨,我接到首个重要任务:清扫明代琉璃碑亭。这里的每块地砖都是文物,传统清洁工具根本不敢靠近。每天以2cm的精准贴边沿碑座作业,如今我每周都要为…

Objective-C方法参数标签怎么设置

在Objective-C中,方法名称可以通过几个标签名称组成,这是跟C/C中完全不一样的地方。每个标签都是字段冒号的写法,冒号后面是方法的参数,参数包括参数类型和参数变量,其中参数类型要用括号括起。方法参数的标签是通过在…

20250910_《SQL Server 数据库事务日志定期清理方案(精简优化版)》以10.1.1.31服务器的gtp-default数据库为例

《SQL Server 数据库事务日志定期清理方案(精简优化版)》 一、前提条件 数据库 gtp-default 已设置为完整恢复模式 (FULL)。 每天凌晨02:00执行完整备份,保证日志备份可用。 SQL Server Agent 已启用。 作业所有者为 sa,具有 sysadmin 权限。 Agent 服务账号 NT Service\S…

实习项目包装--HTTP 协议和 Web API

好的,完全没问题!你问到了一个非常核心且基础的知识领域,这是现代Web开发和几乎所有网络应用的基石。我们暂别嵌入式系统,专门来上一堂关于 HTTP 协议和 Web API 的详细课程。 我会从最根本的概念讲起,逐步深入到你所…

ICCV-2025 | 中科院自动化所世界模型助力具身导航!NavMorph:连续环境中的视觉语言导航自演化世界模型

作者:Xuan Yao1,2^{1,2}1,2, Junyu Gao1,2^{1,2}1,2, Changsheng Xu1,2,3^{1,2,3}1,2,3单位:1^{1}1中科院自动化所多模态人工智能系统国家重点实验室,2^{2}2中国科学院大学人工智能学院,3^{3}3鹏城实验室论文标题:NavM…

【ARDUINO】ESP8266的AT指令返回内容集合

一、基础测试指令(确认模块通信) 1. AT(测试模块是否响应) 功能:检测ESP8266与控制器(如Arduino)的串口通信是否正常。 返回内容: 成功:OK(无额外数据,仅确认通信正常) 失败:无返回(可能是波特率不匹配、接线错误) 示例:发送:AT 返回: OK二、Wi-Fi模式配置指…