【数据结构】无头+单向+非循环链表(SList)(增、删、查、改)详解

一、链表的概念及结构

1、链表的概念

之前学习的顺序表是用一段物理地址连续的存储单元依次存储数据元素的线性结构,而链表是一种物理存储结构上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的,可以实现更加灵活的动态内存管理。


 :之所以引出链表,是因为顺序表存在一些缺点

  • 顺序表在中间 / 头部的插入和删除,需要挪动很多数据,时间复杂度为 O(N),效率低下。
  • 增容需要申请新空间,拷贝数据,释放旧空间。消耗不小。
  • 增容一般是一次增长 2 倍,那就一定会造成空间浪费。例如当前的容量为 100,满了以后增容到 200,这时再继续插入 5 个数据,后面不再插入,那么就浪费了 95 个数据空间。

2、链表的组成

链表由一系列结点(链表中每一个元素称为结点)组成,结点可以在运行时动态生成

每个结点包括两个部分:

  1. 数据域:存储数据元素。
  2. 指针域:存储下一个结点地址。

3、链表的结构

(1)链表的物理结构(现实中)

(2)链表的逻辑结构(想象中)


 

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

 二、无头+单向+非循环链表的接口实现

无头单向非循环链表结构简单,一般不会单独用来存数据。实际中更多是作为其他数据结构的子结构,如哈希桶、图的邻接表等等。


1、创建文件

  • test.c(主函数、测试顺序表各个接口功能)
  • SList.c(单链表接口函数的实现)
  • SList.h(单链表的类型定义、接口函数声明、引用的头文件)


2、SList.h 头文件代码

// SList.h
// 无头+单向+非循环链表增删查改实现
#pragma once
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <stdbool.h>typedef int SLTDateType;typedef struct SListNode
{SLTDateType data; // 数据域struct SListNode* next; // 指针域
}SListNode;// 动态申请一个节点
SListNode* BuySListNode(SLTDateType x);
// 销毁单链表中所有节点
void SListDestory(SListNode** pphead)
// 单链表打印
void SListPrint(SListNode* phead);
// 单链表尾插
void SListPushBack(SListNode** pphead, SLTDateType x);
// 单链表的头插
void SListPushFront(SListNode** pphead, SLTDateType x);
// 单链表的尾删
void SListPopBack(SListNode** pphead);
// 单链表头删
void SListPopFront(SListNode** pphead);
// 单链表查找
SListNode* SListFind(SListNode* phead, SLTDateType x);
// 单链表在pos位置之后插入x
void SListInsertAfter(SListNode* pos, SLTDateType x);
// 单链表删除pos位置之后的值
void SListEraseAfter(SListNode* pos);
// 求单链表长度
int SListSize(SListNode* phead);
// 单链表判空
bool SListEmpty(SListNode* phead);

三、在 SList.c 中实现各个接口函数

1、动态申请一个节点

// 动态申请一个节点
SListNode* BuyListNode(SLTDataType x)
{SListNode* newnode = (SListNode*)malloc(sizeof(SListNode));newnode->data = x;newnode->next = NULL;return newnode;
}

2、销毁(释放)所有节点

