数据结构:双向链表的实现(C实现)

个人主页 : 个人主页
个人专栏 : 《数据结构》 《C语言》
文章目录
- 前言
- 一、实现思路
- 1.节点的结构(ListNode)
- 2.新节点的创建(BuyListNode)
- 3.头结点的创建(ListCreate)
- 4.双向链表的销毁(ListDestroy)
- 5.双向链表的打印(ListPrint)
- 6.双向链表的尾插(ListPushBack)
- 7.双向链表的尾删(ListPopBack)
- 8.双向链表的头插(ListPushFront)
- 9.双向链表的头删(ListPopFront)
- 10.双向链表的查找(ListFind)
- 11.双向链表在pos前插入(ListInsert)
- 12.双向链表删除pos位置处的节点(ListErase)
- 二、代码实现
- 总结
前言
本篇博客,将要实现的是带头双向循环链表,该结构实现尾插,尾删只需要O(1)的时间复杂度。
其结构如下所示:

一、实现思路
1.节点的结构(ListNode)
既然要实现的链表是双向循环的,那么对于指针域,我们就需要指向节点上一个的指针和指向节点下一个的指针。至于双向循环,我们只需要尾节点的next指向头结点,头结点的prev指向尾节点,即可实现双向循环。
如下:

typedef int LTDataType;//方便以后修改数据类型typedef struct ListNode
{LTDataType data;struct ListNode* next;struct ListNode* prev;
}ListNode;
2.新节点的创建(BuyListNode)
动态开辟一块空间,使该节点的prev,next都指向自己(方便头结构的创建),再为data赋值,返回该空间地址。

//创建新节点
ListNode* BuyListNode(LTDataType x)
{ListNode* newnode = (ListNode*)malloc(sizeof(ListNode));if (newnode == NULL){perror("malloc");exit(-1);}newnode->data = x;newnode->next = newnode;newnode->prev = newnode;return newnode;
}
3.头结点的创建(ListCreate)
复用BuyListNode函数,使头结点的data为无效数据(这里即为-1)。
//创建返回链表的头结点
ListNode* ListCreate()
{return BuyListNode(-1);
}
4.双向链表的销毁(ListDestroy)
要销毁链表,我们就要遍历链表,那么如何遍历链表?
- 以遍历到头节点为结束条件
- 从头结点的下一个节点开始遍历
如上,我们就可以循环遍历链表。
销毁节点,我们需要两指针变量,一个记录要free的节点(cur),一个记录要free的节点的下一个节点(curNext)。每次free(cur),使cur = curNext。

//双向链表的销毁
void ListDestroy(ListNode* pHead)
{assert(pHead);ListNode* cur = pHead->next;while (cur != pHead){ListNode* curNext = cur->next;free(cur);cur = curNext;}free(pHead);
}
5.双向链表的打印(ListPrint)
我们只有遍历打印链表即可。
- 以遍历到头节点为结束条件
- 从头结点的下一个节点开始遍历
//双向链表的打印
void ListPrint(ListNode* pHead)
{assert(pHead);ListNode* cur = pHead->next;printf("head<=>");while (cur != pHead){printf("%d<=>", cur->data);cur = cur->next;}printf("head\n");
}
6.双向链表的尾插(ListPushBack)
我们只需要让尾节点(tail)的next指向newnode,newnode的prev指向尾节点(tail),newnode的next指向头结点,头结点的prev指向newnode.
我们知道该链表是双向循环的,那么头结点的上一个节点就是尾节点。(与单链表相比该链表实现尾插并不需要遍历找尾,时间复杂度是O(1) )。

//双向链表的尾插
void ListPushBack(ListNode* pHead, LTDataType x)
{assert(pHead);ListNode* newnode = BuyListNode(x);ListNode* tail = pHead->prev;tail->next = newnode;newnode->prev = tail;pHead->prev = newnode;newnode->next = pHead;
}
7.双向链表的尾删(ListPopBack)
我们只需要两个指针tail (指向尾节点),tailPrev (指向尾节点的上一个节点)。
free掉tail,使tailPrev的next指向头结点,头结点的prev指向tailPrev。
- 注意:head->next == head 时,链表无有效数据,不能尾删数据。

