数据结构——带头双向循环链表

呀哈喽,我是结衣。

前言

说到链表前面我们讲了单链表,但是链表可不止一种,要分类的话。链表可以分为带头或不带头,单向或双向,循环或者不循环,也就是说链表一共应该是有8种结构的,我们上次讲的链表就是不带头单向不循环链表。是链表中结构最简单的一种。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
我们在来简单的解释一下两种链表把
1.无头单向非循环链表:结构简单,一般不会单独用来存数据。实际中更多是作为其他数据结
构的子结构,如哈希桶、图的邻接表等等。另外这种结构在笔试面试中出现很多。
2.带头双向循环链表:结构最复杂,一般用在单独存储数据。实际中使用的链表数据结构,都
是带头双向循环链表。另外这个结构虽然结构复杂,但是使用代码实现以后会发现结构会带来很多优势,实现反而简单了,后面我们代码实现了就知道了。

链表的实现

前边讲了那么多,最重要的还是要自己能把链表给实现了。下面就是实现的教学了。

创建不同文件

创建3个文件,分别要实现的功能为函数的声明,函数的定义,以及测试。目的是方便后续的增删查改。
在这里插入图片描述

结构体的创建

prev表示上一个节点,next表示下一个节点。
在这里插入图片描述

函数的声明

和单链表的函数声明差不多,都是头删,头插,尾插,尾删,打印等等…
我们先函数的声明先写完,后面再对他们挨个实现。

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
typedef int LTDataType;
typedef struct ListNode
{
	LTDataType data;
	struct ListNode* prev;
	struct ListNode* next;
}ListNode;

// 创建返回链表的头结点.
ListNode* ListCreate();
// 双向链表销毁
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);

写完函数声明接下来就是函数的实现了。

函数的实现

函数的实现写在list.c文件里面
我们先来讲头节点的创建。头节点是链表的头部且不会存储有效的数据。

头节点的创建

// 创建返回链表的头结点.
ListNode* ListCreate()
{
	ListNode* head = (ListNode*)malloc(sizeof(ListNode));
	head->data = -1;//可以随便存一个数据,在后续的操作中不会用到头节点的数据。
	head->prev = head;
	head->next = head;//prev和next都要指向本身,以实现循环。
	return head;
}

写完头节点的创建,我们还要写一个打印函数,为了让后续的操作更加直观。

打印函数的实现

// 双向链表打印
void ListPrint(ListNode* pHead)
{
	ListNode* cur = pHead->next;
	printf("phead");
	while (cur != pHead)
	{
		printf("%d<=>", cur->data);
		cur = cur->next;
	}
	printf("phead\n");
}

cur!=pHead,目的是为了不打印头节点。
下面我们要写一个插入函数,才能直观的观察。所以我们接下来写尾插函数。

尾插函数的实现

在这里插入图片描述
画图解释

// 双向链表尾插
void ListPushBack(ListNode* pHead, LTDataType x)
{
	assert(pHead);
	ListNode* newnode = CreatNode(x);
	ListNode* cur = pHead;
	ListNode* tail = pHead->prev;
	tail->next = newnode;
	newnode->prev = tail;
	cur->prev = newnode;
	newnode->next = cur;
}

写到尾插函数,我们又要写一个新节点的创建函数

ListNode* CreatNode(LTDataType x)
{
	ListNode* newnode = (ListNode* )malloc(sizeof(ListNode));
	newnode->data = x;
	newnode->next = NULL;
	newnode->prev = NULL;
	return newnode;
}

欧克,写完了开始打印测试。
在这里插入图片描述
看上去是成功了,那就说明没什么问题了,有问题后续再检查。

头插函数的实现

尾插写完当然就是头插了。
在这里插入图片描述
画图解释

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

接下来测试看看。
在这里插入图片描述
依旧没有问题。

尾删函数的实现

删除后要记得free哦

// 双向链表尾删
void ListPopBack(ListNode* pHead)
{
	assert(pHead->next != pHead);//当只有头节点时报错
	ListNode* cur = pHead;
	ListNode* tail = pHead->prev;
	cur->prev = tail->prev;
	tail->prev->next = cur;
	free(tail);
	tail = NULL;
}

测试
我们先删一些,再全部删完,再多删一个分别测试。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
从这三张图片我们也可以看出来,代码是没有问题的。(在这里我悄悄改了一下打印函数的代码,看出来了吗?)

头删函数的实现

要记得free哦

