【数据结构之单链表的实现(不带头)】

 

1.单链表

1.1概念与结构

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

可以用下图便于理解

节(结)点: 

       与顺序表不同的是,链表里面的每节“车厢”都是独立申请下来的空间,所以我们之为“节/结点”。

       每个节(结)点的组成主要是两部分:当前节点要保存的数据保存下一个节点的地址(指针变量)。           上图中指针变量plist保存的是第一个节点的地址,我们称plist为“指向”第一个节点,如果我们希望plist指向第二个节点时,只需要修改plist保存的内容为0x0012FFA0即可。

       链表中每个节点都是独立申请的(即只需要插入数据时才去申请一块节点的空间),我们需要通过指针变量来保存下一个节点的位置才能从当前节点找到下一个节点。

 链表的结构:

假设当前链表中保存的是整型数据,当然我们也可以保存其它同一类型的数据。因此为了便于操作其它数据类型的操作,我们对类型进行重命名。

typedef int SLTDatatype;
typedef struct SlistNode
{SLTDatatype data;struct SlistNode* next;
}SLTNode;

1.2链表的性质

  • 链式结构在逻辑上是连续的,在物构上不是连续的。
  • 节点一般是从堆上申请的。(涉及动态内存管理)
  • 从堆上申请来的空间,是按照一定策略分配出来的,每次申请的空间可能连续,也可能不连续。

2.单链表的实现 

                   实现链表的时候和实现顺序表的时候类似,都需要三个文件,SlistNode.c文件,SlistNode.h文件和test.c(测试文件)。

2.1 SlistNode.h   链表结构的定义以及函数功能的声明部分

#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
//单链表的结构定义   不带头所以不需要初始化
typedef int SLTDatatype;
typedef struct SlistNode
{SLTDatatype data;struct SlistNode* next;
}SLTNode;//函数的声明部分//单链表的插入
//尾插
void SLTPushBack(SLTNode** pphead, SLTDatatype x);
//头插
void SLTPushFront(SLTNode** pphead, SLTDatatype x);//单链表的数据的打印
void SLTPrint(SLTNode* phead);//单链表的删除
//单链表的尾删
void SLTPopBack(SLTNode** pphead);
//单链表的头删
void SLTPopFront(SLTNode** pphead);//单链表中数据的查找
SLTNode* SLTFind(SLTNode* phead, SLTDatatype x);//单链表指定位置的操作
//指定位置之前的插入
void SLTInsertFront(SLTNode** pphead, SLTNode* pos, SLTDatatype x);
//指定位置之后数据的插入
void SLTInsertBack(SLTNode* pos, SLTDatatype x);//指定位置数据的删除
void SLTErase(SLTNode** pphead, SLTNode* pos);
//指定位置之后的删除
void SLTEraseAfter(SLTNode* pos);//链表的销毁
void SListDestroy(SLTNode**pphead);

2.2 SlistNode.c  函数功能的实现

2.2.1 单链表数据的插入:尾插和头插

//申请新节点
SLTNode* SLTBuyNode(SLTDatatype x)
{SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));if (newnode == NULL){perror("malloc fail");exit(1);}newnode->data = x;newnode->next = NULL;return newnode;
}

实现思路:

         在实现链表的插入之前,需要先封装一个可以申请新节点的函数,由链表的概念以及性质我们只需要用malloc函数进行申请新节点,随后插入时只需要改变节点的的指向关系即可。

//单链表的插入
//尾插
void SLTPushBack(SLTNode** pphead, SLTDatatype x)
{assert(pphead);//链表为空和非空两种情况SLTNode* newnode = SLTBuyNode(x);if (*pphead==NULL){*pphead = newnode;}else{SLTNode* pcur = *pphead;//先遍历找到尾节点while (pcur->next != NULL){pcur = pcur->next;}pcur->next = newnode;}
}
//头插
void SLTPushFront(SLTNode** pphead, SLTDatatype x)
{assert(pphead );SLTNode* newnode = SLTBuyNode(x);newnode->next = *pphead;*pphead = newnode;
}

实现思路:

       尾插头插之前头需要对所传的二级指针进行判空(检查是否为有效地址),除此之外尾插和头插都需要申请到新节点。

       尾插分为两种情况,一种是链表为空,另一种是链表非空。之所以分成这两种情况是因为在尾插的时候需要遍历链表完之后对当前位置的节点进行解引用,如果为空链表,就会报错(对空链表不能进行访问)。所以链表为空时直接将申请的新节点的地址给*pphead,非空时先让链表进行遍历,遍历到下一个节点为空时停下来,将所申请到新节点的地址给当前节点中的next指针。

       头插:头插很简单,对于空链表和非空链表都可以,先将申请到新节点的next指针指向头结点,再将新节点的地址给头结点。

