【初阶数据结构】线性表之双链表
文章目录
目录
一、双链表的概念
二、双链表的实现
1.初始化
2.尾插
3.头插
4.打印
5.判断双链表是否为空
6.尾删
7.头删
8.查找
9.在指定的位置之后插入数据
10.删除指定位置的数据
11.销毁
三、完整源码
总结
一、双链表的概念

今天我们要了解的是带头双向循环链表,即双链表。

二、双链表的实现
双链表的定义
在实现双链表前,先简单了解三个指针:
- pcur:当前结点
- prev:指向当前结点的前一个结点,简称前驱结点
- next:指向当前结点的后一个结点,简称后继结点
//定义双链表结构体
typedef int LTDatatype;
typedef struct ListNode {LTDatatype data;struct ListNode* next;//指向下一个结点,即后继结点struct ListNode* prev;//指向上一个结点,即前驱结点
}LTNode;
1.初始化
代码解析:
初始化链表就需要开辟空间,在堆上申请一个结点的空间,用函数buyNode来进行实现,初始时让next,prev指向自己,并返回头结点
LTNode* buyNode(LTDatatype x)
{LTNode* node = (LTNode*)malloc(sizeof(LTNode));if (node == NULL){perror("NULL");exit(1);}node->data = x;node->next = node->prev = node;return node;
}//初始化
LTNode* LTInit()
{LTNode* phead = buyNode(-1);return phead;
}
注:在双链表中,phead会自己指向自己,不会在指向下一个结点时而丢失,next、prev的指向的变化不会影响当前结点,即地址不会发生改变,所以实现双链表的函数都用一级指针。
2.尾插
代码解析:因为要新插入一个数据,所以开辟一个结点的空间(要插入几个结点,就开辟几个结点的空间)。先设置新结点,把新结点的位置插入到当前链表中,让newnode->prev指向尾结点(phead->prev),newnode->next指向头结点(phead);再更新原链表的指向,把尾结点(phead->prev)的next指向newnode,头结点(phead)的prev指向newnode.

//尾插
void LTPushBack(LTNode* phead, LTDatatype x)
{assert(phead);LTNode* newnode = buyNode(x);//phead phead->prev newnodenewnode->prev = phead->prev;newnode->next = phead;phead->prev->next = newnode;phead->prev = newnode;
}
3.头插
代码解析:
和尾插一样需要开辟一个结点大小的空间。先设置新结点,把新结点的位置插入到当前链表中,让newnode->next指向d1(phead->next),newnode->prev指向头结点(phead);再改变链表中d1(phead->next)的位置,让d1->prev指向newnode,头结点(phead)的next指向newnode

//头插
void LTPushFront(LTNode* phead, LTDatatype x)
{assert(phead);LTNode* newnode = buyNode(x);newnode->next = phead->next;//phead->next为d1newnode->prev = phead;phead->next->prev = newnode;phead->next = newnode;
}
注:把newnode插在d1前面是头插,但插在头结点phead前面是尾插
4.打印
代码解析:通过遍历链表来打印结点
//打印
void LTPrint(LTNode* phead)
{LTNode* pcur = phead->next;while (pcur != phead){printf("%d ->", pcur->data);pcur = pcur->next;}printf("\n");
}
5.判断双链表是否为空
代码解析:
用bool函数判断头结点是否指向自己,是指向自己就为空
//bool类型判断
bool LTEmpty(LTNode* phead)
{assert(phead);return phead->next == phead;
}
6.尾删
代码解析:
先判断链表是否为空,不为空就对最后一个结点进行删除。创建新指针del先保存当前结点(即尾结点phead->prev),避免变成野指针;让d2(del->prev)的next指向头结点,phead->prev指向d2->prev;最后对尾结点进行释放并置为NULL

//尾删
void LTPopBack(LTNode* phead)
{assert(!LTEmpty(phead));LTNode* del = phead->prev;//phead del->prev deldel->prev->next = phead;phead->prev = del->prev;free(del);del = NULL;
}
7.头删
代码解析:
和尾删一样,先判断链表是否为空,不为空就对第一个结点进行删除。这里第一个结点不是头结点是d1。创建新指针del保存当前结点d1(phead->next),改变链表中的位置,让d2(del->next)的prev指向头结点,头结点的next指向d2(del->next)

