数据结构之链表(带头双向循环链表)


前言

在了解了单链表之后,想必大家对于链表已经有了很多的了解,同时对于比单链表更有趣的带头双向循环链表也有了很大的兴趣。
因此今天要带大家了解的是链表中的带头双向循环链表。

一、带头双向循环链表

在这里插入图片描述
结合图片可以了解到,这种链表有头结点(哨兵位),每个节点带有两个指针,一个指向前一个节点,另一个指向后一个节点,这样就可以将前后节点都联系起来。虽然它的结构看上去有点复杂,但实际上的实现和使用都很简单,具体如何我们往下接着看。

二、双向链表

1.双向链表的声明

typedef int LTDataType;
typedef struct ListNode//链表的节点
{
	LTDataType date;
	struct ListNode* next;
	struct ListNode* prev;
}ListNode;

2.双向链表的接口

//创建链表的头结点(返回头结点的地址)
ListNode* ListCreate();
//打印链表
void ListPrint(ListNode* plist);
//链表的销毁
void ListDestory(ListNode* plist);
// 双向链表尾插
void ListPushBack(ListNode* plist, LTDataType x);
//链表的尾删
void ListPopBack(ListNode* plist);
//链表的头插
void ListPushFront(ListNode* plist, LTDataType x);
//链表的头删
void ListPopFront(ListNode* plist);
//链表的查找(如果找到了就返回下标,没找到就报错)
ListNode* ListFind(ListNode* plist, LTDataType x);
//在pos前面插入数据
void ListInsert(ListNode* pos, LTDataType x);
//双向链表删除pos位置处的节点
void ListErase(ListNode* pos);

3.接口的实现

创建返回链表的头结点

//创建返回链表的头结点
ListNode* ListCreate()//头结点(哨兵位)
{
	ListNode* p = (ListNode*)malloc(sizeof(ListNode));
	p->next = p;
	p->prev = p;
	return p;
}

这里有一个要注意点:我之前看的一些书上,对于头结点的date也进行了赋值,主要用于记录链表的长度(结点个数),但是实际上这种做法是不可取的。
原因如下:
链表节点的数据类型不确定;
如果类型为char,那它所存储的最大值是127,如果链表节点个数超过了127就会产生错误;
当然,即便是int类型,如果数据数量过多也会产生问题
所以,我们所定义的链表的头结点不进行赋值。

创建一个新节点

ListNode* ListBuyNewNode(LTDataType x)
{
	ListNode* p = (ListNode*)malloc(sizeof(ListNode));
	if (!p)
	{
		perror("malloc fail");//如果扩容失败,会报警告,告诉使用者扩容失败
	}
	p->next = p;
	p->prev = p;
	p->date = x;
	return p;
}

打印链表

void ListPrint(ListNode* plist)
{
	ListNode* p = plist;
	while (p->next != plist)//因为是循环链表,所以当p->next==plist为真时,p指向末尾节点(注意:不能用p-<next==NULL进行判断,会死循环)
	{
		printf("%d->", p->next->date);
		p = p->next;
	}
	printf("NULL\n");//如果链表为空则打印NULL
}

链表的销毁

void ListDestory(ListNode* plist)
{
	ListNode* tail = plist;
	while (plist->next != plist)
	{
		tail = plist->prev;
		plist->prev = tail->prev;
		plist->prev->next = plist;
		free(tail);
	}
	free(plist);
}

尾插

void ListPushBack(ListNode* plist, LTDataType x)
{
	ListNode* newnode = ListBuyNewNode(x);
	ListNode* tail = plist->prev;
	tail->next = newnode;
	newnode->prev = tail;
	plist->prev = newnode;
	newnode->next = plist;
}

尾删

void ListPopBack(ListNode* plist)
{
	ListNode* tail = plist;
	assert(plist->next != plist);//避免链表中没有数据仍在删除造成越界
	tail = plist->prev;
	plist->prev = tail->prev;
	plist->prev->next = plist;
	free(tail);
}

头插

void ListPushFront(ListNode* plist, LTDataType x)
{
	ListNode* newnode = ListBuyNewNode(x);
	newnode->prev = plist;
	newnode->next = plist->next;
	plist->next->prev = newnode;
	plist->next = newnode;
}

头删

void ListPopFront(ListNode* plist)
{
	assert(plist->next != plist);
	ListNode* begin = plist->next;
	plist->next = begin->next;
	begin->next->prev = plist;
	free(begin);
}

在链表中进行查找

(如果找到了就返回下标,没找到就返回NULL)

ListNode* ListFind(ListNode* plist, LTDataType x)
{
	ListNode* pos = plist->next;
	while (pos != plist)
	{
		if (pos->date == x)
			return pos;
		pos = pos->next;
	}
	return NULL;
}

