NzN的数据结构--实现双向链表

        上一章中,我们学习了链表中的单链表,那今天我们来学习另一种比较常见的链表--双向链表!!

目录

一、双向链表的结构

二、 双向链表的实现

1. 双向链表的初始化和销毁

2. 双向链表的打印

3. 双向链表的头插/尾插

4. 双向链表的头删/尾删

5. 查找数据是否存在

6. 在指定位置之后插入数据

7. 删除指定位置的数据

8. 判断双向链表是否为空

三、顺序表和双向链表的优缺点分析


一、双向链表的结构

        “哨兵位”存在的意义:遍历循环链表避免死循环。

        注意:带头链表里的头节点,实际为“哨兵位,不存储任何有效数据。

typedef int LTDataType;
typedef struct ListNode
{LTDataType data;//存储的数据struct ListNode* prev; //指针保存前一个节点的地址struct ListNode* next; //指针保存下一个节点的地址
}LTNode;

二、 双向链表的实现

        我们先在头文件中定义需要实现的相关接口。

//List.h
#include<stdio.h>
#include <stdbool.h>//引用bool类型
#include<stdlib.h>
typedef int LTDataType;
typedef struct ListNode
{LTDataType data;//存储的数据struct ListNode* prev; //指针保存前一个节点的地址struct ListNode* next; //指针保存下一个节点的地址
}LTNode;
//创建节点
LTNode* LTBuyNode(LTDataType x);
//双向链表有哨兵位,插入数据之前链表中必须初始化一个哨兵位
//需要修改哨兵位就要传二级指针
//void LTInit(LTNode** pphead);
LTNode* LTInit();
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);
//在pos位置之后插入数据
void LTInsert(LTNode* pos, LTDataType x);
//删除pos位置的数据
void LTErase(LTNode* pos);
//判断链表是否为空
bool LTEmpty(LTNode* phead);

1. 双向链表的初始化和销毁

//双向链表初始化
void LTInit(LTNode** pphead)
{*pphead = (LTNode*)malloc(sizeof(LTNode));if (*pphead == NULL){perror("malloc fail");exit(1);}(*pphead)->data = -1;//给哨兵位一个无效的数据,是多少都可以//带头双向循环链表在刚初始化一个哨兵位时,next和prev都指向自己(*pphead)->next = (*pphead)->prev = *pphead;return phead;
}

        这种写法要涉及到二级指针,非常麻烦,那我们尝试简化一下代码。

LTNode* LTInit()
{LTNode*phead= (LTNode*)malloc(sizeof(LTNode));if (phead == NULL){perror("malloc fail");exit(1);}phead->data = -1;phead->next = phead->prev = phead;return phead;
}

        实际上,这段代码还可以进行简化。因为双向链表为空时,仍然有一个哨兵位,那我们在初始化时就可以直接申请一个哨兵位。

//将申请节点的功能进行封装
LTNode* LTBuyNode(LTDataType x) {LTNode* newnode = (LTNode*)malloc(sizeof(LTNode));if (newnode == NULL) {perror("malloc");exit(1);}newnode->data = x;newnode->next = newnode->prev = newnode;return newnode;
}
//双向链表初始化
LTNode* LTInit()
{LTNode* phead = LTBuyNode(-1);//申请哨兵位return phead;
}
//双向链表销毁
void LTDestroy(LTNode* phead) 
{assert(phead);//遍历链表,把每一个节点都释放LTNode* pcur = phead->next;while (pcur != phead){LTNode* next = pcur->next;free(pcur);pcur = next;}//链表中哨兵位也要释放free(phead);phead = NULL;
}

2. 双向链表的打印

//双向链表打印
void LTPrint(LTNode* phead)
{assert(phead);//phead不能为空LTNode* pcur = phead->next;while (pcur != phead){//从第一个节点开始走,走到哨兵位结束printf("%d->", pcur->data);pcur = pcur->next;}printf("\n");
}

3. 双向链表的头插/尾插

void LTPushBack(LTNode* phead, LTDataType x)
{LTNode* newnode = LTBuyNode(x);//ptail->next=phead;//尾节点的next指向哨兵位//phead->prev=ptail//哨兵位的prev指向尾节点//新尾节点的next要指向哨兵位newnode->next = phead;//新尾节点的prev要指向原来的尾节点newnode->prev = phead->prev;//原来尾节点的next指向新的尾节点phead->prev->next = newnode;//哨兵位的prev连接新的尾节点phead->prev = newnode;
}
void LTPushFront(LTNode* phead, LTDataType x)
{assert(phead);LTNode* newnode = LTBuyNode(x);//新的头节点的next指向原来的头节点newnode->next = phead->next;//新的头节点的prev指向哨兵位newnode->prev = phead;//原来头节点的prev指向新的头节点phead->next->prev = newnode;//哨兵位的next指向新的头节点phead->next = newnode;
}

4. 双向链表的头删/尾删

