【数据结构】详解链表结构

目录

  • 引言
  • 一、链表的介绍
  • 二、链表的几种分类
  • 三、不带头单链表的一些常用接口
    • 3.1 动态申请一个节点
    • 3.2 尾插数据
    • 3.3 头插数据
    • 3.4 尾删数据
    • 3.5 头删数据
    • 3.6 查找数据
    • 3.7 pos位置后插入数据
    • 3.8 删除pos位置数据
    • 3.9 释放空间
  • 四、带头双向链表的常见接口
    • 4.1创建头节点(初始化)
    • 4.2pos位置前插入
    • 4.3删除pos位置数据
    • 4.4其他
  • 五、总结

引言

上篇博客已经介绍了顺序表的实现:【数据结构】详解顺序表。最后在里面也谈及了顺序表结构的缺陷,即效率低,空间浪费等等问题,那么为了解决这些问题,于是乎我们引入了链表的概念,下面将对链表结构进行讲解

一、链表的介绍

首先肯定会问,到底什么是链表?链表的概念链表是一种物理存储结构上非连续,非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的
在结构上其与火车的结构相似,分为一个个节点,再将每个节点连接起来,就形成了一个链表,其大致结构如下:
在这里插入图片描述
但还要几点需要注意

  1. 链式结构在逻辑上是连续的,但在物理空间上不一定是连续的
  2. 这些节点一般是在堆上申请出来的,即使用malloc函数来动态申请空间;
  3. 每当需要增加一个数据时,便可申请一段空间,空间可能连续也可能不连续。

二、链表的几种分类

链表的结构大致可以分为8类,即:带头/不带头单向链表,带头/不带头双向链表,带头/不带头单向循环链表,带头/不带头双向循环链表。 今天我所介绍的是其中最简单的结构和最复杂的结构:

  1. 单向不带头不循环链表:
    在这里插入图片描述
    单向不带头不循环链表结构简单,但实现起来并不简单且复杂度高,所以一般不会单独用来存数据。实际中更多是作为其他数据结构的子结构,如哈希桶、图的邻接表等等。另外这种结构在笔试面试中出现很多。
  2. 双向带头循环链表:
    在这里插入图片描述
    带头双向循环链表结构最复杂,一般用在单独存储数据。实际中使用的链表数据结构,都是带头双向循环链表。表面上看这结构十分的复杂,但在后面实现函数时会发现这种结构会带来很多优势,实现起来反而更简单了,复杂度也大大降低

三、不带头单链表的一些常用接口

定义如下结构体,表示链表的一个节点:

typedef int SLDataType;typedef struct SlistNode
{SLDataType val;//所需保存的数据struct SListNode* next;//结构体指针,指向下一个节点的地址
}SLNode;

3.1 动态申请一个节点

为了使链表在各个函数中都可以使用,所以我们需要动态开辟内存来创建节点,再通过指针将他们相连接。在CreatNode()函数中我们创建节点并将他们初始化:

//动态申请节点
SLNode* CreatNode(SLTDateType x)
{SLNode* newnode = (SLNode*)malloc(sizeof(SLNode));//检测if(newnode == NULL){perror("CreatNode()::malloc");return;}newnode->val = x;newnode->next = NULL;return newnode;
}

3.2 尾插数据

根据一般逻辑,我们想要尾插那就要先创建新节点并找到尾节点。那么我们定义指针tail,然后利用循环找尾节点再链接新节点tail->next = newnode,另外还要额外判断链表为空的情况,此情况直接尾插即可,具体如下:

