单向链表的实现

前言:继顺序表后的又一个线性结构——链表,这里将单向链表的实现。


目录

链表简介:

多文件实现:

SList.h:

SList.c实现函数的详解:

因为插入数据需要创建节点,很频繁,所以直接将创建新节点分装成一个函数:

尾插:

头插:

 将链表中的数据打印:

头删:

尾删:

查找:

插入:

1.在指定位置之前插入节点:

2.在指定位置之后插入节点:

删除指定位置的节点:

删除指定位置之后的节点:

销毁链表:

全部源码:

结语:


链表简介:

图解:

上图是链表的逻辑结构,逻辑上是线性的,而在内存中不是连续的,因为链表的节点都是由malloc函数开辟的,所以在内存中不连续;链表是由一个一个的节点构成,节点就是上图中的一个一个的长方形。

节点的结构:

typedef int SLTDataType;
struct SListNode
{struct SListNode* next;//存放下一个节点的地址SLTDataType data;//存放该节点的数据
}

链表与顺序表对比

顺序表在逻辑上和物理上都是线性的,

而链表只在逻辑上是线性的。

顺序表的缺点:

1:顺序表需要增容,增容时,使用的是realloc函数,在原来的空间后没有足够的空间就需要去内存的其他区域去开辟空间,这时需要将原来的数据拷贝进新的空间,效率低:

2:将顺序表中的数据删除,不会释放空间,浪费空间:举个例子,你以前的顺序表中存储了1000个数据,你现在将数据都删除了,那么那块空间现在没有数据并且还没释放,造成了空间浪费。

链表优点:

每想存储一个数据时就需要用malloc开辟一个空间,若删除数据可以直接把存放数据的空间释放。


多文件实现:

文件名称负责的功能
SList.h链表的定义,需要用到库里的头文件的包含,链表需要实现函数的声明。
SList.c链表函数的实现
test.c测试接口(这里大家可以自己测,自己写)

SList.h:

#pragma once
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>typedef int SLTDataType;typedef struct SListNode
{struct SListNode* next;SLTDataType data;
}SLTNode;//头插
void SLTPushFront(SLTNode** pphead, SLTDataType x);
//尾插
void SLTPushBack(SLTNode** pphead, SLTDataType x);
//打印链表中的数据
void SLTPrint(SLTNode* pphead);
//尾删
void SLTPopBack(SLTNode** pphead);
//头删
void SLTPopFront(SLTNode** pphead);
//查找
SLTNode* SLTFind(SLTNode* phead, SLTDataType x);
//在指定位置前插入数据
void SLTInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x);
//在指定位置后插入数据
void SLTInsertAfter(SLTNode* pos, SLTDataType x);
//删除指定位置的数据
void SLTErase(SLTNode** pphead, SLTNode* pos);
//删除指定位置之后的数据
void SLTEraseAfter(SLTNode* pos);
//销毁链表
void SLTDestroy(SLTNode** pphead);

SList.c实现函数的详解:

因为插入数据需要创建节点,很频繁,所以直接将创建新节点分装成一个函数:

SLTNode* SLTBuyNode(SLTDataType x)
{SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));if (newnode == NULL){perror("malloc");exit(1);}newnode->data = x;newnode->next = NULL;//使新节点指向的下一个节点为NULLreturn newnode;
}

x: 想创建的节点中存储的数据

尾插:

void SLTPushBack(SLTNode** pphead, SLTDataType x)
{assert(pphead);SLTNode* pcur = *pphead;SLTNode* newnode = SLTBuyNode(x);//创建一个新节点//链表为空if (*pphead == NULL){*pphead = newnode;}//链表不为空else{//pcur->nextd等于NULL时跳出循环while (pcur->next){pcur = pcur->next;}//此时pcur指向最后一个节点pcur->next = newnode;}
}

为什么要传二级指针呢?

如果尾插时,为空链表,那么尾插时会改变头结点的值,而想在头插函数中改变头结点的值,那么就需要传头结点的地址,头结点是一级指针,一级指针的地址需要二级指针接收,所以参数是二级指针,这一点很重要,以后的函数也有需要传二级指针的。

所以在插入数据时有两种情况:

1,链表为空

2,链表不为空,因为是尾插,所以需要找到最后一个节点,并在其后面插入一个新节点。

头插:

void SLTPushFront(SLTNode** pphead, SLTDataType x)
{assert(pphead);SLTNode* newnode = SLTBuyNode(x);if (*pphead == NULL){*pphead = newnode;}else{newnode->next = *pphead;*pphead = newnode;}
}

