【数据结构】5. 双向链表
文章目录
- 一、链表的分类
- 1、双向链表的结构
- 二、双向链表的实现
- 0、准备工作
- 1、初始化
- 2、打印
- 3、尾插
- 4、头插
- 5、尾删
- 6、头删
- 7、查找
- 8、在指定位置之后插入数据
- 9、删除指定位置
- 10、销毁
一、链表的分类
链表总共分为8种,具体的分组方式如图所示:
-
带头指的是链表中的哨兵位,这个哨兵位也就是头结点,哨兵位存在的意义是 遍历循环链表避免死循环。
-
单向和双向的区别如图:
-
循环主要是看尾结点的next指针是否指向NULL。如图:
我们前面所学的单链表其实就是:不带头单向不循环链表。
1、双向链表的结构
双向链表的结构如图所示:
可以看到双向链表其实是:带头双向循环链表。
二、双向链表的实现
0、准备工作
实现双向链表前需要我们创建三个文件。
List.h
:节点的定义,方法的声明。
List.c
:方法的实现。
test.c
:方法的测试。
接着再在List.h
文件中定义我们双向链表节点的结构体,以及方法的声明和包含的头文件。
#pragma once
#include<stdio.h>
#include<stdlib.h>//malloc
#include<assert.h>//双向链表节点的结构体
typedef int LTDataType;
typedef struct ListNode
{LTDataType data;struct ListNode* next;//后继指针struct ListNode* prev;//前驱指针
}LTNode;//双向链表的初始化
void LTInit(LTNode** pphead);//双向链表的销毁
void LTDesTroy(LTNode* phead);//双向链表的打印
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);//在指定位置之后插入数据
void LTInsert(LTNode* pos, LTDataType x);//删除指定位置
void LTErase(LTNode* pos);
接下来就在List.c
文件中来实现具体的方法。
实现方法前需要包含头文件#include"List.h"
。
1、初始化
双向链表的初始化其实就是给双向链表创建一个哨兵位。
因此我们先要实现一个申请节点的函数:
申请节点函数:
LTNode* LTBuyNode(LTDataType x)
{LTNode* node = (LTNode*)malloc(sizeof(LTNode));if (node == NULL){perror("malloc failed");return 1;}node->data = x;node->next = node->prev = node;return node;
}
接下来再来实现双向链表的初始化:
初始化函数:
void LTInit(LTNode** pphead)
{*pphead = LTBuyNode(-1);
}
我们在test.c
文件中测试方法前,要先包含头文件#include"List.h"
。
现在开始测试:
void test()
{LTNode* plist = NULL;LTInit(&plist);
}int main()
{test();return 0;
}
我们打开调试,观察监视窗口:
发现已经初始化成功!
2、打印
为了方便我们观察方法的正确性,我们可以创建一个函数用来打印链表的内容(除哨兵位的所有节点)。
打印函数:
void LTPrint(LTNode* phead)
{LTNode* pcur = phead->next;while (pcur != phead){printf("%d->", pcur->data);pcur = pcur->next;}printf("\n");
}
3、尾插
尾插函数:
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 test()
{LTNode* plist = NULL;LTInit(&plist);LTPushBack(plist, 1);LTPushBack(plist, 2);LTPushBack(plist, 3);LTPushBack(plist, 4);LTPrint(plist);
}int main()
{test();return 0;
}
运行结果:
4、头插

头插函数:
void LTPushFront(LTNode* phead, LTDataType x)
{assert(phead);LTNode* newnode = LTBuyNode(x);newnode->next = phead->next;newnode->prev = phead;phead->next->prev = newnode;phead->next = newnode;
}
再进行测试:
void test()
{LTNode* plist = NULL;LTInit(&plist);LTPushFront(plist, 1);LTPushFront(plist, 2);LTPushFront(plist, 3);LTPushFront(plist, 4);LTPrint(plist);
}int main()
{test();return 0;
}
运行结果:
5、尾删

void LTPopBack(LTNode* phead)
{LTNode* del = phead->prev;del->prev->next = phead;phead->prev = del->prev;free(del);del = NULL;
}
再进行测试:
void test()
{LTNode* plist = NULL;LTInit(&plist);LTPushBack(plist, 1);LTPushBack(plist, 2);LTPushBack(plist, 3);LTPushBack(plist, 4);LTPopBack(plist);LTPrint(plist);
}int main()
{test();return 0;
}
运行结果:
6、头删