void LTPopBack(LTNode* phead)
{assert(phead);//链表不能为空(只有一个哨兵位)assert(phead->next != phead);LTNode* del = phead->prev;LTNode* prev = del->prev;//原来尾节点的前一个节点的next指向哨兵位prev->next = phead;//哨兵位的prev变成原来尾节点的前一个节点phead->prev = prev;//释放原来的尾节点free(del);del = NULL;
}
void LTPopFront(LTNode* phead)
{assert(phead);//链表不能为空(只有一个哨兵位)assert(phead->next != phead);LTNode* del = phead->next;LTNode* next = del->next;//原来头节点的后一个节点的prev指向哨兵位next->prev = phead;//哨兵位的next变成原来头节点的后一个节点phead->next = next;//释放原来的尾节点free(del);del = NULL;
}

5. 查找数据是否存在

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

6. 在指定位置之后插入数据

void LTInsert(LTNode* pos, LTDataType x)
{assert(pos);LTNode* newnode = LTBuyNode(x);//新节点的next指向pos后面原来的节点newnode->next = pos->next;//新节点的prev指向pos节点newnode->prev = pos;//pos节点后面原来的节点的prev换成新节点pos->next->prev = newnode;//pos节点的next换成新节点pos->next = newnode;
}

7. 删除指定位置的数据

void LTErase(LTNode* pos)
{assert(pos);//pos前面节点的next指向pos后面的节点pos->prev->next = pos->next;//pos后面节点的prev指向pos前面的节点pos->next->prev = pos->prev;free(pos);pos = NULL;
}

8. 判断双向链表是否为空

bool LTEmpty(LTNode* phead)
{return phead->next == phead;
}

三、顺序表和双向链表的优缺点分析

顺序表

带头双向循环链表

优点

下标随机访问(实现二分查找、排序、堆算法等);

Cache命中率高(存储空间连续)

任意位置插入删除数据效率高;

按需申请、释放,不存在空间浪费

缺点

前面部分的插入删除,效率低下;

扩容会有效率损失,还可能会存在空间浪费

不支持下标随机访问;

Cache命中率低(存储空间不连续)

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

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

相关文章

数据如何才能供得出、流得动、用得好、还安全

众所周知&#xff0c;数据要素已经列入基本生产要素&#xff0c;同时成立国家数据局进行工作统筹。目前数据要素如何发挥其价值&#xff0c;全国掀起了一浪一浪的热潮。 随着国外大语言模型的袭来&#xff0c;国内在大语言模型领域的应用也大放异彩&#xff0c;与此同时&#x…

【Web】纯萌新的BUUCTF刷题日记Day1

目录 [RoarCTF 2019]Easy Java [网鼎杯 2018]Fakebook [CISCN2019 华北赛区 Day2 Web1]Hack World [BJDCTF2020]The mystery of ip [网鼎杯 2020 朱雀组]phpweb [BSidesCF 2020]Had a bad day [BJDCTF2020]ZJCTF&#xff0c;不过如此 [BUUCTF 2018]Online Tool [GXYCTF…

数据分析web可视化神器---streamlit框架,无需懂前端也能搭建出精美的web网站页面

✨✨ 欢迎大家来到景天科技苑✨✨ &#x1f388;&#x1f388; 养成好习惯&#xff0c;先赞后看哦~&#x1f388;&#x1f388; 所属的专栏&#xff1a;数据分析系统化教学&#xff0c;零基础到进阶实战 景天的主页&#xff1a;景天科技苑 文章目录 Streamlit什么是streamli…

刷题之Leetcode283题(超级详细)

283.移动零 283. 移动零https://leetcode.cn/problems/move-zeroes/ 给定一个数组 nums&#xff0c;编写一个函数将所有 0 移动到数组的末尾&#xff0c;同时保持非零元素的相对顺序。 请注意 &#xff0c;必须在不复制数组的情况下原地对数组进行操作。 示例 1: 输入: nu…

Rasa X 聊天机器人(部署篇)

一、前言 我们先来了解下 Rasa 是什么&#xff1f;Rasa 是一个开源的自然语言处理 (NLP) 框架&#xff0c;用于构建基于文本的对话系统&#xff0c;如聊天机器人和语音助手。接下来再了解下 Rasa X 是什么&#xff1f;Rasa X 是建立在 Rasa 框架之上的图形用户界面 (GUI) 工具…

.NET8 和 Vue.js 的前后端分离

在.NET 8中实现前后端分离主要涉及到两个部分&#xff1a;后端API的开发和前端应用的开发。后端API通常使用ASP.NET Core来构建&#xff0c;而前端应用则可以使用任何前端框架或技术栈&#xff0c;比如Vue.js、React或Angular等。下面是一个简化的步骤指南&#xff0c;帮助你在…

服装店连锁加盟软件系统权威榜单,商陆花连锁日记再次登顶

随着零售业的不断发展和消费者需求的日益多样化&#xff0c;服装店连锁加盟系统作为商家经营的重要工具&#xff0c;其性能和功能已成为衡量服装连锁店竞争力的关键因素。2023年&#xff0c;经过深入的市场调研和专家评审&#xff0c;我们正式发布本年度服装店连锁加盟系统的权…

Java项目——设计一个消息队列(一)【消息队列的背景知识、项目的需求分析、项目的模块划分】