老问题为什么传二级指针?

因为是头插,所以头结点会指向新创建的节点,需要改变头结点的值,若传一级指针,只是头结点的临时拷贝,改变函数中的参数的值不会改变,头结点的值,所以需要传二级指针

考虑是否为空链表:

1:空链表,只需要在头结点后插入一个新节点

2:不为空链表:不仅需要在头结点后插入一个新节点,还需要让新节点的next指向原来的链表的第一个节点。

图解:

 将链表中的数据打印:

void SLTPrint(SLTNode* phead)
{SLTNode* pcur = phead;//直到pcur指向NULL为止while (pcur){printf("%d->", pcur->data);pcur = pcur->next;}printf("NULL\n");
}

打印效果:

这里有一段代码:

int main()
{SLTNode* list = NULL;SLTPushBack(&list, 1); SLTPushBack(&list, 2);SLTPushBack(&list, 3);SLTPrint(list);return 0;
}

头删:

void SLTPopFront(SLTNode** pphead)
{assert(pphead && *pphead);//防止是无效链表和空链表SLTNode* tmp = (*pphead)->next;free(*pphead);*pphead = tmp;
}

assert(pphead&&*pphead)就相当于assert(pphead!=NULL&&*pphead!=NULL)

pphead!=NULL防止其指向无效链表

*pphead!=NULL防止空链表

这个在之后的函数实现也会有。

为什么传二级指针?

因为是头删需要改变头结点的值,让他指向原来链表的头结点的下一个节点。

尾删:

void SLTPopBack(SLTNode** pphead)
{assert(pphead && *pphead);SLTNode* pcur = *pphead;SLTNode* prev = *pphead;if ((*pphead)->next == NULL){free(*pphead);*pphead = NULL;}else{while (pcur->next){prev = pcur;pcur = pcur->next;}free(pcur);prev->next = NULL;}
}

为什么要传二级指针?

当链表中有一个数据时,删除这个数据,链表就为空链表了,所以头结点指向空,改变了头结点的值。

为什么要定义prev和pcur指针呢?

在删除最后一个节点后,需要将最后一个节点的前一个节点的next置为NULL,使其成为新的尾结点。

因为链表是单向的所以不能通过最后一个节点找到前一个节点,所以需要引入prev指针,prev指针指向pcur指向的节点的前一个节点,所以当pcur指针指向最后一个节点时,prev指向最后一个节点的前一个节点,所以删除最后一个节点后,再将prev->next=NULL,就可以了。

图解:

查找:

SLTNode* SLTFind(SLTNode* phead, SLTDataType x)
{SLTNode* pcur = phead;while (pcur){if (pcur->data == x)return pcur;pcur = pcur->next;}return NULL;
}

从头结点开始查找,一个节点一个节点的走,如果中途找到该数据,则直接返回该节点的地址;若走完最后一个节点都没找到该数据,那么返回NULL

插入:

1.在指定位置之前插入节点:

void SLTInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x)
{assert(pphead&&*pphead);assert(pos);//防止pos为NULLSLTNode* pcur = *pphead;SLTNode* prev = *pphead;SLTNode* newnode = SLTBuyNode(x);if (pos == *pphead){*pphead = newnode;newnode->next = pos;}else{while (pcur!= pos){prev = pcur;pcur = pcur->next;}prev->next = newnode;newnode->next = pos;}
}

二级指针:若在头结点之前插入会改变头结点的值,所以用二级指针。

pcur和prev:因为插入需要找到pos的前一个节点,使前一个节点的next指向新的节点

插入分两种情况:

在头结点前插入和在其他位置插入

2.在指定位置之后插入节点:

void SLTInsertAfter(SLTNode* pos, SLTDataType x)
{assert(pos);SLTNode* newnode = SLTBuyNode(x);newnode->next = pos->next;pos->next = newnode;
}

因为在pos之后插入所以不需要用到prev和pcur,也不需要用到二级指针。

注意代码顺序:如果先改pos->next,那么就找不到pos的下一个节点了。

删除指定位置的节点:

void SLTErase(SLTNode** pphead, SLTNode* pos)
{assert(pphead && *pphead);//该链表不能为空链表assert(pos);SLTNode* pcur = *pphead;SLTNode* prev = *pphead;if (pos == *pphead){*pphead = pos->next;free(pos);pos = NULL;}else{while (pcur != pos){prev = pcur;pcur = pcur->next;}prev->next = pos->next;free(pos);pos = NULL;}
}

