首页 > 其他分享 >【数据结构】—— 双向链表

【数据结构】—— 双向链表

时间:2024-07-10 18:26:18浏览次数:10  
标签:LNode next 链表 phead 双向 newnode 数据结构 节点

文章目录

1、双向链表的概念

双向链表(Doubly Linked List)是一种常见的链表数据结构,它与普通的单向链表不同之处在于,每个节点除了存储数据元素外,还存储着指向前一个节点和后一个节点的指针(或引用)

与单链表的区别:双向链表中可以通过节点本身直接访问其前驱节点和后继节点,而不需要像单向链表那样只能从头节点开始顺序访问。

2、双向链表的接口实现

在这里插入图片描述

2.1结构

  • 双向链表的一个节点由三部分构成
    1)当前节点的数据——date
    2)前驱指针——prev
    3)后驱指针——next
//定义双向链表中节点的结构
typedef int LDateType;
typedef struct ListNode {
	LDateType date;
	struct ListNode* prev;
	struct ListNode* next;
}LNode;

带头双向链表:

  • 双向链表带有哨兵位,插入数据之前必须要初始化哨兵位
  • 哨兵节点(头节点):本身不存储有效数据,它的存在只是为了简化边界条件的处理。

这里我用双向带头循环链表来实现

2.2初始化

申请节点

对申请节点功能进行封装,方便调用。

//申请节点
LNode* LBuyNode(LDateType x) {
	LNode* newnode = (LNode*)malloc(sizeof(LNode));
	if (newnode == NULL)
	{
		perror("malloc fail");
		exit(1);
	}
	newnode->date = x;
	newnode->next = newnode->prev = newnode;
	return newnode;
}

直接调用申请节点函数创建哨兵位

LNode* LInit()
{
	//创建一个哨兵位
	LNode* phead = LBuyNode(-1);
	return phead;
}

2.3插入数据

尾插

当双向链表进行尾插的时候,无需像单链表那样循环找尾节点,双向链表中的尾节点与哨兵位相连,可以直接获取:

ptail = phead->prev

  • 思路:在修改指针连接时,优先对newnode的前驱和后驱进行修改,避免影响链表原来的指向,随后对ptail的后驱和phead的前驱进行修改
void LPushBack(LNode* phead, LDateType x)
{
	assert(phead);
	LNode* newnode = LBuyNode(x);//申请节点
	//phead phead->prev(ptail) newnode 修改指针连接
	newnode->next = phead;
	newnode->prev = phead->prev;

	(phead->prev)->next = newnode;
	phead->prev = newnode;
}
头插

在这里插入图片描述

与尾插相似,修改对应指针连接即可

void LPushFront(LNode* phead, LDateType x)
{
	assert(phead);
	LNode* newnode = LBuyNode(x);
	// phead newnode phead->next 修改这三个节点的连接
	newnode->next = phead->next;
	newnode->prev = phead;

	phead->next->prev = newnode;
	phead->next = newnode;
}
指定位置之后插入数据

对插入数据的位置前驱后驱节点修改指针连接即可
pos-> newnode-> pos->next

