【数据结构】第四讲:双向链表

个人主页深情秋刀鱼@-CSDN博客

数据结构专栏数据结构与算法

循环链表这个轮回的思想很有意思。它强调了不管你今生是贫是富,如果持续行善积德,下辈子就会好过,反之就会遭到报应。

就像每个人的人生一样,欲收获就得付出代价。双向链表既然是比单链表多了如可以反向遍历查找的数据结构,那么也就要付出一些小的代价。

一、链表的分类

链表的结构复杂多样,总计有如下八种形式。

虽然有这么多的链表的结构,但是我们实际中最常⽤还是两种结构: 单链表双向带头循环链表。

  1. ⽆头单向⾮循环链表:结构简单,⼀般不会单独⽤来存数据。实际中更多是作为其他数据结 构的⼦结构,如哈希桶、图的邻接表等等。另外这种结构在笔试⾯试中出现很多。
  2. 带头双向循环链表:结构最复杂,⼀般⽤在单独存储数据。实际中使⽤的链表数据结构,都 是带头双向循环链表。另外这个结构虽然结构复杂,但是使⽤代码实现以 后会发现结构会带 来很多优势,实现反⽽简单了。

二、双向链表的结构及实现

1.带头双向链表的结构

对于带头和不带头,在学习单链表是我们将其理解为链表的头节点(链表的第一个节点),这种称呼很不严谨,在本节中的带头和不带头指的是在该链表中书否含有哨兵结点 ,哨兵结点在双向链表中是名副其实的头节点,哨兵节点内不存储任何有效的数据,他的唯一作用是防止在遍历链表时进入死循环。

cpp 复制代码
//双向链表的定义
typedef int LTDataType;

//定义双向链表的节点
typedef struct ListNode
{
	LTDataType data;//数据域
	struct ListNode* next;//指向后继节点的指针
	struct ListNode* prev;//指向前驱节点的指针
}ListNode;

2.创建节点

cpp 复制代码
ListNode* LTbuyNode(LTDataType x)
{
	ListNode* newNode = (ListNode*)malloc(sizeof(ListNode));
	if (newNode == NULL)
	{
		perror("malloc fail!");
		exit(1);
	}
	newNode->data = x;
	newNode->next = newNode;
	newNode->prev = newNode;//创建的新节点指向自身
	return newNode;
}

3.初始化

cpp 复制代码
//初始化
void LTinit(ListNode** pphead)
{
	*pphead = LTbuyNode(-1);//哨兵位(不存储有效值)
}

4.尾插

cpp 复制代码
//尾插
void LTpushBack(ListNode* phead, LTDataType x)
{
	assert(phead);
	ListNode* node = LTbuyNode(x);
	node->prev = phead->prev;
	node->next = phead;
	phead->prev->next = node;
	phead->prev = node;
}

5.打印

cpp 复制代码
//打印
void LTprint(ListNode* phead)
{
	assert(phead);
	ListNode* pcur = phead->next;
	while (pcur != phead)
	{
		printf("%d->", pcur->data);
		pcur = pcur->next;
	}
	printf("head\n");
}

6.头插

cpp 复制代码
//头插
void LTpushFront(ListNode* phead, LTDataType x)
{
	assert(phead);
	ListNode* node = LTbuyNode(x);
	node->prev = phead;
	node->next = phead->next;
	phead->next->prev = node;
	phead->next = node;
}

7.尾删

cpp 复制代码
//尾删
void LTpopBack(ListNode* phead)
{
	assert(phead && phead->next != phead);//链表不为空
	ListNode* pcur = phead->prev;
	phead->prev->prev->next = phead;
	phead->prev = phead->prev->prev;
	free(pcur);
	pcur = NULL;
}

8.头删

cpp 复制代码
//头删
void LTpopFront(ListNode* phead)
{
	assert(phead && phead->next != phead);
	ListNode* pcur = phead->next;
	phead->next->next->prev = phead;
	phead->next = phead->next->next;
	free(pcur);
	pcur = NULL;
}

9.在pos位置之后插入数据

cpp 复制代码
//pos位置之后插入数据
void LTinsert(ListNode* pos, LTDataType x)
{
	assert(pos);
	ListNode* node = LTbuyNode(x);
	pos->next->prev = node;
	node->prev = pos;
	node->next = pos->next;
	pos->next = node;
}

10.删除pos节点

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

11.查找

cpp 复制代码
//查找
ListNode* LTfind(ListNode* phead, LTDataType x)
{
	assert(phead && phead->next != phead);
	ListNode* pcur = phead->next;
	while (pcur != phead)
	{
		if (pcur->data == x)
		{
			printf("找到了!\n");
			return pcur;
		}
		pcur = pcur->next;
	}
	printf("没有找到!\n");
	return NULL;
}

12.销毁

cpp 复制代码
//销毁
void LTdestroy(ListNode* phead)
{
	assert(phead);
	ListNode* pcur = phead->next;
	while (pcur != phead)
	{
		ListNode* next = pcur->next;
		free(pcur);
		pcur = next;
	}
	free(phead);
	phead = NULL;
}

相关推荐
刘马想放假20 小时前
Modbus 全栈技术解析:TCP、RTU、ASCII、RTU over TCP
数据结构·网络协议
北域码匠2 天前
冒泡排序太慢?鸡尾酒排序双向优化,原生 C# 零第三方库完整代码
数据结构·排序算法·泛型·c# 算法·鸡尾酒排序·原生 c# 开发·冒泡排序优化·嵌入式算法
Darling噜啦啦9 天前
列表转树算法深度解析:从 Map 到 Reduce 的两种实现,面试高频考点
数据结构·算法·面试
小小工匠10 天前
Redis - 事务机制:能实现 ACID 属性吗
数据结构·redis·性能优化·并发·持久化
玖玥拾10 天前
C/C++ 数据结构(七)栈、容器适配器
c语言·数据结构·c++··容器适配器
Qres82110 天前
算法复键——树状数组
数据结构·算法
牛油果子哥q10 天前
并查集(DSU)超精讲,路径压缩、按秩合并、万能模板、连通性判定、最小生成树与刷题实战全解
数据结构·c++·最小生成树·并查集
凌波粒10 天前
LeetCode--491.递增子序列(回溯算法)
数据结构·算法·leetcode
疯狂成瘾者10 天前
Java 集合 LinkedList 详解:链表结构、常用方法和队列使用
java·开发语言·链表
WL学习笔记10 天前
单项不带头不循环链表
数据结构·链表