数据结构--双向链表

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

  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");
}

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

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

相关推荐
vir025 小时前
P1928 外星密码(dfs)
java·数据结构·算法·深度优先·1024程序员节
胡萝卜3.05 小时前
C++ list核心接口与实战技巧
数据结构·c++·list·list使用
仟千意6 小时前
数据结构:排序篇
数据结构
Dream it possible!6 小时前
LeetCode 面试经典 150_链表_合并两个有序链表(58_21_C++_简单)
leetcode·链表·面试·1024程序员节
脚踏实地的大梦想家7 小时前
【Go】P8 Go 语言核心数据结构:深入解析切片 (Slice)
开发语言·数据结构·golang
蒙奇D索大9 小时前
【数据结构】数据结构核心考点:AVL树删除操作详解(附平衡旋转实例)
数据结构·笔记·考研·学习方法·改行学it·1024程序员节
大数据张老师11 小时前
数据结构——平衡二叉树
数据结构·算法·查找
大数据张老师12 小时前
数据结构——BF算法
数据结构·算法·1024程序员节
Yupureki13 小时前
从零开始的C++学习生活 14:map/set的使用和封装
c语言·数据结构·c++·学习·visual studio·1024程序员节
一匹电信狗13 小时前
【LeetCode_876_2.02】快慢指针在链表中的简单应用
c语言·数据结构·c++·算法·leetcode·链表·stl