【双向链表】数据结构双向链表的实现

news2024/11/20 11:45:38

前言:
前一期我们已经学习过单链表了,今天我们来学习链表中的双向链表!

目录

  • 1.概念以及结构
  • 2.双向链表结点结构体
  • 3.接口实现
    • 3.1动态申请一个结点
    • 3.2初始化链表
    • 3.3打印链表
    • 3.4双向链表尾插
    • 3.5 双向链表尾删
    • 3.6双向链表头插
    • 3.7双向链表头删
    • 3.8双向链表查找
    • 3.9双向链表在pos的前面进行插入
    • 3.10双向链表删除pos位置的结点

1.概念以及结构

双向链表也叫双链表,是链表的一种,它的每个数据结点中都有两个指针,分别指向直接后继和直接前驱。所以,从双向链表中的任意一个结点开始,都可以很方便地访问它的前驱结点和后继结点。一般我们都构造双向循环链表。

在这里插入图片描述

2.双向链表结点结构体

双向链表每个结点除了存储数据data外,还有两个指针记录上一个结点和下一个结点的地址,分别是前驱指针prev和后继指针next,所以双向链表结点定义如下:

typedef int LTDataType;
typedef struct ListNode
{
	struct ListNode* next;
	struct ListNode* prev;
	LTDataType data;
}LTNode;

3.接口实现

我们主要进行以下操作:

//动态申请一个结点
LTNode* BuyListNode(LTDataType x);
//初始化链表
LTNode* LTInit();
//打印链表
void LTPrint(LTNode* phead);
// 双向链表尾插
void LTPushBack(LTNode* phead, LTDataType x);
// 双向链表尾删
void LTPopBack(LTNode* phead);
// 双向链表头插
void LTPushFront(LTNode* phead, LTDataType x);
// 双向链表头删
void LTPopFront(LTNode* phead);
// 双向链表查找
LTNode* LTFind(LTNode* phead, LTDataType x);
// 双向链表在pos的前面进行插入
void LTInsert(LTNode* pos, LTDataType x);
// 双向链表删除pos位置的结点
void LTErase(LTNode* pos);

3.1动态申请一个结点

LTNode* BuyListNode(LTDataType x)
{
	LTNode* node = (LTNode*)malloc(sizeof(LTNode));
	if (node == NULL)
	{
		perror("malloc fail");
		exit(-1);
	}

	node->data = x;
	node->next = NULL;
	node->prev = NULL;
	return node;
}

思路:

这一步跟之前单链表的操作类似,大家可以看之前的讲解!

3.2初始化链表

LTNode* LTInit()
{
	LTNode* phead = BuyListNode(-1);
	phead->next = phead;
	phead->prev = phead;

	return phead;
}

思路:

我们进行初始化之前需要清楚我们结构的状态,我们这里写的是带头结点的双向循环链表,开始时我们先定义一个phead指针,它的next指向自己,prev也是指向自己,头结点和尾结点指向NULL空的话就不能满足我们循环的需求。

3.3打印链表

void LTPrint(LTNode* phead)
{
	assert(phead);

	LTNode* cur = phead->next;
	while (cur != phead)
	{
		printf("%d ", cur->data);
		cur = cur->next;
	}
	printf("\n");
}

思路:

原理同单链表的操作基本一致!

3.4双向链表尾插

void LTPushBack(LTNode* phead, LTDataType x)
{
	assert(phead);

	LTNode* newnode = BuyListNode(x);
	LTNode* tail = phead->prev;

	tail->next = newnode;
	newnode->prev = tail;

	newnode->next = phead;
	phead->prev = newnode;
}

思路:

分为两种情况:
链表为空,那插入的结点既是尾结点,也是头结点;
链表不为空,将新结点和链表通过前驱指针prev和后继指针next连接起来,并将尾结点改为新插入的结点,让新节点与最后一个节点进行双层逻辑关系;
在这里插入图片描述

3.5 双向链表尾删

void LTPopBack(LTNode* phead)
{
	assert(phead);
	assert(phead->next != phead);  // 判空

	LTNode* tail = phead->prev;
	LTNode* tailPrev = tail->prev;//作为新的尾结点

	tailPrev->next = phead;
	phead->prev = tailPrev;
	free(tail);
}

思路:

链表为空,不操作,先断言:
链表结点大于1,保存尾结点,新尾结点等于原尾结点的上一个结点,然后释放保存的尾结点的内存;
在这里插入图片描述

3.6双向链表头插

void LTPushFront(LTNode* phead, LTDataType x)
{
	assert(phead);

	LTNode* newnode = BuyListNode(x);
	newnode->next = phead->next;
	phead->next->prev = newnode;

	phead->next = newnode;
	newnode->prev = phead;
}

思路:

跟尾插一样:
链表为空,那插入的结点既是头结点,也是尾结点;
链表不为空,将新结点和链表通过前驱指针prev和后继指针next连接起来,并将头结点改为新插入的结点,一定要注意链接顺序;
在这里插入图片描述

