当前位置: 首页 > news >正文

零基础入门C语言之C语言实现数据结构之单链表

在阅读本篇文章之前,建议读者优先阅读本专栏内前面部分文章。

目录

前言

一、链表概念及结构

二、单链表的实现

三、链表的分类

总结


前言

本篇文章主要介绍数据结构中与单链表相关的知识。


一、链表概念及结构

我们在前面的文章提到了针对顺序表中间插入和头部插入时效率过于低下、增容时降低运行效率和增容造成空间浪费这些问题,我们可以使用链表来解决这些问题,那么什么是链表呢?

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

链表的结构跟火车车厢相似,淡季时车次的车厢会相应减少,旺季时车次的车厢会额外增加几节。只需要将火车里的某节车厢去掉/加上,不会影响其他车厢,每节车厢都是独立存在的。车厢是独立存在的,且每节车厢都有车门。想象一下这样的场景,假设每节车厢的车门都是锁上的状态,需要不同的钥匙才能解锁,每次只能携带一把钥匙的情况下如何从车头走到车尾?最简单的做法:每节车厢里都放一把下一节车厢的钥匙。而在链表里,每节 “车厢” 是什么样的呢?

与顺序表不同的是,链表里的每节 “车厢” 都是独立申请下来的空间,我们称之为“结点/节点”节点的组成主要有两个部分:当前节点要保存的数据和保存下一个节点的地址(指针变量)。图中指针变量plist保存的是第一个节点的地址,我们称plist此时 “指向” 第一个节点,如果我们希望plist“指向” 第二个节点时,只需要修改plist保存的内容为0x0012FFA0。但是为什么还需要指针变量来保存下一个节点的位置?这是因为链表中每个节点都是独立申请的(即需要插入数据时才去申请一块节点的空间),我们需要通过指针变量来保存下一个节点位置才能从当前节点找到下一个节点。结合前面学到的结构体知识,我们可以给出每个节点对应的结构体代码:

struct SListNode
{int data;     //节点数据struct SListNode* next; //指针变量用保存下一个节点的地址
};

当我们想要保存一个整型数据时,实际是向操作系统申请了一块内存,这个内存不仅要保存整型数据,也需要保存下一个节点的地址(当下一个节点为空时保存的地址为空)。当我们想要从第一个节点走到最后一个节点时,只需要在前一个节点拿上下一个节点的地址(下一个节点的钥匙)就可以了。那么请读者思考一下,给定的链表结构中,如何实现节点从头到尾的打印?我们可以看看下面这个图:

那么接下来我们就可以来尝试实现与链表相关的增删查改等操作了。

二、单链表的实现

我们首先先来试着运行一下上面这个顺序打印的代码:

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;void SLTPrint(SLTNode* phead);

SList.c:

#include "SList.h"
void SLTPrint(SLTNode* phead){SLTNode* pcur = phead;while (pcur) {printf("%d->", pcur->data);pcur = pcur->next;}printf("NULL\n");
}

test.c:

#include "SList.h"void SListTest01() {SLTNode* node1 = (SLTNode*)malloc(sizeof(SLTNode));node1->data = 1;SLTNode* node2 = (SLTNode*)malloc(sizeof(SLTNode));node2->data = 2;SLTNode* node3 = (SLTNode*)malloc(sizeof(SLTNode));node3->data = 3;SLTNode* node4 = (SLTNode*)malloc(sizeof(SLTNode));node4->data = 4;node1->next = node2;node2->next = node3;node3->next = node4;node4->next = NULL;SLTNode* plist = node1;SLTPrint(plist);
}int main() {SListTest01();return 0;
}

其运行结果如下:

相信如果你把上面的图片理解清楚之后,再看这个代码你也可以理解地比较清楚了。我们接下来开始常规实现单链表的一些方法,首先是单链表的尾插方法:

//单链表尾插
void SLTPushBack(SLTNode** pphead, SLTDataType x)
{assert(pphead);//*pphead 就是指向第一个节点的指针//空链表和非空链表SLTNode* newnode = SLTBuyNode(x);if (*pphead == NULL){*pphead = newnode;}else{//找尾SLTNode* ptail = *pphead;while (ptail->next){ptail = ptail->next;}//ptail指向的就是尾结点ptail->next = newnode;}
}

我们插入4个节点试试:

这里需要注意我们传入的参数是二级指针,也就是节点指针的地址,如果说我们只传入一个一级指针的话,我们进行操作时只会改变形参的值,等函数运行结束销毁栈帧时,我们的实参并没有发生改变。那么我们接下来实现一下单链表的头插,相比来说这个更简单一些:

//单链表头插
void SLTPushFront(SLTNode** pphead, SLTDataType x) {assert(pphead);SLTNode* newnode = SLTBuyNode(x);newnode->next = *pphead;*pphead = newnode;
}

我们再插入四个节点试试:

实现完插入操作后,我们来尝试实现删除的函数,首先就是尾删函数:

//单链表尾删
void SLTPopBack(SLTNode** pphead)
{//链表不能为空assert(pphead && *pphead);//链表只有一个节点if ((*pphead)->next == NULL) //-> 优先级高于*{free(*pphead);*pphead = NULL;}else {//链表有多个节点SLTNode* prev = *pphead;SLTNode* ptail = *pphead;while (ptail->next){prev = ptail;ptail = ptail->next;}//prev ptailfree(ptail);ptail = NULL;prev->next = NULL;}
}

我们来测试一下,删除两个节点,其结果如下:

相对来说,头删则会比较简单一些:

//单链表头删
void SLTPopFront(SLTNode** pphead)
{//链表不能为空assert(pphead && *pphead);SLTNode* next = (*pphead)->next; //-> 优先级高于*free(*pphead);*pphead = next;
}

