【C语言】双向链表详解

文章目录

  • 关于双向链表
  • 双向链表的初始化
  • 双向链表的打印
  • 双向链表方法调用 - 尾删为例
  • 双向链表的查找 - 指定位置之后插入为例
  • 双向链表结束 - 链表的销毁
  • 小结及整体代码实现


关于双向链表

首先链表有8种基本分法 在这里插入图片描述

其中在笔者之前文章种详细介绍的 单链表不带头单项不循环链表
而今天笔者要介绍的就是 带头双向循环链表 双向链表

我们可以把链表理解为下图

在这里插入图片描述
所以我们双向链表的结构往往是这样的
在这里插入图片描述

typedef int LTDataType;
//定义双向链表节点的结构
typedef struct ListNode
{int data;struct ListNode* next;struct ListNode* prev;
}LTNode;

双向链表的初始化

第一步我们需要进行双向链表的初始化,双向链表的初始化需要注意的一个问题是,传进去的参数是一级指针还是二级指针,这本应该后面大致写完了再讨论,但是这里提前说了

在笔者后面的方法中,例如尾插,头插,尾删,头删等都是用一级指针,这是因为双向链表有一个好处,我们存在一个哨兵位,这个节点是初始化时创立的,不需要改变,同时有存储着前后节点的地址,只需要通过成员名调用即可,这里呈现一下会出现的方法参数

//打印
void LTPrint(LTNode* phead);//尾插
void LTPushBack(LTNode* phead, LTDataType x);//插入数据之前,链表必须初始化到只有一个头结点的情况
//头插
void LTPushFront(LTNode* phead, LTDataType x);//尾删
void LTPopBack(LTNode* phead);
//头删
void LTPopFront(LTNode* phead);//查找
LTNode* LTFind(LTNode* phead, LTDataType x);
//在pos之后插入数据
void LTInsert(LTNode* pos, LTDataType x);

笔者这里据大多数关于双向链表的地址传参都是一级指针,这在一定程度上,保证了接口的一致性,为后来调用双向链表数据结构者降低了理解成本,这也就是我们在设计程序时需要注意的一点

笔者这里通过二级指针传参和无传参的方法各创建了一个初始化方法
大家可以看到两种方法连语句都基本一致,但是后者明显会比前者好,因为在以后不论是我们还是其他人通过一览所有的方法名后,不需要刻意去记初始化需要传参二级指针,直接空着使用就行了

//申请节点
LTNode* LTBuyNode(LTDataType x)
{LTNode* newnode = (LTNode*)malloc(sizeof(LTNode));if (newnode == NULL){perror("malloc newnode");exit(1);}newnode->data = x;newnode->next = newnode->prev = newnode;//指向自己return newnode;
}//初始化1
void LTInit(LTNode** pphead)
{//该双向链表创建一个哨兵位*pphead = LTBuyNode(-1);
}
//初始化2
LTNode* newLTInit()
{return LTBuyNode(-1);
}

双向链表的打印

双向链表的打印比单链表好理解太多了,
因为存在一个无意义的 哨兵位 用来记录开头,链接末尾,
所以只需要新创建一个新的节点用来帮忙定位当前位置,如果这个节点的值跟 哨兵位 一样,便退出

代码如下

//打印
void LTPrint(LTNode* phead)
{LTNode* pcur = phead->next;while (pcur != phead){printf("%d->",pcur->data);pcur = pcur->next;}printf("\n");
}

双向链表方法调用 - 尾删为例

大家可以先看一下下图,我们在双向链表的末尾放上新节点

在这里插入图片描述

为了保证双向链表在使用时不发生意外,
我们先将 newnodeprevnext 对接好 d3 和 头节点phead
这样做只是给新的节点的前后向确定好,不影响原来的双向链表

再将 d3next 对准 newnode
pheadprev 对准 newnode

下面代码呈现

//尾插
void LTPushBack(LTNode* phead, LTDataType x)
{assert(phead);LTNode* newnode = LTBuyNode(x);newnode->prev = phead->prev;newnode->next = phead;phead->prev->next = newnode;phead->prev = newnode;
}

双向链表的查找 - 指定位置之后插入为例

在这里插入图片描述

指定位置之后插入,除了插入这个重要点,就是指定位置这个大头了,那么我们该如何实现找到指定位置呢

同样因为 哨兵位 的存在,我们新定义节点来一个一个对比下去,知道等于 哨兵位 为止,代表整个双向链表中都没有这个要查找的数据

//查找
LTNode* LTFind(LTNode* phead, LTDataType x)
{LTNode* pcur = phead->next;while (pcur != phead){if (pcur->data == x)return pcur;pcur = pcur->next;}return NULL;
}

