【数据结构】励志大厂版·初级(二刷复习)双链表

前引:今天学习的双链表属于链表结构中最复杂的一种(带头双向循环链表),按照安排,我们会先进行复习,如何实现双链表,如基本的头插、头删、尾删、尾插,掌握每个细节,随后进行例题练习,帮助我们了解它的实际挑战,前面的实现只是了解它结构的入门,当然只有打好基础才是最重要的,小编会仔细讲解它的各个环节,正文开始~

目录

知识点速览

双链表的实现

节点结构

设置哨兵节点

开辟节点

尾插

尾删

头插

头删

在目标节点前面插入

在目标节点后面插入

练习题说明


知识点速览

双链表的实现

今天咱们来复习一下最复杂的链表结构------带头双向循环链表。根据字面意思我们大概可以详细想象出来它的特点,首先有一个哨兵节点来充当头节点,其次是循环双向的,逻辑结构如下图:

它较与单链表的结构里面多了一个指针,指向它的前一个节点,以此达到双向。针对哨兵节点:

哨兵节点一般不存储任何数据, 不仅使用方便,可以简化插入删除的操作(不用传二级指针),所有操作无需去处理哨兵节点,逻辑统一,最后可以减少空指针异常

这里的循环是根据节点的空间结构而来的:头节点->prev=尾节点,尾节点->next=头节点

节点结构

较单链表而言只多加了一个指针,用来指向自身的前一个节点。这里小编用 tpedef 重定义了一下数据类型,方便以后进行维护。

cpp 复制代码
typedef int Plastic;

typedef struct DoubleList
{
	//数据域
	Plastic data;
	//前指针域
	struct DoubleList* prev;
	//后指针域
	struct DoubleList* next;
}DoubleList;
设置哨兵节点

哨兵节点的数据域一般不存储数据,但是它之后的链表节点需要给一个数据作为参数开辟节点,所以小编在这里将二者分开,给哨兵节点单独设置一个函数来开辟空间。开始时头指针指向哨兵节点,哨兵节点前后指针应该指向自己,已达到双向循环结构

cpp 复制代码
//设置哨兵节点
DoubleList* pphead = Sentry();
cpp 复制代码
//设置哨兵节点
DoubleList* Sentry()
{
	//开辟节点
	DoubleList* newnode = (DoubleList*)malloc(sizeof(DoubleList));
	//判断空间有效性
	if (newnode == NULL)
	{
		printf("哨兵节点开辟失败\n");
		return NULL;
	}

	//初始化
	newnode->next = newnode;
	newnode->prev = newnode;

	return newnode;
}
开辟节点

开辟节点还是和单链表一样,传一个数据给它就行了

这里需要注意:初始化开辟的节点时应该是前后指向自己的,如下图:

cpp 复制代码
//新增节点
DoubleList* Newnode(Plastic data)
{
	//开辟节点
	DoubleList* newnode = (DoubleList*)malloc(sizeof(DoubleList));
	//判断空间有效性
	if (newnode == NULL)
	{
		printf("节点开辟失败\n");
		return NULL;
	}

	//初始化
	newnode->next = newnode;
	newnode->prev = newnode;
	newnode->data = data;

	return newnode;
}
尾插

尾插需要先找尾,对于双向循环链表而言,头节点的前一个节点就是它的尾。然后再插入新增的节点,连接 next 与 prev 的关系即可

注意:尾插不用判断链表是否存在啊,因为我们这里有哨兵节点,直接找尾、插入即可