PS:      我们在这里之所以传的是二级指针,是因为我们在刚开始(见test.c)中申请节点的时候,使用的是一级指针申请的变量,由指针内容我们可以知道要通过函数改变一个指针指向的内容就需要用该变量的指针,在这里该变量是一级指针,所以我们需要用指针的指针即就是二级指针。

2.2.2 单链表数据的删除 :尾删和头删 

//单链表的删除
//单链表的尾删
void SLTPopBack(SLTNode** pphead)
{assert(pphead && * pphead);//分成两种情况:只有一个节点(不需要遍历链表)  有多个节点if ((*pphead)->next == NULL){*pphead = NULL;}else{SLTNode* prev = *pphead, * ptail = *pphead;while (ptail->next){prev = ptail;ptail = ptail->next;}free(ptail);ptail = NULL;prev->next= NULL;}
}
//单链表的头删
void SLTPopFront(SLTNode** pphead)
{assert(pphead);assert(*pphead);//先把头结点的下一个节点保存下来SLTNode* next = (*pphead)->next;free(*pphead);*pphead = next;
}

实现思路:

 尾删头删实现的时候都需要先进行对pphead和*pphead(确保链表非空)进行判空。

尾删:因为链表中只有一个节点的时候不存在前驱节点,则直接将头结点释放掉且置为空。而当链表中有多个节点时,则需要先找到前驱节点释放掉尾节点再将前驱节点的next和尾节点置为空

头删:先将头结点所指向的next保存在next中,然后释放掉头结点,最后将保存的next赋值给头节点。

2.2.3 单链表数据的查找

//单链表中数据的查找
SLTNode* SLTFind(SLTNode* phead, SLTDatatype x)
{assert(phead);//遍历链表SLTNode* pcur = phead;while (pcur){if (pcur->data == x)return pcur;pcur = pcur->next;}return NULL;
}

实现思路:先对所传的变量进行判空(是否有效)。再遍历链表,如果当前节点的数据为所要查找的数据,则返回当前节点的地址

2.2.4 单链表指定位置的操作

2.2.4.1 指定位置的插入
//指定位置之前的插入     特别注意
void SLTInsertFront(SLTNode** pphead, SLTNode* pos, SLTDatatype x)
{assert(pphead);assert(pos);if (*pphead == pos){//头结点没有前一个节点,故就直接调用头插的方法SLTPushFront(pphead, x);}else{SLTNode* newnode = SLTBuyNode(x);SLTNode* prev = *pphead;while (prev->next != pos){prev = prev->next;}//将prev newnode pos连接起来newnode->next = prev->next;prev->next = newnode;}
}
//指定位置之后数据的插入
void SLTInsertBack(SLTNode* pos, SLTDatatype x)
{assert(pos);SLTNode* newnode = SLTBuyNode(x);//将pos newnode pos->next连接起来newnode->next = pos->next;pos->next = newnode;
}

实现思路: 指定位置之前的插入(还需要判断pphead的有效性)和指定位置之后插入,都需要对要插入位置pos的有效性进行判断。

指定位置之前的插入:

(1.)当插入的位置恰好为头节点时,因为头结点没有前驱节点)所以直接调用头插的方法即可。

(2.)当插入的位置为其他节点的位置时,先申请一个新节点,然后遍历找到要插入位置之前的前驱节点prev,然后将newnode,prev,pos三个位置的节点连接起来。

指定位置之后的插入:

先判断位置的有效性,再申请一个新节点,最后将pos ,newnode,pos->next位置的节点连接起来。

2.2.4.2 指定位置的删除
//指定位置数据的删除     特别注意
void SLTErase(SLTNode** pphead, SLTNode* pos)
{assert(pphead);assert(*pphead&&pos);if (*pphead == pos){//如果是删除的头结点,头结点没有前驱节点SLTPopFront(pphead);}else{SLTNode* prev = *pphead;while (prev->next != pos){prev = prev->next;}//先将申请的节点释放掉,将pos pos->next->next连接起来prev->next = pos->next;free(pos);pos = NULL;}
}
//指定位置之后的删除
void SLTEraseAfter(SLTNode* pos)
{assert(pos&&pos->next);//先释放pos之后的节点,再将pos pos->next->next 连接起来SLTNode* del = pos->next;pos->next = del->next;free(del);del = NULL;
}

实现思路:

指定位置的数据的删除:需要先判断pphead和*pphead的有效性以及pos位置的有效性。(1.)如果要删除的是头结点不存在前驱节点),则只需要调用头删即可。

(2.)如果删除的不是头结点的位置,则需要先遍历链表找到要删除位置的前驱节点,然后先将删除位置的下一个节点保存下来,其次将pos前后位置的节点连接起来再释放掉要删除位置的节点,将pos置为空。