当删除的位置为头结点时,删除该节点会改变头结点的值,所以需要二级指针。

所以删除时分两种情况:1,pos为头结点  2,pos 不为头结点

删除指定位置之后的节点:

void SLTEraseAfter(SLTNode* pos)
{assert(pos&&pos->next);SLTNode* del = pos->next;pos->next = del->next;free(del);del = NULL;
}

注意:pos->next不为NULL否则就会对NULL进行解引用引发错误。

要用del保存要删除的节点的地址否则就会找不到该节点。

图解:

销毁链表:

void SLTDestroy(SLTNode** pphead)
{assert(pphead && *pphead);SLTNode* pcur = *pphead;SLTNode* prev = *pphead;while (pcur){prev = pcur;pcur = pcur->next;free(prev);}*pphead = NULL;
}

思路:用前后指针prev和pcur防止,如果只用一个指针pcur,先释放则找不到下一个节点了,若先使pcur走到下一个节点则找不到上一个节点。

所以用前后指针的释放prev指向的节点,然后再让prev指向pcur,pcur在指向下一个节点,直到pcur走到NULL为止。

图解:


全部源码:

SList.h

#pragma once
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>typedef int SLTDataType;typedef struct SListNode
{struct SListNode* next;SLTDataType data;
}SLTNode;//头插
void SLTPushFront(SLTNode** pphead, SLTDataType x);
//尾插
void SLTPushBack(SLTNode** pphead, SLTDataType x);
//打印链表中的数据
void SLTPrint(SLTNode* pphead);
//尾删
void SLTPopBack(SLTNode** pphead);
//头删
void SLTPopFront(SLTNode** pphead);
//查找
SLTNode* SLTFind(SLTNode* phead, SLTDataType x);
//在指定位置前插入数据
void SLTInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x);
//在指定位置后插入数据
void SLTInsertAfter(SLTNode* pos, SLTDataType x);
//删除指定位置的数据
void SLTErase(SLTNode** pphead, SLTNode* pos);
//删除指定位置之后的数据
void SLTEraseAfter(SLTNode* pos);
//销毁链表
void SLTDestroy(SLTNode** pphead);

SList.c

#include "SList.h"SLTNode* SLTBuyNode(SLTDataType x)
{SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));if (newnode == NULL){perror("malloc");exit(1);}newnode->data = x;newnode->next = NULL;return newnode;
}void SLTPrint(SLTNode* phead)
{SLTNode* pcur = phead;while (pcur){printf("%d->", pcur->data);pcur = pcur->next;}printf("NULL\n");
}void SLTPushFront(SLTNode** pphead, SLTDataType x)
{assert(pphead);SLTNode* newnode = SLTBuyNode(x);if (*pphead == NULL){*pphead = newnode;}else{newnode->next = *pphead;*pphead = newnode;}
}void SLTPushBack(SLTNode** pphead, SLTDataType x)
{assert(pphead);SLTNode* pcur = *pphead;SLTNode* newnode = SLTBuyNode(x);if (*pphead == NULL){*pphead = newnode;}else{while (pcur->next){pcur = pcur->next;}pcur->next = newnode;}
}void SLTPopBack(SLTNode** pphead)
{assert(pphead && *pphead);SLTNode* pcur = *pphead;SLTNode* prev = *pphead;if ((*pphead)->next == NULL){free(*pphead);*pphead = NULL;}else{while (pcur->next){prev = pcur;pcur = pcur->next;}free(pcur);prev->next = NULL;}
}void SLTPopFront(SLTNode** pphead)
{assert(pphead && *pphead);SLTNode* tmp = (*pphead)->next;free(*pphead);*pphead = tmp;
}SLTNode* SLTFind(SLTNode* phead, SLTDataType x)
{SLTNode* pcur = phead;while (pcur){if (pcur->data == x)return pcur;pcur = pcur->next;}return NULL;
}void SLTInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x)
{assert(pphead&&*pphead);assert(pos);SLTNode* pcur = *pphead;SLTNode* prev = *pphead;SLTNode* newnode = SLTBuyNode(x);if (pos == *pphead){*pphead = newnode;newnode->next = pos;}else{while (pcur!= pos){prev = pcur;pcur = pcur->next;}prev->next = newnode;newnode->next = pos;}
}void SLTInsertAfter(SLTNode* pos, SLTDataType x)
{assert(pos->next);assert(pos);SLTNode* newnode = SLTBuyNode(x);newnode->next = pos->next;pos->next = newnode;
}void SLTErase(SLTNode** pphead, SLTNode* pos)
{assert(pphead && *pphead);assert(pos);SLTNode* pcur = *pphead;SLTNode* prev = *pphead;if (pos == *pphead){*pphead = pos->next;free(pos);pos = NULL;}else{while (pcur != pos){prev = pcur;pcur = pcur->next;}prev->next = pos->next;free(pos);pos = NULL;}
}void SLTEraseAfter(SLTNode* pos)
{assert(pos&&pos->next);SLTNode* del = pos->next;pos->next = del->next;free(del);del = NULL;
}void SLTDestroy(SLTNode** pphead)
{assert(pphead && *pphead);SLTNode* pcur = *pphead;SLTNode* prev = *pphead;while (pcur){prev = pcur;pcur = pcur->next;free(prev);}*pphead = NULL;
}

