双向链表(详解)

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

话不多说,直接上货。

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://www.hqwc.cn/news/685878.html

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

相关文章

【机器学习与实现】线性回归分析

目录 一、相关和回归的概念&#xff08;一&#xff09;变量间的关系&#xff08;二&#xff09;Pearson&#xff08;皮尔逊&#xff09;相关系数 二、线性回归的概念和方程&#xff08;一&#xff09;回归分析概述&#xff08;二&#xff09;线性回归方程 三、线性回归模型的损…

vue开发网站—①调用$notify弹窗、②$notify弹窗层级问题、③js判断两个数组是否相同等。

一、vue中如何使用vant的 $notify&#xff08;展示通知&#xff09; 在Vue中使用Vant组件库的$notify方法来展示通知&#xff0c;首先确保正确安装了Vant并在项目中引入了Notify组件。 1.安装vant npm install vant --save# 或者使用yarn yarn add vant2.引入&#xff1a;在ma…

springboot整合redis多数据源(附带RedisUtil)

单数据源RedisUtil(静态) 单数据源RedisUtil,我这里implements ApplicationContextAware在setApplicationContext注入redisTemplate,工具类可以直接类RedisUtil.StringOps.get()使用 package com.vehicle.manager.core.util;import com.alibaba.fastjson.JSON; import lombok.e…

信息系统项目管理基础

目录 一、项目管理概论 1、定义 2、项目管理的十二原则 3、SMART原则 4、项目经理 5、项目的生命周期 二、项目立项管理 1、项目启动过程 三、项目整合管理 1、管理基础 2、项目整合管理过程 ①制定项目章程 ②制定项目管理计划 ③指导与管理项目工作 ④管理项目…

stm32之hal库spi驱动封装(实现阻塞,中断,dma三种方式)

前言 配置功能参考rt-thread驱动代码将中断配置和dma配置单独分开管理 代码 中断管理 头文件 /** Copyright (c) 2024-2024,shchl** SPDX-License-Identifier: Apache-2.0** Change Logs:* Date Author Notes* 2024-5-3 shchl first version*/#ifnd…

Python图形复刻——绘制母亲节花束

各位小伙伴&#xff0c;好久不见&#xff0c;今天学习用Python绘制花束。 有一种爱&#xff0c;不求回报&#xff0c;有一种情&#xff0c;无私奉献&#xff0c;这就是母爱。祝天下妈妈节日快乐&#xff0c;幸福永远&#xff01; 图形展示&#xff1a; 代码展示&#xff1a; …

06.线程同步

互斥锁&#xff08;互斥量&#xff09; 描述 一个进程下的线程是共享资源的&#xff0c;通信便利的同时也造成了许多麻烦&#xff0c;线程程和线程之间如果同时访问一块资源就会出错&#xff0c;所以要引入一个互斥变量给它加锁&#xff0c;让它去协同不同线程程之间的访问&am…

【软件测试】3.开发模型

目录 1.常见的开发模型 1.1瀑布模型 1.2螺旋模型 1.3增量模型和迭代模型 1.4敏捷模型 1.4.1特点&#xff1a; 1.5Scrum模型&#xff08;三个角色和五个重要会议&#xff09; 1.5.1三个角色&#xff1a; 1.5.2Scrum工作流程&#xff08;五个会议&#xff09; 1.6测试模…

Unreal Engine(虚幻引擎)的版本特点

Unreal Engine&#xff08;虚幻引擎&#xff09;是Epic Games开发的游戏引擎&#xff0c;广泛应用于游戏开发、影视制作、建筑设计、虚拟现实等领域。Unreal Engine版本指的是该引擎的发布版本&#xff0c;不同版本之间在功能、性能和稳定性等方面存在差异。北京木奇移动技术有…

【JavaScript】内置对象 - 数组对象 ① ( 数组简介 | 数组创建 | 数组类型检测 )

文章目录 一、数组对象1、数组简介2、数组创建3、数组检测 - Array.isArray() 方法4、数组检测 - instanceof 运算符 Array 数组对象参考文档 : https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Array 一、数组对象 1、数组简介 在 JavaScr…

【vulhub靶场】Apache 中间件漏洞复现

【vulhub靶场】Apache 中间件漏洞复现 一、Apache HTTPD 换行解析漏洞&#xff08;CVE-2017-15715&#xff09;1. 漏洞详情2. 影响版本3. 漏洞复现 二、Apache多后缀解析漏洞&#xff08;apache_parsing_vulnerability&#xff09;1. 漏洞详情2. 漏洞复现 三、Apache HTTP Serv…

JDK不同版本里中国夏令时时间

什么是夏令时&#xff1f; 夏令时&#xff0c;&#xff08;Daylight Saving Time&#xff1a;DST&#xff09;&#xff0c;也叫夏时制&#xff0c;又称“日光节约时制”和“夏令时间”&#xff0c;是一种为节约能源而人为规定地方时间的制度&#xff0c;在这一制度实行期间所采…