查找也可以充当修改。

在pos前面插入数据

void ListInsert(ListNode* pos, LTDataType x)
{
	ListNode* newnode = ListBuyNewNode(x);
	newnode->next = pos;
	newnode->prev = pos->prev;
	pos->prev->next = newnode;
	pos->prev = newnode;
}

链表删除pos位置处的节点

void ListErase(ListNode* pos)
{
	pos->prev->next = pos->next;
	pos->next->prev = pos->prev;
	free(pos);
}

4.主函数(测试)

void test1()//测试尾插
{
	//创建返回链表的头结点
	ListNode* plist = ListCreate();
	// 双向链表尾插
	ListPushBack(plist, 10);
	ListPushBack(plist, 10);
	ListPushBack(plist, 10);
	ListPushBack(plist, 10);
	ListPrint(plist);
	//链表的销毁
	ListDestory(plist);
}
void test2()//测试尾删
{
	//创建返回链表的头结点
	ListNode* plist = ListCreate();
	ListPushBack(plist, 10);
	ListPushBack(plist, 10);
	ListPushBack(plist, 10);
	//链表的尾删
	ListPopBack(plist);
	ListPrint(plist);
	ListPopBack(plist);
	ListPrint(plist);
	ListPopBack(plist);
	ListPrint(plist);
	/*ListPopBack(plist);//再删除就会报错
	ListPrint(plist);*/
	//链表的销毁
	ListDestory(plist);
}
void test3()//测试头插
{
	//创建返回链表的头结点
	ListNode* plist = ListCreate();
	ListPushFront(plist, 20);
	ListPushFront(plist, 21);
	ListPushFront(plist, 25);
	ListPushFront(plist, 24);
	ListPushFront(plist, 22);
	ListPrint(plist);
	//链表的销毁
	ListDestory(plist);
}
void test4()//测试头删
{
	//创建返回链表的头结点
	ListNode* plist = ListCreate();
	ListPushFront(plist, 20);
	ListPushFront(plist, 21);
	ListPushFront(plist, 25);
	ListPushFront(plist, 24);
	ListPushFront(plist, 22);
	ListPopFront(plist);
	ListPrint(plist);
	ListPopFront(plist);
	ListPrint(plist);
	ListPopFront(plist);
	ListPopFront(plist);
	ListPrint(plist);
	ListPopFront(plist);
	ListPrint(plist);
	/*ListPopFront(plist);//再删除就报错
	ListPrint(plist);*/
	//链表的销毁
	ListDestory(plist);
}
void test5()//测试查找
{
	//创建返回链表的头结点
	ListNode* plist = ListCreate();
	ListPushFront(plist, 20);
	ListPushFront(plist, 21);
	ListPushFront(plist, 25);
	ListPushFront(plist, 24);
	ListPushFront(plist, 22);
	ListPrint(plist);
	ListNode*pos = ListFind(plist, 38);
	if (pos)
	{
		printf("找到了\n");
	}
	//链表的销毁
	ListDestory(plist);
}
void test6()//测试在pos位置之前插入一个节点
{
	//创建返回链表的头结点
	ListNode* plist = ListCreate();
	ListPushFront(plist, 20);
	ListPushFront(plist, 21);
	ListPushFront(plist, 25);
	ListPushFront(plist, 24);
	ListPushFront(plist, 22);
	ListPrint(plist);
	ListNode*pos = ListFind(plist, 22);
	ListInsert(pos,80);
	ListPrint(plist);
	//链表的销毁
	ListDestory(plist);
}
void test7()//测试删除pos位置的数据
{
	//创建返回链表的头结点
	ListNode* plist = ListCreate();
	ListPushFront(plist, 20);
	ListPushFront(plist, 21);
	ListPushFront(plist, 25);
	ListPushFront(plist, 24);
	ListPushFront(plist, 22);
	ListPrint(plist);
	ListNode*pos = ListFind(plist, 22);
	ListErase(pos);
	ListPrint(plist);
	//链表的销毁
	ListDestory(plist);
}
int main()
{
	//test1();
	//test2();
	//test3();
	//test4();
	//test5();
	test7();
	return 0;
}

总结

以上就是今天要讲的内容,本文主要介绍了带头双向循环链表,对带头双向循环链表的概念以及它的具体实现都进行了讲解。大家感兴趣的也可以根据作者所写思路自行实现带头双向循环链表。
本文作者目前也是正在学习数据结构的知识,如果文章中的内容有错误或者不严谨的部分,欢迎大家在评论区指出也欢迎大家在评论区提问、交流。
最后,如果本篇文章对你有所启发的话,也希望可以多多支持作者,谢谢大家!

  • 8
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 4
    评论
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

codeJinger

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值