// 双向链表头删
void ListPopFront(ListNode* pHead)
{
	assert(pHead->next != pHead);
	ListNode* first = pHead->next;
	pHead->next = first->next;
	first->next->prev = pHead;
	free(first);
	first = NULL;
}

下面再测试一下,有没有问题。
在这里插入图片描述

在这里插入图片描述
同样也是没有问题的。

销毁函数的实现

为什么就到销毁了呢?当然是想把剩下的指定位置的增删查改当成作业咯。

// 双向链表销毁
void ListDestory(ListNode* pHead)
{
	ListNode* cur = pHead->next;
	while (cur != pHead)
	{
		ListNode* next = cur->next;
		free(cur);
		cur = NULL;
		cur = next;
	}
	//最后free pHead
	free(pHead);
	pHead = NULL;
}

就这样结束咯。
下面我们把代码都贴出来,(也包括查找函数和指定位置的增删查改函数)

代码

list.h

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
typedef int LTDataType;
typedef struct ListNode
{
	LTDataType data;
	struct ListNode* prev;
	struct ListNode* next;
}ListNode;

// 创建返回链表的头结点.
ListNode* ListCreate();
// 双向链表销毁
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);

list.c

#define _CRT_SECURE_NO_WARNINGS
#include "list.h"
ListNode* CreatNode(LTDataType x)
{
	ListNode* newnode = (ListNode* )malloc(sizeof(ListNode));
	newnode->data = x;
	newnode->next = NULL;
	newnode->prev = NULL;
	return newnode;
}
// 创建返回链表的头结点.
ListNode* ListCreate()
{
	ListNode* head = (ListNode*)malloc(sizeof(ListNode));
	head->data = -1;//可以随便存一个数据,在后续的操作中不会用到头节点的数据。
	head->prev = head;
	head->next = head;//prev和next都要指向本身,以实现循环。
	return head;
}
// 双向链表打印
void ListPrint(ListNode* pHead)
{
	ListNode* cur = pHead->next;
	printf("phead<=>");
	while (cur != pHead)
	{
		printf("%d<=>", cur->data);
		cur = cur->next;
	}
	printf("phead\n");
}
// 双向链表尾插
void ListPushBack(ListNode* pHead, LTDataType x)
{
	assert(pHead);
	ListNode* newnode = CreatNode(x);
	ListNode* cur = pHead;
	ListNode* tail = pHead->prev;
	tail->next = newnode;
	newnode->prev = tail;
	cur->prev = newnode;
	newnode->next = cur;
}
// 双向链表头插
void ListPushFront(ListNode* pHead, LTDataType x)
{
	assert(pHead);
	ListNode* newnode = CreatNode(x);
	ListNode* cur = pHead;
	ListNode* next = pHead->next;
	cur->next = newnode;
	newnode->prev = cur;
	newnode->next = next;
	next->prev = newnode;
}
// 双向链表尾删
void ListPopBack(ListNode* pHead)
{
	assert(pHead->next != pHead);//当只有头节点时报错
	ListNode* cur = pHead;
	ListNode* tail = pHead->prev;
	cur->prev = tail->prev;
	tail->prev->next = cur;
	free(tail);
	tail = NULL;
}
// 双向链表头删
void ListPopFront(ListNode* pHead)
{
	assert(pHead->next != pHead);
	ListNode* first = pHead->next;
	pHead->next = first->next;
	first->next->prev = pHead;
	free(first);
	first = NULL;
}
// 双向链表销毁
void ListDestory(ListNode* pHead)
{
	ListNode* cur = pHead->next;
	while (cur != pHead)
	{
		ListNode* next = cur->next;
		free(cur);
		cur = NULL;
		cur = next;
	}
	//最后free pHead
	free(pHead);
	pHead = NULL;
}
// 双向链表查找
ListNode* ListFind(ListNode* pHead, LTDataType x)
{
	ListNode* cur = pHead->next;
	if (x == -1)
	{
		return NULL;
	}
	while (cur != pHead)
	{
		if (cur->data == x)
		{
			return cur;
		}
		cur = cur->next;
	}
	return NULL;
}
// 双向链表在pos的前面进行插入
void ListInsert(ListNode* pos, LTDataType x)
{
	ListNode* newnode = CreateNode(x);
	ListNode* prev = pos->prev;
	prev->next = newnode;
	newnode->prev = prev;
	newnode->next = pos;
	pos->prev = newnode;

}
// 双向链表删除pos位置的节点
void ListErase(ListNode* pos)
{
	ListNode* prev = pos->prev;
	ListNode* next = pos->next;
	prev->next = next;
	next->prev = prev;
	free(pos);
	pos = NULL;
}

