C语言:单链表(不带头节点)

目录

一、单链表概念

单链表的特点

二、单链表的实现

1、打印函数的实现

2、尾插函数的实现

3、全部函数的实现 

总结: 


一、单链表概念

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

单链表是一种链式数据结构,它由一系列节点(Node)组成,每个节点包含两部分数据域(data)和指针域(next)。数据域存储数据,指针域存储下一个节点的地址。单链表中的节点按顺序链接,形成一条链。

单链表的特点
  1. 单向性链表中的每个节点只包含指向下一个节点的指针,因此,链表只能从头节点开始,逐个访问后续节点
  2. 动态性:链表的节点可以动态地进行插入和删除操作,不需要像数组那样事先分配固定大小的空间
  3. 不连续性:链表中的节点在内存中不必是连续存储的,这增加了数据管理的灵活性。

每个节点包括一个数据域data和 一个指针域next(存储下一个节点的地址)

typedef int SLTDataType;
typedef struct SListNode {SLTDataType data;struct SListNode* next;
}SLTNode;

 我们现在就可以定义一个节点了,首先我们定义一个节点node1,然后定义个指针指向该节点,然后让node1的next域指向node2

SLTNode* node1 = (SLTNode*)malloc(sizeof(SLTNode));
SLTNode* node2 = (SLTNode*)malloc(sizeof(SLTNode));
SLTNode* plist = node1;  //定义头指针,头指针指向 node1
node1->next = node2;
node2->next = NULL;

 这里,单链表与顺序表不同的是,链表的每节“车厢”都是单独申请下来的空间,我们这里称为结点。图中,指针变量plist保存的是第一个节点的地址,我们称为plist此时“指向”第一个结点,如果我们希望plist“指向”第二个结点时,只需要修改plist保存的地址内容为0x0000003.

链表的性质:

1、链式结构在逻辑上时连续的,在物理结构上不一定连续

2、结点一般是从堆上申请的

3、从堆上申请来的空间,是按照一定策略分配出来的,每次申请的空间可能连续,也可能不连续

二、单链表的实现

 这里,我们定义了一个头文件,SList.h来实现对单链表操作的一些列函数,有打印、尾插、头插、尾删、头删、查找、插入等操作。

#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
typedef int SLTDataType;
typedef struct SListNode {SLTDataType data;struct SListNode* next;
}SLTNode;//1.打印函数
void SLTPrint(SLTNode* phead);
//2.尾插
void SLTPushBack(SLTNode** pphead, SLTDataType x);
//3.头插
void SLTPushFront(SLTNode** pphead, SLTDataType x);
//4.尾删
void SLTPopBack(SLTNode** pphead);
//5.头删
void SLTPopFront(SLTNode** pphead);
//6.查找
SLTNode* SLTFind(SLTNode* phead, SLTDataType x);
//7.在指定位置之前插入数据
void SLTInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x);
//8.删除pos节点
void SLTErase(SLTNode** pphead, SLTNode* pos);
//9.在指定位置之后插入数据
void SLTInsertAfter(SLTNode* pos, SLTDataType x);
//10.删除pos节点
void SLTErase(SLTNode** pphead, SLTNode* pos);
//11.删除pos之后的节点
void SLTEraseAfter(SLTNode* pos);
//12.销毁链表
void SListDestroy(SLTNode** pphead);

 为了申请节点方便,我们实现了一个对结点初始化的函数,在这里,我们通过调用函数,申请节点,初始化后,我们将该节点的地址返回,因为是在函数中申请的节点,node变量会被栈空间收回,但是节点空间是在堆上申请的申请的空间还会存在,我们在函数外用一个节点指针接收,这时该节点申请成功,且可以通过接收的节点指针操作该节点。

//申请节点,并初始化
SLTNode* SLTBuyNode(SLTDataType x) {SLTNode* node = (SLTNode*)malloc(sizeof(SLTNode));//申请失败if (node == NULL) {perror("malloc fail!\n");exit(1);}//初始化node->data = x;node->next = NULL;return node;
}

1、打印函数的实现

SLTPrint(plist);

将plist存储的第一个节点的地址值phead,这时,变量phead也可以访问单链表,遍历每个节点,就可以实现对单链表的打印。

