【数据结构】带头双向链表,真正的六边形战士

概要

*数据结构中的链表在实际开发中应用非常广泛,但写一个链表并不是一件简单的事情。

链表有八种结构,对于刚入门的新手来说,往往会先经历单链表的折磨。
而今天我要讲的带头双向链表非常适合新手学习,它实现起来不像单链表那么繁琐,应用起来更加得心应手,最主要的是它能让我们更好的理解链表。*

整体架构流程

1.创建哨兵位
哨兵位介绍:带头链表俗称哨兵位,哨兵位的作用非常强大,它可以让我们进行头删头插时不使用双指针,增加代码可读性。往往哨兵位里的内容是没有实际意义的,因此也不算做有效节点。

**.h头文件代码**
// 带头+双向+循环链表增删查改实现
typedef int LTDataType;
typedef struct ListNode
{
	LTDataType data;
	struct ListNode* prev;
	struct ListNode* next;
}ListNode;
// 创建返回链表的头结点.(创建哨兵位)
ListNode* ListCreate();

**.c逻辑代码**
// 创建返回链表的头结点.(创建哨兵位)
ListNode* BuyHead(LTDataType x)
{
	ListNode* phead = (ListNode*)malloc(sizeof(ListNode));
	if (phead == NULL)
	{
		perror("malloc fial:");
	}
	phead->data = x;
	phead->next = NULL;
	phead->prev = NULL;
	return phead;
}
ListNode* ListCreate()
{
	ListNode* guard =  BuyHead(-1);
	guard->next = guard;
	guard->prev = guard;
	return guard;
}

2.双向链表销毁

**.h头文件**
// 双向链表销毁
void ListDestory(ListNode* pHead);

**.c逻辑代码**
// 双向链表销毁
void ListDestory(ListNode* pHead)
{
	assert(pHead);
	ListNode* cur = pHead->next;
	while (cur != pHead)
	{
		ListNode* next = cur->next;
		free(cur);
		cur = next;
	}
	free(pHead);
}

3.双向链表打印

**.h头文件**
// 双向链表打印
void ListPrint(ListNode* pHead);

**.c逻辑代码**
// 双向链表打印
void ListPrint(ListNode* pHead)
{
	assert(pHead);
	ListNode* cur = pHead->next;
	printf("Guard<=>");
	while (cur != pHead)
	{
		printf("%d<=>", cur->data);
		cur = cur->next;
	}
	printf("\n");
}

4.双向链表尾插

**.h头文件**
// 双向链表尾插
void ListPushBack(ListNode* pHead, LTDataType x);

**.c逻辑代码**
// 双向链表尾插
void ListPushBack(ListNode* pHead, LTDataType x)
{
	assert(pHead);
	ListNode* tail = pHead->prev;
	ListNode* Tailnext = BuyHead(x);
	tail->next = Tailnext;
	Tailnext->prev = tail;
	Tailnext->next = pHead;
	pHead->prev = Tailnext;
}

在这里插入图片描述

**5.双向链表尾删 **

.h头文件
// 双向链表尾删
void ListPopBack(ListNode* pHead);

.c逻辑代码
//用于判断链表中是否有节点
bool ListEmpty(ListNode* phead)
{
	assert(phead);
	return phead->next == phead;
}
// 双向链表尾删
void ListPopBack(ListNode* pHead)
{
	assert(pHead);
	assert(!ListEmpty(pHead));
	ListNode* tail = pHead->prev;
	ListNode* tailPrev = tail->prev;
	free(tail);
	tailPrev->next = pHead;
	pHead->prev = tailPrev;
}

在这里插入图片描述

6.判断链表中是否有节点

.h文件
//用于判断链表中是否有节点
bool ListEmpty(ListNode* phead);

.c逻辑代码
//用于判断链表中是否有节点
bool ListEmpty(ListNode* phead)
{
	assert(phead);
	return phead->next == phead;
}

7.双向链表头插

