【探索数据结构】线性表之双链表

🎉🎉🎉欢迎莅临我的博客空间,我是池央,一个对C++和数据结构怀有无限热忱的探索者。🙌

🌸🌸🌸这里是我分享C/C++编程、数据结构应用的乐园✨

🎈🎈🎈期待与你一同在编程的海洋中遨游,探索未知的技术奥秘💞

📝专栏指路:

📘【C++】专栏:深入解析C++的奥秘,分享编程技巧与实践。

📘【数据结构】专栏:探索数据结构的魅力,助你提升编程能力。

前言

之前我们已经探索了顺序表单链表我们继续一起来探索逻辑结构里面的线性结构。线性表在逻辑结构上是连续的,线性表中双链表(本篇主角)在物理结构上是不连续的。

文章重点介绍:带头双向循环链表

一、双链表

1.概念

双链表,也叫双向链表,是链表的一种特殊形式。在双链表中,每个数据节点都有两个指针 ,一个指向前一个节点 (前驱节点),另一个指向后一个节点(后继节点)。这种结构使得从双链表中的任意一个节点开始,都可以很方便地访问它的前驱节点和后继节点。

2.分类

(1)按不同属性分

  • 带头节点的双链表:这种双链表在第一个数据节点之前有一个头结点。头结点是为了操作的统一和方便而设立的,放在第一元素的结点之前,其数据域一般无意义(也可存放链表的长度)。有了头结点,对在第一元素结点前插入结点和删除第一结点,其操作与其它结点的操作就统一了。
  • 不带头节点的双链表:与带头结点的双链表相反,这种链表没有头结点,直接从第一个数据节点开始。

(2)按循环性分

  • 双向循环链表 :在双向链表的基础上,将头结点的后驱指针指向尾节点,尾节点的前驱指针指向头结点,从而形成一个双向环
  • **双向非循环链表:**这是标准的双向链表,没有形成一个环,只是简单地通过前驱和后继指针连接各个节点。

3.链表结构

cpp 复制代码
typedef int LTDataType;
typedef struct LTNode LTNode;
struct LTNode
{
	LTDataType data;//数据
	LTNode* prev;//前驱指针
	LTNode* next;//后继指针
};

二、对双链表的操作

0.创建节点

cpp 复制代码
//创建节点
LTNode* LTBuyNode(LTDataType x)
{
	LTNode* node = (LTNode*)malloc(sizeof(LTNode));
	if (node == NULL)
	{
		perror("malloc fail!");
		exit(1);
	}
	node->data = x;
	node->next = node->prev = node;//不能置为空,都要指向自己
	return node;
}

1.初始化

后续对链表的操作都是不需要改变头结点的,哨兵位节点不能被删除,节点的地址,也不能发生改变只需传一级指针。为了保持接口的一致性。我们没有在初始化方法中选择传二级指针的方式实现

cpp 复制代码
// 初始化
LTNode* LTInit()
{
	LTNode* phead = LTBuyNode(-1);
	return phead;
}

2.打印

cpp 复制代码
// 打印
void LTPrint(LTNode* phead)
{
	//从第一个有效节点遍历
	LTNode* pcur = phead->next;
	while (pcur != phead)
	{
		printf("%d->", pcur->data);
		pcur = pcur->next;
	}
	printf("\n");
}

3.尾插

只有头结点时的尾插:

cpp 复制代码
// 尾插
void LTPushBack(LTNode* phead, LTDataType x)
{
	assert(phead);
	//phead phead->prev phead->next
	LTNode* newnode = LTBuyNode(x);
	//先改变新节点的指针指向不影响原链表
	newnode->prev = phead->prev;
	newnode->next = phead;
	//不可以调换下面两句顺序,否则会找不到原链表的尾结点
	phead->prev->next = newnode;
	phead->prev = newnode;
}

4.头插

cpp 复制代码
// 头插
void LTPushFront(LTNode* phead, LTDataType x)
{
	assert(phead);
	//phead phead->next 
	LTNode* newnode = LTBuyNode(x);
	newnode->next = phead->next;
	newnode->prev = phead;
	//尽量不要调换下面两句顺序
	phead->next->prev = newnode;
	phead->next = newnode;
}

5.尾删

cpp 复制代码
// 尾删
void LTDelBack(LTNode* phead)
{
	assert(phead&&phead->next);//链表不能为空
	//phead  phead->prev
	//把要删除的节点先存起来以防找不到他的前一个节点
	LTNode* del = phead->prev;
	del->prev->next = phead;
	phead->prev = del->prev;
	free(del);
	del = NULL;
}