Java项目——设计一个消息队列 ⼀. 消息队列背景知识⼆. 需求分析核⼼概念核⼼ API交换机类型 (Exchange Type)持久化⽹络通信消息应答 三. 模块划分服务器模块客户端模块公共模块 ⼀. 消息队列背景知识 曾经我们学习过 阻塞队列 (BlockingQueue) , 我们说, 阻塞队列最⼤的⽤途…

超市商品管理系统的设计与实现(全套资料)

一、系统架构 前端&#xff1a;vue | view-design 后端&#xff1a;springboot | mybatis-plus 环境&#xff1a;jdk17 | mysql8 | maven | nodejs | redis 二、代码及数据库 三、功能介绍 01. web端-首页 02. web端-超市概况 03. web端-超市区域 04. …

前端实现打开新标签页后,再次定位到该标签页

需求 A 页面中点击按钮可以打开新的标签页 B 并且向 B 页面发送消息数据。 当新的标签页 B 未关闭且符合同源策略时&#xff0c;再次点击按钮&#xff0c;可以自动跳转到标签页 B 并且发生消息数据。 B.html <script>window.onmessage evt > {console.log(evt.d…

彩虹易支付商户进件插件介绍

插件介绍 商户进件插件&#xff0c;支持多个进件渠道类型&#xff0c;并且可扩展。目前已有《支付宝服务商》、《支付宝直付通》、《微信支付服务商》、《微信支付收付通》进件渠道类型。 支持管理员后台和用户中心提交进件&#xff0c;支持付费进件&#xff0c;用户组限制等…

场景文本检测识别学习 day01(传统OCR的流程、常见的损失函数)

传统OCR的流程 传统OCR&#xff1a;传统光学字符识别常见的的模型主要包括以下几个步骤来识别文本 预处理&#xff1a;预处理是指对输入的图像进行处理&#xff0c;以提高文字识别的准确率。这可能包括调整图像大小、转换为灰度图像、二值化&#xff08;将图像转换为黑白两色&…

一则 MySQL 从节点 hung 死问题分析

作者通过 MySQL 从节点的一个 hung 问题&#xff0c;对数据库连接、日志、innodb status 输出等分析&#xff0c;再结合源码、堆栈等最终明确为由于 redo日志配置不合理导致 hung 死问题根本原因。 作者&#xff1a;李锡超&#xff0c;一个爱笑的江苏苏商银行 数据库工程师&…

2024年最新版FL Studio21.2.3 Build 4004 for Mac 版激活下载和图文激活教程

FL studio21中文别名水果编曲软件&#xff0c;是一款全能的音乐制作软件&#xff0c;包括编曲、录音、剪辑和混音等诸多功能&#xff0c;让你的电脑编程一个全能的录音室&#xff0c;它为您提供了一个集成的开发环境&#xff0c;使用起来非常简单有效&#xff0c;您的工作会变得…

【多线程】Callable详解

Callable接口 先看看Callable接口的源码: Callable是一个函数式接口&#xff0c;此时就可以用lambda表达式更简洁地使用它。Callable是个泛型接口&#xff0c;只有一个方法call&#xff0c;该方法返回类型就是传递进来的V类型。call方法还支持抛出异常. 与Callable对应的是Ru…

openstack中windows虚拟机时间显示异常问题处理

文章目录 一、问题描述二、元数据信息总结 一、问题描述 openstack创建出windows虚拟机的时候&#xff0c;发现时间和当前时间相差8小时&#xff0c;用起来很难受。 参考&#xff1a;https://www.cnblogs.com/hraa0101/p/11365238.html 二、元数据信息 通过设置镜像的元数据…

java对象是怎么在jvm中new出来的,在内存中查看java对象成员变量字段属性值

java对象是怎么在jvm中new出来的 查看java对象字段属性在内存中的值 java 对象 创建 流程 附上java源码 public class MiDept {private int innerFiled999;public MiDept() {System.out.println("new MiDept--------------");}public String show(int data) {Sy…

极客时间: 用 Word2Vec, LangChain, Gemma 模拟全本地检索增强生成(RAG)

每周跟踪AI热点新闻动向和震撼发展 想要探索生成式人工智能的前沿进展吗&#xff1f;订阅我们的简报&#xff0c;深入解析最新的技术突破、实际应用案例和未来的趋势。与全球数同行一同&#xff0c;从行业内部的深度分析和实用指南中受益。不要错过这个机会&#xff0c;成为AI领…

QT的安装

● 查找国内的镜像 ○ 中国科学技术大学&#xff1a;http://mirrors.ustc.edu.cn/qtproject/ ○ 清华大学&#xff1a;https://mirrors.tuna.tsinghua.edu.cn/qt/ ○ 北京理工大学&#xff1a;http://mirror.bit.edu.cn/qtproject/ ○ 中国互联网络信息中心&#xff1a;https:/…

C语言——#define的使用

#define定义常量 基本语法 #define name stuff //&#xff08;#define&#xff09;&#xff08;变量名&#xff09;&#xff08;定义的数值&#xff09; 这里记得&#xff0c;是不加分号的 定义常量&#xff08;这里 就要涉及我们经常说的宏定义&#xff09; 定义常量的使…