test.c

#define _CRT_SECURE_NO_WARNINGS
#include "list.h"
void test1()
{
	ListNode* head = ListCreate();//头节点的创建
	ListPushBack(head, 1);
	ListPushBack(head, 2);
	ListPushBack(head, 3);
	ListPushBack(head, 4);
	ListPrint(head);
	ListPushFront(head, 5);
	ListPushFront(head, 6);
	ListPushFront(head, 7);
	ListPrint(head);
}void test2()
{
	ListNode* head = ListCreate();//头节点的创建
	ListPushBack(head, 1);
	ListPushBack(head, 2);
	ListPushBack(head, 3);
	ListPushBack(head, 4);
	ListPrint(head);
	ListPopFront(head);
	ListPopFront(head);
	ListPopFront(head);
	ListPopFront(head);
	ListPopFront(head);

	ListPrint(head);
}
int main()
{
	test2();

	return 0;
}


在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/143929.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

记一次请求头header丢失问题排查实录

前言 前端小王需要调用兄弟部门老张的后端接口&#xff0c;老张提供的接口&#xff0c;需要token鉴权才能调用成功。当小王按约定携带token调用老张的接口时&#xff0c;起先因为跨域问题&#xff0c;导致前端小王没法成功请求老张的接口。于是小王就跟老张说&#xff0c;能不…

【科研新手指南4】ChatGPT的prompt技巧 心得

ChatGPT的prompt心得 写在最前面chatgpt咒语1&#xff08;感觉最好用的竟然是这个&#xff0c;简单方便快捷&#xff0c;不需要多轮对话&#xff09;chatgpt思维链2&#xff08;复杂任务更适用&#xff0c;简单任务把他弄复杂了&#xff09;机理chatgpt完整咒语1&#xff08;感…

python 文本纠错库pycorrector的使用(API变更,许多介绍文章已不可用)

pycorrector是一个nice的中文检测库&#xff0c;在最新的版本API变更&#xff0c;导致许多之前的介绍文章不可用。 现将新API粘贴如下。

1、 图像和像素

像素我们不陌生,图像我们更不陌生。 学习计算机视觉,我觉得第一步就是要了解我们要处理的对象,就像上一篇说到的,计算机视觉任务中,图像(像素)是原材料,算法是菜谱。 了解了图像的特征,才可以更好的完成更多图像处理任务,比如对一张图片进行分类,或者对一张图片画…

【数据仓库】数仓分层方法详解与层次调用规范

文章目录 一. 数仓分层的意义1. 清晰数据结构。2. 减少重复开发3. 方便数据血缘追踪4. 把复杂问题简单化5. 屏蔽原始数据的异常6. 数据仓库的可维护性 二. 如何进行数仓分层&#xff1f;1. ODS层2. DW层2.1. DW层分类2.2. DWD层2.3. DWS 3. ADS层 4、层次调用规范 一. 数仓分层…

如何使用Echarts

以umi为例 首先是下载两个插件&#xff08;echarts和echarts-for-react&#xff09; npm npm install --save echarts-for-react npm install echarts yarn yarn add echarts-for-react yarn add echarts 接下来是在tsx或jsx中引入使用 import ReactEcharts from "echa…

selenium报错:没有打开网页或selenium.common.exceptions.NoSuchDriverException

文章目录 问题解决方法 问题 当selenium的环境配置没有问题&#xff0c;但在使用selenium访问浏览器时并没有打开网页&#xff0c;或者出现selenium.common.exceptions.NoSuchDriverException报错信息&#xff08;如下图所示&#xff09;。 以上问题可能的原因是没有配置chrom…

Alter database open fails with ORA-00600 kcratr_nab_less_than_odr

Alter database open fails with ORA-00600 kcratr_nab_less_than_odr (Doc ID 1296264.1)​编辑To Bottom APPLIES TO: Oracle Database - Enterprise Edition - Version 11.2.0.1 to 11.2.0.1 [Release 11.2] Oracle Database - Enterprise Edition - Version 12.1.0.1 to …

保护多个子域名——通配符证书

在当今的互联网世界中&#xff0c;许多组织和企业拥有复杂的网站结构&#xff0c;包含许多不同的子域名。而为每个子域名单独购买和管理SSL证书可能会相当繁琐。解决这一问题的理想选择就是通配符证书。 一、什么是通配符SSL证书&#xff1f; 通配符SSL证书又叫泛域名证书&am…

智能电网阻抗模拟的应用背景