void LInsert(LNode* pos, LDateType x) {
	assert(pos);
	LNode* newnode = LBuyNode(x);
	//pos newnode pos->next
	newnode->next = pos->next;
	newnode->prev = pos;

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

2.4删除数据

  • 思路:修改指针连接,free函数释放被删除节点即可
尾删
void LPopBack(LNode* phead) {
	assert(phead);
	//若哨兵位节点的next指针或prev指针指向的是自己,则链表为空
	assert(phead->next != phead);
	LNode* del = phead->prev;
	LNode* prev = del->prev;
	//实现结果一致,无需分情况讨论,
	prev->next = phead;
	phead->prev = prev;
	free(del);
	del = NULL;
}
头删
void LPopFront(LNode* phead) {
	assert(phead);
	assert(phead->next != phead);

	LNode* del = phead->next;
	LNode* next = del->next;
	//改变指针指向,删除指定节点即可
	next->prev = phead;
	phead->next = next;
	free(del);
	del = NULL;
}
指定位置删除
void LErase(LNode* pos) {
	assert(pos);

	//pos->prev pos pos->next
	pos->next->prev = pos->prev;
	pos->prev->next = pos->next;

	free(pos);
	pos = NULL;
}

2.5查找

  • 功能 :查找指定节点并返回节点
  • 实现思路:创建节点指针pcur指向链表中第一个节点(phead->next),
    遍历链表,查找指定节点,如果找到直接返回节点,停止条件为当pcur指向哨兵位,当跳出循环则链表中不存在指定节点,返回NULL
LNode* LFind(LNode* phead, LDateType x) {
	assert(phead);
	LNode* pcur = phead->next;
	while (pcur != phead)
	{
		if (pcur->date == x)
		{
			return pcur;
		}
		pcur = pcur->next;
	}
	return NULL;
}

2.6打印

  • 思路:创建节点指针pcur指向链表中第一个节点(phead->next),
    遍历链表,停止条件为当pcur指向哨兵位,当pcur指向哨兵位则遍历完成
void LPrint(LNode* phead) {
	assert(phead);
	LNode* pcur = phead->next;
	while (pcur != phead)
	{
		printf("%d->", pcur->date);
		pcur = pcur->next;
	}
	printf("\n");
}

2.7销毁

  • 思路:通过遍历删除每个有效节点,最后删除哨兵位
void LDestroy(LNode** pphead) {
	assert(pphead);
	//哨兵位不能为空
	assert(*pphead);

	LNode* pcur = (*pphead)->next;
	while (pcur != *pphead)
	{
		LNode* next = pcur->next;
		free(pcur);
		pcur = next;
	}
	//最后链表只剩哨兵位
	//销毁哨兵位
	free(*pphead);
	*pphead = NULL;
}

3、链表和顺序表的区别

在这里插入图片描述

4、问题与思考

Q:二级指针和一级指针应该什么时候用,如何选择?
在单链表和双向链表中,二级指针和一级指针的选择在于是否需要修改(删除)头节点/头指针(在双向链表中叫哨兵位)
在这里插入图片描述
在实际应用中:

  • 一级指针:直接操作变量和简单数据结构
  • 二级指针:用于处理复杂的数据结构(多级链表等)、动态内存分配以及需要改变指针本身(值)指向的情况

标签:LNode,next,链表,phead,双向,newnode,数据结构,节点
From: https://blog.csdn.net/xasdf338/article/details/140317478

相关文章

  • 浙大数据结构慕课课后习题(02-线性结构2 一元多项式的乘法与加法运算)
    题目要求设计函数分别求两个一元多项式的乘积与和。输入格式:输入分2行,每行分别先给出多项式非零项的个数,再以指数递降方式输入一个多项式非零项系数和指数(绝对值均为不超过1000的整数)。数字间以空格分隔。输出格式:输出分2行,分别以指数递降方式输出乘积多项式以及和多项......
  • [二、状态管理]2管理组件拥有的状态(4)@Provide装饰器和@Consume装饰器:与后代组件双向同
    @Provide和@Consume,应用于与后代组件的双向数据同步,应用于状态数据在多个层级之间传递的场景。不同于上文提到的父子组件之间通过命名参数机制传递,@Provide和@Consume摆脱参数传递机制的束缚,实现跨层级传递。其中@Provide装饰的变量是在祖先节点中,可以理解为被“提供”给后代的状......
  • Day3| 203.移除链表元素 & 707.设计链表 & 206.反转链表
    前两天发烧了,这几天没更的后续会补齐链表结构如下classListNode{intval;ListNodenext;ListNode(){}ListNode(intval){this.val=val;}ListNode(intval,ListNodenext){this.val=val;this.next......
  • 数据结构--第八章排序
    注:内容参考王道2024考研复习指导以及《数据结构》一、排序的基本概念排序(sort),就是重新排列表中的元素,使表中的元素满足按关键字有序的过程。排序算法的评价指标时间复杂度空间复杂度稳定性算法的稳定性,若待排序表中有两个元素Ri​和Rj​,其对应的关键字相同即keyi​=keyj......
  • 太原理工数据结构实验报告(计科)
    实验一线性表一、实验目的和要求本次实验的主要目的是为了使学生熟练掌握线性表的基本操作在顺序存储结构和链式存储结构上的实现,提高分析和解决问题的能力。要求仔细阅读并理解下列例题,上机通过,并观察其结果,然后独立完成后面的实验题。(1学时)二、实验内容和原理1.建立如......
  • 141. 环形链表
    给你一个链表的头节点 head ,判断链表中是否有环。如果链表中有某个节点,可以通过连续跟踪 next 指针再次到达,则链表中存在环。为了表示给定链表中的环,评测系统内部使用整数 pos 来表示链表尾连接到链表中的位置(索引从0开始)。注意:pos 不作为参数进行传递 。仅仅是为......
  • 数据结构--单向链表篇(python实现)
    写在开头链表(Linkedlist)是一种常见的基础数据结构,是一种线性表,但是并不会按线性的顺序存储数据,而是在每一个节点里存到下一个节点的指针(Pointer)链表的优缺点优点不需要预先知道数据大小,实现灵活的内存动态管理插入、删除指定数据速度快缺点读取指定位置数据速......
  • 138. 随机链表的复制
    138.随机链表的复制递归和哈希表时间&空间复杂度O(n)复杂链表的特点是每个节点除了有一个指向下一个节点的指针外,还有一个随机指针可能指向链表中的任意节点或null。通过递归和哈希表的方式,能够确保每个节点只被复制一次,并且正确地复制了next和random指针。/*//Definitionf......
  • 数据结构——并查集 学习笔记
    数据结构——并查集学习笔记并查集是一种用于管理元素所属集合的数据结构,实现为一个森林。并查集中,每棵树表示一个集合,树中的节点表示对应集合中的元素。其思想是,把集合属性绑定到根节点上,避免多余的处理,因此一般难以分离。普通并查集并查集支持两种操作:合并(Union):合并两个元素......
  • 第五篇、Python列表:多功能的数据结构
    在Python编程中,列表是一种极其重要且灵活的数据结构。本文将深入探讨Python中的列表,包括列表的定义、遍历方法和常见操作。一、列表的定义列表是Python中最常用的数据类型之一,它是一个可变的、有序的元素集合。列表的特点包括:可以存储不同类型的数据元素之间用逗号分隔使用......