结语:

又写完一篇数据结构的实现,cheers!

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

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

相关文章

ArcGIS Pro 3D建模简明教程

在本文中&#xff0c;我讲述了我最近一直在探索的在 ArcGIS Pro 中设计 3D 模型的过程。 我的目标是尽可能避免与其他软件交互&#xff08;即使是专门用于 3D 建模的软件&#xff09;&#xff0c;并利用 Pro 可以提供的可能性。 这个短暂的旅程分为三个不同的阶段&#xff1a;…

【免费领取源码】可直接复用的医院管理系统!

今天给大家分享一套基于SpringbootVue的医院管理系统源码&#xff0c;在实际项目中可以直接复用。(免费提供&#xff0c;文中自取) 系统运行图&#xff08;设计报告和接口文档&#xff09; 1、后台管理页面 2、排班管理页面 3、设计报告包含接口文档 源码免费领取方式 后台私信…

Mac版2024 CleanMyMac X 4.15.2 核心功能详解 cleanmymac这个软件怎么样?cleanmymac到底好不好用?

近些年伴随着苹果生态的蓬勃发展&#xff0c;越来越多的用户开始尝试接触Mac电脑。然而很多人上手Mac后会发现&#xff0c;它的使用逻辑与Windows存在很多不同&#xff0c;而且随着使用时间的增加&#xff0c;一些奇奇怪怪的文件也会占据有限的磁盘空间&#xff0c;进而影响使用…

【k8s】:深入理解 Kubernetes 中的污点(Taints)与容忍度(Tolerations)

【k8s】&#xff1a;深入理解 Kubernetes 中的污点&#xff08;Taints&#xff09;与容忍度&#xff08;Tolerations&#xff09; 1、污点&#xff08;Taints&#xff09;2、容忍度&#xff08;Tolerations&#xff09;3、示例演示-测试污点的具体应用场景3.1 给节点打污点&…

Zabbix监控Windows

1.在虚拟机中安装zabbix 安装系统一直托不进虚拟机中&#xff1b;因为没安装Tools组件 点击虚拟机&#xff0c;选择安装VMware Tools 2.配置zabbix

整数运算超越存储单元表示范围:上溢出、下溢出、回绕

示例&#xff1a; /*** brief how about integer-underflow-overflow? show you here.* author wenxuanpei* email 15873152445163.com(query for any question here)*/ #define _CRT_SECURE_NO_WARNINGS//support c-library in Microsoft-Visual-Studio #include <std…

AndroidAutomotive模块介绍(一)整体介绍

前言 Android Automotive 是一个基本 Android 平台&#xff0c;可运行 IVI 系统中预安装的 Android 应用以及可选的第二方和第三方 Android 应用。 本系列文档将会系统的介绍 Android Automotive 的功能、架构、逻辑等。模块逻辑将从 应用api接口、系统服务、底层服务&#x…

【数据结构与算法】之双向链表及其实现!

​ 个人主页&#xff1a;秋风起&#xff0c;再归来~ 数据结构与算法 个人格言&#xff1a;悟已往之不谏&#xff0c;知来者犹可追 克心守己&#xff0c;律己则安&#xff01; 目录 1、双向链表的结构及概念 2、双向链表的实现 2.1 要实现的接口…

spring高级篇(一)

1、ApplicationContext与BeanFactory BeanFactory是ApplicationContext的父级接口&#xff1a;&#xff08;citlaltu查看类关系图&#xff09; 在springboot的启动类中&#xff0c;我们通过SpringApplication.run方法拿到的是继承了ApplicationContext的ConfigurableApplicatio…

PyCharm 2024.1 发布:全面升级,助力高效编程!