//双向链表的尾删
void ListPopBack(ListNode* pHead)
{assert(pHead);//pHead->next == pHead时,链表没有元素assert(pHead->next != pHead);ListNode* tail = pHead->prev;ListNode* tailPrev = tail->prev;pHead->prev = tailPrev;tailPrev->next = pHead;free(tail);
}
8.双向链表的头插(ListPushFront)
我们只需要一个指针 first (头结点的下一个节点),使first的prev指向newnode,newnode的next指向first,head的next指向newnode,newnode的prev指向head。

head节点是哨兵位,不存储有效数据。
//双向链表的头插
void ListPushFront(ListNode* pHead, LTDataType x)
{assert(pHead);ListNode* newnode = BuyListNode(x);ListNode* first = pHead->next;newnode->next = first;first->prev = newnode;newnode->prev = pHead;pHead->next = newnode;
}
9.双向链表的头删(ListPopFront)
我们需要两个指针frist (指向头结点的下一个节点),second (指向头结点的下一个的下一个节点)。
free掉first,使second的prev指向头结点,头结点的next指向second。
- 注意:如果head->next == head,表示链表为空,不能头删。

//双向链表的头删
void ListPopFront(ListNode* pHead)
{assert(pHead);assert(pHead->next != pHead);ListNode* first = pHead->next;ListNode* second = first->next;second->prev = pHead;pHead->next = second;free(first);
}
10.双向链表的查找(ListFind)
我们需要遍历链表,比较链表元素是否是要查找对象。如果找到了,返回该节点的地址。如果找不到,返回NULL。
//双向链表的查找
ListNode* ListFind(ListNode* pHead, LTDataType x)
{assert(pHead);ListNode* cur = pHead->next;while (cur != pHead){if (cur->data == x)return cur;cur = cur->next;}return NULL;
}
11.双向链表在pos前插入(ListInsert)
我们需要一个指针 posPrev (指向pos前一个节点)。
pos的prev指向newnode,newnode的next指向pos;posPrev的next指向newnode,newnode的prev指向posPrev。
- 如果pos指向头结点(哨兵位),那么ListInsert相当与完成尾插。
- 如果pos指向头结点(哨兵位)的下一个节点,那么ListInsert相当于头插。

//双向链表在pos前进行插入
void ListInsert(ListNode* pos, LTDataType x)
{assert(pos);ListNode* newnode = BuyListNode(x);ListNode* posPrev = pos->prev;newnode->prev = posPrev;posPrev->next = newnode;newnode->next = pos;pos->prev = newnode;
}
12.双向链表删除pos位置处的节点(ListErase)
我们需要两个指针posPrev(记录pos的上一个节点的地址),posNext(记录pos的下一个节点的地址)。free掉pos,使posNext的prev指向posPrev,posPrev的next指向posNext。
- 如果pos指向头结点的下一个,那么ListErase相当于头删。
- 如果pos指向头结点的上一个(也就是尾节点),那么ListErase相当于尾删。