我们试着用它来删除两个节点试一试,其运行结果如下:

如果说我们想要查找包含特定数据的节点,那么我们该如何去做呢?我给出我的示例代码:

SLTNode* SLTFind(SLTNode* phead, SLTDataType x)
{SLTNode* pcur = phead;while (pcur)//等价于pcur != NULL{if (pcur->data == x){return pcur;}pcur = pcur->next;}//pcur == NULLreturn NULL;
}

我们试着查找一下含有值为3的节点:

接下来我们来介绍一下在指定位置前或后插入和删除数据的函数:

//在指定位置之前插入数据
void SLTInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x)
{assert(pphead && *pphead);assert(pos);SLTNode* newnode = SLTBuyNode(x);//若pos == *pphead;说明是头插if (pos == *pphead){SLTPushFront(pphead, x);}else {SLTNode* prev = *pphead;while (prev->next != pos){prev = prev->next;}//prev -> newnode -> posnewnode->next = pos;prev->next = newnode;}
}//在指定位置之后插入数据
void SLTInsertAfter(SLTNode* pos, SLTDataType x)
{assert(pos);SLTNode* newnode = SLTBuyNode(x);//pos -> newnode -> pos->nextnewnode->next = pos->next;pos->next = newnode;
}//删除pos节点
void SLTErase(SLTNode** pphead, SLTNode* pos)
{assert(pphead && *pphead);assert(pos);//pos是头结点/pos不是头结点if (pos == *pphead){//头删SLTPopFront(pphead);}else {SLTNode* prev = *pphead;while (prev->next != pos){prev = prev->next;}//prev pos pos->nextprev->next = pos->next;free(pos);pos = NULL;}
}//删除pos之后的节点
void SLTEraseAfter(SLTNode* pos)
{assert(pos && pos->next);SLTNode* del = pos->next;//pos del del->nextpos->next = del->next;free(del);del = NULL;
}

上述代码读者可自行测试。最后是对于整个链表的销毁:

//销毁链表
void SListDesTroy(SLTNode** pphead)
{assert(pphead && *pphead);SLTNode* pcur = *pphead;while (pcur){SLTNode* next = pcur->next;free(pcur);pcur = next;}//pcur*pphead = NULL;
}

三、链表的分类

链表的种类有很多很多,按照下面的组合排列就出现8种结构:

我们来看下这些链表有什么区别:

虽然有这么多的链表的结构,但是我们实际中最常用还是两种结构:单链表和双向带头循环链表。无头单向非循环链表结构简单,一般不会单独用来存数据。实际中更多是作为其他数据结构的子结构,如哈希桶、图的邻接表等等。另外这种结构在笔试面试中出现很多。带头双向循环链表结构最复杂,一般用在单独存储数据。实际中使用的链表数据结构,都是带头双向循环链表。另外这个结构虽然结构复杂,但是使用代码实现以后会发现结构会带来很多优势,实现反而简单了,后面我们代码实现了就知道了。


总结

本文详细介绍了单链表的数据结构及其实现方法。主要内容包括:链表概念及结构特点,通过火车车厢类比解释链表的节点组成和指针连接方式;单链表的实现,包含节点定义、遍历打印、头插尾插、头删尾删等基本操作;链表高级操作如查找特定节点、指定位置插入删除等;链表分类概述,指出实际应用中最常用的是无头单向非循环链表和带头双向循环链表。文章通过代码示例详细讲解了各种操作的实现方法,并强调链表相比顺序表在动态内存管理方面的优势。

http://www.dtcms.com/a/593328.html

相关文章:

  • 4. SpringBoot 自定义Banner使用与原理解析
  • Docker环境搭建:Windows/macOS/Linux全平台教程
  • mac安装GIT
  • 开锁公司做网站网站对企业的好处
  • 我爱你域名的网站html5网站建设报价
  • Diffusion VS Flow Matching
  • 电子电气架构 --- 一个具体项目的需求管理(实例化)
  • 11.string(下)
  • OpenVINS代码解读---State.h
  • 提供深圳网站制作公司永久使用免费虚拟主机
  • 智能时代的缘起:从ChatGPT到修行之路
  • 智能守护绿水青山:视频融合平台EasyCVR在森林防火监控中的实战应用
  • 如何做好网站建设前期网站规划软文写手兼职
  • docsify 本地部署完整配置模板 || 将md文件放到网页上展示
  • Bash Shell脚本学习——唇读数据集格式修复脚本
  • 网站界面用什么软件做建设网站需申请什么
  • 底层视觉及图像增强-项目实践(十六-0-(8):端到端DeepHDRNet:从原理到LED显示工程的跨界实践):从奥运大屏,到手机小屏,快来挖一挖里面都有什么
  • 视频号视频下载到手机的详细教程,以及常使用的工具!
  • 禹城网站建设公司安卓网站开发视频
  • 江国青:从郧阳沃土到法治与媒体前沿的跨界行者
  • Mediasoup的SFU媒体服务转发中心详解(与传统SFU的区别)
  • 招标网站免费企业作风建设心得体会
  • 【Java SE 基础学习打卡】07 Java 语言概述
  • 淘宝/天猫获得淘宝买家秀API,python请求示例
  • MATLAB实现BiLSTM(双向长短时记忆网络)数值预测
  • Prefix-Tuning:大语言模型的高效微调新范式
  • 凡科做的网站为什么搜不到学校网站建设成功案例
  • 通过重新安装 Node.js 依赖来解决环境问题
  • 外贸网站建站注意事项天津市哪里有做网站广告的
  • [設計模式]設計模式的作用