//尾插
void SLPushBack(SLNode** pplist, SLDateType x)
{SLNode* newnode = CreatNode(x);if (*pplist == NULL){*pplist = newnode;}else{//找尾SLNode* tail = *pplist;while (tail->next != NULL){tail = tail->next;}//链接tail->next = newnode;}
}

3.3 头插数据

头插就比较简单了,只需要注意一点:不额外定义变量时,要先将新节点链到链表,即newnode->next = *pplist然后再改头节点,即*pplist = newnode,如下:

void SLPushFront(SLNode** pplist, SLDateType x)
{assert(pplist);SLNode* newnode = CreatNode(x);newnode->next = *pplist;*pplist = newnode;
}

3.4 尾删数据

同样想要尾删,那就必须先找到尾节点然后释放空间。但释放完空间后,上一个节点的next仍然指向释放空间的地址,这就可能造成越界访问,野指针问题。所以我们还需要记录尾节点的上一个节点tailPrev,然后通过这个指针将此节点next置为NULL。此外还需用assert()检测链表不为NULL分类讨论链表只有一个节点和有多个节点的情况。如下:

//尾删
void SLPopBack(SLNode** pplist)
{assert(pplist && *pplist);SLNode* tailPrev = NULL;SLNode* tail = *pplist;// 1.只有一个节点if (tail->next == NULL){free(tail);*pplist = NULL;}// 2.两个及以上的节点else{//找尾及上一个节点while (tail->next){tailPrev = tail;tail = tail->next;}free(tail);tail = NULL;tailPrev->next = NULL;}
}

3.5 头删数据

头删数据时,链表同样不能为空,另外头删无需判断链表节点数问题,这就比较容易实现了:

void SLPopFront(SLNode** pplist)
{//不为空assert(pplist && *pplist);//记录第一个节点SLNode* first= *pplist;*pplist = (*pplist)->next;free(first);
}

3.6 查找数据

给定一个val,再链表中向后寻找,找到时返回此节点地址pos,未找到返回NULL。我们只需定义一个结构体指针SLNode* cur = plist;,让他向后走,找到val时返回cur,直到cur = NULL时循环结束并返回NULL。因为这里无需改变链表指向,所以可以直接传一级指针。

SLNode* SLFind(SLNode* plist, SLDateType x)
{SLNode* cur = plist;while (cur){//寻找valif (cur->val == x)return cur;cur = cur->next;}return NULL;
}

3.7 pos位置后插入数据

如下图,先创建一个节点newnode,然后将newnode->next指向pos位置的下一个节点,最后将pos->next指向新节点。
在这里插入图片描述
当然pos != NULL

//指定位置插入
void SLInsertAfter(SLNode* pos, SLDataType x)
{assert(pos);SLNode* newnode = CreatNode(x);newnode->next = pos->next;pos->next = newnode;
}

3.8 删除pos位置数据

先通过循环找到pos前一个节点地址posPrev,和后一个节点地址posNext,然后释放pos节点,链接posPrevposNext
在这里插入图片描述
同样pos != NULL,还有一点是当pos为头节点,就相当于头删,但无需判断,同样适用。

void SLErase(SLNode* pos, SLNode** pplist)
{assert(pos && pplist);SLNode* posPrev = *pplist, *posNext = pos->next, *cur = *pplist;//找pos前一个节点while(cur != pos){posPrev = cur;cur = cur->next;}//链接posPrev->next = posNext;free(pos);
}

3.9 释放空间

因为这是一个链式结构,且每个节点是malloc动态开辟的,所以最后要将所以节点释放,否则会造成内存泄漏问题。只需定义一个结构体指针SLNode* cur = *pplist,让他向后依次释放节点,直到cur = NULL

void SLDestroy(SLNode** pplist)
{assert(pplist);SLNode* cur = *pplist;while(cur){//记录下一个节点SLNode* next = cur->next;free(cur);cur = next;}
}

四、带头双向链表的常见接口

通过上面对单向不循环链表的介绍,我们不难发现其实单链表的尾插,尾删和指定位置删除其实效率是不高的,时间复杂度为O(n)。而双向带头循环链表是不存在这个问题的,且因为链表带头节点的原因,在函数传参是无需用到二级指针,在实现函数时也会发现很多时候也不需要单独判断链表没节点的情况,因为头节点本身就是一个节点,这也大大降低了代码的难度
双向带头循环链表的每个节点都包含两个指针:一个指向上一个节点,一个指向下一个节点。那么便可这样设计节点:

typedef int DataType;
//节点设计
typedef struct DListNode
{DataType val;struct DListNode* _prev;//指向上一个节点的指针struct DListNode* _next;//指向下一个节点的指针
}DLNode;

4.1创建头节点(初始化)

头节点和其他节点的结构是相同的,就相当于链表自带一个节点,并将此节点初始化成以下格式:
在这里插入图片描述

DLNode* InitDLNode(DLNode* phead)
{//创建DLNode* head = (DLNode*)malloc(sizeof(DLNode));if (head == NULL){perror("InitDLNode()::malloc");return;}//形成循环结构head->_prev = head;head->_next = head;head->val = -1;return head;
}

4.2pos位置前插入

对于pos位置之前插入,可以先通过pos->_prev找到前一个节点的地址,然后再进行插入操作。因为是双向循环链表的原因,找pos前一个节点也不需要循环,时间复杂度只有O(1)
在这里插入图片描述
事实上当链表无有效节点(即只有头节点)也不需要单独判断,这样降低了代码难度,具体实现代码如下:

//指定位置插入
void DLNodeInsert(DLNode* pos, DataType x)
{assert(pos);DLNode* posPrev = pos->_prev;//pos前一个节点DLNode* newnode = CreatNode(x);//创建新节点//链接posPrev->_next = newnode;newnode->_prev = posPrev;pos->_prev = newnode;newnode->_next = pos;
}

4.3删除pos位置数据

删除pos位置的数据,我们可以通过pos->_prev找到上一个节点的地址,再通过pos->_next找到下一个节点的地址。然后将这两个节点链接起来,并释放pos节点,如下:
在这里插入图片描述
当只有一个头节点时我们还需额外判断一下,代码如下:

void DLNodeErase(DLNode* pos)
{assert(pos);assert(pos->_next != pos);//不能为头节点DLNode* posPrev = pos->_prev, * posNext = pos->_next;//链接posPrev->_next = posNext;posNext->_prev = posPrev;free(pos);
}

4.4其他

还有头删/插,尾删/插这四个函数,但这四个函数并不需要额外实现,因为:
头插/删可以当作pos = phead->_next的指定位置插入/删除,尾删也可以当作pos = phead->_prev的指定位置删除,尾插则是pos = phead的位置。头/尾删这两个pos分别代表头节点的后一个和前一个,且判断assert(phead != phead->_next)也是必要的,这里就不代码实现了。


五、总结

对比于顺序表我们发现链表有很多优点,也有一些缺点:
优点:

  1. 链表的空间浪费较小,按需开辟空间;
  2. 任意位置插入和删除数据效率更高,实现了O(1)时间复杂度;

缺点:

  1. 不支持随机访问数据(致命缺陷);
  2. 每个节点还要存储链接下一个节点的指针,这也会造成一点空间浪费;

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

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

相关文章

220V交流转直流的简易电源设计

220V交流转直流的简易电源设计 设计简介设计原理电路图变压器电路交流转直流电路3.3V电源接口电路 PCB3D图 实践检验 设计简介 通过模拟电路的相关知识,尝试将220V的交流电转化为我们指定电压的直流电。 设计原理 将220V交流电转化为直流电的方法常用的有通过变压器…

UE 视差材质 学习笔记

视差材质节点: 第一个是高度图, Heightmap Channel就是高度图的灰色通道,在RGBA哪个上面,例如在R上就连接(1,0,0,0),G上就连接(0,1,0,0)逐次类推 去看看对比效果: 这个是有视差效果…

idea 环境搭建及运行java后端源码

1、 idea 历史版本下载及安装 建议下载和我一样的版本,2020.3 https://www.jetbrains.com/idea/download/other.html,idea分为专业版本(Ultimate)和社区版本(Community),前期可以下载专业版本…

【pytorch深度学习 应用篇02】训练中loss图的解读,训练中的问题与经验汇总

文章目录 loss图解析train loss ↘ \searrow ↘ ↗ \nearrow ↗ 先降后升 loss图解析 train loss ↘ \searrow ↘ 不断下降,test loss ↗ \nearrow ↗ 不断上升:原因很多,我是把workers1,batchSize8192train loss ↘ \searro…

Java系列之 解决 项目 jar 包无法上传到Github

我 | 在这里 🕵️ 读书 | 长沙 ⭐软件工程 ⭐ 本科 🏠 工作 | 广州 ⭐ Java 全栈开发(软件工程师) 🎃 爱好 | 研究技术、旅游、阅读、运动、喜欢流行歌曲 🏷️ 标签 | 男 自律狂人 目标明确 责任心强 ✈️公…

动态规划专项---最长上升子序列模型

文章目录 怪盗基德的滑翔翼登山合唱队形友好城市最大上升子序列和拦截导弹导弹防御系统最长公共上升子序列 一、怪盗基德的滑翔翼OJ链接 本题思路:本题是上升子序列模型中比较简单的模型&#xff0c;分别是从前往后和从后往前走一遍LIS即可。 #include <bits/stdc.h>co…

新零售系统平台解决方案 线上线下小程序怎么做

新零售线上线下解决方案是将传统零售业务与互联网科技相结合&#xff0c;通过数字化、智能化手段提升零售业务效率和用户体验的解决方案&#xff0c;它既有提供消费者线下体验&#xff0c;强调“稳”&#xff0c;又有互联网线上的“快”。 线上线下小程序可以通过一体化的进销存…

Windows核心编程 静态库与动态库

资源文件 .rc 文件 会被 rc.exe 变成 .res 文件(二进制文件) 在链接时链接进入 .exe 文件 一、如何保护源码 程序编译链接过程 不想让别人拿到源代码&#xff0c;但是想让其使用功能&#xff0c;根据上图观察&#xff0c;把自己生成的obj给对方&#xff0c;对方拿到obj后&…

详解ssh远程登录服务

华子目录 简介概念功能 分类文字接口图形接口 文字接口ssh连接服务器浅浅介绍一下加密技术凯撒加密加密分类对称加密非对称加密非对称加密方法&#xff08;也叫公钥加密&#xff09; ssh两大类认证方式&#xff1a;连接加密技术简介密钥解析 ssh工作过程版本协商阶段密钥和算法…

国科大数据挖掘期末复习——聚类分析

聚类分析 将物理或抽象对象的集合分组成为由类似的对象组成的多个类的过程被称为聚类。由聚类所生 成的簇是一组数据对象的集合&#xff0c;这些对象与同一个簇中的对象彼此相似&#xff0c;与其他簇中的对象相异。 聚类属于无监督学习&#xff08;unsupervised learning&…

青岛数字孪生赋能工业制造,加速推进制造业数字化转型

随着企业数字化进程的推进&#xff0c;数字孪生技术逐渐在汽车行业得到广泛应用。5G与数字孪生、工业互联网的融合将加速数字中国、智慧社会建设&#xff0c;加速中国新型工业化进程&#xff0c;为中国经济发展注入新动能。数字孪生、工业物联网、工业互联网等新一代信息通信技…

asp.net健身会所管理系统sqlserver

asp.net健身会所管理系统sqlserver说明文档 运行前附加数据库.mdf&#xff08;或sql生成数据库&#xff09; 主要技术&#xff1a; 基于asp.net架构和sql server数据库 功能模块&#xff1a; 首页 会员注册 教练预约 系统公告 健身课程 在线办卡 用户中心[修改个人信息 修…

Python与ArcGIS系列(九)自定义python地理处理工具

目录 0 简述1 创建自定义地理处理工具2 创建python工具箱0 简述 在arcgis中可以进行自定义工具箱,将脚本嵌入到自定义的可交互窗口工具中。本篇将介绍如何利用arcpy实现创建自定义地理处理工具以及创建python工具箱。 1 创建自定义地理处理工具 在arctoolbox中的自定义工具箱…

上海亚商投顾:三大指数小幅上涨 HBM概念股全天强势

上海亚商投顾前言&#xff1a;无惧大盘涨跌&#xff0c;解密龙虎榜资金&#xff0c;跟踪一线游资和机构资金动向&#xff0c;识别短期热点和强势个股。 一.市场情绪 三大指数早盘窄幅震荡&#xff0c;午后集体拉升翻红&#xff0c;黄白二线走势分化&#xff0c;题材热点快速轮…

数据结构及八种常用数据结构简介

data-structure 数据结构是一种存在某种关系的元素的集合。“数据” 是指元素&#xff1b;“结构” 是指元素之间存在的关系&#xff0c;分为 “逻辑结构” 和 “物理结构&#xff08;又称存储结构&#xff09;”。 常用的数据结构有 数组&#xff08;array&#xff09;、栈&…

【Django-DRF用法】多年积累md笔记,第(4)篇:Django-DRF反序列化详解

本文从分析现在流行的前后端分离Web应用模式说起&#xff0c;然后介绍如何设计REST API&#xff0c;通过使用Django来实现一个REST API为例&#xff0c;明确后端开发REST API要做的最核心工作&#xff0c;然后介绍Django REST framework能帮助我们简化开发REST API的工作。 全…

.NET 8.0 中有哪些新的变化?

1性能提升 .NET 8在整个堆栈中带来了数千项性能改进 。默认情况下会启用一种名为动态配置文件引导优化 (PGO) 的新代码生成器&#xff0c;它可以根据实际使用情况优化代码&#xff0c;并且可以将应用程序的性能提高高达 20%。现在支持的 AVX-512 指令集能够对 512 位数据向量执…

配置VNC环境时,出现xauth: file /root/.Xauthority does not exist的解决方案。

问题描述 在配置VNC&#xff08;Virtual Network Computing&#xff09;环境的过程时&#xff0c;首先安装了tigervnc-server包。在使用&#xff1a; vncserver命令创建VNC会话号的时候出现了一个报错&#xff1a;xauth: file /root/.Xauthority does not exist 原因分析&…

mac清除所有数据,不抹除的情况下如何实现?

mac清除所有数据是一个比较复杂的任务&#xff0c;尤其是在不进行系统抹除的情况下。但是&#xff0c;如果你想要将mac完全恢复到出厂设置的状态&#xff0c;同时保留数据&#xff0c;本文将介绍一些可行的方法&#xff0c;帮助您在不抹除硬盘数据的情况下&#xff0c;让mac清除…

ubuntu20.04在docker下运行ros-noetic进行开发

经常折腾虚拟机各双系统 &#xff0c; 想着不如把docker利用起来&#xff0c;下面算是一个初学者使用docker运行ros的记录&#xff1a; 1. 安装 使用官方安装脚本自动安装 curl -fsSL https://test.docker.com -o test-docker.shsudo sh test-docker.sh验证是否安装成功 doc…