【数据结构】双向带头循环链表的实现

目录

1.概念

 2.示例

3.实现

3.1接口

3.2生成一个节点 

3.3初始化

3.4插入和删除的特点

3.5删除

头删

尾删

删除pos位置的数据

 3.6插入

头插

尾插

pos位置之前插入

3.7打印数据

3.8销毁


1.概念

        双向带头循环链表(下面统称该链表)和普通链表区别,很明显:

1、双向。     是指该链表有两个指针域,一个和普通链表一样,指向后一个节点(next),另一个指向前一个节点(prev)。

2、带头。     是指该链表有哨兵位,该位置不存储任何数据,只是为了帮我们定位该链表

3、循环。     是指该链表头尾连接。

        如下,是该链表每一个节点的定义,以及图示。

typedef int LTDataType;

typedef struct LinkListNode
{
	LTDataType data;
	struct LinkListNode* prev;
	struct LinkListNode* next;
}LTNode;

 

 2.示例

        如下图,是一个双向带头循环链表的示例,可以看出,哨兵位头节点prev指针指向该链表的尾节点,尾节点next指针指向该链表的哨兵位头节点 :

         那么,该链表为空的时候,其结构如何呢?  无论该链表是否为空,哨兵位都存在,那么,哨兵位既是头节点,又是尾节点,所以其prev指针指向自己,而next指针也指向自己。如下图,实际上,该链表为空即该链表初始化。

3.实现

3.1接口

        如下,其各个接口和普通链表类似,涉及头插、尾插、头删、尾删、任意节点的插入和删除等等。

3.2生成一个节点 

         生成一个节点,该接口只需要传入 生成的节点的数据域的值 即可,两个指针指向NULL,然后返回指向该节点的指针。

LTNode* BuyLinkListData(LTDataType x)
{
	LTNode* newnode = (LTNode*)malloc(sizeof(LTNode));
	newnode->data = x;
	newnode->next = NULL;
	newnode->prev = NULL;
	return newnode;
}

3.3初始化

        初始化即生成一个哨兵位头节点,和示例中一样,prev指针和next指针都指向自己,然后返回指向该哨兵位的指针。

LTNode* LinkListInit()
{
	LTNode* phead = (LTNode*)malloc(sizeof(LTNode));
	phead->next = phead;
	phead->prev = phead;
	return phead;
}

3.4插入和删除的特点

        如图,由于该链表循环的,所以可以这样画出来。无论哪个位置是哨兵位,只要非空,那么删除和插入操作都没有太大的区别,任一节点的前后节点都是LTNode类型的数据,要插入数据,就和某个位置的前后节点有关;要删除数据,就和删除节点的前后节点有关。(本质上是前一个节点的next指针后一个节点的prev指针)。

3.5删除

        首先要考虑是该链表是否为空,为空时上文已经说过,就是只有哨兵位头节点,且其两个指针都指向自己,所以如果 phead->next == phead;   那么 这个双向带头循环链表就是空的,就不能进行头删操作。这个判断用  assert(phead->next != phead);  即可。

        不为空时,如下图,要删除头节点cur,直接暴力一点,把该节点拿出来。下图中可以看出,和该节点关联的指针有四个,其中 ①和②(紫色)都是该节点的指针,所以不用去管(反正都要被删除),那么之只剩下cur节点的前一个节点(记为Prev)的指针cur节点的后一个节点(记为Next)的指针,只要处理好这两个节点的指针,那么头删就算是完成。看到图中最下面的部分,只需要Prev节点的next指针指向Next,Next节点的prev指针指向Prev即可。(注意区分大小写)

头删

void LinkListPopFront(LTNode* phead)
{
	assert(phead);
	assert(phead->next != phead);
	LTNode* pphead = phead;
	LTNode* Nnext = phead->next->next;
	LTNode* next = phead->next;
	
	//删除操作
	pphead->next = Nnext;
	Nnext->prev = pphead;

	free(next);
}

尾删

void LinkListPopBack(LTNode* phead)
{
	assert(phead);
	assert(phead->next != phead);
	LTNode* Tail = phead->prev;
	LTNode* NewTail = Tail->prev;
	//删除操作
	NewTail->next = phead;
	phead->prev = NewTail;
	free(Tail);
}

删除pos位置的数据

//删除pos位置的数据
void LinkListErase(LTNode* phead, LTNode* pos)
{
	assert(phead);
	assert(pos);
	assert(phead->next != phead);
	LTNode* Prev = pos->prev;
	LTNode* Next = pos->next;
	Prev->next = Next;
	Next->prev = Prev;
}

 3.6插入

         相比删除,插入操作就不需要考虑 该链表是否为空。如下图,要在pos节点之前插入一个节点。

         首先要获得pos节点前面一个节点的指针,并记为Prev。那么我们现在所拥有的,就是pos、Prev和新节点newnode。对于Prev和pos节点,需要更改的就是 Prev的next指针 和  pos的prev指针(绿色部分)newnode节点两个指针,一个prev指针要指向Prev,另一个next指针要只想pos(紫色部分)

        在这里可以不用考虑这几个指针先后使用顺序,因为我们已经有了Prev、pos、newnode,需要更改的是Prev->next  、pos->prev 、 newnode->prev 、newnode->next ,这几个指针要指向的位置也只是在Prev、pos、newnode这三个里面的,所以其先后更改顺序无所谓。

头插

void LinkListPushFront(LTNode* phead, LTDataType x)
{
	//断言
	assert(phead);
	LTNode* newnode = BuyLinkListData(x);
	LTNode* next = phead->next;
	//插入操作
	phead->next = newnode;
	newnode->prev = phead;
	newnode->next = next;
	next->prev = newnode;
}

尾插

void LinkListPushBack(LTNode* phead, LTDataType x)
{
	assert(phead);
	LTNode* newnode = BuyLinkListData(x);
	LTNode* Tail = phead->prev;

	//插入操作
	Tail->next = newnode;
	newnode->prev = Tail;
	newnode->next = phead;
	phead->prev = newnode;
}

pos位置之前插入

//在pos前面插入数据
void LinkListInsert(LTNode* phead, LTNode* pos, LTDataType x)
{
	assert(phead);
	assert(pos);
	LTNode* newnode = BuyLinkListData(x);
	LTNode* Prev = pos->prev;
	Prev->next = newnode;
	newnode->prev = Prev;
	newnode->next = pos;
	pos->prev = newnode;
}

3.7打印数据

        遍历每个节点并打印数据域的数据即可,注意循环结束条件,当指针回到指向哨兵位,那么就是遍历完了。

void LinkListPrint(LTNode* phead)
{
	LTNode* cur = phead->next;
	while (cur != phead)
	{
		printf("%d ", cur->data);
		cur = cur->next;
	}
}

3.8销毁

        同理打印,遍历然后free,区别就是,销毁要先存好下一个节点的指针。

void LinkListDestory(LTNode* phead)
{
	assert(phead);
	LTNode* cur = phead->next;
	while (cur != phead)
	{
		LTNode* temp = cur->next;
		free(cur);
		cur = temp;
	}
	//free(phead);
}

        关于双向带头循环链表的介绍就到这里,希望多多支持!!!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

努力努力再努力.xx

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

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

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

打赏作者

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

抵扣说明:

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

余额充值