数据结构之链表(双链表)

目录

一、双向带头循环链表

概念

二、哨兵位的头节点

优点:

头节点的初始化

三、带头双向链表的实现

1.双链表的销毁

2.双链表的打印

3.双链表的尾插和头插

尾插:

头插:

4.双链表的尾删和头删

尾删:

头删:

5.双链表的查找

四、测试代码


一、双向带头循环链表

概念

名如其实,这个链表结构有哨兵位头节点,双向并且循环, 结构最复杂。一般用在单独存储数据。实际中使用的链表数据结构,都是带头双向循环链表。另外这个结构虽然结构复杂,但是使用代码实现以后会发现结构会带 来很多优势,实现反而简单了,后面我们代码实现了就知道了这里我们用一张图片就能很好的看清楚双向带头循环链表的结构了。

二、哨兵位的头节点

从上一篇文章我们就在说带头/不带头,那么这个头是什么呢?其实它就是哨兵位的头节点。这个节点一般在一个链表的最前方的位置,不用来储存数据。

优点:

  1. 简化边界条件处理:

• 在没有哨兵节点的情况下,链表的头插、头删等操作需要特别处理头节点为空的情况。

• 使用哨兵节点后,头节点始终存在,简化了插入和删除操作的逻辑,不需要单独处理头节点为空的情况。

  1. 统一操作逻辑:

• 无论是头插、尾插、头删还是尾删,操作逻辑都可以统一处理,不需要区分是否是第一个节点。

• 例如,插入操作总是插入到哨兵节点之后,删除操作总是删除哨兵节点之后的节点。

  1. 提高代码可读性和维护性:

• 由于边界条件处理简化,代码逻辑更加清晰,减少了出错的可能性。

• 代码维护起来也更加方便,因为不需要在多个地方处理特殊情况。

  1. 便于实现某些算法:

• 在某些算法中,使用哨兵节点可以避免多次检查链表是否为空的情况,提高算法的效率。

头节点的初始化

cpp 复制代码
// 创建返回链表的头结点.
ListNode* ListCreate()
{
	ListNode* newnode = (ListNode*)malloc(sizeof(ListNode));
	if (newnode == NULL)
	{
		perror("malloc failed");
		exit(-1);
	}
	newnode->data = 0;
	newnode->next = newnode;
	newnode->prev = newnode;
	return newnode;
}

三、带头双向链表的实现

1.双链表的销毁

与单链表的销毁类似,需要定义一个指针来遍历整个链表,但是注意,如果是从头节点开始遍历,我们会因为无法很好的控制停止条件而导致无限循环,所以我们从头节点的下一个开始遍历,当这个cur指针指向头节点时就停止,这里后面还会反复用到,请务必想清楚。

cpp 复制代码
// 双向链表销毁
void ListDestory(ListNode* plist)
{
	assert(plist);
	ListNode* cur = plist->next;
	while (cur != plist)
	{
		ListNode* next = cur->next;
		free(cur);
		cur = next;
	}
	free(plist);
	plist = NULL;
}

2.双链表的打印

cpp 复制代码
// 双向链表打印
void ListPrint(ListNode* plist)
{
	ListNode* cur = plist->next;
	while (cur != plist)
	{
		printf("%d<=>",cur->data);
		cur = cur->next;
	}
	printf("NULL\n");
}

3.双链表的尾插和头插

尾插:

cpp 复制代码
// 双向链表尾插
void ListPushBack(ListNode* plist, LTDataType x)
{
	// 双向链表的找尾很简单,只需要指向plist的前一个节点就行
	ListNode* newnode = buyNewnode(x);
	ListNode* tail = plist->prev;
	tail->next = newnode;
	newnode->prev = tail;
	newnode->next = plist;
	plist->prev = newnode;
}

头插:

cpp 复制代码
// 双向链表头插
void ListPushFront(ListNode* plist, LTDataType x)
{
	assert(plist);
	ListNode* newnode = buyNewnode(x);
	ListNode* head = plist->next;
	plist->next = newnode;
	newnode->prev = plist;
	newnode->next = head;
	head->prev = newnode;
}

4.双链表的尾删和头删

尾删:

cpp 复制代码
// 双向链表尾删
void ListPopBack(ListNode* plist)
{
	assert(plist);
	if (plist->next == plist)
	{
		return;
	}
	ListNode* tail = plist->prev;
	ListNode* prev = tail->prev;
	free(tail);
	tail = NULL;
	prev->next = plist;
	plist->prev = prev;
}

头删:

cpp 复制代码
// 双向链表头删
void ListPopFront(ListNode* plist)
{
	assert(plist);
	if (plist->next = plist)
	{
		return;
	}
	ListNode* head = plist->next;
	ListNode* newHead = head->next;
	free(head);
	head = NULL;
	plist->next = newHead;
	newHead->prev = plist;
}