//1.打印链表
void SLTPrint(SLTNode* phead) {SLTNode* pcur = phead;  //定义一个新的头指针指向 node1while (pcur != NULL) {printf("%d -> ", pcur->data);pcur = pcur->next;      //指针指向下一个节点}printf("NULL\n");
}

2、尾插函数的实现

这里,我们可以看到,我们是用二级指针接收的plist的地址通过接收plist的地址,我们可以直接操作原单链表中的plist,实现各种操作。

注意:这里和上面打印函数中用一级指针的区别是,上面用一级指针接收地址操作单链表的方式不能操作plist的指向关系,因为在上面的函数中,plist是一直存在的,ppphead是函数中的临时变量,可以改变每个节点中的值(data域,和next域),但是一旦函数执行完毕,对phead的操作就会消失

//2.尾插
void SLTPushBack(SLTNode** pphead, SLTDataType x) {assert(pphead);//申请一个新的节点SLTNode* newnode = SLTBuyNode(x);if (*pphead == NULL) {   //头指针为空(链表为空)*pphead = newnode;   //将头指针指向新的节点}else {   //链表不为空,找尾节点SLTNode* ptail = *pphead;   //把头节点的地址赋值给 ptailwhile (ptail->next != NULL) {ptail = ptail->next;   //ptail指向下一个节点}ptail->next = newnode;}
}

这里,就得举个特殊情况得例子,当plist的值为NULL,就是没有申请节点的时候我们要尾插一个节点,这时候我们要插入的地方时plist后面,即使当函数完成后,pphead变量被回收,也不会影响plist的指向关系,如同:

如果我们这个时候不是用的二级指针接收的plist的地址,而是直接使用一级指针,这个时候传递的是plist的值, 我们操作的是当前的指针,也就是在pphead后面插入数据当数据插入完成后,函数完成,pphead被释放申请的节点就找不到了,plist后面还是没有连接节点

3、全部函数的实现 

SList.h

#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
typedef int SLTDataType;
typedef struct SListNode {SLTDataType data;struct SListNode* next;
}SLTNode;//1.打印函数
void SLTPrint(SLTNode* phead);
//2.尾插
void SLTPushBack(SLTNode** pphead, SLTDataType x);
//3.头插
void SLTPushFront(SLTNode** pphead, SLTDataType x);
//4.尾删
void SLTPopBack(SLTNode** pphead);
//5.头删
void SLTPopFront(SLTNode** pphead);
//6.查找
SLTNode* SLTFind(SLTNode* phead, SLTDataType x);
//7.在指定位置之前插入数据
void SLTInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x);
//8.删除pos节点
void SLTErase(SLTNode** pphead, SLTNode* pos);
//9.在指定位置之后插入数据
void SLTInsertAfter(SLTNode* pos, SLTDataType x);
//10.删除pos节点
void SLTErase(SLTNode** pphead, SLTNode* pos);
//11.删除pos之后的节点
void SLTEraseAfter(SLTNode* pos);
//12.销毁链表
void SListDestroy(SLTNode** pphead);

Slist.c