6.头删

cpp 复制代码
// 头删
void LTDelFront(LTNode* phead)
{
	assert(phead && phead->next);//链表不能为空
	//把要删除的节点先存起来以防找不到他的后一个节点
	LTNode* del = phead->next;
	del->next->prev = phead;
	phead->next = del->next;
	free(del);
	del = NULL;
}

7.查找

cpp 复制代码
LTNode* LTFind(LTNode* phead, LTDataType x)
{
	assert(phead);
	//从第一个有效节点遍历
	LTNode* pcur = phead->next;
	while (pcur != phead)
	{
		if (pcur->data == x)
		{
			return pcur;
		}
		pcur = pcur->next;
	}
	return NULL;
}

8.在指定位置之后插入数据

cpp 复制代码
// 指定位置之后插入
void LTPushPos(LTNode* pos, LTDataType x)
{
    assert(pos);
	//pos->next pos 
	LTNode* newnode = LTBuyNode(x);
	//先改变新节点的指针指向不影响原链表
	newnode->prev = pos;
	newnode->next = pos->next;

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

9.删除指定节点

pos理论上来说不能为phead,但是没有参数phead,无法增加校验

cpp 复制代码
// 删除指定节点
void LTErase(LTNode* pos)
{
    assert(pos);
	//pos->next pos->prev
	pos->prev->next = pos->next;
	pos->next->prev = pos->prev;
	free(pos);
	pos = NULL;
}

10.销毁链表

cpp 复制代码
// 销毁
void LTDestroy(LTNode* phead)
{
	assert(phead);
	//从第一个有效节点遍历
	LTNode* pcur = phead->next;
	while (pcur != phead)
	{
		//先把要删除节点的下一个节点存起来
		//不然要删除后续节点无法被找到
		LTNode* next = pcur->next;
		free(pcur);
		pcur = next;
	}
	//把哨兵位置为空
	free(phead);
	phead = NULL;
}

三、测试(仅供参考)

cpp 复制代码
#include"List.h"
void test01()
{
	LTNode* plist = LTInit();
	//尾插
	LTPushBack(plist, 3);
	LTPushBack(plist, 2);
	LTPushBack(plist, 1);
	LTPrint(plist);
	//头插
	LTPushFront(plist, 4);
	LTPushFront(plist, 5);
	LTPushFront(plist, 6);
	LTPrint(plist);
	//尾删
	LTDelBack(plist);
	LTPrint(plist);
	//头删
	LTDelFront(plist);
	LTPrint(plist);
	LTNode* find = LTFind(plist, 5);
	if (find == NULL)
	{
		printf("没有找到\n");
	}
	else
	{
		printf("找到了\n");
	}
	/*LTErase(find);
	LTPrint(plist);*/
	LTPushPos(find, 10);
	LTPrint(plist);
	LTDestroy(plist);
	//为保持接口一致性没有传二级指针
	//需要手动把实参置为空
	plist = NULL;
}
int main()
{
	test01();
	return 0;
}

下回预告:栈

持续更新中...

敬请期待

相关推荐
码智社6 小时前
AES加密原理详解及Java实现加解密实战
java·开发语言
AI云海6 小时前
python 列表、元组、集合和字典
开发语言·python
萧瑟余晖7 小时前
JDK 26 新特性详解
java·开发语言
马优晨8 小时前
Freemarker 完整讲解(后端 Java 模板引擎)
java·开发语言·freemarker·freemarker 完整讲解·freemarker模板引擎
a1117769 小时前
2FA 验证码生成器(github登录验证 app)
笔记·学习
我的xiaodoujiao9 小时前
API 接口自动化测试详细图文教程学习系列32--Allure测试报告2
python·学习·测试工具·pytest
人邮异步社区9 小时前
怎么把C语言学到精通?
c语言·开发语言
心平气和量大福大10 小时前
C#-WPF-控件-TextBox 数据绑定
开发语言·c#·wpf
ttwuai10 小时前
Cursor 生成 CRUD 后,Go 后台接口别只测 200:JWT、RBAC 和 tenant_id 怎么验
开发语言·后端·golang
এ慕ོ冬℘゜10 小时前
前端基础:什么是时间戳?JS获取时间戳三种方法与实战用途
开发语言·前端·javascript