5.双链表的查找

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

四、测试代码

cpp 复制代码
// 2、带头+双向+循环链表增删查改实现
typedef int LTDataType;
typedef struct ListNode
{
	LTDataType data;
	struct ListNode* next; 
	struct ListNode* prev;
}ListNode;
// 创建返回链表的头结点.
ListNode* buyNewnode(LTDataType x)
{
	ListNode* newnode = (ListNode*)malloc(sizeof(ListNode));
	if (newnode == NULL)
	{
		perror("malloc failed");
		exit(-1);
	}
	newnode->data = x;
	newnode->next = NULL;
	newnode->prev = NULL;
	return newnode;
}
ListNode* ListCreate()
{
	ListNode* newnode = (ListNode*)malloc(sizeof(ListNode));
	if (newnode == NULL)
	{
		perror("malloc failed");
		exit(-1);
	}
	newnode->data = 0;
	newnode->next = newnode;
	newnode->prev = newnode;
	return newnode;
}
// 双向链表销毁
void ListDestory(ListNode* plist)
{
	assert(plist);
	ListNode* cur = plist->next;
	while (cur != plist)
	{
		ListNode* next = cur->next;
		free(cur);
		cur = next;
	}
	free(plist);
	plist = NULL;
}
// 双向链表打印
void ListPrint(ListNode* plist)
{
	ListNode* cur = plist->next;
	while (cur != plist)
	{
		printf("%d<=>",cur->data);
		cur = cur->next;
	}
	printf("NULL\n");
}
// 双向链表尾插
void ListPushBack(ListNode* plist, LTDataType x)
{
	// 双向链表的找尾很简单,只需要指向plist的前一个节点就行
	ListNode* newnode = buyNewnode(x);
	ListNode* tail = plist->prev;
	tail->next = newnode;
	newnode->prev = tail;
	newnode->next = plist;
	plist->prev = newnode;
}
// 双向链表尾删
void ListPopBack(ListNode* plist)
{
	assert(plist);
	if (plist->next == plist)
	{
		return;
	}
	ListNode* tail = plist->prev;
	ListNode* prev = tail->prev;
	free(tail);
	tail = NULL;
	prev->next = plist;
	plist->prev = prev;
}
// 双向链表头插
void ListPushFront(ListNode* plist, LTDataType x)
{
	assert(plist);
	ListNode* newnode = buyNewnode(x);
	ListNode* head = plist->next;
	plist->next = newnode;
	newnode->prev = plist;
	newnode->next = head;
	head->prev = newnode;
}
// 双向链表头删
void ListPopFront(ListNode* plist)
{
	assert(plist);
	if (plist->next = plist)
	{
		return;
	}
	ListNode* head = plist->next;
	ListNode* newHead = head->next;
	free(head);
	head = NULL;
	plist->next = newHead;
	newHead->prev = plist;
}
// 双向链表查找
ListNode* ListFind(ListNode* plist, LTDataType x)
{
	assert(plist);
	ListNode* cur = plist->next;
	while (cur != plist)
	{
		if (cur->data == x)
		{
			return cur;
		}
		cur = cur->next;
	}
	return NULL;
}
int main()
{
	ListNode* plist = ListCreate();
	ListPushBack(plist, 1);
	ListPushBack(plist, 2);
	ListPushBack(plist, 3);
	ListPrint(plist);

	ListPopBack(plist);
	ListPrint(plist);

	ListPushFront(plist, 0);
	ListPrint(plist);

	ListPopFront(plist);
	ListPrint(plist);

	return 0;
}
相关推荐
眼镜哥(with glasses)2 小时前
蓝桥杯 国赛2024python(b组)题目(1-3)
数据结构·算法·蓝桥杯
int型码农6 小时前
数据结构第八章(一) 插入排序
c语言·数据结构·算法·排序算法·希尔排序
怀旧,7 小时前
【数据结构】6. 时间与空间复杂度
java·数据结构·算法
积极向上的向日葵7 小时前
有效的括号题解
数据结构·算法·
Java 技术轻分享8 小时前
《树数据结构解析:核心概念、类型特性、应用场景及选择策略》
数据结构·算法·二叉树··都差速
chao_7899 小时前
链表题解——两两交换链表中的节点【LeetCode】
数据结构·python·leetcode·链表
曦月逸霜9 小时前
第34次CCF-CSP认证真题解析(目标300分做法)
数据结构·c++·算法
吴声子夜歌12 小时前
OpenCV——Mat类及常用数据结构
数据结构·opencv·webpack
笑口常开xpr13 小时前
数 据 结 构 进 阶:哨 兵 位 的 头 结 点 如 何 简 化 链 表 操 作
数据结构·链表·哨兵位的头节点
@我漫长的孤独流浪14 小时前
数据结构测试模拟题(4)
数据结构·c++·算法