既然知道如何找到指定位置,那么接下来就是插入了,
我们通过指定位置的节点信息,按照前面尾插的方法就行了,也是很简单的

//在pos之后插入数据
void LTInsert(LTNode* pos, LTDataType x)
{assert(pos);LTNode* newnode = LTBuyNode(x);newnode->next = pos->next->next;newnode->prev = pos;pos->next->prev = newnode;pos->next = newnode;
}

双向链表结束 - 链表的销毁

因为我们前面有提到为保证接口的一致性我们同意设定为一级指针,但这样我们能做的就是将除 哨兵位 以外的节点全部置为空,但是外部所创建的节点,即我们一开始所初始化的点无法在内部置为空,所以出去后仍然需要置为空

//销毁
void LTDestory(LTNode* phead)
{assert(phead);LTNode* pcur = phead;while (pcur != phead){LTNode* next = pcur->next;free(pcur);pcur = next;}free(phead);phead = NULL;pcur = NULL;//这点无所谓,跳出方法,就会为空
}

这是内部的销毁,出去后,在置为空一次,即

LTDestory(plist);
plist = NULL;

小结及整体代码实现

有一说一,双向链表真的很好理解,而且写方法时,往往不用循环就能很容易解出来,比单链表和数组方便许多,大家如果想要自己实现双向链表,尽量没完成一次方法时,调试检验一下,方便找到错误

下面为大家展现一下笔者的代码

“List.h”

#pragma once#define _CRT_SECURE_NO_WARNINGS  1
#pragma warning(disable:6031)#include<stdio.h>
#include<stdlib.h>
#include<assert.h>typedef int LTDataType;
//定义双向链表节点的结构
typedef struct ListNode
{int data;struct ListNode* next;struct ListNode* prev;
}LTNode;//声明双向链表中提供的方法
//尽量都用一级指针,降低调用方的理解成本,保持接口的一致性
//初始化
//void LTInit(LTNode** pphead);
LTNode* newLTInit();//打印
void LTPrint(LTNode* phead);//尾插
void LTPushBack(LTNode* phead, LTDataType x);//插入数据之前,链表必须初始化到只有一个头结点的情况
//头插
void LTPushFront(LTNode* phead, LTDataType x);//尾删
void LTPopBack(LTNode* phead);
//头删
void LTPopFront(LTNode* phead);//查找
LTNode* LTFind(LTNode* phead, LTDataType x);
//在pos之后插入数据
void LTInsert(LTNode* pos, LTDataType x);

“List.c”

#include"List.h"//申请节点
LTNode* LTBuyNode(LTDataType x)
{LTNode* newnode = (LTNode*)malloc(sizeof(LTNode));if (newnode == NULL){perror("malloc newnode");exit(1);}newnode->data = x;newnode->next = newnode->prev = newnode;//指向自己return newnode;
}//初始化
void LTInit(LTNode** pphead)
{//该双向链表创建一个哨兵位*pphead = LTBuyNode(-1);
}LTNode* newLTInit()
{return LTBuyNode(-1);
}//销毁
void LTDestory(LTNode* phead)
{assert(phead);LTNode* pcur = phead;while (pcur != phead){LTNode* next = pcur->next;free(pcur);pcur = next;}free(phead);phead = NULL;pcur = NULL;//这点无所谓,跳出方法,就会为空
}//打印
void LTPrint(LTNode* phead)
{LTNode* pcur = phead->next;while (pcur != phead){printf("%d->",pcur->data);pcur = pcur->next;}printf("\n");
}//尾插
void LTPushBack(LTNode* phead, LTDataType x)
{assert(phead);LTNode* newnode = LTBuyNode(x);newnode->prev = phead->prev;newnode->next = phead;phead->prev->next = newnode;phead->prev = newnode;
}//头插
void LTPushFront(LTNode* phead, LTDataType x)
{assert(phead);LTNode* newnode = LTBuyNode(x);newnode->prev = phead;newnode->next = phead->next;phead->next->prev = newnode;phead->next = newnode;
}//尾删
void LTPopBack(LTNode* phead)
{//链表必须有效且不为空assert(phead && phead->next != phead);phead->prev->prev->next = phead;phead->prev = phead->prev->prev;
}
//头删
void LTPopFront(LTNode* phead)
{assert(phead && phead->next != phead);phead->next->next->prev = phead;phead->next = phead->next->next;
}//查找
LTNode* LTFind(LTNode* phead, LTDataType x)
{LTNode* pcur = phead->next;while (pcur != phead){if (pcur->data == x)return pcur;pcur = pcur->next;}return NULL;
}//在pos之后插入数据
void LTInsert(LTNode* pos, LTDataType x)
{assert(pos);LTNode* newnode = LTBuyNode(x);newnode->next = pos->next->next;newnode->prev = pos;pos->next->prev = newnode;pos->next = newnode;
}
//在pos之前插入数据
void LTInsertAfter(LTNode* pos, LTDataType x)
{assert(pos);LTNode* newnode = LTBuyNode(x);newnode->next = pos;newnode->prev = pos->prev;pos->prev->next = newnode;pos->prev = newnode;
}
//删除pos节点
void LTErase(LTNode* pos)
{assert(pos);pos->next->prev = pos->prev;pos->prev->next = pos->next;free(pos);pos = NULL;
}

