数据结构--双向链表

在讲双向链表之前,我们先了解一下链表的分类:
链表的结构⾮常多样,主要分为带头与不带头、单向与双向、循环与不循环。三个种类可以任意搭配,所以总共可以形成八种链表,但是最常用的是单向不带头不循环链表和双向带头循环链表。

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

前者即单链表,在前一篇文章中已经讲过了,所以我们再来看看双向链表。
注意:这⾥的"带头"跟前⾯我们说的"头节点"是两个概念,实际前⾯的在单链表阶段称呼不严
谨,但是为了同学们更好的理解就直接称为单链表的头节点。带头链表⾥的头节点,实际为"哨兵位",哨兵位节点不存储任何有效元素,只是站在这⾥"放哨的"。
"哨兵位"存在的意义:遍历循环链表避免死循环。

双向链表的实现

文件:DoubleListNode.h

cpp 复制代码
//双链表结构体创建
typedef int slDataType;
typedef struct DoubleList
{
	slDataType data;
	struct DoubleList* next;
	struct DoubleList* prev;
}DLN;

//双向链表的初始化
void DbleInit(DLN** pphead);

//双向链表打印
void DLNPrint(DLN* phead);

//双向链表尾插函数
void DbleTailAdd(DLN* phead,int data);

//双向链表头插函数
void DbleHeadAdd(DLN* phead, slDataType data);

//双向链表头删函数
void DbleHeadDel(DLN* phead);

//双向链表尾删函数
void DbleTailDel(DLN* phead);

//双向链表查找
DLN* DbleSearch(DLN* phead);

//双向链表修改函数
void DbleModify(DLN* phead);

//双向链表销毁函数
void DbleStroy(DLN* phead);

文件DoubleListNode.c

cpp 复制代码
//申请新节点
DLN* ListByNode(slDataType data)
{
	DLN* newnode = (DLN*)malloc(sizeof(DLN));
	if (newnode == NULL)
	{
		perror("malloc fail");
		exit(1);
	}
	newnode->data = data;
	return newnode;
}

//双向链表的初始化
void DbleInit(DLN** pphead)
{
	*pphead = (DLN*)malloc(sizeof(DLN));
	if (*pphead == NULL)
	{
		perror("malloc fail");
		exit(1);
	}
	(*pphead)->data = -1;
	(*pphead)->next = (*pphead);
	(*pphead)->prev = (*pphead);
}

//双向链表打印
void DLNPrint(DLN* phead)
{
	assert(phead);
	DLN* pcur = phead->next;
	while (pcur != phead)
	{
		printf("%d->",pcur->data);
		pcur = pcur->next;
	}
	printf("\n");
}

//双向链表尾插函数
void DbleTailAdd(DLN* phead, slDataType data)
{
	assert(phead);
	DLN* newnode = ListByNode(data);
	newnode->data = data;
	//尾插新节点
	newnode->next = phead;
	newnode->prev = phead->prev;

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

//双向链表头插函数
void DbleHeadAdd(DLN* phead, slDataType data)
{
	assert(phead);
	DLN* newnode = ListByNode(data);

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

//双向链表头删函数
void DbleHeadDel(DLN* phead)
{
	assert(phead);
	DLN* pcur = phead->next;
	if (pcur == phead)
	{
		printf("删除失败,不存在有效节点\n");
	}
	else
	{
		phead->next = pcur->next;
		pcur->next->prev = phead;
		free(pcur);
		pcur = NULL;
	}
}

//双向链表尾删函数
void DbleTailDel(DLN* phead)
{
	assert(phead);
	DLN* pcur = phead->next;
	while (pcur->next != phead)
	{
		pcur = pcur->next;
	}
	phead->prev = pcur->prev;
	pcur->prev->next = phead;
	free(pcur);
	pcur = NULL;
}

//双向链表查找
DLN* DbleSearch(DLN* phead,slDataType pos)
{
	assert(phead);
	DLN* pcur = phead->next;
	while (pcur != phead)
	{
		if (pcur->data == pos)
		{
			printf("找到了\n");
			return pcur;
		}
		pcur = pcur->next;
	}
	printf("没找到\n");
	return NULL;
}

//双向链表修改函数
void DbleModify(DLN* phead,slDataType pos,slDataType data)
{
	DLN* find = DbleSearch(phead,pos);
	if (find != NULL)
	{
		find->data = data;
		printf("修改成功\n");
		return;
	}
	else
	{
		printf("修改失败,未找到该节点\n");
	}
}

//双向链表销毁函数
void DbleStroy(DLN* phead)
{
	assert(phead);
	DLN* pcur = phead->next;
	DLN* next = pcur->next;
	while (pcur != phead)
	{
		free(pcur);
		pcur = next;
		next = next->next;
	}
	printf("销毁成功\n");
}

这里单独讲解一个点,我们在创建"哨兵位"的时候使用的参数是一级指针的地址,并用二级指针接收,目的是创建一个节点的地址用作节点的链表头部,之后的其他函数均使用一级指针是为了防止头节点被修改。

测试代码由各位自行书写,我们下期间。

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