双向链表(详解)

在单链表专题中我们提到链表的分类,其中提到了带头双向循环链表,今天小编将详细讲下双向链表。

话不多说,直接上货。

1.双向链表的结构

带头双向循环链表

80f0f03a9fbd4ef6afe363958f5a3f9a.png

注意

这几的“带头”跟前面我们说的“头节点”是两个概念,实际前面的在单链表阶段称呼不严
谨,但是为了更好的理解就直接称为单链表的头节点。
带头链表里的头节点,实际为“哨兵位”,哨兵位节点不存储任何有效元素,只是站在这里“放哨 的”。
“哨兵位”存在的意义: 遍历循环链表避免死循环。

插入操作时,不需要检查是否在头部插入,因为哨兵节点作为头结点,总是存在。

删除操作时,不需要处理删除的是否是头节点的情况,因为哨兵节点不会被删除。

简化了代码,因为不需要为头节点和普通节点编写不同的处理逻辑。
双向链表文字上没有什么好说的,具体主要是代码的实现,有了单链表的基础铺垫,双向链表实现也会轻松很多, 主要是理清楚前后节点的关系。

2.双向链表的实现

我们同样是按照项目的格式。

1.头文件的建立(函数库引入,所需函数的导入) 

#pragma once
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
//#include <stdbool.h>
typedef int Datatype;
//链表节点创建
typedef struct  ListNode {Datatype data;struct ListNode* next;struct ListNode* prev;
}Node;
//相关函数实现
// 链表初始化,双向链表头节点不能为空
//void LTInit(Node** pphead);
Node* LTInit();
//链表销毁
void LTDestroy(Node* phead);
//链表打印
void LTPrint(Node* phead);
//bool LTEmpty(Node* phead);
//尾插和头插
void LTPushBack(Node* phead, Datatype x);
void LTPushFront(Node* phead, Datatype x);
//尾删和头删
void LTPopBack(Node* phead);
void LTPopFront(Node* phead);
//在pos位置之后插⼊数据
void LTInsert(Node* pos, Datatype x);
//删除指定节点
void LTErase(Node* pos);
//节点找寻,方便指定插入和删除
Node* LTFind(Node* phead, Datatype x);

2.相关函数的实现

2.1 新节点建立

Node* Buynode(Datatype x) {Node* node = (Node*)malloc(sizeof(Node));if (node == NULL) {perror("malloc error");exit(1);}node->data = x;node->next = node->prev=node;return node;
}

这里为什么节点前后没有指向空指针,因为在后续中链表初始化时,若指向都是空指针,则创建的链表不是双向链表。

在双向链表中,每个节点都有两个指针,一个指向前一个节点(prev),一个指向后一个节点(next)。这样可以实现双向遍历和操作。 当初始化一个双向链表时,需要创建一个头节点(head node),这个头节点不存储实际的数据,只用于标识链表的起始位置。头节点的prev指针和next指针都应该指向自己,这样可以在链表中任意位置插入和删除节点而不需要特殊处理边界情况。

如果初始化时将prev和next指针都指向空,那么在插入和删除节点时就需要特殊处理头节点和尾节点的情况,增加了代码的复杂性。因此,在初始化时将prev和next指针都指向自己是一种简化设计,方便后续操作的方式。

总结来说,prev和next指针不能指向空,是为了简化双向链表的设计和操作。

2.2 链表初始化

Node* LTInit() {Node* pphead = Buynode(-1);if (pphead == NULL) {printf("初始化失败!");}return pphead;
}

2.3 链表的打印

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

2.4 尾插和头插