头删函数:
void LTPopFront(LTNode* phead)
{LTNode* del = phead->next;phead->next = del->next;del->next->prev = phead;free(del);del = NULL;
}
再进行测试:
void test()
{LTNode* plist = NULL;LTInit(&plist);LTPushBack(plist, 1);LTPushBack(plist, 2);LTPushBack(plist, 3);LTPushBack(plist, 4);LTPopFront(plist);LTPrint(plist);
}int main()
{test();return 0;
}
运行结果:
7、查找
查找思路:遍历除了哨兵位的所有节点,如果节点对应的数据等于要查找的数据,那就返回节点的地址,否则返回 NULL。

查找函数:
LTNode* LTFind(LTNode* phead, LTDataType x)
{LTNode* pcur = phead->next;while (pcur != phead){if (x == pcur->data){return pcur;}pcur = pcur->next;}//没有找到return NULL;
}
再进行测试:
void test()
{LTNode* plist = NULL;LTInit(&plist);LTPushBack(plist, 1);LTPushBack(plist, 2);LTPushBack(plist, 3);LTPushBack(plist, 4);int find = LTFind(plist, 2);if (find == NULL){printf("没有找到\n");}else{printf("找到了\n");}}int main()
{test();return 0;
}
运行结果:
8、在指定位置之后插入数据
注:指定位置指的是节点对应的地址,一般需要搭配查找函数一起使用。

在指定位置后插入函数:
void LTInsert(LTNode* pos, LTDataType x)
{assert(pos);LTNode* newnode = LTBuyNode(x);newnode->prev = pos;newnode->next = pos->next;pos->next->prev = newnode;pos->next = newnode;
}
再进行测试:
void test()
{LTNode* plist = NULL;LTInit(&plist);LTPushBack(plist, 1);LTPushBack(plist, 2);LTPushBack(plist, 3);LTPushBack(plist, 4);int find = LTFind(plist, 2);LTInsert(find, 10);LTPrint(plist);
}int main()
{test();return 0;
}
运行结果:
9、删除指定位置
注:指定位置也指的是节点对应的地址,要搭配查找函数一起使用。

删除指定位置函数:
void LTErase(LTNode* pos)
{pos->prev->next = pos->next;pos->next->prev = pos->prev;free(pos);pos = NULL;
}
再进行测试:
void test()
{LTNode* plist = NULL;LTInit(&plist);LTPushBack(plist, 1);LTPushBack(plist, 2);LTPushBack(plist, 3);LTPushBack(plist, 4);int find = LTFind(plist, 2);LTErase(find);LTPrint(plist);
}int main()
{test();return 0;
}
10、销毁
思路:要先记录后一个节点,再销毁当前的节点。

销毁函数:
void LTDesTroy(LTNode* phead)
{LTNode* pcur = phead->next;while (pcur != phead){LTNode* next = pcur->next;free(pcur);pcur = next;}free(phead);phead = NULL;
}
再进行测试:
void test()
{LTNode* plist = NULL;LTInit(&plist);LTPushBack(plist, 1);LTPushBack(plist, 2);LTPushBack(plist, 3);LTPushBack(plist, 4);printf("销毁前:");LTPrint(plist);LTDesTroy(plist);printf("销毁后:");LTPrint(plist);
}int main()
{test();return 0;
}
运行结果:
相关文章:

【数据结构】5. 双向链表
文章目录 一、链表的分类1、双向链表的结构 二、双向链表的实现0、准备工作1、初始化2、打印3、尾插4、头插5、尾删6、头删7、查找8、在指定位置之后插入数据9、删除指定位置10、销毁 一、链表的分类 链表总共分为8种,具体的分组方式如图所示: 带头指的…...

【Linux手册】冯诺依曼体系结构
目录 前言 五大组件 数据信号 存储器(内存)有必要吗 常见面试题 前言 冯诺依曼体系结构是当代计算机基本架构,冯诺依曼体系有五大组件,通过这五大组件直观的描述了计算机的工作原理;学习冯诺依曼体系可以让给我们更…...

Mobile App UI自动化locator
在开展mobile app UI层自动化测试时,编写目标元素的locator是比较耗时的一个环节,弄清楚locator背后的逻辑,可以有效降低UI层测试维护成本。此篇博客以webdriverioappium作为UI自动化工具为例子,看看有哪些selector方法࿰…...
PaloAlto-Expedition OS命令注入漏洞复现(CVE-2025-0107)
免责申明: 本文所描述的漏洞及其复现步骤仅供网络安全研究与教育目的使用。任何人不得将本文提供的信息用于非法目的或未经授权的系统测试。作者不对任何由于使用本文信息而导致的直接或间接损害承担责任。如涉及侵权,请及时与我们联系,我们将尽快处理并删除相关内容。 前…...