测试文件就不展示了,没有什么参考价值,能够独自理解并写出上述方法名,一个简简单单的调试自然不在话下

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

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

相关文章

C++学习知识

C知识小菜单&#xff1a; 备赛蓝桥杯过程中的一些小知识积累&#xff0c;持续更新中&#xff01; 文章目录 C知识小菜单&#xff1a;1.小数取整&#xff1a;2.小数点后保留几位&#xff1a;3.数字占几位字符&#xff1a;4. 求x 的 y 次幂&#xff08;次方&#xff09;5. 求平方…

STM32完成软件I2C通讯

今天的重点是利用STM32的软件方案和MPU60506轴姿态传感器建立通讯&#xff0c;今天只完成了简单的发送地址和接收应答的部分&#xff0c;特此记录一下过程&#xff0c;以后忘记可以随时翻出来看看。 先介绍最基本的I2C通讯的最基本的6个时序&#xff1a; 一&#xff1a;起始条…

TCP/IP协议—UDP

TCP/IP协议—UDP UDP协议UDP通信特点 UDP头部报文UDP检验 UDP协议 用户数据传输协议 (UDP&#xff0c;User Datagram Protocol) 是一种无连接的协议&#xff0c;提供了简单的数据传输服务&#xff0c;不保证数据的顺序以及完整性。应用层很多通信协议都基于UDP进行传输&#x…

未佩戴厨师帽识别检测 厨房管理系统 自动监测未佩戴厨师帽行为 实时报警

在厨房环境中&#xff0c;佩戴厨师帽对于食品安全和卫生至关重要。厨师帽能够有效地防止头发、皮屑等杂质掉入食物中&#xff0c;减少了食品受到污染的可能性&#xff0c;从而保障了食品安全。特别是在学校、餐厅等场景中&#xff0c;对于未佩戴厨师帽的检测更是必不可少。相关…

深入理解Linux系统中的前后台任务与守护进程

⭐小白苦学IT的博客主页 ⭐初学者必看&#xff1a;Linux操作系统入门 ⭐代码仓库&#xff1a;Linux代码仓库 ❤关注我一起讨论和学习Linux系统 1.前言 在Linux系统中&#xff0c;进程管理是至关重要的一个环节。其中&#xff0c;前后台任务和守护进程是进程管理中不可忽视的两…

Vue结合el-table实现合并单元格(以及高亮单元表头和指定行)

实现效果如下&#xff1a; 思路&#xff1a; 1.首先使用动态表头表格。 2.其次实现动态计算合并单元格。&#xff08;计算规则 传递需要合并的字段&#xff09; 3.然后封装公共的计算单元格方法 export导出供多个页面使用。 4.同时需要封装成公共的组件供多个页面使用。 5…

电动车新国标迎来修订机会,用户的真实需求能被满足吗?

文&#xff5c;新熔财经 作者&#xff5c;宏一 自2019年4月《电动自行车安全技术规范》发布至今&#xff0c;电动车的新国标标准已经实施5年&#xff0c;市场上的争议也此起彼伏地持续了5年。 因为新国标对电动车的各项技术标准提出的明确要求&#xff0c;其中&#xff0c;最…

Nifi同步过程中报错create_time字段找不到_实际目标表和源表中没有这个字段---大数据之Nifi工作笔记0066

很奇怪的问题,在使用nifi的时候碰到的,这里是用NIFI,把数据从postgresql中同步到mysql中, 首先postgresql中的源表,中是没有create_time这个字段的,但是同步的过程中报错了. 报错的内容是说,目标表中有个create_time字段,这个字段是必填的,但是传过来的flowfile文件中,的数据没…

[RK3399 Linux] 使用ubuntu 20.04.5制作rootfs

一、ubuntu base ubuntu base是用于为特定需求创建自定义映像的最小rootfs,是ubuntu可以运行的最小环境。 1.1 下载源码 下载ubuntu-base的方式有很多,可以从官方的地址:ttp://cdimage.ubuntu.com/ubuntu-base/releases。 也可以其它镜像地址下载,如清华源:https://mi…

使用shell管理和配置网络服务_1

1、请使用nmcli命令配置仅主机模式网络环境&#xff0c;要求如下: 1) 创建一个新的网卡连接eth1&#xff0c;该连接映射到ens32网卡上; 首先&#xff0c;确保 ens32 网卡没有被其他网络配置文件使用。然后&#xff0c;使用 nmcli 创建一个新的连接&#xff0c;并将其绑定到 e…

在Linux中使用dpkg -i离线安装下载的deb软件包

“dpkg -i” 是 Debian 系统中用于安装 .deb 格式软件包的命令。它是 Debian Package Management System&#xff08;DPMS&#xff09;的一部分&#xff0c;允许用户通过命令行界面来管理软件包。 使用该命令时&#xff0c;需要提供要安装的软件包的路径作为参数。例如&#…

实战纪实 | 编辑器漏洞之Ueditor-任意文件上传漏洞 (老洞新谈)

UEditor 任意文件上传漏洞 前言 前段时间在做某政府单位的项目的时候发现存在该漏洞&#xff0c;虽然是一个老洞&#xff0c;但这也是容易被忽视&#xff0c;且能快速拿到shell的漏洞&#xff0c;在利用方式上有一些不一样的心得&#xff0c;希望能帮助到一些还不太了解的小伙…

大模型日报|今日必读的9篇大模型论文

大家好&#xff0c;今日必读的大模型论文来啦&#xff01; 1.Google DeepMind 提出 RecurrentGemma&#xff1a;超越 Transformer&#xff0c;迈向高效开放语言模型 Google DeepMind 团队提出了 RecurrentGemma&#xff0c;一个使用 Google 新颖的 Griffin 架构的开放语言模型…

PHP01——php快速入门 之 使用phpstudy快速搭建PHP环境

PHP01——php快速入门 之 使用phpstudy快速搭建PHP环境 0. 前言1. 下载小皮面板1.1 下载phpstudy&#xff08;小皮面板&#xff09;1.2 启动、简单访问1.2.1 启动Apache1.2.2 访问1.2.3 访问自定义文件或页面 2. 创建网站2.1 创建网站2.2 可能遇到的问题2.2.1 hosts权限问题&am…

【新版HI3559AV100开发注意事项(四)】

新版HI3559AV100开发注意事项&#xff08;四&#xff09; 三十、HI3559A参数中对输入分辨率限制的原因是&#xff1f; 答&#xff1a;分辨率限制有两个来源&#xff1a; 一个是时钟频率最高为600M&#xff0c;开启一拍两像素之后相当于1200M。你这个数据量太大了&#xff0c;6…

[大模型]基于 ChatGLM3 和 LangChain 搭建知识库助手

基于 ChatGLM3 和 LangChain 搭建知识库助手 环境配置 在已完成 ChatGLM3 的部署基础上&#xff0c;还需要安装以下依赖包&#xff1a; pip install langchain0.0.292 pip install gradio4.4.0 pip install chromadb0.4.15 pip install sentence-transformers2.2.2 pip inst…

微服务项目实战-黑马头条(一):环境搭建 + APP登录功能【前端静态资源+nginx反向代理+网关+过滤器】

文章目录 一、搭建nacos二、初始工程的搭建2.1 环境准备2.2主体结构 三、APP登录3.1 需求分析3.2 表结构分析3.3 接口定义3.4 功能实现 四、接口工具postman、swagger、knife4j4.1 postman4.2 swagger4.3 knife4j 五、网关5.1 网关搭建5.2 全局过滤器实现jwt校验 六、前端集成6…

目前电视盒子哪个最好?测评工作室盘点超强电视盒子推荐

每年我们会进行数十次电视盒子测评&#xff0c;对各个品牌的产品都有深入的了解&#xff0c;最近我们收到了很多私信不知道目前电视盒子哪个最好&#xff0c;这次我们结合配置总结了五款性能超强的电视盒子推荐给各位&#xff0c;预算足够想买高配电视盒子的朋友们可不要错过啦…

高级Java开发工程师手把手教你用AI人工智能帮你写JAVA代码实际案例二(生成自定义springboot定时器组件)全网唯一

高级Java开发工程师手把手教你用AI人工智能帮你写JAVA代码实际案例二全网唯一 一、需求背景案例说明 笔者有个实际的生产项目&#xff0c;想自己自定义定时器到数据库表里面&#xff0c;不想配置到配置文件里面&#xff0c;很麻烦&#xff01;这次想用AI帮我写代码&#xff0…

蓝桥杯算法题:栈(Stack)

这道题考的是递推动态规划&#xff0c;可能不是很难&#xff0c;不过这是自己第一次靠自己想出状态转移方程&#xff0c;所以纪念一下&#xff1a; 要做这些题目&#xff0c;首先要把题目中会出现什么状态给找出来&#xff0c;然后想想他们的状态可以通过什么操作转移&#xf…