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

数据结构之单链表及其实现!

目录

​编辑

1.  顺序表的问题及思考

2.链表的概念结构和分类

2.1 概念及结构

2.2 分类

3. 单链表的实现

3.1 新节点的创建

3.2 打印单链表

3.3 头插

3.4 头删

3.5 尾插

3.6 尾删

3.7 查找元素X

3.8 在pos位置修改

3.9 在任意位置之前插入

3.10 在任意位置删除

3.11 单链表的销毁

4. 完整代码

5. 完结散花


                                            悟已往之不谏,知来者犹可追  

创作不易,宝子们!如果这篇文章对你们有帮助的话,别忘了给个免费的赞哟

1.  顺序表的问题及思考

问题:

1. 中间/头部的插入删除,时间复杂度为O(N)

2. 增容需要申请新空间,拷贝数据,释放旧空间。会有不小的消耗。

3. 增容一般是呈2倍的增长,势必会有一定的空间浪费。例如当前容量为100,满了以后增容到 200,我们再继续插入了5个数据,后面没有数据插入了,那么就浪费了95个数据空间。

思考:如何解决以上问题呢?下面给出了链表的结构来看看。

2.链表的概念结构和分类

2.1 概念及结构

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

2.2 分类

实际中链表的结构非常多样,以下情况组合起来就有8种链表结构:

1. 单项或者双向

 2. 带头或者不带头

 3. 循环或者非循环

虽然有这么多的链表的结构,但是我们实际中最常用还是两种结构:

 1. 无头单向非循环链表:结构简单,一般不会单独用来存数据。实际中更多是作为其他数据结 构的子结构,如哈希桶、图的邻接表等等。另外这种结构在笔试面试中出现很多。

2. 带头双向循环链表:结构最复杂,一般用在单独存储数据。实际中使用的链表数据结构,都是带头双向循环链表。另外这个结构虽然结构复杂,但是使用代码实现以后会发现结构会带 来很多优势,实现反而简单了,后面我们代码实现了就知道了。

下面我就来实现一下无头单向非循环链表~

3. 单链表的实现

这里我就具体实现一下接口~(SList.h)

#define _CRT_SECURE_NO_WARNINGS
#pragma once//避免头文件被多次引用
#include<stdio.h>
#include<assert.h>
#include<stdlib.h>
typedef int SListDataType;//便于改变数据类型//定义一个结构体类型的节点
typedef struct SListNode
{SListDataType data;struct SListNode* next;//存储下一个节点的地址
}SListNode;//1. 新节点的创建
SListNode* SListCreatNode(SListDataType x);//2. 打印单链表
void PrintSList(SListNode* phead);//3. 头插
void SListPushFront(SListNode** phead, SListDataType x);//4. 头删
void  SListPopFront(SListNode** phead);//5. 尾差
void SListPushBack(SListNode** phead, SListDataType x);//6. 尾删
void  SListPopBack(SListNode** phead);//7. 查找元素X
SListNode* SListFind(SListNode* phead, SListDataType x);//8. 在pos位置修改
void SListModify(SListNode* pos, SListDataType x);//9. 在任意位置之前插入
void SListInsert(SListNode** phead, SListNode* pos, SListDataType x);//10. 在任意位置删除
void SListErase(SListNode** phead, SListNode* pos);//11. 销毁单链表
void SListDestroy(SListNode** phead);

3.1 新节点的创建

(1)代码实现

//1. 新节点的创建
SListNode*  SListCreatNode(SListDataType x)
{SListNode* NewNode= (SListNode*)malloc(sizeof(SListNode));//开辟空间if (NewNode == NULL)//判断空间是否开辟成功{perror("malloc fail");return NULL;}NewNode->data = x;//赋值NewNode->next = NULL;//置空return NewNode;
}

3.2 打印单链表

(1)代码实现

