数据结构篇 --- 带头双向循环链表增删查改

简述:有了前篇单链表的增删查改的基础 也是出于带头双向循环链表本身的优势  双链表的增删就变得格外简单。让我们一起来体验一下。

目录

带头双向循环链表图解:

带头双向循环链表基本结构:

带头双向循环链表的实现:

初始化:

创建新节点:

尾插:

打印:

尾删:

头插:

头删:

查找:

在某位置之前插入节点:

删除某位置的节点:

删除链表


带头双向循环链表图解:

带头双向循环链表基本结构:

typedef struct ListNode
{
LTDataType _data;
struct ListNode* next;
struct ListNode* prev;
}ListNode;

带头双向循环链表的实现:

函数实现基本框架的创建不再详述...

初始化:

与无头单向不循环链表不同的是 这里需要创建哨兵位 即所谓的头

这里有两种方式:第一种是传二级指针 另一种是返回值法 我们来了解一下

void ListInit(LTNode** phead)//用二级指针的方法
{
	*phead = (LTNode*)malloc(sizeof(LTNode));//要发生对phead的修改 所以这里传二级指针
	(*phead)->next = *phead;//调整存放的前后指针
	(*phead)->prev = *phead;
}
LTNode* ListInit(LTNode* phead)//返回值法
{
	phead = (LTNode*)malloc(sizeof(LTNode));
	phead->next = phead;
	phead->prev = phead;
	return phead;
}

两种方法均可以实现 但是返回值法要注意记得接收返回值

创建新节点:

因为后面尾插、头插、指定位置前插节点都要创建新节点 因此我们这里将其包装成一个函数

prev、next前后设空是个好习惯

LTNode* BuyListNode(LTDateType x)
{
	LTNode* newnode = (LTNode*)malloc(sizeof(LTNode));
	newnode->val = x;
	newnode->next = NULL;
	newnode->prev = NULL;
	return newnode;
}

尾插:

assert也是个好习惯

链接思路看图解

void ListPushBack(LTNode* phead, LTDateType x)
{
	assert(phead);
	LTNode* newnode = BuyListNode(x);
	//链接
	LTNode* tail = phead->prev;
	phead->prev = newnode;//哨兵的头
	newnode->next = phead;//新节点的尾
	tail->next = newnode;
	newnode->prev = tail;
	//ListInsert(phead,x);//后面就知道啦
}

打印:

与单向不循环链表思路相同 不再详述

void ListPrint(LTNode* phead)
{
	assert(phead);
	LTNode* cur = phead->next;
	while (cur != phead)
	{
		printf("%d ", cur->val);
		cur = cur->next;
	}
	printf("\n");

}

尾删:

值得注意得是 我们这里的头结点不存储任何有效数据  作为哨兵位

要设置条件避免误删  我们这里可以设置assert(phead->next!=phead)

void ListPopBcak(LTNode* phead)
{
	assert(phead);
	assert(phead->next != phead);//不注意会把哨兵位也删掉
	LTNode* tail = phead->prev;
	tail->prev->next = phead;
	phead->prev = tail->prev;
	free(tail);
	//ListErase(phead->prev);//后面就知道啦
}

头插:

void ListPushFront(LTNode* phead,LTDateType x)
{
	assert(phead);
	LTNode* next = phead->next;
	LTNode* newnode = BuyListNode(x);

	phead->next = newnode;
	newnode->next = next;
	next->prev = newnode;
	newnode->prev = phead;
	//ListInsert(phead->next, x);//后面就知道啦
}

头删:

void ListPopFront(LTNode* phead)
{
	assert(phead);
	assert(phead->next != phead);//不注意会把哨兵位也删掉
	LTNode* next = phead->next;
	phead->next = next->next;
	next->next->prev = next->prev;
	free(next);
	//ListErase(phead->next);//后面就知道啦
}

查找:

与无头单向不循环链表相似 

但是需要注意的是停止循环的条件: 如果还是到next==NULL停止 那么就死循环了

同时避免他一遍又一遍的找 多找几遍效果也是一样的 

因此这里我们设置当他循环回哨兵位的时候 循环结束

LTNode* ListFind(LTNode* phead, LTDateType x)
{
	assert(phead);
	LTNode* next = phead->next;
	while (next != phead)
	{
		if (next->val == x)
			return next;
		next = next->next;
	}
	return NULL;
}

在某位置之前插入节点:

来解密了:前面的《后面就知道啦》

void ListInsert(LTNode* pos, LTDateType x)
{
	assert(pos);
	LTNode* newnode = BuyListNode(x);
	LTNode* prev = pos->prev;
	
	prev->next = newnode;
	newnode->prev = prev;
	newnode->next = pos;
	pos->prev = newnode;
}

这个函数是可以应用在之前的头插尾插函数中的。

删除某位置的节点:

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

删除链表

void Destroy(LTNode* phead)
{
	assert(phead);
	LTNode* cur = phead->next;
	while (cur != phead)
	{
		LTNode* next = cur->next;
		free(cur);
		cur = next;
	}
	free(phead);
	phead = NULL;
}

需要注意的是这里的phead=NULL 并不能实际改变phead的值 (传值操作)

但是为了保持接口的一致性 因此需要在函数之外 给phead=NULL

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值