【数据结构】双向循环链表


一、前言

上一篇文章我们详解了单向链表,现在我们进行对双向链表的学习

点击进入单项链表的学习:单项链表


二、双向带头循环链表

1.结构

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

在这里插入图片描述

与单项链表不同的是,双向循环链表的每个结点都增加了一个Prev指针,指向了上一个结点,实现了链表的双向访问。

带头的意思是增加了一个头部head结点,此结点不存放数据仅仅表示头部。


2.代码实现

跟单项链表一样,我们同样需要实现增、删、查、改。所以我们要构建以下函数接口:

#pragma once

#include <stdio.h>
#include <stdlib.h>
#include <assert.h>

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

// 创建返回链表的头结点.
ListNode* ListBuyNode(LTDataType x);
//链表初始化
ListNode* ListInit();
// 双向链表销毁
void ListDestory(ListNode* pHead);
// 双向链表打印
void ListPrint(ListNode* pHead);
// 双向链表尾插
void ListPushBack(ListNode* pHead, LTDataType x);
// 双向链表尾删
void ListPopBack(ListNode* pHead);
// 双向链表头插
void ListPushFront(ListNode* pHead, LTDataType x);
// 双向链表头删
void ListPopFront(ListNode* pHead);
// 双向链表查找
ListNode* ListFind(ListNode* pHead, LTDataType x);
// 双向链表在pos的前面进行插入
void ListInsert(ListNode* pos, LTDataType x);
// 双向链表删除pos位置的节点
void ListErase(ListNode* pos);

下面我带大家一一实现以上接口:


创建返回链表的头结点:ListBuyNode

第一步:在内存中malloc一片空间
第二步:将申请的空间的date赋值,并将next与prev置为空指针
这样我们就得到了一个单独的链表结点
在这里插入图片描述

ListNode* ListBuyNode(LTDataType x)
{
	ListNode* newnode = (ListNode*)malloc(sizeof(ListNode));
	if (newnode == NULL)
	{
		perror("malloc error");
		exit(-1);
	}

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

链表初始化:ListInit

第一步:创建一个头结点
第二步:将这个头节点以循环的结构链接起来
在这里插入图片描述

ListNode* ListInit()
{
	ListNode* phead = ListBuyNode(0);
	phead->next = phead;
	phead->prev = phead;
	return phead;
}

双向链表打印:ListPrint

第一步:找到头节点的下一个结点
在这里插入图片描述
第二步:依次向后遍历,直到cur != pHead结束
在这里插入图片描述

void ListPrint(ListNode* pHead)
{
	assert(pHead);
	printf("pHead<=>");
	ListNode* cur = pHead->next;
	while (cur != pHead)
	{
		printf("%d<=>", cur->data);
		cur = cur->next;
	}
	printf("pHead\n");
}

双向链表尾插:ListPushBack

第一步:找尾tail = pHead->prev,因为是双向循环结构,找尾只需要找通过头结点的上一个即可
在这里插入图片描述
第二步:将tail与newhead进行链接,newhead与pHead进行链接即可
在这里插入图片描述

void ListPushBack(ListNode* pHead, LTDataType x)
{
	assert(pHead);
	ListNode* tail = pHead->prev;
	ListNode* newhead = ListBuyNode(x);

	newhead->next = pHead;
	pHead->prev = newhead;
	tail->next = newhead;
	newhead->prev = tail;
}

双向链表尾删:ListPopBack

第一步:找出倒数第二个结点tail
在这里插入图片描述
第二步:释放掉倒数第一个结点
第三步:链接tail与pHead

void ListPopBack(ListNode* pHead)
{
	assert(pHead);
	assert(pHead->next != pHead);

	ListNode* tail = pHead->prev->prev;
	free(tail->next);
	tail->next = pHead;
	pHead->prev = tail;
}

三、链表与顺序表的区别

不同点顺序表链表
存储空间上物理上一定连续逻辑上连续,但物理上不一定连续
随机访问支持O(1)不支持:O(N)
任意位置插入或者删除元素可能需要搬移元素,效率低O(N)只需修改指针指向
插入动态顺序表,空间不够时需要扩容没有容量的概念
应用场景元素高效存储+频繁访问任意位置插入和删除频繁
缓存利用率

在这里插入图片描述
在这里插入图片描述


总结

双向链表剩下的接口与上文非常相似,本文不再赘述,有兴趣的请移步小编的个人博客:https://github.com/wuming12138/C_CODE

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

楼鱼睡觉的猫

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值