指定位置之后的数据的删除: 需要先判断pos和pos->next的有效性。

先将pos位置之后的节点保存到del中,然后将pos和del->next连接起来,再释放掉del,最后将del置为空即可。

2.2.5 单链表的销毁

//链表的销毁
void SListDestroy(SLTNode** pphead)
{assert(pphead&&*pphead);//每次循环释放 每次释放节点时先将下一个节点保存下来SLTNode* pcur = *pphead;while (pcur){SLTNode* next = pcur->next;free(pcur);pcur = next;}*pphead = NULL;
}

实现思路:应先判断pphead和*pphead的有效性。

因为每一个节点都是动态内存申请的,所以我们应该遍历链表一一释放节点。但是需要注意的是每次释放释放节点时,应该先把当前要删除节点的下一个位置保存下来,在释放完之后,将保存的节点的位置赋值给当前节点

希望大家通过 我的博客可以对知识有更新,更深的理解!

 如果有错,还望指出!!!

关注博主,优质内容不断更新!!!

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

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

相关文章

NRK3301识别语音芯片在智能按摩椅中的应用与体验提升

在健康与舒适日益受到关注的今天&#xff0c;按摩椅作为缓解疲劳、舒缓压力的设备受到了广大消费者的喜爱。然而&#xff0c;传统的按摩椅操作方式往往繁琐且不直观。在这一背景下&#xff0c;NRK3301语音识别芯片的应用为按摩椅带来了新的变革。‌ 一、高识别准确率和快速响应…

halcon深度学习语义分割预处理图片遇到的坑

1.最近使用halcon深度学习语义分割&#xff0c;做缺陷检测。 2.在使用halcon的深度学习标准工具&#xff0c;标注图片 3.标注好图片后&#xff0c;到处预处理&#xff0c;发现报错&#xff0c;[‘Multiple matching segmentation files for image /1.jpg’]意思是:[’ image /…

二十天刷leetcode【hot100】算法- day1[前端Typescript]

哈希表 1. 两数之和 给定一个整数数组 nums 和一个整数目标值 target&#xff0c;请你在该数组中找出 和为目标值 target 的那 两个 整数&#xff0c;并返回它们的数组下标。 你可以假设每种输入只会对应一个答案。但是&#xff0c;数组中同一个元素在答案里不能重复出现。你…

go语言day21 goland使用gin框架、gorm框架操作mysql数据库redis数据库 使用宝塔创建redis数据库

GORM 指南 | GORM - The fantastic ORM library for Golang, aims to be developer friendly. gorm package - github.com/jinzhu/gorm - Go Packages go语言day20实现投票功能项目包-CSDN博客 gin框架标准项目结构&#xff1a; models&#xff1a;存放对应实体类和gorm包增删…

DVWA(SQL注入)medium、high

medium &#xff08;1&#xff09;判断注入是字符型还是数值型 数值型&#xff0c;获得了用户信息。 id 1 or 11 &#xff08;2&#xff09;查询字段数 为3时报错&#xff0c;代表字段数为2。 1 order by 3 &#xff08;3&#xff09;显示字段顺序 1 union select 1,2 &…

机器学习练手(三):基于决策树的iris 多分类和波士顿房价预测

总结&#xff1a;本文为和鲸python 可视化探索训练营资料整理而来&#xff0c;加入了自己的理解&#xff08;by GPT4o&#xff09; 原活动链接 原作者&#xff1a;vgbhfive&#xff0c;多年风控引擎研发及金融模型开发经验&#xff0c;现任某公司风控研发工程师&#xff0c;对…

【精通Redis】Redis事务

文章目录 前言一、标准事务1.1 标准事务的特性1.2 标准事务的生命周期1.3 事务的作用 二、Redis事务2.1 Redis事务的特性2.2 Redis事务与普通事务的区别 三、Redis事务常用命令总结 前言 我们在使用Redis的时候&#xff0c;有时为了处理多个结构&#xff0c;需要向Redis中一次…

Linux系统窗口水印难点分析

给应用程序加水印是保护数据的一种方式&#xff0c;window上可以通过给进程通过注入的方法给进程的窗口创建一个同大小的副窗口&#xff0c;在副窗口上绘制水印内容&#xff0c;同时设置副窗口透明同时透传事件&#xff0c;这样就可以达到在源窗口上显示水印的效果且不影响程序…

深⼊理解指针(3)

1. 字符指针变量 2. 数组指针变量 3. ⼆维数组传参的本质 4. 函数指针变量 5. 函数指针数组 6. 转移表 1. 字符指针变量 在指针的类型中我们知道有⼀种指针类型为字符指针 ⼀般使⽤: char* 这两种方式都是把字符串中的首字符的地址赋值给pc。 在这串代码中 str1内容的地…