//2. 打印单链表
void PrintSList(SListNode* phead)
{if (phead == NULL){printf("NULL");//如果链表没有元素就打印NULLreturn;}SListNode* cur = phead;//循环单链表打印while (cur != NULL){printf("%d->", cur->data);cur = cur->next;}printf("NULL\n");
}

(2) 复杂度分析

  • 时间复杂度:因为需要遍历整个链表,显然时间复杂度为O(N)。
  • 空间复杂度:打印链表不需要格外的空间,所以空间复杂度为O(1).

3.3 头插

(1)代码实现

//3. 头插
void SListPushFront(SListNode** phead, SListDataType x)
{assert(phead);SListNode* newnode =SListCreatNode(x);//创建一个新节点newnode->next =*phead;*phead = newnode;
}

(2) 复杂度分析

  • 时间复杂度:不需要额外时间的消耗,时间复杂度为O(1)。
  • 空间复杂度:固定创造一个节点,空间复杂度为O(1)。

3.4 头删

(1)代码实现

//4. 头删
void  SListPopFront(SListNode** phead)
{assert(phead);assert(*phead);//如果没有数据就不用头删,并报错SListNode* cur = (*phead)->next;free(*phead);*phead = cur;
}

(2) 复杂度分析

  • 时间复杂度:不需要额外时间的消耗,时间复杂度为O(1)。
  • 空间复杂度:不需要格外的空间消耗,空间复杂度为O(1)。

3.5 尾插

(1)代码实现

//5. 尾插
void SListPushBack(SListNode** phead, SListDataType x)
{assert(phead);if (*phead == NULL){*phead = SListCreatNode(x);//创建新节点并插入}else{SListNode* tail = *phead;while (tail->next != NULL)//找到尾节点{tail = tail->next;}tail->next = SListCreatNode(x);//创建新节点并插入}
}

(2) 复杂度分析

  • 时间复杂度:需要遍历整个链表,时间复杂度为O(N)。
  • 空间复杂度:固定创造一个节点,空间复杂度为O(1)。

3.6 尾删

(1)代码实现

//6. 尾删
void  SListPopBack(SListNode** phead)
{assert(phead);assert(*phead);//链表为空就不进行尾删SListNode* tail = *phead;if (tail->next == NULL)//如果链表就只有一个元素就进行头删{SListPopFront(phead);}else{while (tail->next->next != NULL){tail = tail->next;}free(tail->next);tail->next = NULL;}
}

(2) 复杂度分析

  • 时间复杂度:需要遍历整个链表,时间复杂度为O(N)。
  • 空间复杂度:不需要格外的空间消耗,空间复杂度为O(1)。

3.7 查找元素X

(1)代码实现

//7. 查找元素X
SListNode* SListFind(SListNode* phead, SListDataType x)
{assert(phead);while (phead->next!=NULL)//注意最后一个节点是没有查找的{if (phead->data == x)return phead;phead = phead->next;}if (phead->data == x)return phead;//最后一个节点没有查找elsereturn NULL;//没找到
}

(2) 时间复杂度

  • 时间复杂度:可能需要遍历整个链表,时间复杂度为O(N)。
  • 空间复杂度:不需要格外的空间消耗,空间复杂度为O(1)。

3.8 在pos位置修改

(1)代码实现


//8. 在pos位置修改
void SListModify( SListNode* pos, SListDataType x)
{assert(pos);pos->data = x;
}

(2) 时间复杂度

  • 时间复杂度:可能需要遍历整个链表,时间复杂度为O(N)。
  • 空间复杂度:不需要格外的空间消耗,空间复杂度为O(1)。

3.9 在任意位置之前插入

(1)代码实现

//9. 在任意位置之前插入
void SListInsert(SListNode** phead, SListNode* pos, SListDataType x)
{assert(phead);assert(*phead);if (pos==*phead)//如果pos位置刚好是第一个节点就进行头插{SListPushFront( phead,x);}else{SListNode* newnode = SListCreatNode(x);SListNode* cur = *phead;while (cur->next != pos)//找到pos前一个节点{cur = cur->next;}cur->next = newnode;newnode->next  = pos;}
}

(2) 复杂度分析

  • 时间复杂度:可能需要遍历整个链表,时间复杂度为O(N)。
  • 空间复杂度:不需要格外的空间消耗,空间复杂度为O(1)。

3.10 在任意位置删除

(1)代码实现

//10. 在任意位置删除
void SListErase(SListNode** phead, SListNode* pos)
{assert(phead && *phead && pos);if (pos==*phead)//如果pos位置就是第一个节点就进行头删{SListPopFront(phead);}else{SListNode* cur = *phead;while (cur->next != pos)//找到pos前一个节点{cur = cur->next;}cur->next = pos->next;free(pos);}
}

(2) 复杂度分析

  • 时间复杂度:可能需要遍历整个链表,时间复杂度为O(N)。
  • 空间复杂度:不需要格外的空间消耗,空间复杂度为O(1)。

3.11 单链表的销毁

(1)代码实现

//11. 销毁单链表
void SListDestroy(SListNode** phead)
{assert(*phead && phead);SListNode* cur = *phead;while (cur!= NULL){SListNode* tmp = cur->next;free(cur);cur = tmp;}*phead = NULL;
}

(2) 复杂度分析

  • 时间复杂度:可能需要遍历整个链表,时间复杂度为O(N)。
  • 空间复杂度:不需要格外的空间消耗,空间复杂度为O(1)。

4. 完整代码

SList.c

#define _CRT_SECURE_NO_WARNINGS#include"SList.h"//1. 新节点的创建
SListNode*  SListCreatNode(SListDataType x)
{SListNode* NewNode= (SListNode*)malloc(sizeof(SListNode));//开辟空间if (NewNode == NULL)//判断空间是否开辟成功{perror("malloc fail");return NULL;}NewNode->data = x;//赋值NewNode->next = NULL;//置空return NewNode;
}//2. 打印单链表
void PrintSList(SListNode* phead)
{if (phead == NULL){printf("NULL");//如果链表没有元素就打印NULLreturn;}SListNode* cur = phead;//循环单链表打印while (cur != NULL){printf("%d->", cur->data);cur = cur->next;}printf("NULL\n");
}//3. 头插
void SListPushFront(SListNode** phead, SListDataType x)
{assert(phead);SListNode* newnode =SListCreatNode(x);//创建一个新节点newnode->next =*phead;*phead = newnode;
}//4. 头删
void  SListPopFront(SListNode** phead)
{assert(phead);assert(*phead);//如果没有数据就不用头删,并报错SListNode* cur = (*phead)->next;free(*phead);*phead = cur;
}//5. 尾插
void SListPushBack(SListNode** phead, SListDataType x)
{assert(phead);if (*phead == NULL){*phead = SListCreatNode(x);//创建新节点并插入}else{SListNode* tail = *phead;while (tail->next != NULL)//找到尾节点{tail = tail->next;}tail->next = SListCreatNode(x);//创建新节点并插入}
}//6. 尾删
void  SListPopBack(SListNode** phead)
{assert(phead);assert(*phead);//链表为空就不进行尾删SListNode* tail = *phead;if (tail->next == NULL)//如果链表就只有一个元素就进行头删{SListPopFront(phead);}else{while (tail->next->next != NULL){tail = tail->next;}free(tail->next);tail->next = NULL;}
}//7. 查找元素X
SListNode* SListFind(SListNode* phead, SListDataType x)
{assert(phead);while (phead->next!=NULL)//注意最后一个节点是没有查找的{if (phead->data == x)return phead;phead = phead->next;}if (phead->data == x)return phead;//最后一个节点没有查找elsereturn NULL;//没找到
}//8. 在pos位置修改
void SListModify( SListNode* pos, SListDataType x)
{assert(pos);pos->data = x;
}//9. 在任意位置之前插入
void SListInsert(SListNode** phead, SListNode* pos, SListDataType x)
{assert(phead);assert(*phead);if (pos==*phead)//如果pos位置刚好是第一个节点就进行头插{SListPushFront( phead,x);}else{SListNode* newnode = SListCreatNode(x);SListNode* cur = *phead;while (cur->next != pos)//找到pos前一个节点{cur = cur->next;}cur->next = newnode;newnode->next  = pos;}
}//10. 在任意位置删除
void SListErase(SListNode** phead, SListNode* pos)
{assert(phead && *phead && pos);if (pos==*phead)//如果pos位置就是第一个节点就进行头删{SListPopFront(phead);}else{SListNode* cur = *phead;while (cur->next != pos)//找到pos前一个节点{cur = cur->next;}cur->next = pos->next;free(pos);}
}//11. 销毁单链表
void SListDestroy(SListNode** phead)
{assert(*phead && phead);SListNode* cur = *phead;while (cur!= NULL){SListNode* tmp = cur->next;free(cur);cur = tmp;}*phead = NULL;
}

5. 完结散花

好了,这期的分享到这里就结束了~

如果这篇博客对你有帮助的话,可以用你们的小手指点一个免费的赞并收藏起来哟~

如果期待博主下期内容的话,可以点点关注,避免找不到我了呢~

我们下期不见不散~~

相关文章:

数据结构之单链表及其实现!

目录 ​编辑 1. 顺序表的问题及思考 2.链表的概念结构和分类 2.1 概念及结构 2.2 分类 3. 单链表的实现 3.1 新节点的创建 3.2 打印单链表 3.3 头插 3.4 头删 3.5 尾插 3.6 尾删 3.7 查找元素X 3.8 在pos位置修改 3.9 在任意位置之前插入 3.10 在任意位置删除…...

Ubuntu 22.04修改静态ip

1. 备份原网络配置文件 # 配置文件名称因机器设置有异 cd /etc/netplan cp 01-network-config.yaml 01-network-config.yaml.bak# 文件内容如下 network:version: 2renderer: NetworkManager2. 修改配置文件 使用 ipconfig 命令查看网络信息&#xff0c;ip addr 命令也可 我这…...

kali当中不同的python版本切换(超简单)

kali当中本身就是自带两个python版本的 配置 update-alternatives --install /usr/bin/python python /usr/bin/python2 100 update-alternatives --install /usr/bin/python python /usr/bin/python3 150 切换版本 update-alternatives --config python 0 1 2编号选择一个即可…...

MongoDB聚合运算符;$dateToString

$dateToString聚合运算符按用户指定的格式将日期对象转为字符串。 语法 { $dateToString: {date: <dateExpression>,format: <formatString>,timezone: <tzExpression>,onNull: <expression> } }字段说明&#xff1a; 字段是否必须描述date是<da…...

【开源】SpringBoot框架开发教学资源共享平台

目录 一、摘要1.1 项目介绍1.2 项目录屏 二、功能模块2.1 数据中心模块2.2 课程档案模块2.3 课程资源模块2.4 课程作业模块2.5 课程评价模块 三、系统设计3.1 用例设计3.2 类图设计3.3 数据库设计3.3.1 课程档案表3.3.2 课程资源表3.3.3 课程作业表3.3.4 课程评价表 四、系统展…...

python基础——条件判断和循环【if,while,for,range】

&#x1f4dd;前言&#xff1a; 这篇文章主要讲解一下条件判断语句if和循环语句while&#xff0c;for在python中需要注意的地方。 建议已有一定了解&#xff08;对语句的执行逻辑清楚&#xff09;的读者观看&#xff0c;如果对条件判断和循环的执行逻辑不太清楚&#xff0c;也可…...

Pytorch 复习总结 6

Pytorch 复习总结&#xff0c;仅供笔者使用&#xff0c;参考教材&#xff1a; 《动手学深度学习》Stanford University: Practical Machine Learning 本文主要内容为&#xff1a;Pytorch 计算机视觉。 本文先介绍了计算机视觉中两种常见的改进模型泛化性能的方法&#xff1a…...

借助 Terraform 功能协调部署 CI/CD 流水线-Part 1

在当今快节奏的开发环境中&#xff0c;实现无缝、稳健的 CI/CD 流水线对于交付高质量软件至关重要。在本文中&#xff0c;我们将向您介绍使用 Bitbucket Pipeline、ArgoCD GitOps 和 AWS EKS 设置部署的步骤&#xff0c;所有步骤都将利用 Terraform 的强大功能进行编排。在Part…...

云原生基础知识:容器技术的历史

容器化的定义&#xff1a; 容器化是一种轻量级的虚拟化技术&#xff0c;将应用程序及其所有依赖项&#xff08;包括运行时、系统工具、系统库等&#xff09;打包到一个称为容器的单独单元中。容器提供了一种隔离的执行环境&#xff0c;使得应用程序可以在不同的环境中运行&…...

golang实现正向代理和反向代理

文章目录 正向代理反向代理区别与联系:总结代理服务器实现正向代理反向代理正向代理 正向代理是客户端代理,它位于客户端和目标服务器之间。它的作用是保护客户端的隐私和安全。 如我们现在想要访问谷歌,但是由于某些原因,无法直接访问到谷歌,我们可以通过连接一台代理服务…...

grpc四种数据流

grpc四种数据流 简介 1.简单模式 这种模式最为传统,即客户端发起一次请求,服务端响应一个数据,这和大家平时熟悉的rpc没什么区别,所以不在详细介绍 2.服务端数据流模式 这种模式是客户端发起一次请求&#xff0c;服务端返回一段连续的数据流。典型的例子是客户端向服务端发…...

SpringCloud-Alibaba-Nacos教程

SpringCloud-Alibaba-Nacos教程 下载地址 https://github.com/alibaba/nacos/releases/tag/2.2.3 直接进入bin包 运行cmd命令 startup.cmd -m standalone 运行成功后 进入nacos可视化页面 账号密码默认都是nacos http://localhost:8848/nacos 微服务入驻Nacos服务注册…...

bug_java

文章目录 1.创建Maven时&#xff1a; idea报错为&#xff1a;java&#xff1a;错误&#xff1a;不支持发行版本52. Springbot启动报错-类文件具有错误的版本 61.0, 应为 52.0 1.创建Maven时&#xff1a; idea报错为&#xff1a;java&#xff1a;错误&#xff1a;不支持发行版本…...

【目标检测】旋转目标检测DOTA格式转YOLO格式标注

准备DOTA格式数据集&#xff1a; dota_dataset -- images |----- train |----- val -- labels |----- train |----- train_original |----- val |----- val_original 修改class_mapping和图片格式&#xff1a; ultralytics/data/converter.py convert_dota_to_yolo_obb() 转换标…...

运动想象 (MI) 迁移学习系列 (3) : MSFT

运动想象迁移学习系列:MSFT 0. 引言1. 主要贡献2. 数据增强方法3. 基于度量的空间滤波转换器3.1 空间过滤3.2 脑电图ViT3.2.1 变压器编码器层3.2.2 基于度量的损失函数 4. 实验结果4.1 消融实验4.2 基线任务对比4.3 跨主体 5. 总结欢迎来稿 论文地址&#xff1a;https://www.s…...

NeRF模型NeRF模型

参考视频&#xff1a;https://www.youtube.com/watch?vHfJpQCBTqZs&ab_channelVision%26GraphicsSeminaratMIT NeRF模型的输入输出: 输入: (x, y, z): 一个三维空间坐标,代表场景中的一个位置点(θ, φ): 视线方向,θ表示与y轴的夹角,φ表示与x轴的夹角,用两个角度可以…...

python爬虫(4)

#前期先说明一下为啥爬虫需要学习数组的存储和处理&#xff0c;只是说在你后期接触到最简单的爬虫后有一个地方可以存放你的数据# 下面为大家带来一个我在做excel表整理时的代码以及上次代码的结果 上次代码的结果&#xff1a; 新的代码&#xff1a; import numpy as np im…...

递归神经网络 (RNN) 及其变体 LSTM (长短期记忆) 和 GRU (门控循环单元)

递归神经网络&#xff08;RNN, Recurrent Neural Networks&#xff09;是一类用于处理序列数据的神经网络&#xff0c;特别适合于时间序列数据、语音、文本等连续数据的处理。RNN之所以独特&#xff0c;是因为它们在模型内部维持一个隐藏状态&#xff0c;该状态理论上可以捕获到…...

Redis的HyperLogLog原理介绍

Redis 的 HyperLogLog 数据结构实现了一种基于概率的基数估算算法&#xff0c;用于在占用极小内存的情况下估算一个集合中不重复元素&#xff08;唯一值&#xff09;的数量。以下是 HyperLogLog 算法的基本原理&#xff1a; 哈希函数&#xff1a; HyperLogLog 使用一个强散列函…...

微信小程序开发系列(二十六)·小程序运行机制(启动、前后台状态、挂起、销毁)和小程序更新机制

目录 1. 小程序运行机制 1.1 启动 1.2 前台和后台状态 1.3 挂起 1.4 销毁 2. 小程序更新机制 1. 小程序运行机制 1.1 启动 小程序启动可以分为两种情况&#xff0c;一种是冷启动&#xff0c;一种是热启动。 冷启动&#xff1a;如果用户首次打开&#xff0c;或小…...

国防科技大学计算机基础课程笔记02信息编码

1.机内码和国标码 国标码就是我们非常熟悉的这个GB2312,但是因为都是16进制&#xff0c;因此这个了16进制的数据既可以翻译成为这个机器码&#xff0c;也可以翻译成为这个国标码&#xff0c;所以这个时候很容易会出现这个歧义的情况&#xff1b; 因此&#xff0c;我们的这个国…...

Linux链表操作全解析

Linux C语言链表深度解析与实战技巧 一、链表基础概念与内核链表优势1.1 为什么使用链表&#xff1f;1.2 Linux 内核链表与用户态链表的区别 二、内核链表结构与宏解析常用宏/函数 三、内核链表的优点四、用户态链表示例五、双向循环链表在内核中的实现优势5.1 插入效率5.2 安全…...

vscode(仍待补充)

写于2025 6.9 主包将加入vscode这个更权威的圈子 vscode的基本使用 侧边栏 vscode还能连接ssh&#xff1f; debug时使用的launch文件 1.task.json {"tasks": [{"type": "cppbuild","label": "C/C: gcc.exe 生成活动文件"…...

Go 语言接口详解

Go 语言接口详解 核心概念 接口定义 在 Go 语言中&#xff0c;接口是一种抽象类型&#xff0c;它定义了一组方法的集合&#xff1a; // 定义接口 type Shape interface {Area() float64Perimeter() float64 } 接口实现 Go 接口的实现是隐式的&#xff1a; // 矩形结构体…...

关于iview组件中使用 table , 绑定序号分页后序号从1开始的解决方案

问题描述&#xff1a;iview使用table 中type: "index",分页之后 &#xff0c;索引还是从1开始&#xff0c;试过绑定后台返回数据的id, 这种方法可行&#xff0c;就是后台返回数据的每个页面id都不完全是按照从1开始的升序&#xff0c;因此百度了下&#xff0c;找到了…...

Springcloud:Eureka 高可用集群搭建实战(服务注册与发现的底层原理与避坑指南)

引言&#xff1a;为什么 Eureka 依然是存量系统的核心&#xff1f; 尽管 Nacos 等新注册中心崛起&#xff0c;但金融、电力等保守行业仍有大量系统运行在 Eureka 上。理解其高可用设计与自我保护机制&#xff0c;是保障分布式系统稳定的必修课。本文将手把手带你搭建生产级 Eur…...

三体问题详解

从物理学角度&#xff0c;三体问题之所以不稳定&#xff0c;是因为三个天体在万有引力作用下相互作用&#xff0c;形成一个非线性耦合系统。我们可以从牛顿经典力学出发&#xff0c;列出具体的运动方程&#xff0c;并说明为何这个系统本质上是混沌的&#xff0c;无法得到一般解…...

【论文阅读28】-CNN-BiLSTM-Attention-(2024)

本文把滑坡位移序列拆开、筛优质因子&#xff0c;再用 CNN-BiLSTM-Attention 来动态预测每个子序列&#xff0c;最后重构出总位移&#xff0c;预测效果超越传统模型。 文章目录 1 引言2 方法2.1 位移时间序列加性模型2.2 变分模态分解 (VMD) 具体步骤2.3.1 样本熵&#xff08;S…...

【学习笔记】深入理解Java虚拟机学习笔记——第4章 虚拟机性能监控,故障处理工具

第2章 虚拟机性能监控&#xff0c;故障处理工具 4.1 概述 略 4.2 基础故障处理工具 4.2.1 jps:虚拟机进程状况工具 命令&#xff1a;jps [options] [hostid] 功能&#xff1a;本地虚拟机进程显示进程ID&#xff08;与ps相同&#xff09;&#xff0c;可同时显示主类&#x…...

AI书签管理工具开发全记录(十九):嵌入资源处理

1.前言 &#x1f4dd; 在上一篇文章中&#xff0c;我们完成了书签的导入导出功能。本篇文章我们研究如何处理嵌入资源&#xff0c;方便后续将资源打包到一个可执行文件中。 2.embed介绍 &#x1f3af; Go 1.16 引入了革命性的 embed 包&#xff0c;彻底改变了静态资源管理的…...