//双向链表删除pos位置的节点
void ListErase(ListNode* pos)
{assert(pos);ListNode* posNext = pos->next;ListNode* posPrev = pos->prev;posPrev->next = posNext;posNext->prev = posPrev;free(pos);
}
二、代码实现
对于头插,尾插函数,我复用了ListInsert函数。
对于头删,尾删函数,我复用了ListErase函数。
//Lish.h 文件#pragma once#include <stdio.h>
#include <stdlib.h>
#include <assert.h>typedef int LTDataType;typedef struct ListNode
{LTDataType data;struct ListNode* next;struct ListNode* prev;
}ListNode;//创建返回链表的头结点
ListNode* ListCreate();//创建新节点
ListNode* BuyListNode(LTDataType x);//双向链表的销毁
void ListDestroy(ListNode* pHead);//双向链表的打印
void ListPrint(ListNode* pHead);//双向链表的尾插
void ListPushBack(ListNode* pHead, LTDataType x);//双向链表的尾删
void ListPopBack(ListNode* pHead);//双向链表的头插
void ListPushFront(ListNode* pHead, LTDataType x);//双向链表的头删
void ListPopFront(ListNode* pHead);//双向链表的查找
ListNode* ListFind(ListNode* pHead, LTDataType x);//双向链表在pos前进行插入
void ListInsert(ListNode* pos, LTDataType x);//双向链表删除pos位置的节点
void ListErase(ListNode* pos);
//Lish.c 文件#include "List.h"//创建返回链表的头结点
ListNode* ListCreate()
{return BuyListNode(-1);
}//创建新节点
ListNode* BuyListNode(LTDataType x)
{ListNode* newnode = (ListNode*)malloc(sizeof(ListNode));if (newnode == NULL){perror("malloc");exit(-1);}newnode->data = x;newnode->next = newnode;newnode->prev = newnode;return newnode;
}//双向链表的打印
void ListPrint(ListNode* pHead)
{assert(pHead);ListNode* cur = pHead->next;printf("head<=>");while (cur != pHead){printf("%d<=>", cur->data);cur = cur->next;}printf("head\n");
}//双向链表的尾插
void ListPushBack(ListNode* pHead, LTDataType x)
{assert(pHead);/*ListNode* newnode = BuyListNode(x);ListNode* tail = pHead->prev;tail->next = newnode;newnode->prev = tail;pHead->prev = newnode;newnode->next = pHead;*/ListInsert(pHead, x);
}//双向链表的尾删
void ListPopBack(ListNode* pHead)
{assert(pHead);//pHead->next == pHead时,链表没有元素assert(pHead->next != pHead);/*ListNode* tail = pHead->prev;ListNode* tailPrev = tail->prev;pHead->prev = tailPrev;tailPrev->next = pHead;free(tail);*/ListErase(pHead->prev);
}//双向链表的头插
void ListPushFront(ListNode* pHead, LTDataType x)
{assert(pHead);/*ListNode* newnode = BuyListNode(x);ListNode* first = pHead->next;newnode->next = first;first->prev = newnode;newnode->prev = pHead;pHead->next = newnode;*/ListInsert(pHead->next, x);
}//双向链表的头删
void ListPopFront(ListNode* pHead)
{assert(pHead);assert(pHead->next != pHead);/*ListNode* first = pHead->next;ListNode* second = first->next;second->prev = pHead;pHead->next = second;free(first);*/ListErase(pHead->next);
}//双向链表的查找
ListNode* ListFind(ListNode* pHead, LTDataType x)
{assert(pHead);ListNode* cur = pHead->next;while (cur != pHead){if (cur->data == x)return cur;cur = cur->next;}return NULL;
}//双向链表在pos前进行插入
void ListInsert(ListNode* pos, LTDataType x)
{assert(pos);ListNode* newnode = BuyListNode(x);ListNode* posPrev = pos->prev;newnode->prev = posPrev;posPrev->next = newnode;newnode->next = pos;pos->prev = newnode;
}//双向链表删除pos位置的节点
void ListErase(ListNode* pos)
{assert(pos);ListNode* posNext = pos->next;ListNode* posPrev = pos->prev;posPrev->next = posNext;posNext->prev = posPrev;free(pos);
}//双向链表的销毁
void ListDestroy(ListNode* pHead)
{assert(pHead);ListNode* cur = pHead->next;while (cur != pHead){ListNode* curNext = cur->next;free(cur);cur = curNext;}free(pHead);
}
总结
以上就是双向链表的实现!!!