//头删
void LTPopFront(LTNode* phead)
{assert(!LTEmpty(phead));LTNode* del = phead->next;//phead del del->nextdel->next->prev = phead;phead->next = del->next;free(del);del = NULL;
}
8.查找
代码解析:
遍历链表中的结点,找到需要查找的结点就返回当前的结点,找不到就返回NULL
//查找
LTNode* LTFind(LTNode* phead, LTDatatype x)
{LTNode* pcur = phead->next;while (pcur){if (pcur->data = x){return pcur;}}return NULL;
}
9.在指定的位置之后插入数据
代码解析:
在指定的位置之后插入数据之前,需要用函数LTFind对其进行查找,避免出错。和前面尾插 头插一样需要申请一个结点空间的大小。先设置新结点,把新结点的位置插入到当前链表中,比如在d2的后面插入一个结点,d2=pos,让newnode->next指向d2->next,newnode->prev指向d2;再改变链表结点的位置,让d3(d2->next)的prev指向newnode,d3指向newnode.

void LTInsert(LTNode* pos, LTDatatype x)
{assert(pos);LTNode* newnode = buyNode(x);//pos newnode pos->nextnewnode->next = pos->next;newnode->prev = pos;pos->next->prev = newnode;pos->next = newnode;
}
10.删除指定位置的数据
代码解析:
先断言当前结点是否为空,不为空对其进行删除。比如删除d2,d2=pos,让d2->prev的next指向d2->nxet,在让d2->next的prev指向d2->prev.