// 销毁单链表中所有节点
void SListDestory(SListNode** pphead)
{assert(pphead);SListNode* cur = *pphead;while (cur){SListNode* next = cur->next;free(cur); // 释放节点cur = next;}*pphead = NULL;
}

assert() 放在函数里面检查参数,一方面是为了安全,另一方面是为了防止其他人在调用该函数时,出现不正确的使用,导致错误传入参数,这样可以及时提醒到他。所以写代码时一定要考虑到其他人不正确的使用该函数时的场景,以此避免不必要的麻烦。 


3、单链表打印

// 单链表打印
void SListPrint(SListNode* phead)
{// 不需要断言 -- 空链表也可以打印SListNode* cur = phead;while (cur){printf("%d->", cur->data);cur = cur->next;}printf("NULL\n");
}

4、单链表尾插

// 单链表尾插
void SListPushBack(SListNode** pphead, SLTDateType x)
{SListNode* newnode = BuySListNode(x);  //动态申请一个节点if (*pphead == NULL) // 单链表中没有节点时{*pphead = newnode; // plist指向新节点}else // 单链表中已经有节点时{SListNode* tail = *pphead;while (tail->next != NULL) // 找到单链表中的最后一个节点{tail = tail->next;}tail->next = newnode; // 最后一个节点的next指向新节点}
}

  错误写法❌:

(传一级指针的值,用一级指针接收)指针传值,相当于把 plist 指针变量的值拷贝给 phead,给 phead 赋值 newnode,phead 的改变不会影响 plist

// 错误写法:
// 单链表尾插
void SListPushBack(SListNode* phead, SLTDateType x)
{SListNode* newnode = BuySListNode(x);  //动态申请一个节点if (phead == NULL) // 单链表中没有节点时{phead = newnode; // plist指向新节点}else // 单链表中已经有节点时{SListNode* tail = phead; // 找到尾节点while (tail->next != NULL) // 找到单链表中的最后一个节点{tail = tail->next;}tail->next = newnode; // 最后一个节点的next指向新节点}
}

因为当链表为空时,我们需要改变 plist 的指向,使其指向第一个节点。而初始 plistphead 都指向 NULL,调用函数后,phead 指向了新的节点,而 plist 还是指向 NULL 的。


 解决方法:

plist 是指向第一个节点的指针,想要在函数中改变 plist 的值(指向),必须要把 plist 指针的地址 作为实参传过去形参用二级指针接收,这样在函数中对二级指针解引用得到 plist 的值,就可以改变 plist 的值(指向)了。


  • 单链表为空时,plist 直接指向新节点
  • 单链表不为空时,先找到单链表的尾节点,然后将尾节点的 next 指向新节点


5、单链表头插

// 单链表的头插
void SListPushFront(SListNode** pphead, SLTDataType x)
{assert(pphead);SListNode* newnode = BuyListNode(x); // 动态申请一个节点newnode->next = *pphead; // 新节点的next指针指向plist指向的位置*pphead = newnode; // plist指向头插的新节点
}


6、单链表尾删

// 单链表的尾删
void SListPopBack(SListNode** pphead)
{assert(pphead);assert(*pphead); //链表为空就无法再进行尾删了// 温柔处理方式/*if (*pphead == NULL){return;}*/// 粗暴处理方式assert(*pphead);if ((*pphead)->next == NULL) // 链表一个节点{free(*pphead);*pphead = NULL;}else // 链表中有多个节点{// 写法一:/* SListNode* prev = NULL;SListNode* tail = *pphead;while(tail->next != NULL) // 找到链表的尾节点的上一个节点{prev = tail;tail = tail->next;}free(tail); // 删除尾节点tail = NULL;prev->next = NULL;  // 置空 *///写法二:SListNode* tail = *pphead;while (tail->next->next != NULL) // 找到链表的尾节点的上一个节点{tail = tail->next;}free(tail->next); // 删除尾节点tail->next = NULL; // 置空}
}


  •  单链表只有一个节点时,删除节点plist 指向 NULL
  • 单链表有多个节点时,先找到单链表尾节点的上一个节点删除尾节点,然后将该节点的 next 指向 NULL
  • 因为可能要改变外部 plist 的指向,所以用二级指针接收指针 plist 的地址

7、单链表头删

// 单链表头删
void SListPopFront(SListNode** pphead)
{assert(pphead);assert(*pphead); // 链表为空就无法再进行头删了/*if (*pphead == NULL){return;}else{SListNode* next = (*pphead)->next;free(*pphead);*pphead = next;}*/SListNode* cur = *pphead; // 保存头节点的地址*pphead = cur->next; // plist指向头节点的下一个节点free(cur); // 删除头节点
}

:因为可能要改变外部 plist 的指向,所以用二级指针接收指针 plist 的地址。 


8、单链表查找指定值的节点

// 单链表查找
SLTNode* SListFind(SListNode* phead, SLTDataType x)
{SListNode* cur = phead;while (cur){if (cur->data == x){return cur; // 找到了 返回该节点的地址}else{cur = cur->next;}}return NULL; // 未找到,返回NULL
}


9、单链表在pos位置之后插入x

// 单链表在pos位置之后插入x
void SListInsertAfter(SListNode* pos, SLTDateType x)
{assert(pos); // pos位置不能为空SListNode* newnode = BuySListNode(x); // 动态申请一个节点newnode->next = pos->next; // 新节点的next指针指向pos位置后一个节点pos->next = newnode; // pos位置的next指向新节点
}

为什么不在pos位置之前插入?
  • 单链表不适合在 pos 位置之前插入,因为需要遍历链表找到 pos 位置的前一个节点,时间复杂度为O(N)。
  • 单链表更适合在 pos 位置之后插入,如果在后面插入,只需要知道 pos 位置即可,简单得多。
  • C++ 官方库里面单链表给的也是在之后插入


10、单链表删除指定pos位置之后的节点

// 单链表删除pos位置之后的值
void SListEraseAfter(SListNode* pos)
{assert(pos); // pos位置不能为空assert(pos->next); // pos位置不能是尾节点SListNode* next = pos->next; // 保存pos位置的后一个节点pos->next = pos->next->next;free(next); // 释放pos位置的后一个节点
}

为什么不删除pos位置?
void SListErase(SListNode** pphead, SLTNode* pos) // O(N)
{assert(pphead);assert(pos);if (*pphead == pos){/* *pphead = pos->next;free(pos); */SListPopFront(pphead);}else{SListNode* prev = *pphead;while (prev->next != pos){prev = prev->next;}prev->next = pos->next;free(pos);//pos = NULL;}
}
  • 删除 pos 位置同样需要传入单链表的二级指针。
  • 单链表不适合在 pos 位置插入,因为需要遍历链表找到 pos 位置的前一个节点,以改变其指向,时间复杂度大。


11、求单链表长度

// 求单链表长度
int SListSize(SListNode* phead)
{int size = 0;SListNode* cur = phead;while (cur){size++;cur = cur->next;}return size;
}

12、判断单链表是否为空

// 单链表判空
bool SListEmpty(SListNode* phead)
{// 写法一:return phead == NULL;// 写法二://return phead == NULL ? true : false;
}

四、代码整合

// SList.c
#include "SList.h"// 动态申请一个节点
SListNode* BuyListNode(SLTDataType x)
{SListNode* newnode = (SListNode*)malloc(sizeof(SListNode));newnode->data = x;newnode->next = NULL;return newnode;
}// 销毁单链表中所有节点
void SListDestory(SListNode** pphead)
{assert(pphead);SListNode* cur = *pphead;while (cur){SListNode* next = cur->next;free(cur); // 释放节点cur = next;}*pphead = NULL;
}// 单链表打印
void SListPrint(SListNode* phead)
{SListNode* cur = phead;while (cur){printf("%d->", cur->data);cur = cur->next;}printf("NULL\n");
}// 单链表尾插
void SListPushBack(SListNode** pphead, SLTDateType x)
{SListNode* newnode = BuySListNode(x);  //动态申请一个节点if (*pphead == NULL) // 单链表中没有节点时{*pphead = newnode; // plist指向新节点}else // 单链表中已经有节点时{SListNode* tail = *pphead;while (tail->next != NULL) // 找到单链表中的最后一个节点{tail = tail->next;}tail->next = newnode; // 最后一个节点的next指向新节点}
}// 单链表的头插
void SListPushFront(SListNode** pphead, SLTDataType x)
{assert(pphead);SListNode* newnode = BuyListNode(x); // 动态申请一个节点newnode->next = *pphead; // 新节点的next指针指向plist指向的位置*pphead = newnode; // plist指向头插的新节点
}// 单链表的尾删
void SListPopBack(SListNode** pphead)
{assert(pphead);assert(*pphead); //链表为空就无法再进行尾删了assert(*pphead);if ((*pphead)->next == NULL) // 链表一个节点{free(*pphead);*pphead = NULL;}else // 链表中有多个节点{SListNode* tail = *pphead;while (tail->next->next != NULL) // 找到链表的尾节点的上一个节点{tail = tail->next;}free(tail->next); // 删除尾节点tail->next = NULL; // 置空}
}// 单链表头删
void SListPopFront(SListNode** pphead)
{assert(pphead);assert(*pphead); // 链表为空就无法再进行头删了SListNode* cur = *pphead; // 保存头节点的地址*pphead = cur->next; // plist指向头节点的下一个节点free(cur); // 删除头节点
}// 单链表查找
SLTNode* SListFind(SListNode* phead, SLTDataType x)
{SListNode* cur = phead;while (cur){if (cur->data == x){return cur; // 找到了 返回该节点的地址}else{cur = cur->next;}}return NULL; // 未找到,返回NULL
}// 单链表在pos位置之后插入x
void SListInsertAfter(SListNode* pos, SLTDateType x)
{assert(pos); // pos位置不能为空SListNode* newnode = BuySListNode(x); // 动态申请一个节点newnode->next = pos->next; // 新节点的next指针指向pos位置后一个节点pos->next = newnode; // pos位置的next指向新节点
}// 单链表删除pos位置之后的值
void SListEraseAfter(SListNode* pos)
{assert(pos); // pos位置不能为空assert(pos->next); // pos位置不能是尾节点SListNode* next = pos->next; // 保存pos位置的后一个节点pos->next = pos->next->next;free(next); // 释放pos位置的后一个节点
}// 求单链表长度
int SListSize(SListNode* phead)
{int size = 0;SListNode* cur = phead;while (cur){size++;cur = cur->next;}return size;
}// 单链表判空
bool SListEmpty(SListNode* phead)
{// 写法一:return phead == NULL;
}

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

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

相关文章

下载离线版的VS Visual Studio 并下载指定的版本

一、先下载引导程序 下载地址VS VisualStudio官网 在这个页面翻到最下面 在这里下载需要的版本 下载引导程序 二、下载离线安装包 写一个批处理文件&#xff08;vs.bat&#xff09; 命令格式如下 <vs引导程序exe> --layout <离线安装包下载的路径> --add <功能…

1、Tomcat

java介绍 Java语言和平台由以下几个主要部分组成&#xff1a; 1、Java编程语言(Java Language)&#xff1a;这是Java的核心部分&#xff0c;包括Java语法、关键字、数据类型、运算符、控制结构等。程序员使用Java语言来编写应用程序的源代码。 2、Java开发工具包(Java Developm…

Android Layout大点兵

原文链接 Android Layout大点兵 智能手机催生了移动互联时代&#xff0c;现如今移动应用越来越成为最为核心的终端。而GUI页面是移动互联终端的标配&#xff0c;做好一个GUI页面&#xff0c;是非常重要的&#xff0c;能极大的提升用户体验和用户满意度。安桌生态&#xff0c;自…

kotlin 编写一个简单的天气预报app(五)增加forcast接口并显示

参考资料 OpenWeatherMap提供了一个/forecast接口&#xff0c;用于获取未来几天的天气预报。你可以使用HTTP GET请求访问该接口&#xff0c;并根据你所在的城市或地理坐标获取相应的天气数据。 以下是一个示例请求的URL和一些常用的参数&#xff1a; URL: http://api.openwe…

K8s安全配置:CIS基准与kube-bench工具

01、概述 K8s集群往往会因为配置不当导致存在入侵风险&#xff0c;如K8S组件的未授权访问、容器逃逸和横向攻击等。为了保护K8s集群的安全&#xff0c;我们必须仔细检查安全配置。 CIS Kubernetes基准提供了集群安全配置的最佳实践&#xff0c;主要聚焦在两个方面&#xff1a;主…

C语言指针进阶-2

本篇文章带 1. 数组传参和指针传参 2. 函数指针 3. 函数指针数组 的相关知识详细讲解&#xff01; 如果您觉得文章不错&#xff0c;期待你的一键三连哦&#xff0c;你的鼓励是我创作动力的源泉&#xff0c;让我们一起加油&#xff0c;一起奔跑&#xff0c;让我们顶峰相见&…

(文章复现)梯级水光互补系统最大化可消纳电量期望短期优化调度模型matlab代码

参考文献&#xff1a; [1]罗彬,陈永灿,刘昭伟等.梯级水光互补系统最大化可消纳电量期望短期优化调度模型[J].电力系统自动化,2023,47(10):66-75. 1.基本原理 1.1 目标函数 考虑光伏出力的不确定性&#xff0c;以梯级水光互补系统的可消纳电量期望最大为目标&#xff0c;函数…

kernel32.dll如何修复,快速解决kernel32.dll缺失的方法

Kernel32.dll是Windows操作系统中一个重要的系统文件&#xff0c;对于系统的正常运行至关重要。然而&#xff0c;由于各种原因&#xff0c;用户可能会遇到kernel32.dll文件的缺失问题。今天小编就来给大家详细的介绍一下kernel32.dll这个文件&#xff0c;并且详细的介绍一下ker…

Iptables

常用名词 容器&#xff1a;存放东西 表(table)&#xff1a;存放链的容器&#xff0c;防火墙的最大概念 链(chain)&#xff1a;存放规则的容器 规则(policy)&#xff1a;准许或拒绝规则 Iptables处理流程 [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上…

小程序通过ip+port+路径获取服务器中的图片

配置IIS 首先需要配置IIS。 打开控制面板&#xff0c;接下来的流程按下图所示。 安装好后&#xff0c;按“win”键&#xff0c;搜索IIS 选择一个ip地址&#xff0c;或手动填写&#xff0c;端口号按需更改 填写别名和物理路径&#xff0c;别名就是后续通过url访问物理…

html5播放器视频切换和连续播放的实例

当前播放器实例可以使用changeVid接口切换正在播放的视频。当有多个视频&#xff0c;在上一个视频播放完毕时&#xff0c;自动播放下一个视频时也可采用该处理方式。 const option {vid: 88083abbf5bcf1356e05d39666be527a_8,//autoplay: true,//playsafe: , //PC端播放加密视…

牛客网Verilog刷题——VL41

牛客网Verilog刷题——VL41 题目答案 题目 请设计一个可以实现任意小数分频的时钟分频器&#xff0c;比如说8.7分频的时钟信号&#xff0c;注意rst为低电平复位。提示&#xff1a;其实本质上是一个简单的数学问题&#xff0c;即如何使用最小公倍数得到时钟周期的分别频比。设小…

数据中台系列2:rabbitMQ 安装使用之 window 篇

RabbitMQ 是一个开源的消息队列系统&#xff0c;是高级消息队列协议&#xff08;AMQP&#xff09;的标准实现&#xff0c;用 erlang 语言开发。 因此安装 RabbitMQ 之前要先安装好 erlang。 1、安装 erlang 到 这里 下载本机能运行的最新版 erlang 安装包。如果本机没有装过 …

Mac/win开发快捷键、vs插件、库源码、开发中的专业名词

目录 触控板手势&#xff08;2/3指&#xff09; 鼠标右键 快捷键 鼠标选择后shift⬅️→改变选择 mac command⬅️&#xff1a;删除←边的全部内容 commadtab显示下栏 commandshiftz向后撤回 commandc/v复制粘贴 command ⬅️→回到行首/末 commandshift3/4截图 飞…

PHP使用PhpSpreadsheet实现导出Excel时带下拉框列表 (可支持三级联动)

因项目需要导出Excel表 需要支持下拉 且 还需要支持三级联动功能 目前应为PHPExcel 不在维护&#xff0c;固采用 PhpSpreadsheet 效果如图&#xff1a; 第一步&#xff1a;首先 使用composer 获取PhpSpreadsheet 我这里PHP 版本 7.4 命令如下&#xff1a; composer r…

富士通“Actlyzer”提供基于AI的基于视频的行为分析

想象一下这样的场景&#xff1a;一个男人走近一个家的前门&#xff0c;蹲下并检查钥匙孔。这是丢失房屋钥匙的居民还是客人&#xff1f;还是寻找入口点的窃贼&#xff1f;“Actlyzer”是一种新的人工智能安全系统&#xff0c;旨在区分这种情况。富士通实验室和研发中心的行为分…

k8s安装Jenkins

目录 ​编辑 一、环境准备 1.1 环境说明 二、安装nfs 2.1 安装NFS 2.2 创建NFS共享文件夹 2.3 配置共享文件夹 2.4 使配置生效 2.5 查看所有共享目录 2.6 启动nfs 2.7 其他节点安装nfs-utils 三、创建PVC卷 3.1 创建namespace 3.2 创建nfs 客户端sa授权 3.3 创建…

XtraBackup 8.0.33-28 prepare 速度提升 20 倍!

在这篇博文中&#xff0c;我们将描述 Percona XtraBackup 8.0.33-28 的改进&#xff0c;这显著减少了备份准备所需的时间&#xff0c;以便进行恢复操作。 Percona XtraBackup 中的这一改进显着缩短了新节点加入 Percona XtraDB 集群&#xff08;PXC&#xff09; 所需的时间。 …

Python - OpenCV识别条形码、二维码(已封装,拿来即用)

此代码可识别条形码和二维码&#xff0c;已封装好&#xff0c;拿来即用&#xff1a; import cv2 import pyzbar.pyzbar as pyzbar import numpy from PIL import Image, ImageDraw, ImageFontclass CodeScan():def __init__(self):super(CodeScan, self).__init__()def decode…

阿丹千问vue页面升级-使用Markdown形式展示回答--markdown-it库

阿丹&#xff1a; 在之前开发的阿丹千问 发现回复的文章格式使用 Markdown的格式。所以想使用Markdown的方式来给页面来个升级。 下面就是升级以及开发的过程。 升级思路 使用vue中的markdown-it库 在Vue页面中使用Markdown文档 安装markdown-it&#xff1a; 在Vue项目中…