.h文件
// 双向链表头插
void ListPushFront(ListNode* pHead, LTDataType x);

.c逻辑代码
// 双向链表头插
void ListPushFront(ListNode* pHead, LTDataType x)
{
	assert(pHead);
	ListNode* newhead = BuyHead(x);
	ListNode* cur = pHead->next;
	pHead->next = newhead;
	newhead->prev = pHead;
	newhead->next = cur;
	cur->prev = newhead;
}

在这里插入图片描述

8.双向链表头删

.h文件
// 双向链表头删
void ListPopFront(ListNode* pHead);

.c逻辑代码
// 双向链表头删
void ListPopFront(ListNode* pHead)
{
	assert(pHead);
	assert(!ListEmpty(pHead));
	ListNode* cur = pHead->next;
	ListNode* headnext = cur->next;
	pHead->next = headnext;
	headnext->prev = pHead;
	free(cur);
}

在这里插入图片描述

9.双向链表查找

.h文件
// 双向链表查找
ListNode* ListFind(ListNode* pHead, LTDataType x);

// 双向链表查找
ListNode* ListFind(ListNode* pHead, LTDataType x)
{
	assert(pHead);
	ListNode* cur = pHead->next;
	while (cur != pHead)
	{
		if (cur->data == x)
		{
			return cur;
		}
		cur = cur->next;
	}
	return NULL;
}

10.双向链表在pos的前面进行插入

.h文件
// 双向链表在pos的前面进行插入
void ListInsert(ListNode* pos, LTDataType x);

.c逻辑代码
// 双向链表在pos的前面进行插入
void ListInsert(ListNode* pos, LTDataType x)
{
	assert(pos);
	ListNode* prev = pos->prev;
	ListNode* newnode = BuyHead(x);
	prev->next = newnode;
	newnode->prev = prev;
	newnode->next = pos;
	pos->prev = newnode;
}

.c测试代码
	//常和查找一起使用
	ListNode* Find = ListFind(p,3);
	if (Find)
	{
		ListInsert(Find, 6);
	}

在这里插入图片描述

11.双向链表删除pos位置的节点

.h头文件
// 双向链表删除pos位置的节点
void ListErase(ListNode* pos);

.c逻辑代码
// 双向链表删除pos位置的节点
void ListErase(ListNode* pos)
{
	assert(pos);
	assert(!ListEmpty(pos));
	ListNode* prev = pos->prev;
	ListNode* next = pos->next;
	prev->next = next;
	next->prev = prev;
	free(pos);
}

.c测试代码
	ListNode* Find = ListFind(p,3);
	if (Find)
	{
		LisErase(Find);
	}

在这里插入图片描述

12.插入(头插、尾插)复用
分析序号10代码,我们可以得到该代码也适用于头插、尾插,因此我们可以将头插、尾插简化:

// 双向链表头插
void ListPushFront(ListNode* pHead, LTDataType x)
{
	assert(pHead);
	ListNode* cur = pHead->next;
	ListInsert(cur, x);
}

// 双向链表尾插
void ListPushBack(ListNode* pHead, LTDataType x)
{
	assert(pHead);
	ListNode* tail = pHead;
	ListInsert(tail, x);
}

13.删除(头删、尾删)复用
分析序号11代码,我们可以得到该代码也适用于头删、尾删,因此我们可以将头删、尾删简化:

// 双向链表头删
void ListPopFront(ListNode* pHead)
{
	assert(pHead);
	ListNode* cur = pHead->next;
	ListErase(cur);
}

// 双向链表尾删
void ListPopBack(ListNode* pHead)
{
	assert(pHead);
	ListNode* tail = pHead->prev;
	ListErase(tail);
}

小结

带头双向链表的实现并不复杂,但需要我们对单链表有一定的了解程度,因此在学习带头双向链表前,最好先学习单链表,带头双向链表虽然被称为六边形战士,但我们也需要先学好基本知识,
再利用好带头双向链表,完全有可能做到十分钟实现链表,谢谢大家!

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值