void LTErase(LTNode* pos)
{assert(pos);//pos->prev pos pos->nextpos->prev->next = pos->next;pos->next->prev = pos->prev;free(pos);pos = NULL;
}
11.销毁
代码解析:
创建新指针pcur,让pcur从phead->next开始销毁。循环遍历,又创建一个新指针next,让next从pcur->next开始走,保证next走在pcur前面,对下一个结点pcur->next进行保存,避免删除当前结点pcur时出现野指针的情况;保存好pcur->next就对当前pcur进行释放,让pcur继续往后走pcur=next,一直循环释放,只剩头结点就跳出循环。最后对头结点进行释放并置为空。
void LTDesTroy(LTNode* phead)
{LTNode* pcur = phead->next;while (pcur != phead){LTNode* next = pcur->next;free(pcur);pcur = next;}free(phead);phead = NULL;
}
三、完整源码
List.h
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#include<stdbool.h>//定义双链表结构体
typedef int LTDatatype;
typedef struct ListNode {LTDatatype data;struct ListNode* next;struct ListNode* prev;
}LTNode;//初始化
LTNode* LTInit();//打印
void LTPrint(LTNode* phead);
//bool类型判断
bool LTEmpty(LTNode* phead);
//查找
LTNode* LTFind(LTNode* phead, LTDatatype x);//尾插
void LTPushBack(LTNode* phead, LTDatatype x);//头插
void LTPushFront(LTNode* phead, LTDatatype x);//尾删
void LTPopBack(LTNode* phead);//头删
void LTPopFront(LTNode* phead);//在pos位置之后插入数据
void LTInsert(LTNode* pos, LTDatatype x);//删除pos位置数据
void LTErase(LTNode* pos);//销毁
void LTDesTroy(LTNode* phead);
List.c
#include"List.h"LTNode* buyNode(LTDatatype x)
{LTNode* node = (LTNode*)malloc(sizeof(LTNode));if (node == NULL){perror("NULL");exit(1);}node->data = x;node->next = node->prev = node;return node;
}//初始化
LTNode* LTInit()
{LTNode* phead = buyNode(-1);return phead;
}//打印
void LTPrint(LTNode* phead)
{LTNode* pcur = phead->next;while (pcur != phead){printf("%d ->", pcur->data);pcur = pcur->next;}printf("\n");
}//bool类型判断
bool LTEmpty(LTNode* phead)
{assert(phead);return phead->next == phead;
}//尾插
void LTPushBack(LTNode* phead, LTDatatype x)
{assert(phead);LTNode* newnode = buyNode(x);//phead phead->prev newnodenewnode->prev = phead->prev;newnode->next = phead;phead->prev->next = newnode;phead->prev = newnode;
}//头插
void LTPushFront(LTNode* phead, LTDatatype x)
{assert(phead);LTNode* newnode = buyNode(x);newnode->next = phead->next;//phead->next为d1newnode->prev = phead;phead->next->prev = newnode;phead->next = newnode;
}//尾删
void LTPopBack(LTNode* phead)
{assert(!LTEmpty(phead));LTNode* del = phead->prev;//phead del->prev deldel->prev->next = phead;phead->prev = del->prev;free(del);del = NULL;
}//头删
void LTPopFront(LTNode* phead)
{assert(!LTEmpty(phead));LTNode* del = phead->next;//phead del del->nextdel->next->prev = phead;phead->next = del->next;free(del);del = NULL;
}//查找
LTNode* LTFind(LTNode* phead, LTDatatype x)
{LTNode* pcur = phead->next;while (pcur){if (pcur->data = x){return pcur;}}return NULL;
}//在pos位置之后插入数据
void LTInsert(LTNode* pos, LTDatatype x)
{assert(pos);LTNode* newnode = buyNode(x);//pos newnode pos->nextnewnode->next = pos->next;newnode->prev = pos;pos->next->prev = newnode;pos->next = newnode;
}//删除pos位置数据
void LTErase(LTNode* pos)
{assert(pos);//pos->prev pos pos->nextpos->prev->next = pos->next;pos->next->prev = pos->prev;free(pos);pos = NULL;
}//销毁
void LTDesTroy(LTNode* phead)
{LTNode* pcur = phead->next;while (pcur != phead){LTNode* next = pcur->next;free(pcur);pcur = next;}free(phead);phead = NULL;
}
Test.c
#include"List.h"void test()
{//LTNode* plist = NULL;//LTInit(&plist);//尾插LTNode* plist = LTInit();LTPushBack(plist, 1);LTPushBack(plist, 2);LTPushBack(plist, 3);LTPushBack(plist, 4);LTPrint(plist);//头插//LTNode* plist = LTInit();//LTPushFront(plist, 1);//LTPushFront(plist, 2);//LTPushFront(plist, 3);//LTPushFront(plist, 4);//LTPrint(plist);//尾删//LTPopBack(plist);//LTPrint(plist);//LTPopBack(plist);//LTPrint(plist);//LTPopBack(plist);//LTPrint(plist);//头删//LTPopFront(plist);//LTPrint(plist);//LTPopFront(plist);//LTPrint(plist);//LTPopFront(plist);//LTPrint(plist);//在pos位置之后插入数据//LTNode* find = LTFind(plist, 4);//if (find == NULL)//{// printf("无\n");//}//else {// printf("有\n");//}//LTInsert(find, 77);//LTErase(find);//LTPrint(plist);//销毁LTDesTroy(plist);plist = NULL;}int main()
{test();return 0;
}
总结
非常感谢大家阅读完这篇博客。希望这篇文章能够为您带来一些有价值的信息和启示。如果您有任何问题或建议,欢迎在评论区留言,我们一起交流学习。
相关文章:
【初阶数据结构】线性表之双链表
文章目录 目录 一、双链表的概念 二、双链表的实现 1.初始化 2.尾插 3.头插 4.打印 5.判断双链表是否为空 6.尾删 7.头删 8.查找 9.在指定的位置之后插入数据 10.删除指定位置的数据 11.销毁 三、完整源码 总结 一、双链表的概念 链表的结构非常多样࿰…...
java网盘项目 目前 文件有分享表 文件夹也要创建分享表吗
java网盘项目 目前 文件有分享表 文件夹也要创建分享表吗 根据网盘系统的典型设计模式及搜索结果中的最佳实践,建议为文件夹创建独立的分享表。以下是具体分析及实施建议: 一、需要独立文件夹分享表的核心原因 权限控制差异 文件分享:通常基…...
智能路由系统-信息泄露漏洞挖掘
1.漏洞描述: Secnet-智能路由系统 actpt_5g.data 信息泄露,攻击者可利用此漏洞收集敏感信息,从而为下一步攻击做准备。 2.fofa搜索语句 title"安网-智能路由系统" || title"智能路由系统" || title"安网科技-智能…...
表格图表切换,图表无法展示问题复盘
项目背景 103项目CPC卡使用模块在原有的表格展示数据的基础之上,增加环状饼图图表展示,采用tab切换的方式实现 问题描述 图表无法设置宽高,导致饼图无法渲染 具体代码 // 入口页<el-tabs type"card" class"cts_flex_t…...
css 实现闪烁光标
要实现闪烁光标(比如文本输入框内常见的闪烁效果),可以使用 CSS 动画。下面是一个简单的方法: 代码示例 <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta n…...
AI赋能python数据处理、分析与预测操作流程
以数据集预测鱼类种类(Species)开展以下研究。数据格式如下: 以下是一个系统的分析思路及推荐的机器学习算法: 1. 数据预处理与探索性分析 缺失值与异常值处理: 检查数据完整性(如Roach类中Weight=0的记录需修正或删除)。 通过箱线图或Z-Score检测异常值,判断是否需…...
基于74LS192的十进制两位数正向计时器(proteus仿真)
在数字电路设计中,计时器是一个非常常见的应用。今天,我将分享一个基于 74LS192 双向计数器 的十进制两位数正向计时器电路设计。这个电路可以实现从 00 到 99 的十进制正向计数,并通过两个七段数码管显示结果。 最终效果如图: 各…...
#C8# UVM中的factory机制 #S8.5# 对factory机制的重载进一步思考(二)
今天我们反思,然后总结。 一 先看代码 `timescale 1ns/1ps module tb_top;class Base;function void print(int a);$display("Base: int = %0d", a);endfunction endclassclass Sub extends Base;function void print(string s);$display("Sub: string = %s&…...
算法-前缀和与差分
一、前缀和(Prefix Sum) 1. 核心思想 前缀和是一种预处理数组的方法,通过预先计算并存储数组的前缀和,使得后续的区间和查询可以在**O(1)**时间内完成。 2. 定义 给定数组 nums,前缀和数组 prefixSum 的每个元素 p…...
React(六)React过渡动画-CSS编写方式
React过渡动画 react-transition-group介绍 在开发中,我们想要给一个组件的显示和消失添加某种过渡动画,提高用户体验→可通过react-transition-group实现。React曾为开发者提供过动画插件 react-addons-css-transition-group,后由社区维护…...
第十五章:Python的Pandas库详解及常见用法
在数据分析领域,Python的Pandas库是一个不可或缺的工具。它提供了高效的数据结构和数据分析工具,使得数据处理变得简单而直观。本文将详细介绍Pandas库的基本功能、常见用法,并通过示例代码演示如何使用Pandas进行数据处理。最后,…...
Python自动化模块:开启高效编程新时代
一、写在前面 在数字化时代,自动化技术已成为提高效率、降低成本的关键手段。Python 作为一种简洁、高效且功能强大的编程语言,凭借其丰富的库和框架,在自动化领域占据了举足轻重的地位,成为众多开发者的首选工具之一。从简单的文…...
【蓝桥杯速成】| 15.完全背包
题目:携带研究材料 问题描述 52. 携带研究材料(第七期模拟笔试) 小明是一位科学家,他需要参加一场重要的国际科学大会,以展示自己的最新研究成果。他需要带一些研究材料,但是他的行李箱空间有限。这些研…...
C++:allocator类(动态数组续)
1.为什么需要 allocator? 在 C 中,动态内存管理通常通过 new 和 delete 完成: int* p new int; // 分配内存 构造对象 delete p; // 析构对象 释放内存 但 new 和 delete 有两个问题: 耦合性:将内…...
libva基础
Libva(Lib Video Acceleration)是一个开源的库,实现了 **VA-API**(Video Acceleration API),旨在为视频处理提供跨平台的硬件加速支持。 1、核心功能与作用 硬件加速抽象层:Libva 作为中间层&…...
【C++20】format格式化输出
C20 format格式化输出 在C20之前,格式化能力都依赖于三方格式化库FMT, 而C20 标准委员会终于在C标准库引入了格式化功能,从使用方式和风格来看其实就是FMT库转正了 直接使用 包含<format.h>头文件既可以直接使用,类似pyt…...
c++游戏开发第一期
以后我将要发c游戏开发的教程,可能更得比较慢。(目测几个星期一更)。 今天先讲个配置编译器。 我用的是Visual studio 2022和EasyX。 安装studio: 首先找到下载链接(点我)下拉找到下面图片的东西。 下完…...
Elasticsearch:人工智能时代的公共部门数据治理
作者:来自 Elastic Darren Meiss 人工智能(AI)和生成式人工智能(GenAI)正在迅速改变公共部门,从理论探讨走向实际应用。正确的数据准备、管理和治理将在 GenAI 的成功实施中发挥关键作用。 我们最近举办了…...
Web开发:数据的加密和解密
一、常见通用术语解析 加盐:在密码中加入随机数据,提高安全性。摘要:固定长度的输出,用于数据完整性验证。加密:将数据转换为不可读形式,确保安全。撞库:通过暴力破解比对常见密码的攻击方式。…...
低功耗LPWAN模块开发指南:远距离无线通信与边缘计算融合实战
在远程资产追踪、野外环境监测等场景中,稳定可靠的长距离通信与超低功耗是系统设计的核心挑战。eFish-SBC-RK3576通过 原生双UART接口 USB OTG扩展能力 ,可无缝集成主流LPWAN模组(LoRa/NB-IoT),实现“数据采集-边…...
RHCA核心课程技术解析5:红帽高可用性集群架构与深度实践
一、红帽高可用集群架构全景 1.1 核心组件交互逻辑 graph TD A[节点1] -->|Corosync 心跳| B[节点2] A -->|Pacemaker 资源管理| C[共享存储] B --> C D[Fencing设备] -->|STONITH| A D -->|STONITH| B C -->|GFS2锁管理| A C -->|GFS2锁管理| B 1.2 集…...
Python切片中的步长秘密
Python切片中的步长秘密 大家好!今天我们来聊聊Python切片中一个有趣的话题 - 步长(step)。 基本格式回顾 Python切片的完整格式是: [起点:终点:步长] 但你是否注意到,很多代码里的切片都只写了起点和终点?没错,步长是可以省略的! 步长的默认…...
Spring Boot事务管理详解(附银行转账案例)
一、事务基础概念 事务的ACID特性: 原子性(Atomicity):操作要么全部成功,要么全部失败一致性(Consistency):数据在事务前后保持合法状态隔离性(Isolation)&…...
【超详细教程】2025年3月最新Pytorch安装教程(同时讲解安装CPU和GPU版本)
目录 一、前言二、pytorch简介三、安装准备工作3.1、下载Anaconda 四、判断是否有NVIDIA显卡五、安装pytorch-CPU版本六、安装pytorch-GPU版本6.1、查看CUDA显卡驱动版本6.2、安装CUDA6.3、安装CuDNN(加速器)6.4、安装pytorch-GPU6.5 其他方法安装注意 七…...
Unity光线传播体积(LPV)技术实现详解
一、LPV技术概述 光线传播体积(Light Propagation Volumes)是一种实时全局光照技术,通过将场景中的间接光信息存储在3D网格中,实现动态物体的间接光照效果。 核心优势: 实时性能:相比传统光照贴图,支持动态场景 硬件…...
Git和GitCode使用(从Git安装到上传项目一条龙)
第一步 菜鸟教程-Git教程 点击上方链接,完成Git的安装,并了解Git 工作流程,知道Git 工作区、暂存区和版本库的区别 第二步 GitCode官方帮助文档-SSH 公钥管理 点击上方链接,完成SSH公钥设置 第三步(GitCode的官方引…...
通信之光纤耦合器
以下是关于光纤耦合器的详细介绍: 定义与原理 - 定义:光纤耦合器是一种能使传输中的光信号在特殊结构的耦合区发生耦合,并进行再分配的器件,也叫分歧器、连接器、适配器、光纤法兰盘。 - 原理:利用不同光纤面紧邻光纤芯…...
5G核心网(5GC)开户中,DNN(Data Network Name,数据网络名称)
在5G核心网(5GC)开户中,DNN(Data Network Name,数据网络名称)是关键概念之一,以下是关于它的详细介绍: 定义 DNN是5G网络中用于标识外部数据网络的名称,相当于4G中的APN(Access Point Name),两者功能等价。 组成 DNN由两部分组成: 网络ID(NI):必选,至少包…...
OpenCV、YOLO与大模型的区别与关系
OpenCV、YOLO 和大模型的区别与关系 1. OpenCV(Open Source Computer Vision Library) 定位:开源的计算机视觉基础库。功能:提供传统的图像处理算法(如图像滤波、边缘检测、特征提取)和基础工具ÿ…...
虚拟电商-话费充值业务(二)话费充值对接供应商模块开发
一、对接供应商模块开发 供应商对接模块chongba_recharge_supplier主要负责的就是调用外部的供应商系统进行充值下单,这种调用是一种基于HTTP协议的调用。 此外在供应商对接模块中主要是实现的业务逻辑有: 1:余额或押金不足情况下的失败轮…...