相关文章:
数据结构:双向链表的实现(C实现)
个人主页 : 个人主页 个人专栏 : 《数据结构》 《C语言》 文章目录 前言 一、实现思路1.节点的结构(ListNode)2.新节点的创建(BuyListNode)3.头结点的创建(ListCreate)4.双向链表的销毁(ListDestroy)5.双向链表的打印(ListPrint)6.双向链表的尾插(ListPu…...
linuxARM裸机学习笔记(4)----GPIO中断以及定时器中断实验
1.中断向量表 这个表里面存放的都是中断向量,中断服务程序的入口地址或存放中断服务程序的首地址成为中断向量。中断向量表是一系列中断服务程序入口地址组成的表,当某个中断触发的时候会自动跳转到中断向量表对应的中断服务程序的入口。 2.NVIC(内嵌向…...
第十二次CCF计算机软件能力认证
第一题:最小差值 给定 n 个数,请找出其中相差(差的绝对值)最小的两个数,输出它们的差值的绝对值。 输入格式 输入第一行包含一个整数 n。 第二行包含 n 个正整数,相邻整数之间使用一个空格分隔。 输出格式 …...
ceph pg inconsistent修复(unexpected clone)
问题概述: ceph -s 显示pg 10.17 inconsistent 且命令ceph pg repair 10.17无法修复,/var/log/ceph/cep-osd.3.log报错内容如下: pg 10.17 osd [3,4] 权威副本osd:3 repair 10.17 10:e889b16a:::rbd_data.88033092ad95.00000000…...
供求重构是产业互联网的核心 个体崛起是产业互联网的终点
文章开头提到的网约车市场缘何会出现这样的困境?其中一个很重要的原因在于,建构于互联网模式之下的供求关系业已走到了尽头,仅仅只是依靠撮合和中介,仅仅只是凭借平台和中心开始无法破解供求两端的矛盾和问题。如何解决这一问题&a…...
torchvision.datasets数据加载失败
torchvision.datasets数据加载失败 如何使用torchvision.datasets进行自动下载数据失败,可以使用手动下载数据 Ctrl点击可以进入相关包文件,查找下载地址:https://www.cs.toronto.edu/~kriz/cifar-10-python.tar.gz 手动下载之后解压&#x…...
【UEC++学习】UE网络 - Replication、RPC
1. UE网络架构 (1)UE的网络架构是SC(Server - Client)的模式,这种模式的优势:这种模式让所有客户端都在服务器端进行安全验证,这样可以有效的防止客户端上的作弊问题。 (2ÿ…...
C语言案例 按序输出三个整数-02
题目:输入三个整数a,b,c,按从小到大的顺序输出 步骤一:定义程序的目标 编写一个C程序,随机输入三个整数,按照从小到大的顺序输出。 步骤二:程序设计 整个程序由三个模块组成,第一个为scanf输入函数模块&a…...
区块链实验室(16) - FISCO BCOS实验环境
经过多次重复,建立一个FISCO BCOS实验环境。该环境是一个VMWare虚拟机,能够启动FISCO BCOS自创建的4节点区块链,不必下载依赖包即可编译Fisco Bcos目标文件,安装有VsCode1.81版本。 启动4节点的Fisco Bcos区块链 启动控制台 编译…...
Java事件监听机制
这里写目录标题 先进行专栏介绍再插一句 开始喽事件监听机制分析观察者模式观察者模式由以下几个角色组成:观察者模式的工作流程如下:观察者模式的优点包括:观察者模式适用于以下场景:总结 事件监听机制的工作流程如下:…...
记一次ubuntu16误删libc.so.6操作的恢复过程
背景 操作系统:ubuntu16 glibc版本:2.23 修改原因: 经过一系列报错和手工构建之后,vulkansdk成功安装(起码运行./vulkansdu成功),在进行./vulkaninfo进行验证时,报错:…...
MAVLINK—C语言demoWindows版本
mavlink/examples/c/udp_example.c 在学习mavlink时准备学习一下官网的C语言example,发现是unix系统的,打算在Windows系统下尝试,于是将示例修改了一下。 #include <stdio.h> #include <errno.h> #include <string.h> #in…...
区块链实验室(15) - 编译FISCO BCOS的过程监测
首次编译开源项目,一般需要下载很多依赖包,尤其是从github、sourceforge等下载依赖包时,速度很慢,编译进度似乎没有一点反应,似乎陷入死循环,似乎陷入一个没有结果的等待。本文提供一种监测方法,…...
java_IO其它架包使用
文章目录 apache-common包的使用 apache-common包的使用 IO技术开发中,代码量很大,而且代码的重复率较高,为此Apache软件基金会,开发了IO技术的工具类commonsIO,大大简化了IO开发。 Apahce软件基金会属于第三方&…...
一、7.协同式任务切换与抢占式任务切换
使用TSS来在任务切换时保护现场和恢复现场 内核任务:单纯由内核组成的任务,和其他用户程序组成其他任务 内核任务的创建 ;为内核任务创建任务控制块TCB mov ecx, 0x46 call sys_routine_seg_sel:allocate_memory call append_to_tcb_link ;将此TCB添加…...
JavaScript实践:用Canvas开发一个可配置的大转盘抽奖功能
🏆作者简介,黑夜开发者,全栈领域新星创作者✌,阿里云社区专家博主,2023年6月csdn上海赛道top4。 🏆数年电商行业从业经验,历任核心研发工程师,项目技术负责人。 🏆本文已…...
yay无法更新问题解决
背景 更新yay后,yay安装软件捞出问题,查的github上的都不靠谱。因此需要把yay的版本固定下,正常的11版本是可用的 解决方案 sudo pacman -S --needed git base-devel git clone https://aur.archlinux.org/yay.git cd yay makepkg -si # 注…...
C语言 — 动态内存管理(动态内存函数)
前言 本期分为三篇介绍动态内存管理相关内容,关注博主了解更多 博主博客链接:https://blog.csdn.net/m0_74014525 本期介绍动态内存函数,函数如何使用、函数格式、在使用在所需要的注意点及C/C程序的内存开辟区域 系列文章 第一篇ÿ…...
Visual ChatGPT:Microsoft ChatGPT 和 VFM 相结合
推荐:使用 NSDT场景编辑器助你快速搭建可二次编辑的3D应用场景 什么是Visual ChatGPT? Visual ChatGPT 是一个包含 Visual Foundation 模型 (VFM) 的系统,可帮助 ChatGPT 更好地理解、生成和编辑视觉信息。VFM 能够指…...
基于java理发店预约系统微信小程序设计与实现
摘要 多姿多彩的世界带来了美好的生活,行业的发展也是形形色色的离不开技术的发展。作为时代进步的发展方面,信息技术至始至终都是成就行业发展的重要秘密。不论何种行业,大到国家、企业,小到团体、个人都在多方位的结合信息化技术…...
Ubuntu系统下交叉编译openssl
一、参考资料 OpenSSL&&libcurl库的交叉编译 - hesetone - 博客园 二、准备工作 1. 编译环境 宿主机:Ubuntu 20.04.6 LTSHost:ARM32位交叉编译器:arm-linux-gnueabihf-gcc-11.1.0 2. 设置交叉编译工具链 在交叉编译之前&#x…...
Cesium1.95中高性能加载1500个点
一、基本方式: 图标使用.png比.svg性能要好 <template><div id"cesiumContainer"></div><div class"toolbar"><button id"resetButton">重新生成点</button><span id"countDisplay&qu…...
渲染学进阶内容——模型
最近在写模组的时候发现渲染器里面离不开模型的定义,在渲染的第二篇文章中简单的讲解了一下关于模型部分的内容,其实不管是方块还是方块实体,都离不开模型的内容 🧱 一、CubeListBuilder 功能解析 CubeListBuilder 是 Minecraft Java 版模型系统的核心构建器,用于动态创…...
Android Bitmap治理全解析:从加载优化到泄漏防控的全生命周期管理
引言 Bitmap(位图)是Android应用内存占用的“头号杀手”。一张1080P(1920x1080)的图片以ARGB_8888格式加载时,内存占用高达8MB(192010804字节)。据统计,超过60%的应用OOM崩溃与Bitm…...
云原生玩法三问:构建自定义开发环境
云原生玩法三问:构建自定义开发环境 引言 临时运维一个古董项目,无文档,无环境,无交接人,俗称三无。 运行设备的环境老,本地环境版本高,ssh不过去。正好最近对 腾讯出品的云原生 cnb 感兴趣&…...
使用Matplotlib创建炫酷的3D散点图:数据可视化的新维度
文章目录 基础实现代码代码解析进阶技巧1. 自定义点的大小和颜色2. 添加图例和样式美化3. 真实数据应用示例实用技巧与注意事项完整示例(带样式)应用场景在数据科学和可视化领域,三维图形能为我们提供更丰富的数据洞察。本文将手把手教你如何使用Python的Matplotlib库创建引…...
站群服务器的应用场景都有哪些?
站群服务器主要是为了多个网站的托管和管理所设计的,可以通过集中管理和高效资源的分配,来支持多个独立的网站同时运行,让每一个网站都可以分配到独立的IP地址,避免出现IP关联的风险,用户还可以通过控制面板进行管理功…...
省略号和可变参数模板
本文主要介绍如何展开可变参数的参数包 1.C语言的va_list展开可变参数 #include <iostream> #include <cstdarg>void printNumbers(int count, ...) {// 声明va_list类型的变量va_list args;// 使用va_start将可变参数写入变量argsva_start(args, count);for (in…...
LCTF液晶可调谐滤波器在多光谱相机捕捉无人机目标检测中的作用
中达瑞和自2005年成立以来,一直在光谱成像领域深度钻研和发展,始终致力于研发高性能、高可靠性的光谱成像相机,为科研院校提供更优的产品和服务。在《低空背景下无人机目标的光谱特征研究及目标检测应用》这篇论文中提到中达瑞和 LCTF 作为多…...
高考志愿填报管理系统---开发介绍
高考志愿填报管理系统是一款专为教育机构、学校和教师设计的学生信息管理和志愿填报辅助平台。系统基于Django框架开发,采用现代化的Web技术,为教育工作者提供高效、安全、便捷的学生管理解决方案。 ## 📋 系统概述 ### 🎯 系统定…...