#define _CRT_SECURE_NO_WARNINGS 1
#include"SList.h"
//测试函数ff
void ff(SLTNode* pphead ,int x) {SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));newnode->data = x;newnode->next = NULL;if (pphead == NULL) {   //头指针为空(链表为空)pphead = newnode;   //将头指针指向新的节点}else {   //链表不为空,找尾节点SLTNode* ptail = pphead;   //把头节点的地址赋值给 ptailwhile (ptail->next != NULL) {ptail = ptail->next;   //ptail指向下一个节点}ptail->next = newnode;}SLTPrint(pphead);
}
//1.打印链表
void SLTPrint(SLTNode* phead) {SLTNode* pcur = phead;  //定义一个新的头指针指向 node1while (pcur != NULL) {printf("%d -> ", pcur->data);pcur = pcur->next;      //指针指向下一个节点}printf("NULL\n");
}
//申请节点,并初始化
SLTNode* SLTBuyNode(SLTDataType x) {SLTNode* node = (SLTNode*)malloc(sizeof(SLTNode));//申请失败if (node == NULL) {perror("malloc fail!\n");exit(1);}//初始化node->data = x;node->next = NULL;return node;
}
//2.尾插
void SLTPushBack(SLTNode** pphead, SLTDataType x) {assert(pphead);//申请一个新的节点SLTNode* newnode = SLTBuyNode(x);if (*pphead == NULL) {   //头指针为空(链表为空)*pphead = newnode;   //将头指针指向新的节点}else {   //链表不为空,找尾节点SLTNode* ptail = *pphead;   //把头节点的地址赋值给 ptailwhile (ptail->next != NULL) {ptail = ptail->next;   //ptail指向下一个节点}ptail->next = newnode;}
}
//3.头插
void SLTPushFront(SLTNode** pphead, SLTDataType x) {assert(pphead);SLTNode* newnode = SLTBuyNode(x);  //创建新的节点newnode->next = *pphead;      //新的节点指向当前的头节点*pphead = newnode;     //头指针指向新的节点
}
//4.尾删
void SLTPopBack(SLTNode** pphead) {assert(pphead && *pphead);   //传的参数不能为空,链表也不能为空//如果只有一个节点if ((*pphead)->next == NULL) {free(*pphead);*pphead = NULL;}else {SLTNode* prev = NULL;     //空指针SLTNode* ptail = *pphead;  //将头节点赋值给ptailwhile (ptail->next != NULL) {prev = ptail;    //prev指向当前的位置ptail = ptail->next;   //ptail指向下一个节点}prev->next = NULL;   //前一个节点置为NULLfree(ptail);        //释放掉最后一个节点ptail = NULL;}
}
//5.头删
void SLTPopFront(SLTNode** pphead) {assert(pphead && *pphead);SLTNode* next = (*pphead)->next; //保存头节点的下一个地址free(*pphead);     //释放头节点*pphead = next;    //头节点下移}
//6.查找,找到了,返回该指向的指针(原单链表的节点地址)
SLTNode* SLTFind(SLTNode* phead, SLTDataType x) {SLTNode* pcur = phead;while (pcur)  //pcur != NULL{if (pcur->data == x){return pcur;}pcur = pcur->next;     //pcur往后走}//没找到return NULL;
}
//7.在指定位置之前插入数据
void SLTInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x) {assert(pphead && pos);//当pos就是头节点,相当于头插if (pos == *pphead){SLTPushFront(pphead, x);}else {SLTNode* newnode = SLTBuyNode(x);  //创建新的节点并赋初值  SLTNode* prev = *pphead;    //prev 指向头节点while (prev->next != pos) {     //prev指向pos前面一个节点prev = prev->next;}//改变指向关系newnode->next = pos;prev->next = newnode;}
}
//9.在指定位置之后插入数据
void SLTInsertAfter(SLTNode* pos, SLTDataType x) {SLTNode* next = pos->next;   //将pos的下个节点地址存储SLTNode* newnode = SLTBuyNode(x);   //创建一个新节点  newnode->next = next;      pos->next = newnode;
}
//10.删除pos节点
void SLTErase(SLTNode** pphead, SLTNode* pos) {assert(pphead && pos);//删除的节点就是头节点if (pos == *pphead) {SLTPopFront(pphead);}else {SLTNode* prev = *pphead;//找pos前面的节点while (prev->next != pos) {   prev = prev->next;}prev->next = pos->next;   //pos前一个节点指向pos后一个节点free(pos);pos = NULL;}
}
//11.删除pos之后的节点
void SLTEraseAfter(SLTNode* pos) {assert(pos && pos->next);    //pos之后的节点不能为NULLSLTNode* del = pos->next;  //找到pos的下一个的节点pos->next = del->next;    //pos的下一个节点为del的下一个节点free(del);del = NULL;
}
//12.销毁链表
void SListDestroy(SLTNode** pphead) {SLTNode* pcur = *pphead;while (pcur) {SLTNode* next = pcur->next;   //指向pcur下一个节点free(pcur);   //释放当前的pcurpcur = next;  //pcur后移到next位置}*pphead = NULL;
}

 test.c

void test03()
{SLTNode* plist = NULL;   //申请一个空指针SLTNode* node1 = (SLTNode*)malloc(sizeof(SLTNode));node1->data = 1;node1->next = NULL;SLTPrint(plist);
}
int main()
{test03();return 0;
}

总结: 

这里给出完整实现单链表的函数方法,其他的函数实现比较简单,这里重要的是解决单链表中的传递二级指针和一级指针的区别,方便个位读者明了的知道函数的实现原理,更好的学习其他的数据结构,希望各位读者可以多多对文章提出建议,方便我后期改进文章以供其他的读者阅读。

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

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

相关文章

沈阳乐晟睿浩科技有限公司:引领抖音小店迈向新纪元

在当今数字化浪潮汹涌的时代&#xff0c;电子商务以其独特的魅力和无限潜力&#xff0c;正深刻改变着人们的消费习惯与商业模式。在这场变革中&#xff0c;沈阳乐晟睿浩科技有限公司凭借其敏锐的市场洞察力和卓越的技术实力&#xff0c;成为了抖音小店领域的佼佼者&#xff0c;…

Maven与Gradle的区别

Maven与Gradle是两种流行的构建工具&#xff0c;广泛用于Java项目的管理和构建。以下是它们的对比&#xff0c;包括官网、Windows 11配置环境、在IDEA中的相同点和不同点&#xff0c;以及它们各自的优缺点。 官网 Maven官网: https://maven.apache.orgGradle官网: https://gr…

Print Settings Page 打印设置页面

“打印设置”页面提供了设计时工具&#xff0c;用于自定义控制视图打印版本外观的打印选项。此页面如下图所示。 “选项”和“行为”选项卡式页面提供对视图打印选项的设计时访问&#xff0c;这些选项可通过其 GridView.OptionsPrint 属性或卡片视图的 CardView.OptionsPrint 进…

【Next.js 项目实战系列】07-分配 Issue 给用户

原文链接 CSDN 的排版/样式可能有问题&#xff0c;去我的博客查看原文系列吧&#xff0c;觉得有用的话&#xff0c;给我的库点个star&#xff0c;关注一下吧 上一篇【Next.js 项目实战系列】06-身份验证 分配 Issue 给用户 本节代码链接 Select Button​ # /app/issues/[i…

51单片机快速入门之 串行通信 2024/10/21

51单片机快速入门之 串行通信 并行通信: 好处:传输快 适合短距离通信弊端:占用大量io 接线形式为8对8 串行通信 异步通信: 数据一帧一帧传送,传输完一帧之后,可继续或者等待(等待时为高电平) 其帧细分为(图片来源) 起始位:数据帧开始,一定为 0 外部设备只有接受到 0 之后…

北京大学冯惠:与卓越者同行,方能更快的成长 | OceanBase数据库大赛获奖选手访谈

本文邀请2022 OceanBase 数据库大赛的季军&#xff0c;来自北京大学的冯惠同学&#xff0c;与我们分享如何寻找自己的兴趣&#xff1b;在一番经历后&#xff0c;对于产品与研发的职业方向观察&#xff1b;以及如何在学生时期提升个人专业能力&#xff0c;和参加数据库大赛的个人…

微信小程序用开发工具在本地真机调试可以正常访问摄像头,发布了授权后却无法访问摄像头,解决方案

今天开发上线了一个拍照的微信小程序&#xff0c;用uniapp的Vue3开发的&#xff0c;调用的camera组件&#xff0c;相关代码如下&#xff1a; <!-- 微信小程序相机组件 --><view v-if"showCamera" class"camera-container"><camera :device…

Ability内页面的跳转和数据传递(router和want显/隐跳转)

目录 案例:使用router完成页面跳转 1.创建一个Arkts项目 2.创建第二个页面 3.手动创建第三个页面 4.编写跳转路由 5.编写接受路由 6.编写返回上一个页面的代码 7.第三个界面代码完善 8.效果 案例:使用want启动Ability 1.创建一个新的项目 2.创建第二个界面 3.创建一个Ability 4…

23年408数据结构

第一题&#xff1a; 解析&#xff1a; 第一点&#xff0c;我们要知道顺序存储的特点&#xff1a;优点就是随用随取&#xff0c;就是你想要查询第几个元素可以直接查询出来&#xff0c;时间复杂度就是O(1)&#xff0c;缺点就是不适合删除和插入&#xff0c;因为每次删除和插入一…

android app执行shell命令视频课程补充android 10/11适配-千里马android

(https://blog.csdn.net/learnframework/article/details/120103471) https://blog.csdn.net/learnframework/article/details/120103471 hi&#xff0c;有学员在学习跨进程通信专题课程时候&#xff0c;在实战app执行一个shell命令的项目时候&#xff0c;对课程本身的android …

MySQL-13.DQL-聚合函数

一.DQL-分组查询 二.聚合函数 -- DQL:分组查询 -- 聚合函数 -- 1.统计该企业员工数量 count select count(id) from tb_emp; select count(job) from tb_emp;select count(A) from tb_emp; select count(*) from tb_emp;-- 2.统计该企业最早入职的员工 min select min(entr…

Pyside6 布局管理器(3)--- 控件尺寸、尺寸策略与布局的关系详解

在学习QWidget时我们已经学习了控件尺寸的一些基本设置&#xff0c;比如设置其作为顶层窗口时resize()方法&#xff0c;setGeometry()等方法。但在将控件添加到布局中后我们会发现&#xff0c;这些方法对于QWidget做为子控件时却是无效的。而布局的显示与大小也受到控件的影响。…

网络资源模板--Android Studio 实现简易新闻App

目录 一、项目演示 二、项目测试环境 三、项目详情 四、完整的项目源码 一、项目演示 网络资源模板--基于Android studio 实现的简易新闻App 二、项目测试环境 三、项目详情 登录页 用户输入&#xff1a; 提供账号和密码输入框&#xff0c;用户可以输入登录信息。支持“记…

RabbitMQ最新版本4.0.2在Windows下的安装及使用

RabbitMQ 是一个开源的消息代理和队列服务器&#xff0c;提供可靠的消息传递和队列服务。它支持多种消息协议&#xff0c;包括 AMQP、STOMP、MQTT 等。本文将详细介绍如何在 Windows 系统上安装和使用最新版本的 RabbitMQ 4.0.2。 前言 RabbitMQ 是用 Erlang 语言开发的 AMQP&…

【Linux】【命令】diff

diff DescriptionsArgumentsExamples直接使用diff命令-u 输出格式-c 输出格式并列输出-s 和 -q 脚本示例示例1&#xff1a;目录及文件差异 Descriptions diff命令用于对比两个文件或者两个文件夹的不同之处&#xff0c;求基本语法如下所示&#xff1a; diff [OPTION]... FILES…

信号与噪声分析——第一节-确定信号的分析

目录 1.确定信号的分析 1.1确定信号的分类&#xff1a; 1.周期信号与非周期信号&#xff1a; 周期信号的定义&#xff1a; 性质&#xff1a; 2.能量信号与功率信号&#xff1a; 定义 区别&#xff1a; 3.基带信号与频带信号&#xff1a; 基带信号的定义&#xff1a; …

使用Matplotlib绘制箱线图:详细指南与示例

在数据分析和可视化领域&#xff0c;箱线图&#xff08;Box Plot&#xff09;是一种强大的工具&#xff0c;用于展示数据的分布特征&#xff0c;包括中位数、四分位数、异常值等。本文将详细介绍如何使用Matplotlib库在Python中绘制箱线图&#xff0c;并通过一个实际的血压数据…

基于微信小程序二手物品调剂系统设计与实现

文章目录 前言项目介绍技术介绍功能介绍核心代码数据库参考 系统效果图文章目录 前言 文章底部名片&#xff0c;获取项目的完整演示视频&#xff0c;免费解答技术疑问 项目介绍 二手物品调剂系统是一种在线平台&#xff0c;旨在促进用户之间的二手物品交易。该系统提供了一个…

数智合同 | 业财一体与履约联动的数字化转型

随着信息化技术的发展&#xff0c;合同数智化管理为应对合同管理挑战提供了新机遇。企业需要深入思考数智化手段在合同管理中的应用&#xff0c;以提高合同管理水平&#xff0c;应对新形势下的市场竞争挑战与合规要求&#xff0c;实现企业的高质量发展。 2024年5月&#xff0c;…

数据中心母线槽测温监控装置的优势和如何选型

在当今数字化高速发展的时代&#xff0c;数据中心成为了信息存储与处理的核心枢纽。而确保数据中心的稳定运行&#xff0c;对于企业和社会来说至关重要。其中&#xff0c;母线作为数据中心电力传输的关键环节&#xff0c;其正常运行直接关系到整个数据中心的可靠性。为了保障数…