(LeetCode 每日一题) 1061. 按字典序排列最小的等效字符串 (并查集)
题目:1061. 按字典序排列最小的等效字符串 思路:使用并查集,来将等价的字符连起来,形成一棵树。这棵树最小的字母,就代表整颗树,时间复杂度0(n),细节看注释。 C版本: class Solutio…...

linux 安装mysql8.0;支持国产麒麟,统信uos系统
一:使用我已经改好的mysql linux mysql8.0解压可用,点我下载 也在国产麒麟系统,统信uos系统也测试过,可用; 下载后,上传mysql.tar.gz 然后使用root角色去执行几个命令即可;数据库密码…...

C#实现远程锁屏
前言 这是一次提前下班没有锁屏进而引发的一次思考后的产物,思考的主要场景是当人离开电脑后,怎么能控制电脑锁屏,避免屏幕上的聊天记录被曝光。 首先想到通过系统的电源计划设置闲置超时时间熄屏,这可能是最接近场景的解决方案&a…...
历史记录隐藏的安全风险
引言 在数字化生活与工作场景中,历史记录功能广泛存在于浏览器、办公软件、移动应用等各类平台。它通过记录用户的搜索内容、操作痕迹、访问路径等信息,为用户提供便捷的操作体验和个性化服务。然而,这种看似便利的功能背后,却隐藏…...

SpringBoot3整合MySQL8的注意事项
版权声明 本文原创作者:谷哥的小弟作者博客地址:http://blog.csdn.net/lfdfhl 注意事项 1、请添加添加如下依赖: <dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><…...
网络安全大模型理解
一、网络安全大模型的概述 网络安全大模型是一种用于识别和应对各种网络安全威胁的模型。它通过分析网络数据包、网络行为等信息,识别潜在的网络安全事件,并采取相应的措施进行防御。网络安全大模型主要包括以下几个部分: 1. 数据预处理&am…...

智语心桥:当AI遇上“星星的孩子”,科技如何点亮沟通之路?
目录: 引言:当科技的温度,遇见“星星的孩子”“智语心桥”:一座为孤独症儿童搭建的AI沟通之桥核心技术探秘:AI如何赋能“读心”与“对话”?个性化魔法:AI如何实现“千人千面”的精准干预?应用场景畅想:从家庭到机构,AI的全方位支持为什么是“智语心桥”?——价值、可…...

