数据结构--链表和单链表详解及实现

news/2024/12/12 22:27:43/

一.前言

数据结构思维导图如下,灰色标记的是之前讲过的,本文将带你走近单链表(红色标记部分),希望大家有所收获🌹🌹

在这里插入图片描述

二.链表的定义和概念

在讲单链表之前,我们先学习一下链表

2.1 链表的定义

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

  • 逻辑结构:线性
  • 物理结构:非线性

2.2 节点的构成要素

链表由一系列结点(链表中每一个元素称为结点)组成,结点可以在运行时动态生成。
每个结点包括两个部分:一个是存储数据元素的数据域,另一个是存储下一个结点地址的指针域。即数据+指向下一个结点的指针,节点常用结构体实现。

struct ListNode
{
LTDataType data;
struct ListNode* next;
}

2.3 与数组结构的对比差异

数组链表
存储方式连续存储
需要预先分配固定大小的内存空间
非连续存储,通过指针连接
动态分配内存,不需要预先确定大小
内存使用空间利用率高 (因为所有元素都紧密排列)
可能存在未使用的空间(e.g.数组大小固定但实际使用元素少)
空间利用率相对较低
每个节点需要额外的内存来存储指针
插入和删除操作可能需要移动大量元素以保持连续性
时间复杂度为O(n),因为可能需要移动插入点之后的所有元素
通常只需要改变指针,不需要移动元素
时间复杂度为O(1) (已知插入或删除位置的节点)
随机访问支持高效的随机访问,可通过索引快速访问任意元素
访问时间复杂度为O(1)
不支持高效的随机访问,必须从头节点开始遍历链表
访问时间复杂度为O(n)。
空间局部性有很好的空间局部性,因为元素连续存储,适合缓存优化空间局部性差,因为元素分散存储,可能导致缓存未命中
实现复杂度实现简单,大多数编程语言内置支持实现相对复杂,需要手动管理节点和指针
适用场景适合需要频繁随机访问的场景
适合元素数量已知且变化不大的场景
适合插入和删除操作频繁的场景
适合元素数量频繁变化的场景

三.链表的分类

链表的结构非常多样,以下情况组合起来就有8种(2*2*2)链表结构
在这里插入图片描述
链表说明:
在这里插入图片描述
在这里插入图片描述
虽然有这么多的链表结构,但是我们实际中最常用的还是两种结构:

  1. 链表:不带头单向不循环链表
  2. 双向链表:带头双向循环链表

四.单链表

前期准备

创建三个文件:

  1. SList.h: 存放各种头文件和函数声明
  2. SLIst.c : 各种函数的实现
  3. test.c: 测试和执行代码 最好写完一部分测试一部分 防止后期调试一堆bug

实现

首先在头文件中写上

#include <stdio.h>
#include <stdlib.h>
#include <assert.h>

接下来我们一步步实现单链表各种操作

1.定义链表结构

typedef int SLTDataType;//方便之后一键替换类型typedef struct SListNode
{SLTDataType data; //结点数据struct SListNode* next; //指针保存下⼀个结点的地址
}SLTNode;

2.申请新节点和打印函数

因为后面会多次用到申请新节点和打印函数,所以我们把它们都各自封装成函数方便调用

//申请新节点
SLTNode* SLTBuyNode(SLTDataType x)
{SLTNode* node = (SLTNode*)malloc(sizeof(SLTNode));if (node == NULL){perror("malloc fail!");exit(1);}node->data = x;node->next = NULL;return node;
}
//打印函数
void SLTPrint(SLTNode* phead)
{SLTNode* pcur = phead;while (pcur){printf("%d->", pcur->data);pcur = pcur->next;}printf("NULL\n");
}

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;
}

4.插入操作

包括尾插、头插、在指定位置之前插入数据、在指定位置之后插入数据
(1)尾插

void SLTPushBack(SLTNode** pphead, SLTDataType x)
{assert(pphead);//pphead --> &plist// *pphead --> plist//申请新节点SLTNode* newnode = SLTBuyNode(x);if (*pphead == NULL){*pphead = newnode;}else{//找尾结点SLTNode* pcur = *pphead;while (pcur->next){pcur = pcur->next;}//pcur  newnodepcur->next = newnode;}
}

(2)头插

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

(3)在指定位置之前插入数据

void SLTInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x)
{assert(pphead);assert(pos);if (pos == *pphead){SLTPushFront(pphead, x);}else{SLTNode* newnode = SLTBuyNode(x);//找prev :pos的前一个结点SLTNode* prev = *pphead;while (prev->next != pos){prev = prev->next;}//prev newnode --> posnewnode->next = pos;prev->next = newnode;}
}

(4)在指定位置之后插入数据

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

测试如下
在这里插入图片描述

5.删除操作

包括尾删、头删、删除指定节点、删除指定位置之后的结点、删除指定位置之前的结点(该操作是给你们留的作业😜 学完这篇自己独立实现一下 答案可以让我发给你或者其实调试通过了应该没什么问题跟前面的实现逻辑一样)
(1)尾删

void SLTPopBack(SLTNode** pphead)
{//链表为空:不可以删除assert(pphead && *pphead);//处理只有一个结点的情况:要删除的就是头结点	if ((*pphead)->next == NULL){free(*pphead);*pphead = NULL;}else{//找 prev ptailSLTNode* ptail = *pphead;SLTNode* prev = NULL;while (ptail->next){prev = ptail;ptail = ptail->next;}prev->next = NULL;free(ptail);ptail = NULL;}
}

测试如下
在这里插入图片描述

(2)头删

void SLTPopFront(SLTNode** pphead)
{assert(pphead && *pphead);SLTNode* next = (*pphead)->next;//*pphead --> nextfree(*pphead);*pphead = next;
}

(3)删除指定节点

void SLTErase(SLTNode** pphead, SLTNode* pos)
{assert(pphead && *pphead);assert(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;}
}

(4)删除指定位置之后的结点

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

6.销毁链表

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

在这里插入图片描述

五.总结

链表的详细实现代码已经上传到我的资源了,大家点进主页或者在本文最上方可以下载查看,自己去写一下,边写边调试会更容易理解和掌握。
接下来会逐步介绍上述思维导图数据结构剩下的部分,创作不易,希望大家多多支持,有什么想法欢迎讨论🌹🌹


http://www.ppmy.cn/news/1554605.html

相关文章

[免费]SpringBoot+Vue毕业设计论文管理系统【论文+源码+SQL脚本】

大家好&#xff0c;我是java1234_小锋老师&#xff0c;看到一个不错的SpringBootVue毕业设计论文管理系统&#xff0c;分享下哈。 项目视频演示 【免费】SpringBootVue毕业设计论文管理系统 Java毕业设计_哔哩哔哩_bilibili 项目介绍 现代经济快节奏发展以及不断完善升级的信…

Ubuntu nginx E 无法修正错误,因为您要求某些软件包保持现状,就是它们破坏了软件包间的依赖关系。

这个问题通常发生在尝试安装一个软件包时&#xff0c;该软件包依赖于一个与系统中已安装版本不兼容的版本。以下是一些可能的解决方案&#xff1a; 这是一个依赖问题&#xff0c;其中 nginx 软件包依赖于 nginx-core 、 nginx-full 、 nginx-light 或 nginx-extras 的…

K8S存储实战案例:NFS+StorageClass+PV/PVC+Deployment

本篇文章分享一下在 Kubernetes (K8s) 中搭建 NFS 存储&#xff0c;并实现 PersistentVolume (PV)、PersistentVolumeClaim (PVC)、动态存储卷StorageClass&#xff0c;以及通过 Deployment 使用这些存储卷的完整流程&#xff0c;可以按照以下步骤进行。 实验步骤&#xff1a;…

网络安全协议

1. 概述 1.1 网络安全需求 五种需求&#xff1a; 机密性&#xff1a;防止数据未授权公开&#xff0c;让消息对无关听众保密 完整性&#xff1a;防止数据被篡改 可控性&#xff1a;限制对网络资源&#xff08;硬件和软件&#xff09;和数据&#xff08;存储和通信&#xff0…

第二篇:脚手架搭建 — React 和 Express 的搭建

目录 1 React搭建2 Express搭建总结 第一篇我们介绍了开发环境的搭建过程&#xff0c;介绍了vscode、git、nodejs和mongodb的安装过程。有了基础的开发环境就需要搭建我们的前后端脚手架了。 1 React搭建 前端我们选用React框架解决界面的渲染和用户交互的问题&#xff0c;Rea…

基于Python实现web网页内容爬取

文章目录 1. 网页分析2. 获取网页信息2.1 使用默认的urllib.request库2.2 使用requests库1.3 urllib.request 和 requests库区别 2. 更改用户代理3. BeautifulSoup库筛选数据3.1 soup.find()和soup.find_all() 函数 4. 抓取分页链接参考资料 在日常学习和工作中&#xff0c;我们…

Docker保存镜像和导入镜像文件(图文详解)

Docker保存镜像和导入镜像文件&#xff08;图文详解&#xff09; Docker 保存和导入镜像文件是 Docker 镜像管理中的两个关键操作&#xff0c;它们在不同的场景下有着各自的意义和用途。以下是对这两个操作的详细说明&#xff1a; 1 基本命令介绍 1.1 Docker 保存镜像&#…

关于idea-Java-servlet-Tomcat-Web开发中出现404NOT FOUND问题的解决

在做web项目时&#xff0c;第一次使用servlet开发链接前端和后端的操作&#xff0c;果不其然&#xff0c;遇到了诸多问题&#xff0c;而遇到最多的就是运行项目打开页面时出现404NOT FOUND的情况。因为这个问题我也是鼓捣了好久&#xff0c;上网查了许多资料才最终解决&#xf…