带头循环双向链表

带头循环双向链表

1. 介绍

带头双向循环链表:结构最复杂,一般用在单独存储数据。实际中使用的链表数据结构,都是带头双向循环链表。另外这个结构虽然结构复杂,但是使用代码实现以后会发现结构会带来很多优势,实现反而简单了,后面我们代码实现了就知道了。

在这里插入图片描述

2. 带头循环双向链表的项目实现

文件名功能
List.h创建双链表,完成函数名的声明
List.c实现双链表的各个功能函数
test.c测试双链表函数的正确性

2.1 双链表结构定义

typedef int LTDataType;
typedef struct ListNode
{
	struct ListNode* next;
	struct ListNode* prev;
	LTDataType data;
}LTNode;

2.2 动态申请一个结点

LTNode* BuyListNode(LTDataType x)
{
	LTNode* newnode = (LTNode*)malloc(sizeof(LTNode));
	if (newnode == NULL)
	{
		perror("malloc fail");
		exit(-1);
	}

	newnode->data = x;
	newnode->prev = NULL;
	newnode->next = NULL;

	return newnode;
}

2.3 初始化双链表

初始化双链表其实就是在创建哨兵位的头结点
需要改变phead,因为实参是一个一级指针,如果形参也只是一级指针,就属于值传递了,函数内pphead的值改变并不会影响外面的实参。而初始化是需要修改实参的,这里介绍两种方法: 1. 用二级指针 2. 采用返回值
这里使用返回值

//初始化 ---> 创建哨兵位的头结点
LTNode* LTInit()
{
	LTNode* phead = BuyListNode(-1);     //创建带哨兵位的头结点, 其存储数据随便给
	phead->prev = phead;
	phead->next = phead;

	return phead;
}

2.4 打印双链表

while循环遍历打印,遇到哨兵位头结点不打印

void LTPrint(LTNode* phead)
{
	assert(phead);

	LTNode* cur = phead->next;

	while (cur != phead)
	{
		printf("%d ", cur->data);
		cur = cur->next;
	}
	printf("\n");
}

2.5 双链表尾插

原版

定义一个尾结点(哨兵位头结点的prev), 开辟新结点newnode, 将newnode链接到tail后面并链接好哨兵位头结点

在这里插入图片描述

void LTPushBack(LTNode* phead, LTDataType x)
{
	assert(phead);    //链表中还有哨兵位的头结点不能删除

	LTNode* newnode = BuyListNode(x);

	LTNode* tail = phead->prev;
	tail->next = newnode;
	newnode->prev = tail;

	newnode->next = phead;
	phead->prev = newnode;
}
复用版

将LTInsert中的第一个参数换成phead(在哨兵位头结点的前面尾插)

在这里插入图片描述

void LTPushBack(LTNode* phead, LTDataType x)
{
	LTInsert(phead, x);
}

2.6 双链表尾删

原版

记录尾结点的上一个, 并将其与head结点链接起来, 最后释放掉tail结点

注意: 哨兵位头结点不能删

在这里插入图片描述

void LTPopBack(LTNode* phead)
{
	assert(phead);
	assert(phead->prev != phead);    //不能删掉哨兵位头结点

	LTNode* tail = phead->prev;
	LTNode* tailPrev = tail->prev;

	tailPrev->next = phead;
	phead->prev = tailPrev;

	free(tail);
}
复用版

将LTErase中的参数换成phead->prev(双链表的尾)

void LTPopBack(LTNode* phead)
{
	LTErase(phead->prev);
}

2.7 双链表头插

原版

注意: 双链表的头结点是指哨兵位头结点的下一个
开辟一个新结点, 将此结点前后链接起来

在这里插入图片描述

void LTPushFront(LTNode* phead, LTDataType x)
{
	assert(phead);

	LTNode* newnode = BuyListNode(x);
	LTNode* nextnode = phead->next;
	nextnode->prev = newnode;

	newnode->next = nextnode;
	newnode->prev = phead;
	phead->next = newnode;
}
复用版

将LTInsert中的第一个参数换成phead->next(双链表真正的头)

void LTPushFront(LTNode* phead, LTDataType x)
{
	LTInsert(phead->next, x);
}

2.8 双链表头删

原版

记录头结点的下一个, 释放掉头结点, 将原头结点的下一个与head结点链接起来
注意: 哨兵位头结点不能删

在这里插入图片描述

void LTPopFront(LTNode* phead)
{
	assert(phead);
	assert(phead->prev != phead);    //不能删掉哨兵位头结点

	LTNode* first = phead->next;
	LTNode* second = first->next;

	free(first);
	second->prev = phead;
	phead->next = second;
}
复用版

将LTErase中的参数换成phead->next(双链表真正的头)

void LTPopFront(LTNode* phead)
{
	LTErase(phead->next);
}

2.9 查找双链表中的元素

遍历查找双链表中的元素,找到返回此结点处的指针,找不到返回NULL

注意: 不查找哨兵位头结点

LTNode* LTFind(LTNode* phead, LTDataType x)
{
	assert(phead);

	LTNode* cur = phead->next;

	while (cur != phead)
	{
		if (cur->data == x)
			return cur;

		cur = cur->next;
	}

	return NULL;
}

2.10 双链表在pos位置前插入元素

调用LTFind函数查找到pos位置, 开辟新结点, 将其链接起来
注意: 判断pos位置的合法性

在这里插入图片描述

void LTInsert(LTNode* pos, LTDataType x)
{
	assert(pos);       //pos位置的合法性

	LTNode* newnode = BuyListNode(x);

	LTNode* prev = pos->prev;
	prev->next = newnode;

	newnode->prev = prev;
	newnode->next = pos;
	pos->prev = newnode;
}

2.11 双链表删除pos位置的元素

调用LTFind函数查找到pos位置, 记录pos位置的前后结点, 释放pos位置, 并将原pos位置前后链接起来
注意: 判断pos位置的合法性

在这里插入图片描述

void LTErase(LTNode* pos)
{
	assert(pos);

	LTNode* next = pos->next;
	LTNode* prev = pos->prev;
	free(pos);

	prev->next = next;
	next->prev = prev;
}

2.12 判断双链表是否为空

双链表为空是指, 链表中只剩下哨兵位的头结点
故判空操作,就是判断phead->prev == phead;phead->pnext == phead;

在这里插入图片描述

bool LTEmpty(LTNode* phead)
{
	assert(phead);

	return phead->prev == phead;
}

2.13 计算双链表中结点个数

定义一个变量size, 遍历链表计算链表中结点个数并返回

size_t LTsize(LTNode* phead)
{
	assert(phead);

	LTNode* cur = phead->next;

	size_t size = 0;
	while (cur != phead)
	{
		size++;
		cur = cur->next;
	}
	return size;
}

2.14 销毁双链表

先while循环释放除哨兵位以外其他结点, 最后再释放哨兵位头结点

void LTDestroy(LTNode* phead)
{
	assert(phead);

	LTNode* cur = phead->next;

	//释放处哨兵位以外其他结点
	while (cur != phead)
	{
		LTNode* next = cur->next;
		free(cur);
		cur = next;
	}

	free(phead);         //释放哨兵位头结点
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值