ArkTS通用属性

目录 一、尺寸设置 宽高&#xff0c;外边距&#xff0c;内边距&#xff0c;尺寸size layoutWeight constraintSize 二、位置设置 align direction position offset 使用Edge方式position,offset 三、布局约束 aspectRatio displayPriority 四、Flex布局 flexBas…

RabbitMQ高级篇(如何保证消息的可靠性、如何确保业务的幂等性、延迟消息的概念、延迟消息的应用)

文章目录 1. 消息丢失的情况2. 生产者的可靠性2.1 生产者重连2.2 生产者确认2.3 生产者确认机制的代码实现2.4 如何看待和处理生产者的确认信息 3. 消息代理&#xff08;RabbitMQ&#xff09;的可靠性3.1 数据持久化3.2 LazyQueue&#xff08; 3.12 版本后所有队列都是 Lazy Qu…

如何对我们要多次使用的页面进行一个抽取

有的时候,一个页面我们要多次使用,该怎么抽取呢? 创建一个文件夹,用于存放多次使用的页面 将要多次使用的组件(<template>)和风格(<style>)剪切出来,放入新建的页面 直接进行引用 导入 然后就可以使用

嵌入式C++、QML与MQTT:智能化农业灌溉管理系统设计思路(代码示例)

目录 一、项目概述 二、系统架构 三、环境搭建 1. 硬件环境 2. 软件环境 四、代码实现 1. 硬件端代码示例 2. 软件端代码示例 a. 后端代码&#xff08;Node.js MQTT&#xff09; b. 前端代码&#xff08;QML&#xff09; 五、项目总结 一、项目概述 随着全球对农业…

文件包含漏洞Tomato靶机渗透_详解

一、导入靶机 将下载好的靶机拖入到VMware中&#xff0c;填写靶机机名称(随便起一个)和路径 虚拟机设置里修改网络状态为NAT模式 二、信息收集 1、主机发现 用御剑扫描工具扫描虚拟机的NAT网段&#xff0c;发现靶机的IP是192.168.204.141 2、端口扫描 用御剑端口扫描扫描全…

windows 文件夹下的文件名称全部输入到txt文件中(已解决)

打开cmd 命令行&#xff0c;记住一定是cmd命令行 进入cmd 目前在C盘&#xff0c;跳转D盘&#xff0c;输入d:。 d: 回车&#xff1b; 在输入或者粘贴你的目的路径 我的是 D:\opencv****\build\x64\vc14\lib&#xff0c;回车进入目的路径。 然后 再输入&#xff1a;dir /b &…

Tantivy使用Rust 开发的全文搜索引擎库

一、概述 Tantivy是一个全文搜索引擎库&#xff0c;灵感来自Apache Lucene&#xff0c;用Rust编写。 如果你正在寻找Elasticsearch或Apache Solr的替代品&#xff0c;请查看我们基于Tantivy构建的分布式搜索引擎Quiuckwit。 Tantivy更接近Apache Lucene&#xff0c;而不是E…

K8s集群部署

操作系统初始化配置 #关闭防火墙 systemctl stop firewalld systemctl disable firewalld iptables -F && iptables -t nat -F && iptables -t mangle -F && iptables -X#关闭selinux setenforce 0 sed -i s/enforcing/disabled/ /etc/selinux/config…

当Vercel的域名验证规则碰上JPDirect这种不配合的同学把我的脑袋擦出了火星子

文章目录 前言问题简单说明Vercel主要功能和特点 JPDirectNameServers解决方案 总结 前言 处理域名转移这件事已经过去好几天&#xff0c;终于抽出点时间来总结一下&#xff0c;解决这件事大概花了2周多时间&#xff0c;因为时差的原因导致沟通缓慢&#xff0c;今天准备长话短…

Python 爬虫项目实战(二):爬取微博热搜榜

前言 网络爬虫&#xff08;Web Crawler&#xff09;&#xff0c;也称为网页蜘蛛&#xff08;Web Spider&#xff09;或网页机器人&#xff08;Web Bot&#xff09;&#xff0c;是一种按照既定规则自动浏览网络并提取信息的程序。爬虫的主要用途包括数据采集、网络索引、内容抓…

L-H、BytePlus 和 INOVAI在东京成功举办Web3 AI未来峰会

7月30日&#xff0c;L-H (Legendary Humanity)、字节跳动旗下BytePlus 和日本知名Web3孵化器 INOVAI 在东京联合举办Web3&AI未来峰会&#xff0c;水滴资本等行业重磅机构共同参与此次峰会&#xff0c;探讨AI与 Web3的融合性未来。 在此次峰会上&#xff0c;L-H (Legendary…