/尾插和头插
void LTPushBack(Node* phead, Datatype x) {assert(phead);//Node* ptail = (Node*)malloc(sizeof(Node));Node* newnode = Buynode(x);newnode->prev = phead->prev;//新节点指向原来的尾节点newnode->next = phead;phead->prev->next = newnode; //让原本的尾节点指向新的节点phead->prev = newnode;
}
void LTPushFront(Node* phead, Datatype x) {assert(phead);Node* newnode = Buynode(x);newnode->next = phead->next;newnode->prev = phead;phead->next->prev= newnode;//两行不能完全交换,若交换phead->next = newnode;//phead->next=newnode;newnode->prev=newnode;}

2.5 尾删和头删

//尾删和头删
void LTPopBack(Node* phead) {//链表必须有效且链表不能为空(只有一个哨兵位)assert(phead && phead->next!=phead);Node* del = phead->prev;Node* ptail = del->prev;ptail->next = phead;phead->prev = ptail;free(del);del = NULL;
}void LTPopFront(Node* phead) {assert(phead && phead->next != phead);Node* del = phead->next;phead->next = del->next;del->next->prev = phead;free(del);del = NULL;
}

2.6 节点的找寻

方便在指定的节点前后进行相关操作

Node* LTFind(Node* phead, Datatype x) {Node* find = phead->next;while (find!=phead) {if (find->data == x)return find;elsefind = find->next;}return NULL;
}

2.7 指定节点处的操作

//在pos位置之后插⼊数据
void LTInsert(Node* pos, Datatype x) {assert(pos);Node* newnode = Buynode(x);newnode->next = pos->next;newnode->prev = pos;pos->next->prev = newnode;pos->next = newnode;
}//删除指定节点
void LTErase(Node* pos) {assert(pos);pos->next->prev = pos->prev;pos->prev->next = pos->next;free(pos);pos = NULL;
}

2.8 链表的销毁

void LTDestroy(Node* phead) {assert(phead);Node* pcur = phead->next;while (pcur->next != phead) {Node* next = pcur->next;free(pcur);pcur = next;}free(phead);phead = NULL;
}

在实现相关函数时,都是从前后节点入手,改变next和prev的指向。

3. 链表的测试

#define _CRT_SECURE_NO_WARNINGS
#include "List.h"
void test1() {Node*plist=LTInit();//printf("%d", phead->data);//头插LTPushFront(plist, 1);LTPushFront(plist, 2);LTPrint(plist);//尾插LTPushBack(plist, 5);LTPushBack(plist, 3);//LTPrint(plist);//尾删//LTPopBack(plist, 3);//LTPopBack(plist, 5);//LTPrint(plist);//头删//LTPopFront(plist, 1);//LTPopFront(plist, 2);//LTPrint(plist);//节点查找Node* find = LTFind(plist, 5);if (find == NULL)printf("Not Found\n");elseprintf("找到了\n");//指定插入LTInsert(find, 66);LTPrint(plist);//指定节点删除LTErase(find);LTPrint(plist);//链表销毁LTDestroy(plist);plist = NULL;
}
int main() {test1();return 0;
}

3.顺序表和链表的优缺点对比(了解)

a1a0210b061f427e879f8a00415db020.png

看完给小编留下点赞,关注加三连吧!!!

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

如若内容造成侵权/违法违规/事实不符,请联系瞎胡闹网进行投诉反馈,一经查实,立即删除!

相关文章

35个矩阵账号,如何通过小魔推打造2704万+视频曝光?

在如今的短视频时代&#xff0c;矩阵发布的作用被发挥到极致&#xff0c;通过各个短视频平台的流量分发&#xff0c;虽然视频质量不如那些头部的IP&#xff0c;但是在视频数量上却能做到轻松碾压&#xff0c;让自己的品牌与门店有更多的声量&#xff0c;这就是如今短视频平台对…

2024/5/9 英语每日一段

With runoff from this year’s snow and rain boosting the levels of California’s reservoirs, state water managers on Tuesday announced plans to increase deliveries of supplies from the State Water Project to 40% of full allotments, up from 30% last month. …

C++进阶 | [3] 搜索二叉树

摘要&#xff1a;什么是搜索二叉树&#xff0c;实现搜索二叉树&#xff08;及递归版本&#xff09; 什么是搜索二叉树 搜索二叉树/二叉排序树/二叉查找树BST&#xff08;Binary Search Tree&#xff09;&#xff1a;特征——左小右大&#xff08;不允许重复值&#xff09;。即…

【Vue2】关于response返回数据的错误小记

关于Vue2中response返回数据的一个错误小记 如图&#xff0c;在这里返回的时候&#xff0c;后端是通过List< String >返回的&#xff0c;response接收到的实际上是一个Array数组&#xff0c;但是赋值给searchedTaskList的时候&#xff0c;需要在.then包括的范围里面赋值给…

智能创作时代:AI引领下的内容生产革命与效率提升

✨✨ 欢迎大家来访Srlua的博文&#xff08;づ&#xffe3;3&#xffe3;&#xff09;づ╭❤&#xff5e;✨✨ &#x1f31f;&#x1f31f; 欢迎各位亲爱的读者&#xff0c;感谢你们抽出宝贵的时间来阅读我的文章。 我是Srlua小谢&#xff0c;在这里我会分享我的知识和经验。&am…

【2022 深圳 ArchSummit 】大数据架构稳定性保障实践

文章目录 一、前言二、现状三、大数据架构的历史变迁&#xff08;一&#xff09;洪荒期&MR&#xff08;二&#xff09;远古期&MPP&#xff08;四&#xff09;近现代&Flink/Spark&#xff08;五&#xff09;现如今&实时数据湖架构 四、架构稳定的关键因素&#…

网络编程--tcp三次握手四次挥手

1、三次握手 &#xff08;1&#xff09;三次握手的详述 首先Client端发送连接请求报文&#xff0c;Server段接受连接后回复ACK报文&#xff0c;并为这次连接分配资源。Client端接收到ACK报文后也向Server段发生ACK报文&#xff0c;并分配资源&#xff0c;这样TCP连接就建立了。…

vue + element-plus 开发中遇到的问题

1.问题之路由守卫 初写路由守卫&#xff0c;对于next()的理解不是很透彻&#xff0c;就想着都放行&#xff0c;不然看不到效果&#xff0c;结果控制台出现了警告&#xff0c;想着报黄的问题就不是问题&#xff0c;但仔细一看发现他说&#xff0c;如果再生产阶段就会失败&#x…

AI图书推荐:使用FastAPI框架构建AI服务

《使用FastAPI构建生成式AI服务》&#xff08;Building Generative AI Services with FastAPI (Early Release) &#xff09;是一本由Ali Parandeh编写的书籍&#xff0c;计划于2025年3月首次出版&#xff0c;该书以实践为导向&#xff0c;指导读者如何开发具备丰富上下文信息的…

mysql基础概念

文章目录 登录mysqlmysql和mysqld数据库操作主流数据库MYSQL架构SQL分类 登录mysql 登录mysql连接服务器&#xff0c;mysql连接时可以指明主机用-h选项&#xff0c;然后就可以指定主机Ip地址&#xff0c;-P可以指定端口号 -u指定登录用户 -P指定登录密码 查看系统中有无mysql&…

嵌入式C语言高级教程:实现基于STM32的智能照明系统

智能照明系统不仅可以自动调节光源的亮度和色温&#xff0c;还可以通过感应用户的行为模式来优化能源消耗。本教程将指导您如何在STM32微控制器上实现一个基本的智能照明系统。 一、开发环境准备 硬件要求 微控制器&#xff1a;STM32F103RET6&#xff0c;具有足够的处理能力…

Python语言基础学习(上)

目录 一、常量和表达式 二、变量和类型 2.1 认识变量 2.2 定义变量 2.3 变量类型 1、整数 int 2、浮点数&#xff08;小数&#xff09;float 3、字符串 str 4、布尔类型 2.4 类型转换 三、注释 3.1 单行注释 3.2 文档注释&#xff08;或者多行注释&#xff09; …

数字工厂管理系统如何助力企业数据采集与分析

随着科技的不断进步&#xff0c;数字化已成为企业发展的重要趋势。在制造业领域&#xff0c;数字工厂管理系统的应用日益广泛&#xff0c;它不仅提升了生产效率&#xff0c;更在数据采集与分析方面发挥着举足轻重的作用。本文旨在探讨数字工厂管理系统如何助力企业数据采集与分…

[uniapp] 配置ts类型声明

我想引进图片,但是报错 声明一下就行 TypeScript 支持 | uni-app官网 创建tsconfig.json文件,复制官网的配置 然后在随便一个目录下写一个随便名字的.d.ts文件 例如这样 保存就行 因为ts是默认扫描全部的,所以要按照官网的写法 把不必要的排除掉就行,免得浪费性能

数据库的一些知识点

数据模型的组成要素中&#xff0c;描述数据库的组成对象以及对象之间的联系的是&#xff08; &#xff09;。 A 数据结构 B 数据操作 C 数据的完整性约束条件 D 数据的安全性约束条件 2.单选题 (2分) 若关系中的某一组属性的值能够唯一地标识一个元组&#xff0c;而其子集…

ROS实操:通信机制的实现

最近闲来无事&#xff0c;打算重温了一下ROS方面的相关知识。先前的学习都是一带而过&#xff0c;发现差不多都忘了&#xff0c;学习的不够深入。因此&#xff0c;在重温的同时&#xff0c;写下了这篇关于ROS通信实操的学习博客。 上一篇博客的链接为&#xff1a;ROS架构的学习…

OpenCompass大模型评估

作业链接&#xff1a; Tutorial/opencompass/homework.md at camp2 InternLM/Tutorial GitHub 项目链接&#xff1a; GitHub - open-compass/opencompass: OpenCompass is an LLM evaluation platform, supporting a wide range of models (Llama3, Mistral, InternLM2,GPT-…

Modown9.1主题无限制使用+Erphpdown17.1插件

Modown9.1主题无限制使用 1、Erphpdown17.1插件Modown9.1主题 2、送Modown主题详细教程。 1、Erphpdown插件和Modown主题无需激活 2、送的插件均无需激活 3、主题插件均不包更新 4、已亲测可以完美使用。 功能强大&#xff0c;适用于绝大多数虚拟资源站&#xff01;物超所值&a…

远程桌面连接不上怎么连服务器,原因是什么?如何解决?

远程桌面连接不上怎么连服务器&#xff0c;原因是什么&#xff1f;如何解决&#xff1f; 面对远程桌面连接不上的困境&#xff0c;我们有办法&#xff01; 当你尝试通过远程桌面连接服务器&#xff0c;但遭遇连接失败的挫折时&#xff0c;不要慌张。这种情况可能由多种原因引起…

Netty底层数据交互源码分析

文章目录 1. 前题回顾2. 主线流程源码分析3. Netty底层的零拷贝4. ByteBuf内存池设计 书接上文 1. 前题回顾 上一篇博客我们分析了Netty服务端启动的底层原理&#xff0c;主要就是将EventLoop里面的线程注册到了Select中&#xff0c;然后调用select方法监听客户端连接&#xf…