数据结构初阶(c语言)-双向链表

这里首先纠正上篇文章一个错误,链表的一个有效数据点应该称为结点而不是节点。

一,双向链表的概念与结构

1.1概念与结构示意图

我们所说的双向链表全称为带头双向循环链表,也就是说此链表带有哨兵位结点(不存放任何数据的结点,且为头结点)。图示结构如下:

注意:这里的"带头"跟前面我们说的"头结点"是两个概念,实际前面的在单链表阶段称呼不严谨。

1.2双向链表结构代码

cpp 复制代码
typedef int LTNDataType;
typedef struct ListNode
{
	LTNDataType val;
	struct ListNode* prev;
	struct ListNode* next;
}LTNode;

prev为指向前一个结点的指针,next为指向后一个结点的指针,val为该结点中存储的数据。

二,实现双向链表的功能

2.1创建链表节点函数LTBuyNode

由于我们的双向链表在没有存放任何数据时,还有一个哨兵位结点,所以我们需要先实现链表结点创建函数:

cpp 复制代码
LTNode* LTBuyNode(LTNDataType x)
{
	LTNode* newnode = (LTNode*)malloc(sizeof(LTNode));
	if (newnode == NULL)
	{
		perror("buynode:");
		exit(1);
	}
	newnode->val = x;
	newnode->next = newnode->prev = newnode;
	return newnode;
}

我们的双向链表为循环链表,所以在只有一个结点时,我们创建的新结点需要自己的两个前后指针均指向自己,以便实现我们的双向链表初始化。

2.2双向链表的初始化函数LTInit

由于我们的哨兵位结点不存放有效数据,所以我们需要给初始结点直接返回一个存放数据为-1(无效数据)的结点,作为双向链表的初始结点:

cpp 复制代码
LTNode* LTInit()
{
	LTNode* pcur = LTBuyNode(-1);
	return pcur;
}

2.3双向链表的尾插函数LTPushBack

cpp 复制代码
void LTPushBack(LTNode* phead, LTNDataType x)
{
	assert(phead);
	LTNode* newnode = LTBuyNode(x);
	phead->prev->next = newnode;
	newnode->prev = phead->prev;
	newnode->next = phead;
	phead->prev = newnode;
}

先创建一个新结点并用临时变量进行接收,下一步则是使尾节点的next指向我们的新结点,同时将新结点的prev指向先前的尾结点,接下来由于我们创建的新结点为当前的尾结点,所以我们需要让其next指针指向哨兵位结点,最后再使哨兵位结点的prev指向我们的新结点即完成我们的尾插函数。

2.4双向链表的头插函数LTPushStart

这里需要注意,如果我们是直接将数据插到哨兵位前面,我们的方法实际上此时与尾插法无异,所以要实现头插,我们则需要将新结点直接插入到哨兵位结点的后面,从而实现头插:

cpp 复制代码
void LTPushStart(LTNode* phead, LTNDataType x)
{
	assert(phead);
	LTNode* newnode = LTBuyNode(x);
	newnode->prev = phead;
	newnode->next = phead->next;
	phead->next = newnode;
	newnode->next->prev = newnode;
}

为了插入数据时的方便,我们先改变新插入结点的next与prev,这样不会对当前链表的结构产生影响,接下来我们便改变哨兵位结点的next与原哨兵位结点的next结点的prev,使其均指向我们创建的新结点,实现我们的头插法。

2.5判空函数LTEmpty

当我们的链表只剩下哨兵位结点时,我们判定此时链表不存放任何有效数据,又由于我们此时只有一个结点,所以我们直接判定当前哨兵位结点的prev是否指向它自己即可:

cpp 复制代码
bool LTEmpty(LTNode* phead)
{
	assert(phead);
	return (phead->next == phead);
}

2.6尾删函数LTDeltBack

在进行尾删之前,我们也需要对链表进行判空,然后我们需要先用临时变量去保存尾结点的地址,同时改变尾结点的前结点的next,使其指向哨兵位,然后改变哨兵位结点的prev使其指向我们当前尾结点的前一个结点,最后释放尾结点即可:

cpp 复制代码
void LTDeltBack(LTNode* phead)
{
	assert(phead && !LTEmpty(phead));
	LTNode* pcur = phead->prev;
	phead->prev->prev->next = phead;
	phead->prev = phead->prev->prev;
	free(pcur);
	pcur = NULL;
}

2.7头删函数LTDeltStart

与尾删函数类似,也需要创建临时变量去记住要释放结点的位置,然后接下来步骤的思想与尾删基本相同:

cpp 复制代码
void LTDeltStart(LTNode* phead)
{
	assert(phead && !LTEmpty(phead));
	LTNode* pcur = phead->next;
	phead->next = phead->next->next;
	phead->next->next->prev = phead;
	free(pcur);
	pcur = NULL;
}

2.8寻找目标位置函数LTFind

与单链表的寻找方法基本一致,不过要注意终止条件应该是遍历到哨兵位结点时停止:

cpp 复制代码
LTNode* LTFind(LTNode* phead,LTNDataType x)
{
	assert(phead && !LTEmpty(phead));
	LTNode* pcur = phead->next;
	while (pcur != phead)
	{
		if (pcur->val == x)
		{
			return pcur;
		}
		pcur = pcur->next;
	}
	printf("没有找到\n");
	return NULL;
}

2.9删除指定位置和向指定位置之后插入数据函数LTDeltDesBack与LTInserDesBack

与单链表一样,但是这里向指定位置之前或之后插入数据方法一致,只是你实现之后插入后,如果想实现之前,只需要去用prev寻找上一结点即可:

LTDeltDesBack:

cpp 复制代码
void LTDeltDesBack(LTNode* pos,LTNode* phead)
{
	assert(pos && (pos->next != phead));
	LTNode* pcur = pos->next;
	pos->next->next->prev = pos;
	pos->next = pos->next->next;
	free(pcur);
	pcur = NULL;
}

LTInserDesBack:

cpp 复制代码
void LTInserDesBack(LTNode* pos,LTNDataType x)
{
	assert(pos);
	LTNode* newnode = LTBuyNode(x);
	newnode->prev = pos;
	newnode->next = pos->next;
	newnode->next->prev = newnode;
	pos->next = newnode;
}

2.10销毁链表函数DestoLTN

cpp 复制代码
void DestoLTN(LTNode* phead)
{
	assert(phead);
	LTNode* pcur = phead->next;
	LTNode* pcur1 = NULL;
	while (pcur != phead)
	{
		pcur1 = pcur->next;
		free(pcur);
		pcur = pcur1;
	}
	free(phead);
}

三,顺序表与链表的对比

当我们学习完顺序表与链表之后,下篇文章我们将介绍栈与队列的实现,在理解顺序表与链表功能的实现后,栈和队列的实现非常简单,仅仅只会有概念上不同的问题,我们下篇文章见。

相关推荐
汤米粥几秒前
小皮PHP连接数据库提示could not find driver
开发语言·php
冰淇淋烤布蕾3 分钟前
EasyExcel使用
java·开发语言·excel
行然梦实7 分钟前
学习日记_20241110_聚类方法(K-Means)
学习·kmeans·聚类
拾荒的小海螺10 分钟前
JAVA:探索 EasyExcel 的技术指南
java·开发语言
马船长12 分钟前
制作图片木马
学习
秀儿还能再秀24 分钟前
机器学习——简单线性回归、逻辑回归
笔记·python·学习·机器学习
WCF向光而行29 分钟前
Getting accurate time estimates from your tea(从您的团队获得准确的时间估计)
笔记·学习
马剑威(威哥爱编程)34 分钟前
哇喔!20种单例模式的实现与变异总结
java·开发语言·单例模式
娃娃丢没有坏心思36 分钟前
C++20 概念与约束(2)—— 初识概念与约束
c语言·c++·现代c++
白-胖-子1 小时前
【蓝桥等考C++真题】蓝桥杯等级考试C++组第13级L13真题原题(含答案)-统计数字
开发语言·c++·算法·蓝桥杯·等考·13级