双向链表的实现(详解)

目录

前言

链表的分类: 带头 不带头 单向 双向 循环 不循环

一共有 (2 * 2 * 2) 种链表

带头指的是:带有哨兵位节点

哨兵位(头结点):可以放随机数据,哨兵位指向的下一个节点就是我们的第一个有效节点,我们指的头节点是哨兵位

单向:指针只能从前向后遍历

双向:指针既可以从前向后遍历又可以从后向前遍历

循环:可以通过尾节点找到头节点,从而达到循环

主要使用的链表是单链表和双向链表

单链表:不带头单向不循环

双向链表:带头双向循环

初始化

c 复制代码
ListNode* TLInit()
{
	ListNode* phead = ListByNode(-1);//ListByNode后面会介绍
	//为头节点开辟一个空间,并将头结点指向的值赋值为-1
	return phead;
	//将开辟出的头节点返回我们的主函数,主函数中的头节点就知道我们有一个哨兵位了
}
c 复制代码
//初始化
void TLInit(ListNode** pphead)
{
	//给双向链表创建一个哨兵位
	*pphead = ListByNode(-1);
}

初始化有两种写法,第二种写法也是可行的,但是用第一种,可以使得接口一致,都是一级指针,使用者也会更方便的使用,不用去记忆那个要用一级指针还是二级指针

双向链表的结构

c 复制代码
//定义双向链表的结构
typedef int DataType;
typedef struct ListNode//双向链表
{
	DataType x;//双向链表中的数据
	struct ListNode* next;//指向后一个节点,后继指针
	struct ListNode* prev;//指向前一个节点,前驱指针
}ListNode;

为双向链表的节点开辟空间

c 复制代码
ListNode* ListByNode(DataType x)
{
	ListNode* node = (ListNode*)malloc(sizeof(ListNode));
	if (node == NULL)
	{
		perror("malloc");
		return NULL;
	}
	//开辟成功
	node->next = node->prev = node;
	//初始化一个哨兵位的下一个节点和前一个节点指向的是自己
	node->x = x;

	return node;
}

头插

c 复制代码
//头插
//在第一个有效节点之前插入一个节点
void TLPushFront(ListNode* phead,DataType x)
//用一级指针为了不改变原链表的哨兵位
{
	assert(phead);
	//断言一下是不是空链表
	ListNode* newnode = ListByNode(x);

	ListNode* pcur = phead->next;
	newnode->next = pcur;
	newnode->prev = phead;

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

}

尾插

c 复制代码
//尾插
//在头节点之后或者之前插入
//因为在哨兵位前,哨兵位的prev指向尾结点,尾结点的next指向哨兵位
void TLPushBack(ListNode* phead,DataType x)
{
	assert(phead);
	//链表不为空
	ListNode* newnode = ListByNode(x);
	//为尾插的节点开辟空间
	ListNode* pcur = phead->prev; 

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

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

打印链表

c 复制代码
//打印链表
void TLPrint(ListNode* phead)
{
	ListNode* pcur = phead->next;
	while (pcur != phead)
	{
		printf("%d->", pcur->x);
		pcur = pcur->next;
	}
	printf("\n");
}

尾删

c 复制代码
//尾删
//注意哨兵位不能被删除,所以理论上至少有一个节点,才能够删除
void TLPopBack(ListNode* phead)
{
	assert(phead&&phead->next!=phead);

	ListNode* pcur = phead->prev;

	ListNode* del = pcur->prev;
	del->next = phead;
	phead->prev = del;

	free(pcur);
	pcur = NULL;

}

头删

c 复制代码
//头删
//注意哨兵位不能被删除,所以理论上至少有一个节点,才能够删除
void TLPopFront(ListNode* phead)
{
	assert(phead&&phead->next!=phead);
	//phead不能为空

	ListNode* pcur = phead->next;

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

	free(pcur);
	pcur = NULL;

}

查找

c 复制代码
//查找 x 数据的位置并返回
ListNode* SLFind(DataType x,ListNode* phead)
{
	ListNode* pcur = phead->next;
	while (pcur != phead)
	{
		if (pcur->x == x)
		{
			return pcur;
			//找到了
		}
		pcur = pcur->next;
	}
	return NULL;
	//找不到了
}

指定位置之后的插入

c 复制代码
//指定位置之后的插入
//知道了pos节点,就知道了pos之前的数和之后的数
//指定位置之后的插入和指定位置之前的插入是一样的
void TLInsert(ListNode* pos,DataType x)
{
	assert(pos);
	//插入的pos位置不能为空

	ListNode* newnode = ListByNode(x);
	//为插入的数据开辟空间
    
	ListNode* pcur = pos->next;
	newnode->next = pcur;
	newnode->prev = pos;

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

}

删除pos节点

c 复制代码
//删除pos节点
void Erase(ListNode* pos)
{
	assert(pos);
	//理论上pos不能为phead,因为phead不能被删除,它是双向链表
	
	pos->prev->next = pos->next;
	pos->next->prev = pos->prev;

	free(pos);
	pos = NULL;

}

销毁双向链表

c 复制代码
//销毁双向链表
void LTDestroy(ListNode* phead)
{
	assert(phead);
	//phead不为空,才销毁,为空,不用销毁
	//销毁时phead也要释放(销毁)

	ListNode* pcur = phead->next;
	ListNode* next = pcur->next;
	while (pcur != phead)
	{
		next = pcur->next;
		free(pcur);
		pcur = next;
	}
	//出来后pcur指向phead
	free(phead);
	phead = NULL;
}
相关推荐
ahauedu1 小时前
案例分析-Stream List 中取出值最大的前 5 个和最小的 5 个值
数据结构·list
X同学的开始3 小时前
数据结构之二叉树遍历
数据结构
AIAdvocate6 小时前
Pandas_数据结构详解
数据结构·python·pandas
jiao000016 小时前
数据结构——队列
c语言·数据结构·算法
kaneki_lh6 小时前
数据结构 - 栈
数据结构
铁匠匠匠6 小时前
从零开始学数据结构系列之第六章《排序简介》
c语言·数据结构·经验分享·笔记·学习·开源·课程设计
C-SDN花园GGbond6 小时前
【探索数据结构与算法】插入排序:原理、实现与分析(图文详解)
c语言·开发语言·数据结构·排序算法
CV工程师小林8 小时前
【算法】BFS 系列之边权为 1 的最短路问题
数据结构·c++·算法·leetcode·宽度优先
Navigator_Z8 小时前
数据结构C //线性表(链表)ADT结构及相关函数
c语言·数据结构·算法·链表
还听珊瑚海吗8 小时前
数据结构—栈和队列
数据结构