一起来学数据结构——双向循环链表

一起来学数据结构——双向循环链表

双向带头循环链表是链表中的一个比较重要的链表了。因为它最为复杂的结构,也使得在某些方面显得更加的容易。

和简单的单链表但是操作复杂产生了鲜明的对比。

链表的结构

  1. 每一个节点都有两个节点指针,一个指向前,一个指向后。
  2. 带有一个指向空的哨兵节点。
  3. 尾的next指向头,头的prev指向尾,构成一个循环
typedef struct ListNode
{
	struct ListNode* prev;
	struct ListNode* next;
	LTDataType data;
}ListNode;

image-20210930135633371

建立双向链表

万事开头难,所以链表是如何建立的是十分重要的。

对于这个双向链表,我们所需要的呢就是一个哨兵节点。就是这个哨兵节点将链表建立起来。

ListNode* plist = NULL;

初始化节点

只要有了哨兵节点,那我们就算是建立了链表,但是这个链表中没有任何的东西,我们应该先把他们初始化。

ListNode* ListInit()
{
	//创建一个头节点
	ListNode* phead = BuyListNode(0);
	//让头和尾都指向它自己
	//这个很重要
	phead->next = phead;
	phead->prev = phead;
	return phead;
}

然后再让这个初始化后的节点等于我们的建立的节点

ListNode* plist = ListInit();

增删查改

下面我们就来看一看双向链表的增删查改吧

尾插

  1. 先是malloc出来一个节点
  2. 找到尾节点
  3. 尾节点和新节点进行连接
	//找到尾
	ListNode* ptail = phead->prev;
	//新创建一个节点
	ListNode* newnode = BuyListNode(x);
	//链接
	ptail->next = newnode;
	newnode->prev = ptail;
	phead->prev = newnode;
	newnode->next = phead;

提问:

只有哨兵节点,没有其他的头节点怎么办,还可不可以使用上面的方法呢?

答案是可以的。

这就是双向节点的优势

头插

  1. malloc出来一个节点
  2. 找到第一个节点
  3. 进行链接
	assert(phead);
	//c创建一个新节点
	ListNode* newnode = BuyListNode(x);
	//链接
	// phead newnode node 三个指针进行链接
		ListNode* pnode = phead->next;
		newnode->next = pnode;
		pnode->prev = newnode;
		phead->next = newnode;
		newnode->prev = phead;
	//如果就只是有头节点也是满足的

尾删

  1. 利用phead找到尾节点
  2. 将他free掉
  3. 处理新的尾节点
	assert(phead);
	//不能出现只有头节点的情况
	assert(phead->next != phead);
	ListNode* ptail = phead->prev;
	ListNode* pcur = ptail->prev;
	// pcur ptail phead 三个进行解绑
	pcur->next = phead;
	phead->prev = pcur;
	free(ptail);
	//如果只有一个数据也满足

头删

  1. 利用phead找到头节点
  2. 将他free掉
  3. 处理新的头节点
	assert(phead);
	不能出现新的头节点的情况
	assert(phead->next != phead);
	ListNode* pcur = phead->next;
	ListNode* pcurnext = pcur->next;
	//phead pcur pcurnext三个进行解绑
	phead->next = pcurnext;
	pcurnext->prev = phead;
	free(pcur);
	//如果只有一个数据也满足

寻找节点

这个就只能去遍历了从哨兵节点的下一个开始,直到该节点为phead,注意不是NULL哦

ListNode* ListFind(ListNode* phead, LTDataType x)
{
	ListNode* pcur = phead->next;
	while (pcur != phead)
	{
		//如果找到了就返回
		if (x == pcur->data)
		{
			return pcur;
		}
		pcur = pcur->next;
	}
	//没找到就返回NULL
	return NULL;
	//如果只有一个数据也满足
}

在任意位置插入节点

在指定的pos的前面插入节点

这个也是十分简单的。

void ListInsert(ListNode* pos, LTDataType x)
{
	assert(pos);
	ListNode* prev = pos->prev;
	ListNode* pinsert = BuyListNode(x);
	pinsert->next = pos;
	pinsert->prev = prev;
	prev->next = pinsert;
	pos->prev = pinsert;
	//如果只有一个节点也满足
}

删除该节点

删除在pos位置上的节点

//删除该节点的值
void ListErase(ListNode* pos)
{
	assert(pos);
	ListNode* pfront = pos->prev;
	ListNode* pbehind = pos->next;
	pfront->next = pbehind;
	pbehind->prev = pfront;
	free(pos);
	pos = NULL;
	//如果只有一个节点也满足

销毁链表

将每一个节点并且包括头节点进行删除。后面的具体节点就正常删除就可以了。

但是对于哨兵节点来说,要特殊处理。

因为在我们函数传参的时候传的是哨兵节点的值,所以改变形参的值不会改变实参。

所以我们要在调用函数的主函数内手动再置为NULL

assert(phead);
	ListNode* pcur = phead->next;
	while (pcur != phead)
	{
		ListNode* pnext = pcur->next;
		free(pcur);
		pcur = pnext;
	}
	//不要忘记将phead也要释放
	free(phead);
	phead = NULL;
	//但是这里是值传递,这样不起作用,所以要在test中
	//手动置空
	plist = NULL;
	//这里为了保持接口一致,采用了值传递。
	//在函数中的置空不起作用,所以要在这里手动置null

这就是我们的双向链表的实现。

传值实现or传址实现?

还有一个很值得去说的问题,那就是在上面的整个过程中,我们一直使用的都是哨兵节点的值,而没有去传指针。

因为这里传值就可以起到效果,不需要再去传址了。因为我们直接就是在进行链接工作,并不没有对

下面就可以解释:

image-20210930155808332
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值