【数据结构】双向链表

🔥博客主页:小王又困了

📚系列专栏:数据结构

🌟人之为学,不日近则日退

❤️感谢大家点赞👍收藏⭐评论✍️


目录

一、双向链表

1.1带头双向循环链表的结构

二、链表的实现

📒2.1初始化

📒2.2尾插

📒2.3尾删

📒2.4头插

📒2.5头删

[📒 2.6在pos位置之前插入](#📒 2.6在pos位置之前插入)

[📒 2.7删除pos位置](#📒 2.7删除pos位置)


🗒️前言:

在上一期中我们介绍了单链表,也做了一些练习题,在一些题中使用单链表会十分繁琐。因为单链表只能正着走,不能倒着走,例如:回文、逆置。本期我们将学习带头双向循环链表。

一、双向链表

1.1带头双向循环链表的结构

特点:带头双向循环链表结构最复杂,一般用在单独存储数据。结构虽然结构复杂,但是使用代码实现以后会发现结构会带来多优势,实现反而简单了。

二、链表的实现

📒2.1初始化

cpp 复制代码
LTNode* BuyLTNode(LTDateType x)
{
	LTNode* newnode = (LTNode*)malloc(sizeof(LTNode));
	if (newnode == NULL)
	{
		perror("malloc");
		exit(-1);
	}
	newnode->date = x;
	newnode->next = NULL;
	newnode->prev = NULL;
	return newnode;
}

LTNode* LTInit()
{
	LTNode* phead = BuyLTNode(0);
	phead->next = phead;
	phead->prev = phead;
	return phead;
}

📒2.2尾插

带哨兵位的链表尾插时不用判断是否有节点,两种情况的插入相同,而且还不用传递二级指针。

cpp 复制代码
void LTPushBack(LTNode* phead, LTDateType x)
{
	assert(phead);
	
	LTNode* tail = phead->prev;
	LTNode* newnode = BuyLTNode(x);

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

📒2.3尾删

在尾删时我们通过 assert(phead->next != phead); 判断链表是否有节点。同时这个代码就有普遍性,不用单独考虑剩一个节点的情况。

cpp 复制代码
void LTPopBack(LTNode* phead)
{
	assert(phead);
	assert(phead->next != phead);
	LTNode* tail = phead->prev;
	LTNode* prevtail = tail->prev;

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

📒2.4头插

头删重要的是赋值的顺序,顺序错误会找不到后面的节点,导致内存泄漏。带哨兵位的链表不需要传递二级指针,因为改变的是结构体的变量。

cpp 复制代码
void LTPushFront(LTNode* phead, LTDateType x)
{
	assert(phead);
	LTNode* newnode = BuyLTNode(x);
	
	newnode->next = phead->next;
	phead->next->prev = newnode;
	phead->next = newnode;
	newnode->prev = phead;
}

📒2.5头删

我们可以多定义几个指针来保存后面节点的地址,这样就不会造成节点的丢失,不用考虑赋值的顺序,会更加方便。

cpp 复制代码
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;
}

📒 2.6在pos位置之前插入

cpp 复制代码
void LTInsert(LTNode* pos, LTDateType x)
{
	assert(pos);
	LTNode* newnode = BuyLTNode(x);
	LTNode* posprev = pos->prev;

	newnode->next = pos;
	pos->prev = newnode;
	newnode->prev = posprev;
	posprev->next = newnode;
}

我们可以通过复用LTInsert,实现链表的头插和尾插,使程序更简单。

  • 当pos传递的是phead->next,就可以实现头插
  • 当pos传递的是phead,就可以实现尾插
cpp 复制代码
//头插
void LTPushFront(LTNode* phead, LTDateType x)
{
	assert(phead);
	
	LTInsert(phead->next, x);
}

//尾插
void LTPushBack(LTNode* phead, LTDateType x)
{
	assert(phead);

	LTInsert(phead, x);
}

📒 2.7删除pos位置

cpp 复制代码
void LTErase(LTNode* pos)
{
	assert(pos);
	LTNode* posprev = pos->prev;
	LTNode* posnext = pos->next;
	free(pos);
	posprev->next = posnext;
	posnext->prev = posprev;
}

我们可以通过复用LTInsert,实现链表的头删和尾删,使程序更简单。

  • 当pos传递的是phead->next,就可以实现头删
  • 当pos传递的是phead->prev,就可以实现尾插删
cpp 复制代码
//头删
void LTPopFront(LTNode* phead)
{
	assert(phead);
	assert(phead->next != phead);

	LTErase(phead->next);
}

//尾删
void LTPopBack(LTNode* phead)
{
	assert(phead);
	assert(phead->next != phead);
	
	LTErase(phead->prev);
}

通过上面链表的实现,我们已经感受到了带头双向循环链表的方便和简单,它不需要去考虑链表是否有元素,还可以找到前一个元素,在我们使用中提供很大的便利。

本次的内容到这里就结束啦。希望大家阅读完可以有所收获,同时也感谢各位读者三连支持。文章有问题可以在评论区留言,博主一定认真认真修改,以后写出更好的文章。

相关推荐
iCxhust1 分钟前
51单片机电力载波应用开发(七)广播通讯
c语言·开发语言·单片机·嵌入式硬件·51单片机
薛定e的猫咪8 分钟前
(AISTATS 2023)BaCaDI 逐章阅读
人工智能·深度学习·算法·机器学习
Benny_Tang21 分钟前
AT_arc180_d [ARC180D] Division into 3 题解
数据结构·c++·算法
wangbing112525 分钟前
开发指南148-WebSocket-前端查报文
网络·websocket·网络协议
ESDWAN33 分钟前
企业跨境网络合规建设指南:从线路选择到数据出境的全流程方案
网络·架构
殷色玫瑰35 分钟前
C++ STL:stack、queue、priority_queue 与容器适配器详解
java·开发语言·数据结构·c++·算法·visualstudio
Hi2024021739 分钟前
同一个 NMS 算子,两种语言实现:CUDA C 与 Rust 生成 LLVM IR 和 Vortex 汇编的差异
c语言·汇编·rust·gpgpu·vortex·cudac
wuyk5551 小时前
《WiFi 嵌入式物联网开发全套实战》| 第 30 章 WiFi 抗干扰、抗闪断、网络抖动过滤量产策略
网络
不会就选b1 小时前
Linux之TCP<2>
linux·网络·tcp/ip
努力努力再努力wz1 小时前
【CUDA入门系列】CUDA编程实战:设备选择、显存管理、Kernel、Stream 与 Event 全流程梳理
开发语言·c++