PyCharm 2024.1 发布&#xff1a;全面升级&#xff0c;助力高效编程&#xff01; 文章目录 PyCharm 2024.1 发布&#xff1a;全面升级&#xff0c;助力高效编程&#xff01;摘要引言 Hugging Face&#xff1a;模型和数据集的快速文档预览针对 JavaScript 和 TypeScript 的全行代…

深度学习-多尺度训练的介绍与应用

一、引言 在当今快速发展的人工智能领域&#xff0c;多尺度训练已经成为了一种至关重要的技术&#xff0c;特别是在处理具有复杂结构和不同尺度特征的数据时。这种技术在许多应用中发挥着关键作用&#xff0c;例如图像识别、自然语言处理和视频分析等。 多尺度训练的定义 多尺…

软考 - 系统架构设计师 - 嵌入式真题

问题 1&#xff1a; &#xff08;1&#xff09;.HTML 静态化&#xff1a;可以实现对系统经常访问的页面进行静态化以提高系统访问的效率&#xff0c;但系统页面通常需要数据库中的用户信息和用户选择来动态显示&#xff0c;因此不适合采用。 HTML 静态化&#xff1a; 将动态生成…

Python爬虫:requests模块的基本使用

学习目标&#xff1a; 了解 requests模块的介绍掌握 requests的基本使用掌握 response常见的属性掌握 requests.text和content的区别掌握 解决网页的解码问题掌握 requests模块发送带headers的请求掌握 requests模块发送带参数的get请求 1 为什么要重点学习requests模块&…

第23次修改了可删除可持久保存的前端html备忘录:增加了百度引擎

第22次修改了可删除可持久保存的前端html备忘录视频背景分离&#xff0c;增加了本地连接&#xff0c;增加了纯CSS做的折叠隐藏修改说明 <!DOCTYPE html> <html lang"zh"> <head><meta charset"UTF-8"><meta name"viewport…

WPF中DataGrid主从数据(父子数据)展示

在wpf中可以使用DataGrid控件,进行主从数据展示,也称父子数据展示。下面展示纯原生控件编码实现功能(样式自己可以根据需求进行修改)。 效果如下: 点击图标,展开和收缩可以自由的切换,也可以自己重新写一个样式,比如+,-或者类似图标的样式,都是可以的。 1.首先创建一…

【光伏企业】光伏项目怎么做才能提高效率?

一、精细化项目管理 项目规划&#xff1a;在项目启动前&#xff0c;进行充分的调研和规划&#xff0c;明确项目的目标、规模、预算和时间表&#xff0c;确保各项资源得到合理分配。 团队建设&#xff1a;组建一支高效、专业的项目团队&#xff0c;确保团队成员具备光伏领域的…

计算机视觉——图像特征提取D2D先描述后检测特征提取算法原理

概述 局部特征提取是计算机视觉中的一个重要任务&#xff0c;它旨在从图像中提取出能够代表图像局部结构和外观信息的特征。这些特征通常用于图像匹配、物体识别、三维重建、跟踪和许多其他应用。传统方法&#xff0c;如尺度不变特征变换&#xff08;SIFT&#xff09;&#xf…

浅谈Java JVM

Java虚拟机&#xff08;Java Virtual Machine&#xff0c;简称JVM&#xff09;是Java语言的核心组成部分&#xff0c;它是一个抽象的计算机&#xff0c;负责执行Java字节码指令。JVM是Java平台无关性的基石&#xff0c;它为Java代码提供了一个标准的运行环境&#xff0c;使Java…

【C/C++笔试练习】read函数、虚拟存储、用户态、线程特点、缺页处理、调度算法、进程优先级、锁的使用、创建进程、不用加减乘除做加法、三角形

文章目录 C/C笔试练习选择部分&#xff08;1&#xff09;read函数&#xff08;2&#xff09;虚拟存储&#xff08;3&#xff09;用户态&#xff08;4&#xff09;线程特点&#xff08;5&#xff09;缺页处理&#xff08;6&#xff09;调度算法&#xff08;7&#xff09;进程优先…

《由浅入深学习SAP财务》:第2章 总账模块 - 2.6 定期处理 - 2.6.6 年初操作:科目余额结转

2.6.6 年初操作&#xff1a;科目余额结转 在使用事务代码 FAGLB03 查询科目余额时&#xff0c;可以看到按期间的发生额清单。其中&#xff0c;第一行称为“余额结转”&#xff0c;该行的累计余额代表上年度遗留下来的余额&#xff0c;也就是年初余额。对于资产负债表科目而言&a…