itop-3568开发板机器视觉opencv开发手册-图像绘制-画线
本小节代码在配套资料“iTOP-3568 开发板\03_【iTOP-RK3568 开发板】指南教程 \04_OpenCV 开发配套资料\11”目录下,如下图所示: cv2.line 函数功能: 绘制一条直线。 函数原型: cv2.line(img,pt1,pt2,color,thicknessNone,lin…...
【高频面试题】快慢指针及相关应用
文章目录 1 简介2 相关应用3 相关题目4 典型例题4.1 判断链表是否有环4.2 寻找链表的入环点4.3 寻找链表的中点4.4 寻找链表的倒数第k个节点4.5 重排链表 (反转链表找链表中点合并链表)4.6 寻找重复数(快慢指针 or 二分)4.7 回文链…...

sudo docker exec -it backend bash 以交互方式(interactive)进入正在运行的 Docker 容器的命令行环境
sudo docker exec -it backend bash🔍 总体作用 这条命令的作用是: 以交互方式(interactive)进入名为 backend 的正在运行的 Docker 容器的命令行环境。 你会进入容器的“终端”,就像登录到一个 Linux 系统一样&#…...
[论文阅读] 人工智能 | 当AI遇见绿色软件工程:可持续AI实践的研究新方向
【论文解读】当AI遇见绿色软件工程:可持续AI实践的研究新方向 论文信息 作者:Maja H. Kirkeby, Enrique Barba Roque, Justus Bogner等 标题:Greening AI-enabled Systems with Software Engineering: A Research Agenda for Environment…...
[论文阅读] 人工智能 | 用大语言模型抓虫:如何让网络协议实现与RFC规范对齐
用大语言模型抓虫:如何让网络协议实现与RFC规范对齐? 论文信息 arXiv:2506.01249 SysLLMatic: Large Language Models are Software System Optimizers Huiyun Peng, Arjun Gupte, Ryan Hasler, Nicholas John Eliopoulos, Chien-Chou Ho, Rishi Mantr…...

浅析EXCEL自动连接PowerBI的模板
浅析EXCEL自动连接PowerBI的模板 之前我分享过:PowerBI链接EXCEL实现自动化报表 ,其中一个关键工具就是提到的EXCEL链接模板,即宏工作薄。 今天就大概来聊一聊这个宏工作簿的底层原理是啥,怎么实现的。 第一步: 打开…...
DeepSeek 赋能金融反洗钱:AI 驱动的风险监测革新之路
目录 一、引言二、金融反洗钱监测的现状与挑战2.1 现状概述2.2 面临的挑战 三、DeepSeek 技术原理剖析3.1 核心架构3.2 关键技术 四、DeepSeek 在金融反洗钱监测中的应用优势4.1 强大的数据处理与分析能力4.2 精准的风险识别与预警4.3 提升工作效率与降低成本 五、DeepSeek 在金…...

java32
1.反射 获取类: 获取构造方法: 获取权限修饰符: 获取参数信息: 利用反射出来的构造器来创建对象: 获取成员变量: 获取成员方法: 综合练习: 动态代理:...

【Redis】zset 类型
zset 一. zset 类型介绍二. zset 命令zaddzcard、zcountzrange、zrevrange、zrangebyscorezpopmax、zpopminzrank、zrevrank、zscorezrem、zremrangebyrank、zremrangebyscorezincrby阻塞版本命令:bzpopmax、bzpopmin集合间操作:zinterstore、zunionstor…...

从Gartner报告看Atlassian在生成式AI领域的创新路径与实践价值
本文来源atlassian.com,由Atlassian全球白金合作伙伴——龙智翻译整理。 二十余年来,Atlassian始终是创新领域的领军者。凭借对团队协作本质的深刻理解,Atlassian在AI时代仍持续引领协作方式的革新。如今,这一领先地位再次获得权威…...

Kafka 安装教程(支持 Windows / Linux / macOS)
一、下载 1、kafka官网下载地址:https://kafka.apache.org/downloads 根据实际情况下载对应的版本 2、JDK的版本最好是17+ JDK下载地址:https://www.oracle.com/java/technologies/javase/jdk17-0-13-later-archive-downloads.html 二、安装 前置条件 安装 Java(至少 Jav…...

OpenCV种的cv::Mat与Qt种的QImage类型相互转换
一、首先了解cv::Mat结构体 cv::Mat::step与QImage转换有着较大的关系。 step的几个类别区分: step:矩阵第一行元素的字节数step[0]:矩阵第一行元素的字节数step[1]:矩阵中一个元素的字节数step1(0):矩阵中一行有几个通道数step1(1):一个元素有几个通道数(channel()) cv::Ma…...
机器学习——什么时候使用决策树
无论是决策树,包括集成树还是神经网络都是非常强大、有效的学习方法。 下面是各自的优缺点: 决策树和集成树通常在表格数据上表现良好,也称为结构化数据,这意味着如果你的数据集看起来像一个巨大的电子表格,那么决策…...
llm-d:面向Kubernetes的高性能分布式LLM推理框架
在生成式AI(GenAI)浪潮中,高效、经济地部署和扩展大型语言模型(LLM)推理服务是企业面临的核心挑战。传统基于Kubernetes的横向扩展(Scale-out)和负载均衡策略在处理独特的LLM推理工作负载时往往…...

前端没有“秦始皇“,但可以做跨端的王[特殊字符]
前端各领域的 “百家争鸣” 框架之争:有 React、Vue、Angular 等多种框架。它们各有优缺点,开发者之间还存在鄙视链,比如 Vue 嫌 React 难用,React 嫌 Vue 不够灵活。样式处理: CSS 预处理器:像 Sass、Les…...
Flutter如何支持原生View
在 Flutter 中集成原生 View(如 Android 的 SurfaceView、iOS 的 WKWebView)是通过 平台视图(Platform View) 实现的。这一机制允许在 Flutter UI 中嵌入原生组件,解决了某些场景下 Flutter 自身渲染能力的不足&#x…...

mongodb源码分析session异步接受asyncSourceMessage()客户端流变Message对象
mongo/transport/service_state_machine.cpp已经分析startSession创建ASIOSession过程,并且验证connection是否超过限制,ASIOSession和connection是循环接受客户端命令,状态转变流程是:State::Created 》 State::Source 》State::…...

【数据分析】什么是鲁棒性?
引言 —— 为什么我们需要“抗折腾”的系统? 当你乘坐的飞机穿越雷暴区时机体剧烈颠簸,自动驾驶汽车在暴雨中稳稳避开障碍物,或是手机从口袋摔落后依然流畅运行——这些场景背后,都藏着一个工程领域的“隐形守护者”:…...
适老化场景重构:现代家政老年照护虚拟仿真实训室建设方案
随着老龄化社会的深度发展,老年照护服务的专业化需求对人才培养提出了更高要求。 凯禾瑞华以现代家政管理理念为核心,推出老年照护虚拟仿真实训室建设方案,通过虚拟仿真技术重构适老化生活场景,融合数字课程、产教融合及搭载智能…...