数据结构之双向链表

双向链表

  • 一、双向链表是什么?
  • 二、双向链表的特点
  • 三、双向链表的操作
    • (1)初始化双向链表
    • (2)插入操作
      • ① 在链表头部插入节点
      • ② 在链表尾部插入节点
      • 指定位置插入
    • (3)删除操作
      • ① 在链表头部删除节点
      • ② 在链表尾部删除节点
      • 指定位置删除
    • (4)销毁链表
    • (5)链表的打印
  • 四、总结

在数据结构的世界中,链表是一种非常基础且重要的结构。今天,我们来深入探讨一种特殊的链表------双向链表。它不仅继承了单链表的灵活性,还通过增加反向链接,极大地提升了操作效率。本文将详细介绍双向链表的结构、操作、优势以及应用场景,帮助你更好地理解和使用这种强大的数据结构。

一、双向链表是什么?

双向链表是一种线性数据结构,它由一系列节点组成,每个节点包含三个部分:

  1. 数据域:存储实际数据。
  2. 指向前一个节点的指针(prev):指向当前节点的前驱节点。
  3. 指向后一个节点的指针(next):指向当前节点的后继节点。

与单链表相比,双向链表的每个节点都可以方便地访问其前驱和后继节点,这使得它在某些操作上更加高效。

c 复制代码
typedef struct Node {
    int data; // 数据域
    struct Node* prev; // 指向前一个节点的指针
    struct Node* next; // 指向后一个节点的指针
} Node;

二、双向链表的特点

(1)灵活性高

双向链表可以方便地向前或向后遍历。这种双向遍历的特性使得它在某些场景下比单链表更加灵活。

(2)插入和删除操作高效

在已知节点位置的情况下,插入和删除操作的时间复杂度为 O(1)。这是因为可以直接通过前驱和后继指针调整链接关系,而不需要像单链表那样从头开始遍历。

(3)空间开销较大

每个节点需要额外存储两个指针,相比单链表,空间开销更大。因此,在内存资源有限的情况下,需要权衡使用。

三、双向链表的操作

(1)初始化双向链表

创建一个双向链表时,通常会引入一个头节点(哨兵节点),它不存储实际数据,但可以简化插入和删除操作。

c 复制代码
ListNode* ListInit()
{
	ListNode* phead = BuyListNode(0);
	phead->next = phead;
	phead->prev = phead;
	return phead;
}
ListNode* BuyListNode(LTDataType x)
{
	ListNode* newnode = (ListNode*)malloc(sizeof(ListNode));
	newnode->data = x;
	newnode->next = NULL;
	newnode->prev = NULL;

	return newnode;
}

(2)插入操作

① 在链表头部插入节点

c 复制代码
void ListPushFront(ListNode* phead, LTDataType x)
{
	assert(phead);

	ListNode* newnode = BuyListNode(x);
	newnode->next = phead->next;
	phead->next->prev = newnode;

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

② 在链表尾部插入节点

c 复制代码
void ListPushBack(ListNode* phead, LTDataType x)
{
	assert(phead);

	ListNode* tail = phead->prev;
	ListNode* newnode = BuyListNode(x);
	tail->next = newnode;
	newnode->prev = tail;
	newnode->next = phead;
	phead->prev = newnode;
}

指定位置插入

c 复制代码
// pos位置之前插入x
void ListInsert(ListNode* pos, LTDataType x)
{
	assert(pos);
	ListNode* prev = pos->prev;
	ListNode* newnode = BuyListNode(x);

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

(3)删除操作

① 在链表头部删除节点

c 复制代码
void ListPopFront(ListNode* phead)
{
	assert(phead);
	assert(phead->next != phead);

	ListNode* first = phead->next;
	ListNode* second = first->next;
	phead->next = second;
	second->prev = phead;

	free(first);
	first = NULL;
}

② 在链表尾部删除节点

c 复制代码
void ListPopBack(ListNode* phead)
{
	assert(phead);
	assert(phead->next != phead);

	ListNode* tail = phead->prev;
	ListNode* prev = tail->prev;

	prev->next = phead;
	phead->prev = prev;

	free(tail);
	tail = NULL;
}

指定位置删除

c 复制代码
void ListErase(ListNode* pos)
{
	assert(pos);

	ListNode* prev = pos->prev;
	ListNode* next = pos->next;
	prev->next = next;
	next->prev = prev;
	free(pos);
}

(4)销毁链表

c 复制代码
void ListDestory(ListNode* phead)
{
	assert(phead);
	ListNode* cur = phead->next;
	while (cur != phead)
	{
		ListNode* next = cur->next;
		free(cur);
		cur = next;
	}

	free(phead);
	phead = NULL;
}

(5)链表的打印

c 复制代码
void ListPrint(ListNode* phead)
{
	assert(phead);

	ListNode* cur = phead->next;
	while (cur != phead)
	{
		printf("%d ", cur->data);
		cur = cur->next;
	}
	printf("\n");
}

四、总结

双向链表是一种非常强大的数据结构,它通过增加反向链接,极大地提升了操作的灵活性和效率。虽然它比单链表占用更多空间,但在需要频繁插入、删除或双向遍历的场景中,双向链表无疑是更好的选择。

相关推荐
郝学胜-神的一滴5 小时前
深入解析Python字典的继承关系:从abc模块看设计之美
网络·数据结构·python·程序人生
不知名XL11 小时前
day50 单调栈
数据结构·算法·leetcode
cpp_250113 小时前
P10570 [JRKSJ R8] 网球
数据结构·c++·算法·题解
cpp_250113 小时前
P8377 [PFOI Round1] 暴龙的火锅
数据结构·c++·算法·题解·洛谷
季明洵13 小时前
C语言实现单链表
c语言·开发语言·数据结构·算法·链表
only-qi14 小时前
leetcode19. 删除链表的倒数第N个节点
数据结构·链表
cpp_250114 小时前
P9586 「MXOI Round 2」游戏
数据结构·c++·算法·题解·洛谷
浅念-14 小时前
C语言编译与链接全流程:从源码到可执行程序的幕后之旅
c语言·开发语言·数据结构·经验分享·笔记·学习·算法
爱吃生蚝的于勒14 小时前
【Linux】进程信号之捕捉(三)
linux·运维·服务器·c语言·数据结构·c++·学习
数智工坊15 小时前
【数据结构-树与二叉树】4.6 树与森林的存储-转化-遍历
数据结构