数据结构——双向链表

本文详细介绍了双向链表的实现,包括带头双向循环链表的结构、常用的接口如初始化、销毁、头插、尾插、头删、尾删、位置插入和查找操作,以及与顺序表的优缺点比较。
摘要由CSDN通过智能技术生成


基于单链表的一些缺点,这里对双链表进行一个实现。

一、带头双向循环链表

结构图大概如下,每个结点有两个指针域,一个数据域。指针分别指向前一个和后一个结点。尾结点的下一个是头结点,头结点的前一个是尾结点。
在这里插入图片描述
双链表可以往前找也可以往后找,虽然它结构复杂,但是代码实现确实很简单,这也源于它复杂结构。
头结点是不存储有效数据的。它只起到一个链接首尾结点的作用。

二、常用接口

双链表的结构体如下

typedef int SLTDataType;

typedef struct SLTNode
{
	struct SLTNode* prev;//前驱指针
	struct SLTNode* next;//后继指针
	SLTDataType val;
}Node;

1、初始化和销毁

初始化双链表就是开辟一个头结点并返回,只有一个结点时,链表的前一个和后一个都是自己。

销毁时,遍历链表释放其余结点后,在释放头结点。

//初始化
Node* SLTInit()
{
	//创建头结点
	Node* newNode = (Node*)malloc(sizeof(Node));
	if (newNode == NULL)
	{
		printf("malloc,failed!");
		return NULL;
	}
	newNode->prev = newNode;
	newNode->next = newNode;
	return newNode;
}

//销毁链表
void SLTDestroy(Node* phead)
{
	assert(phead);
	//phead的下一个不是自己,说明还有结点没释放
	while (phead->next != phead)
	{
		Node* front = phead->next;
		front->next->prev = phead;
		phead->next = front->next;
		free(front);
	}
	free(phead);//最后释放头结点
	phead = NULL;
}

销毁链表的过程
在这里插入图片描述

2、头插和尾插

对于头插,可以看成是在头结点后插入一个数据。插入只需要改变四个指针的指向即可,但需要注意顺序。

//头插
void SLTPushFront(Node* phead, SLTDataType x)
{
	assert(phead);
	//创建结点
	Node* newNode = (Node*)malloc(sizeof(Node));
	if (newNode == NULL)
	{
		printf("malloc,failed!");
		return;
	}
	newNode->val = x;
	//修改指针指向
	Node* front = phead->next;
	newNode->next = front;
	newNode->prev = phead;
	front->prev = newNode;
	phead->next = newNode;
}

在这里插入图片描述
对于尾插,可以看成在尾结点后插入,也可以看成在头结点(head)前插入,因为head的前驱结点就是尾。
代码与修改指向和头插类似,这里就不画图带大家理解了。

//尾插
void SLTPushBack(Node* phead, SLTDataType x)
{
	assert(phead);
	//创建结点
	Node* newNode = (Node*)malloc(sizeof(Node));
	if (newNode == NULL)
	{
		printf("malloc,failed!");
		return;
	}
	newNode->val = x;
	//修改指针指向
	Node* tail = phead->prev;
	newNode->prev = tail;
	newNode->next = phead;
	phead->prev = newNode;
	tail->next = newNode;
}

3、头删和尾删

头删,即删除第一个结点,也就是头结点的下一个,但是不包括头结点。
但不能直接释放,因为第一个结点存储第二个结点的地址。直接释放会导致找不到第二个结点,所以需要保存一下第二个结点的地址。

可以删除的前提必然是链表不为空。

//头删
void SLTPopFront(Node* phead)
{
	assert(phead);
	//链表不为空
	if (!SLTEmpty(phead))
	{
		Node* front = phead->next;
		phead->next = front->next;
		front->next->prev = phead;
		free(front);
	}
}

next是一个地址,=是赋值,改变指针的指向就是修改结构体内指针的值,也就是修改地址。
在这里插入图片描述
同样,尾删可以看成是删除尾结点或者删除头结点的前一个。删除方法和头删类似。

//尾删
void SLTPopBack(Node* phead)
{
	assert(phead);
	//链表不为空
	if (!SLTEmpty(phead))
	{
		Node* tail = phead->prev;
		tail->prev->next = phead;
		phead->prev = tail->prev;
		free(tail);
		tail = NULL;
	}
}

4、pos位置插入和删除

pos是一个结构体指针,插入是在pos前插入,删除就直接删除pos位置的结点。

插入:和头插尾插类似,只需要修改四个指针的指向即可。

//pos位置前插入
void SLTInsert(Node* phead, SLTDataType x, Node* pos)
{
	assert(phead);
	assert(pos);//插入位置不能为NULL
	//创建新结点
	Node* newNode = (Node*)malloc(sizeof(Node));
	if (newNode == NULL)
	{
		printf("malloc,failed!");
		return;
	}
	newNode->val = x;
	//修改指针指向
	Node* prev = pos->prev;
	newNode->next = pos;
	newNode->prev = prev;
	pos->prev = newNode;
	prev->next = newNode;
}

删除:同头删和尾删,先改变指针指向,在释放pos位置的结点。
有了头删和尾删的基础,pos位置的删除也会更容易理解。

//删除pos位置数据
void SLTErase(Node* phead, Node* pos)
{
	assert(phead);
    //删除位置要合理,不能删除头结点
	assert(pos&&pos!=phead);
	Node* prev = pos->prev;
	Node* next = pos->next;
	prev->next = next;
	next->prev = prev;
	free(pos);
	pos = NULL;
}

4、查找和判空

查找:给一个值,查找链表内有无该数据,有就返回该结点的地址,没有就返回NULL;

遍历链表并做判断即可。

//查找数据
Node* SLTFind(Node* phead, SLTDataType x)
{
	Node* cur = phead->next;
	while (cur != phead)
	{
		if (cur->val == x)
			return cur;
		cur = cur->next;
	}
	printf("数据不存在!\n");
	return NULL;
}

判空:链表内只有一个头结点时,为空链表。只需要判断头结点的下一个或前一个 是不是自己即可。

//判断空链表
bool SLTEmpty(Node* phead)
{
	assert(phead);
	if (phead->next == phead)
		return true;
	return false;
}

三、链表和顺序表的优缺和区别

对于链表和顺序表,都有一定的优略。

它们的不同大致有以下这些。
在这里插入图片描述
关于链表的介绍,就到这里啦。

  • 19
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值