智能电网阻抗模拟是一种利用计算机模拟技术&#xff0c;对智能电网中各种电力设备和电力系统的阻抗特性进行模拟和分析的方法。智能电网是指通过信息通信技术和先进的控制策略&#xff0c;实现电力系统高效、安全、可靠和可持续运行的电网。在智能电网中&#xff0c;各种电力设…

Spring全家桶源码解析--2.4 Spring bean 的依赖注入--@Resource

文章目录 前言一、Resource 作用&#xff1a;二、Resource 源码实现&#xff1a;2.1 Resource 注入点获取&#xff1a;2.2 Resource 对注入点依赖注入&#xff1a; 三、 总结 前言 Spring 中不仅可以使用Spring 包中的Autowired 还可以使用java 层面提供的Resource 进行依赖注…

阿里云学生及教师优惠活动,学生用户享3折购买优惠,教师享5折购买优惠

阿里云推出高校计划“云工开物”&#xff0c;助力高校师生云上“创世界”&#xff0c;学生用户享300元优惠券和3折购买优惠&#xff0c;教师享5折购买优惠。“云工开物”将倾力支持高校教师云上科研提速&#xff0c;取得有世界级影响力的成果&#xff1b;助力高校学生在云上探索…

无代码:解决非程序员的开发难题

最近&#xff0c;有个小型企业的负责人找上我&#xff0c;说他公司需要一个内部管理系统&#xff0c;来提高工作和协作效率&#xff0c;但他没有编程经验&#xff0c;也不打算花费大量时间和金钱雇佣专业的开发团队&#xff0c;他问我有没有什么解决方案。 针对这个问题&#…

FusionDiff:第一个基于扩散模型实现的多聚焦图像融合的论文

文章目录 1. 论文介绍2. 研究动机3. 模型结构3.1 网络架构3.2 前向扩散过程3.3 逆向扩散过程3.4 训练和推理过程 4. 小样本学习4. 实验结果 1. 论文介绍 题目&#xff1a;FusionDiff: Multi-focus image fusion using denoising diffusion probabilistic models 作者&#xf…

ARPG----C++学习记录05 Section9 动画蓝图,腿部ik

这节课比较难懂,我也不是很理解 动画蓝图 新建一个动画蓝图。首先新建一个人物蓝图的变量用来获取人物的属性&#xff0c;使用第一行蓝图来初始化&#xff0c;当人物为Echo时获取它的movement组件&#xff0c;存为变量。然后动画的每一帧都从movement组件里拿出xy的速度用作后边…

软件外包的需求整理技巧

在软件开发中&#xff0c;整理需求是确保项目成功的重要步骤之一。以下是一些整理需求的技巧&#xff0c;这些技巧有助于确保需求的清晰性、完整性和可行性&#xff0c;为项目的成功打下坚实的基础。北京木奇移动技术有限公司&#xff0c;专业的软件外包开发公司&#xff0c;欢…

有什么方法可以改善CRM实施投资回报?

数据统计显示&#xff0c;几乎70%以上CRM客户管理系统项目的投资回报是负数。这意味着超过半数的CRM项目的结果是失败的。那么我们有什么方法可以改善CRM实施投资回报吗&#xff1f;当然有&#xff0c;下面我们就来说一说。 如何改善CRM实施投资回报 首先&#xff0c;您选择的…

新品 | 飞凌嵌入式FCU2601工商业储能EMS能量控制单元发布

FCU2601嵌入式控制单元是飞凌嵌入式为锂电池储能行业设计的EMS能量控制单元产品&#xff0c;设计兼具高性能&#xff0c;多接口&#xff0c;低功耗&#xff0c;广泛满足各类储能系统的本地能源管理应用需求。 FCU2601嵌入式控制单元综合考虑到了储能行业不同场景的差异化需求&…

RT1170的ITM SWO配置,实现printf输出及PC指针的采样分析

最近公司准备启动一个新的项目&#xff0c;使用NXP的MIMXRT1170芯片作为主控&#xff0c;在熟悉芯片的过程中发现RT1176具备ITM和SWO功能模块&#xff0c;于是针对之前项目中因工程庞大导致调试困难的问题&#xff0c;决定使用SWO输出调试信息&#xff0c;这样既可以节省硬件的…

Python生成随机数插件Faker的用法

目录 引言 一、Faker库的安装 二、Faker库的基本用法 1、导入Faker类 2、创建Faker对象 3、使用Faker对象生成随机数据 三、Faker库的高级用法 1、自定义数据生成规则 2、使用子模块进行特定领域的数据生成 3、与其他库结合使用 四、Faker库的应用场景 1、单元测试…