cpp 复制代码
//尾插
void Tail_insert(DoubleList* pphead, Plastic data)
{
	//找尾
	DoubleList* tail = pphead->prev;
	//开辟节点
	DoubleList* newnode = Newnode(data);

	//连接
	pphead->prev = newnode;
	newnode->next = pphead;

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

下面我们通过打印函数来看一下尾插的效果如何:

cpp 复制代码
//打印
void List_Print(DoubleList* pphead)
{
	//如果只有哨兵节点
	if (pphead->next == pphead)
	{
		printf("无元素可以打印\n");
		return;
	}
	//因为如果只有哨兵节点,pphead->next就越界了
	DoubleList* first = pphead->next;

	while (first != pphead)
	{
		printf("%d -> ", first->data);
		first = first->next;
	}
	printf("pphead\n");
}
尾删

尾删需要先找尾,然后将头节点与尾的前一个节点进行连接,再释放之前标记的尾,思维上并不难

cpp 复制代码
//尾删
void Tail_deletion(DoubleList* pphead)
{
	//如果只有头节点无法删除
	if (pphead->prev == pphead)
	{
		printf("无法删除\n");
		return;
	}

	//找尾
	DoubleList* tail = pphead->prev;
	//找倒数第二个节点
	DoubleList* cur = pphead->prev->prev;

	//更新关系,重新连接
	pphead->prev = cur;
	cur->next = pphead;

	free(tail);
	tail = NULL;
}
头插

先标记头节点的下一个节点,然后在头节点与这个标记的节点中间插入即可,最后更新连接关系

cpp 复制代码
//头插
void Head_insert(DoubleList* pphead, int data)
{
	//标记头节点的下一个节点
	DoubleList* first = pphead->next;
	DoubleList* newnode = Newnode(5);

	//更新连接关系
	pphead->next = newnode;
	newnode->next = first;

	first->prev = newnode;
	newnode->prev = pphead;
}
头删

对于只有哨兵节点的双链表是无法头删的,因此需要先进行判断。其次是标记链表的第一个节点、第二个节点,重新确立头节点和第二个节点的关系,再释放掉第一个节点

cpp 复制代码
//头删
void Head_deletion(DoubleList* pphead)
{
	//判断是否只有哨兵节点
	if (pphead->prev == pphead)
	{
		printf("无法删除\n");
		return;
	}

	//标记第一个节点
	DoubleList* first = pphead->next;
	//标记第二个节点
	DoubleList* second = first->next;

	//重新确立头节点和第二个节点的关系
	pphead->next = second;
	second->prev = first;

	//释放第一个节点
	free(first);
	first = NULL;
}
在目标节点前面插入

我们先找到目标节点,然后再标记目标节点前面的一个节点,再确立三者之间的 next 与 prev 的关系,如下图:

cpp 复制代码
//在目标节点前面插入
void Before_target(DoubleList* pphead, int  data)
{
	//找目标节点
	DoubleList* cur = pphead->next;
	while (cur->data != data)
	{
		if (cur == pphead)
		{
			printf("没有找到\n");
			return;
		}
		cur = cur->next;
	}
	//标记cur前面的节点
	DoubleList* prev = cur->prev;
	DoubleList* newnode = Newnode(6);

	//将三者进行连接
	prev->next = newnode;
	newnode->prev = prev;

	newnode->next = cur;
	cur->prev = newnode;
}
在目标节点后面插入

先找到目标节点,然后标记目标节点后面的一个节点,再确立新增节点、目标节点、标记节点的 next prev 的关系,与"在目标节点前面插入"很类似

cpp 复制代码
//在目标节点后面插入
void Behind_target(DoubleList* pphead, int data)
{
	//找目标节点
	DoubleList* cur = pphead->next;
	while (cur->data != data)
	{
		if (cur == pphead)
		{
			printf("没有找到\n");
			return;
		}
		cur = cur->next;
	}
	//标记cur后面的节点
	DoubleList* next = cur->next;
	DoubleList* newnode = Newnode(7);

	//将三者进行连接
	cur->next = newnode;
	newnode->prev = cur;

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

练习题说明

一般链表题,比如考研、面试、校招会以单链表居多,大家可以看我的上一篇文章来学习链表题目

相关推荐
CoderCodingNo29 分钟前
【GESP】C++五级考试大纲知识点梳理, (5) 算法复杂度估算(多项式、对数)
开发语言·c++·算法
学编程就要猛33 分钟前
数据结构初阶:时间和空间复杂度
数据结构
maxruan1 小时前
PyTorch学习
人工智能·pytorch·python·学习
故事与他6451 小时前
XSS_and_Mysql_file靶场攻略
前端·学习方法·xss
MYX_3091 小时前
第三章 线型神经网络
深度学习·神经网络·学习·算法
_李小白1 小时前
【Android Gradle学习笔记】第八天:NDK的使用
android·笔记·学习
摇滚侠2 小时前
Spring Boot 3零基础教程,WEB 开发 自定义静态资源目录 笔记31
spring boot·笔记·后端·spring
摇滚侠2 小时前
Spring Boot 3零基础教程,WEB 开发 Thymeleaf 遍历 笔记40
spring boot·笔记·thymeleaf
Code小翊2 小时前
C语言bsearch的使用
java·c语言·前端
坚持编程的菜鸟2 小时前
LeetCode每日一题——三角形的最大周长
算法·leetcode·职场和发展