3.7双向链表头删

void LTPopFront(LTNode* phead)
{
	assert(phead);
	assert(phead->next != phead); // 判空

	LTNode* first = phead->next;
	LTNode* second = first->next;

	free(first);

	phead->next = second;
	second->prev = phead;
	
}

思路:

思路跟之前的尾删大差不差,具体对照下图,第二张图就是判断删空的情况:

在这里插入图片描述

3.8双向链表查找

LTNode* LTFind(LTNode* phead, LTDataType x)
{
	assert(phead);

	LTNode* cur = phead->next;
	while (cur != phead)
	{
		if (cur->data == x)
		{
			return cur;
		}

		cur = cur->next;
	}

	return NULL;
}

这跟我们单链表的情况也基本一样,大家可以对照学习!

3.9双向链表在pos的前面进行插入

void LTInsert(LTNode* pos, LTDataType x)
{
	assert(pos);

	LTNode* prev = pos->prev;
	LTNode* newnode = BuyListNode(x);
	// prev newnode pos
	prev->next = newnode;
	newnode->prev = prev;
	newnode->next = pos;
	pos->prev = newnode;

思路:

还是分为两种情况考虑:
当插入的位置为第一个位置时,这时就相当于我们的头插操作:
第二种就是正常的插入的操作,具体结合下图:
在这里插入图片描述

3.10双向链表删除pos位置的结点

void LTErase(LTNode* pos)
{
	assert(pos);

	LTNode* prev = pos->prev;
	LTNode* next = pos->next;
	free(pos);

	prev->next = next;
	next->prev = prev;
}

思路:

这需要注意的就是删除之后的链接关系!
结合图进行理解,我相信大家都能明白。
在这里插入图片描述

总结:
以上就是关于带头循环双向链表的基本实现过程!大家之前学过单链表理解起来就很轻松。

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

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

相关文章

Linux常用命令——pvscan命令

在线Linux命令查询工具(http://www.lzltool.com/LinuxCommand) pvscan 扫描系统中所有硬盘的物理卷列表 补充说明 pvscan命令会扫描系统中连接的所有硬盘,列出找到的物理卷列表。使用pvscan命令的-n选项可以显示硬盘中的不属于任何卷组的物理卷,这些…

OAuth2代码演示

目录 1 创建项目结构 1.1 客户 1.2 认证服务器 1.3 资源拥有者 1.4 资源服务器 client 客户 authorization-server 认证服务 resource-owner 资源所有者 resource-server 资源服务器 工作流程: 客户向资源所有者申请授权码 资源所有者下发授权码 客户拿到授权…

springboot+mongodb初体验

MongoDB 是一个基于分布式文件存储的数据库。由 C 语言编写,旨在为 WEB 应用提供可扩展的高性能数据存储解决方案。 MongoDB 是一个介于关系数据库和非关系数据库之间的产品,是非关系数据库当中功能最丰富,最像关系数据库的。 1、mongodb服务…

JavaScript 算术运算符

JavaScript 算术运算符 加减乘除以及取模&#xff08;求余数&#xff09;、、– <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8" /><meta http-equiv"X-UA-Compatible" content"IEedge" />…

代码随想录算法训练营第十六天 | 104.二叉树的最大深度、559.n叉树的最大深度,111.二叉树的最小深度,222.完全二叉树的节点个数

Day15 周日休息一、参考资料二叉树的最大深度 &#xff08;优先掌握递归&#xff09;题目链接/文章讲解/视频讲解&#xff1a; https://programmercarl.com/0104.%E4%BA%8C%E5%8F%89%E6%A0%91%E7%9A%84%E6%9C%80%E5%A4%A7%E6%B7%B1%E5%BA%A6.html 二叉树的最小深度 &#xff08…

车载网络 - BootLoader - CAN/CANFD刷写前提

刷写作为车载网络测试极其重要的一个模块一直拖到今天才开始写,之前确实没有一个太好的想法怎么介绍这一块,虽然现在也没有想出来怎么写能够更好的介绍这块的内容,不过我也尽量用通俗的语言让大家看懂。 刷写流程 刷写流程我也根据用例的设计分为3个阶段:前置条件、刷写程序…

UDP+有穷自动状态机构造网络指令系统

UDP有穷自动状态机构造网络指令系统 项目背景 某展厅的小项目&#xff0c;使用Unity制作了一个视频播放器&#xff0c;作为受控端&#xff0c;需要接收解说员手中的“PAD”或“触控屏电脑”等设备发来的控制指令。要求指令系统满足以下功能&#xff1a; 能够随意切换要播放的…

剑指Offer 第17天 Top K问题 优先级队列解决数据流中位数

目录 剑指 Offer 40. 最小的k个数 剑指 Offer 41. 数据流中的中位数 剑指 Offer 40. 最小的k个数 输入整数数组 arr &#xff0c;找出其中最小的 k 个数。例如&#xff0c;输入4、5、1、6、2、7、3、8这8个数字&#xff0c;则最小的4个数字是1、2、3、4。 示例 1&#xff1a; …

图像处理中的微分算子

摘要 微分算子在图像处理中的作用主要是用在图像的边缘检测&#xff0c;而图像边缘检测必须满足两个条件&#xff1a;一能有效的抑制噪声&#xff0c;二能必须尽量精确定位边缘位置。现在常用的微分算子主要有&#xff1a;Sobel算子&#xff0c;Robert算子&#xff0c;Prewitt…

【数据结构-JAVA】堆和优先级队列

前面介绍过队列&#xff0c;队列是一种先进先出(FIFO)的数据结构&#xff0c;但有些情况下&#xff0c;操作的数据可能带有优先级&#xff0c;一般出队 列时&#xff0c;可能需要优先级高的元素先出队列&#xff0c;该中场景下&#xff0c;使用队列显然不合适&#xff0c;比如&…

Hugo博客教程(一)

秋风阁——北溪入江流&#xff1a;https://focus-wind.com/ 秋风阁——计算机视觉实验&#xff1a;边缘提取与特征检测 文章目录Hugo博客教程&#xff08;一&#xff09;博客静态博客静态博客的优缺点常见的静态博客HexoHugo动态博客动态博客的优缺点常见的动态博客WordPressTy…

sql进阶教程

sql进阶教程第一章、神奇的sql1.1 CASE 表达式将已有编号方式转换为新的方式并统计用一条 SQL 语句进行不同条件的统计用 CHECK 约束定义多个列的条件关系在 UPDATE 语句里进行条件分支表之间的数据匹配在 CASE 表达式中使用聚合函数本节要点1.2 自连接的用法面向集合语言SQL可…

shiro(二):springboot整合shiro

1. 整合思路 2. 加入jsp相关配置方便测试 2.1 加入依赖&#xff1a; <!--引入JSP解析依赖--> <dependency><groupId>org.apache.tomcat.embed</groupId><artifactId>tomcat-embed-jasper</artifactId></dependency> <dependenc…

Golang——包

1、GOPATH 项目代码肯定要需要保存在一个目录中&#xff0c;但是如果目录不统一&#xff0c;每个人有一套自己的目录结构&#xff0c;读取配置文件的位置不统一&#xff0c;输出的二进制运行文件也不统一&#xff0c;这样会导致开发的标准不统一。 所以&#xff0c;产生环境变量…

QEMU安装Windows 11的完整过程

零、环境介绍 宿主机&#xff1a; Ubuntu 22.04.1 LTS Windows 11镜像&#xff1a; Win11_Chinese(Simplified)_x64v1 QEMU版本&#xff1a; qemu-img version 7.1.0 Copyright (c) 2003-2022 Fabrice Bellard and the QEMU Project developers 一、安装过程 1. 创建…

随机过程与排队论(二)

随机试验 如果一个试验E满足下列条件&#xff0c;就称此试验为随机试验&#xff1a; 在相同条件下可以重复进行。每次试验的结果不止一个&#xff0c;并且能事先明确知道试验的所有结果。一次试验结束之前&#xff0c;不能确定哪一个结果会出现。 样本空间、随机事件体 随机…

估值85亿美元!智驾前装赛道又添新“巨头”,已开始量产交付

随着智能汽车技术与供应链的发展&#xff0c;可以看到很多高端汽车也逐渐开始采用过去在L4上才使用的传感器&#xff0c;例如激光雷达。同时&#xff0c;多传感器融合技术也已进入规模化量产阶段&#xff0c;为L2在乘用车上的大规模应用打开了一个新窗口。 而作为L4领域的资深…

Leetcode力扣秋招刷题路-0124

从0开始的秋招刷题路&#xff0c;记录下所刷每道题的题解&#xff0c;帮助自己回顾总结 124. 二叉树中的最大路径和&#xff08;Hard&#xff09; 路径 被定义为一条从树中任意节点出发&#xff0c;沿父节点-子节点连接&#xff0c;达到任意节点的序列。同一个节点在一条路径序…

智能驾驶 车牌检测和识别(五)《C++实现车牌检测和识别(可实时车牌识别)》

智能驾驶 车牌检测和识别&#xff08;五&#xff09;《C实现车牌检测和识别&#xff08;可实时车牌识别&#xff09;》 目录 智能驾驶 车牌检测和识别&#xff08;五&#xff09;《C实现车牌检测和识别&#xff08;可实时车牌识别&#xff09;》 1. 前言 2. 车牌检测模型&a…

栈与队列——滑动窗口最大值

力扣题目链接 239. 滑动窗口最大值 给你一个整数数组 nums&#xff0c;有一个大小为 k 的滑动窗口从数组的最左侧移动到数组的最右侧。你只可以看到在滑动窗口内的 k 个数字。滑动窗口每次只向右移动一位。 返回 滑动窗口中的最大值 。 示例 1&#xff1a; 输入&#xff1a…