数据结构篇其三---链表分类和双向链表

news2024/12/27 12:02:08

前言

数据结构篇其二实现了一个简单的单链表,链表的概念,单链表具体实现已经说明,如下:
单链表
事实上,前面的单链表本质上是无头单向不循环链表。此篇说明的双向链表可以说完全反过来了了。无论是之前的单链表还是双向链表,本质都是链表家族的两位成员。
主题一:链表分类
详细说说链表的特征,以及这些特征组合的链表种类。
主题二:双向链表的实现
像上次实现单链表一样,这次也试着独立实现双向链表吧。
学习收获:十分钟手搓一个链表

为什么学习双向链表?
因为虽然字面上双向链表好像还难一点,结构虽然复杂,但是实现起来特别简单。应用场景有显著的优势。

链表的分类

  • 单向与双向

链表的单向与双向:这说明节点与节点之间的联系。单向链表节点的指针一路往后。双向链表节点指针指前指后。
单向与双向

可见,从定义上,双向链表天生应该有两个指针,所以在单链表的基础上,我们可以推出双向链表的定义。

//双向链表的定义
typedef int LTDataType;
typedef struct ListNode {
	LTDataType data;//数据域
	struct ListNode* prev;//前驱指针
	struct ListNode* next;//后继指针
}ListNode;

链表双向和单向决定了它节点指针的数量

  • 带头与不带头

为了方便对链表进行操作,我们会在链表的第一个节点前附带一个头节点(哨兵位),注意头节点不是第一个节点,第一个节点存储的是有效数据。
头节点的数据域不存储有效的数据,指针域next指向第一个节点,若是双向的话,则前驱指针指向尾结点。
需注意这个时候头指针就指向头节点,而不是第一个节点了。
单链表带哨兵位

  • 循环非循环

若链表的尾结点指向头节点而不是NULL,则链表闭合形成了一个环,可以循环了,就称为循环链表。反之,则为不循环链表。
循环与非循环

  1. 以上就是链表的三大特征,每种特征又分两种情况。组合起来一共8种,所以链表种类一共8种。
  2. 下面介绍双向链表,来熟悉一下双向,带头,循环的链表吧。

双向链表的实现

下面实现这些函数

// 创建返回链表的头结点.
ListNode* ListCreate();
// 双向链表销毁
void ListDestory(ListNode* plist);
// 双向链表打印
void ListPrint(ListNode* plist);
// 双向链表尾插
void ListPushBack(ListNode* plist, LTDataType x);
// 双向链表尾删
void ListPopBack(ListNode* plist);
// 双向链表头插
void ListPushFront(ListNode * plist, LTDataType x);
// 双向链表头删
void ListPopFront(ListNode* plist);

前面已经给出了双向链表的定义。但下图只体现了双向,循环和带头还要我们具体实现。

typedef int LTDataType;
typedef struct ListNode {
	LTDataType data;//数据域
	struct ListNode* prev;//前驱指针
	struct ListNode* next;//后继指针
}ListNode;

开局一个头指针,能这样做吗?


int main() {
	ListNode* plist = NULL;
	return 0;
}

这个是带头的链表,起码有头节点吧,所以先创建一个头节点。
其次,这个是循环链表,头节点的前驱指针和后继指针应该都指向自己吧。
头节点

节点创建

ListNode* ListCreate(LTDataType x) {
	ListNode* phead = (ListNode*)malloc(sizeof(ListNode));
	//判断动态空间是否开辟失败。
	if (phead == NULL) {
		perror("malloc fail");
		exit(-1);
	}
	phead->data = x;//头节点数据随便赋值
	phead->next = phead;//前驱,后继指针指向自己
	phead->prev = phead;
	return phead;
}

双向链表初始化

ListNode* ListInit() {
	return ListCreate(-1);//头节点的数据域随便给值。
}

还没有元素啊,那就先插入节点吧

双向链表的尾插

如何实现尾插呢?

  1. 保证每个节点的两个指针有明确的指向。

  2. 尾插操作的节点有三个,头节点,尾结点,新节点。

  3. 按照上面图片的步骤写代码。

  4. 后面请自行画图分析,多创建临时变量,良好的命名习惯。写完一个函数去测试一下。

void ListPushBack(ListNode* phead, LTDataType x) {
	assert(phead);
	ListNode* newnode = ListCreate(x);//创建新节点
    ListNode* tail = phead->prev;//记录尾结点

	//执行尾插操作
	phead->prev = newnode;
	newnode->next = phead;
	tail->next = newnode;
	newnode->prev = tail;
}

打印函数

void ListPrint(ListNode* phead) {
	assert(phead);
	ListNode* pcur = phead->next;
	printf("head->");
	while (pcur!= phead) {
		printf("%d->", pcur->data);
		pcur = pcur->next;
	}
	pcur = NULL;
	printf("return\n");
}

双链表的头插

void ListPushFront(ListNode* phead,LTDataType x) {
	assert(phead);
	ListNode* newnode = ListCreate(x);
	ListNode* first = phead->next;

	phead->next = newnode;
	newnode->prev = phead;
	newnode->next = first;
	first->prev = newnode;


}

双向链表的尾删

//尾删函数
void ListPopBack(ListNode* phead) {
	assert(phead);
	ListNode* tail = phead->prev;
	ListNode* tailprev = tail->prev;


	phead->prev = tailprev;
	tailprev->next = phead;
	if(phead->prev!=phead)//判断是否为空表,哨兵位不能释放了。
	      free(tail);
	tail = NULL;
	
}

双向链表的头删

/头删函数
void ListPopFront(ListNode* phead) {
	assert(phead);
	ListNode* first = phead->next;
	ListNode* second = first->next;

	phead->next = first->next;
	second->prev = phead;
	if (phead != first)//哨兵位不能释放
	{
		free(first);
	}
	first = NULL;
	second = NULL;
}

双向链表的销毁

void ListDestory(ListNode** pphead) {
	assert(pphead);
	assert(*pphead);
	ListNode* pcur = (*pphead)->next;
	ListNode* next = pcur->next;
	while (pcur != *pphead)
	{
		free(pcur);
		pcur = next;
		next = next->next;
	}
	free(pcur);
	pcur = NULL;
	next = NULL;
	*pphead = NULL;

}

双向链表补充

// 双向链表查找
ListNode* ListFind(ListNode* plist, LTDataType x);
// 双向链表在pos的前面进行插入
void ListInsert(ListNode* pos, LTDataType x);
// 双向链表删除pos位置的节点
void ListErase(ListNode* pos);

双向链表查找指定数据

ListNode* ListFind(ListNode* phead, LTDataType x) {
	assert(phead);
	ListNode* pcur = phead->next;
	while (pcur != phead) {
		if (pcur->data == x) {
			return pcur;//找到了返回当前节点的地址
		}
		pcur=pcur->next;
	}
	return NULL;//双链表跑完一遍都没找到,返回空。
}

在pos节点之前插入新节点

//在pos之前插入
void ListInsert(ListNode* pos, LTDataType x) {
	assert(pos);
	ListNode* newnode = ListCreate(x);
	ListNode* prev = pos->prev;

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

	pos = NULL;
	prev = NULL;
	newnode = NULL;
}

删除位置为pos的节点

void ListErase(ListNode* pos) {
	assert(pos);
	ListNode* prev = pos->prev;
	ListNode* next = pos->next;
	free(pos);
	prev->next = next;
	next->prev = prev;

	pos = NULL;
	prev = NULL;
	next = NULL;
}

十分钟实现一个链表

  1. 实现什么类型的链表?
  2. 需要写什么函数?

双向链表;
实现函数,增删查改,还有来链表的初始化,销毁。


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


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

LT* LTInit(void) {
	LT* newnode = (LT*)malloc(sizeof(LT));
	if (newnode == NULL) {
		perror("malloc fail");
		exit(-1);
	}
	newnode->next = newnode;
	newnode->prev = newnode;
	return newnode;
}

LT LTDestory(LT** pphead) {
	assert(pphead);
	assert(*pphead);
	LT* pcur = (*pphead)->next;
	LT* next = pcur->next;
	while (pcur != *pphead) {
		free(pcur);
		pcur = next;
		next = next->next;
	}
	free(pcur);
	*pphead = NULL;

}

//在pos之前插入新节点
void LTInsert(LT* pos, LTDataType x) {
	assert(pos);
	LT* prev = pos->prev;
	LT * newnode = (LT*)malloc(sizeof(LT));
	if (newnode == NULL) {
		perror("malloc fail");
		exit(-1);
	}
	newnode->data = x;
	prev->next = newnode;
	newnode->prev = prev;
	newnode->next = pos;
	pos->prev = newnode;

}

void LTErase(LT* pos) {
	assert(pos);
	LT* prev = pos->prev;
	LT* next = pos->next;
	free(pos);
	prev->next = next;
	next->prev = prev;
}


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

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

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

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


LT* LTFind(LT* phead, LTDataType x) {
	LT* pcur = phead->next;
	while (pcur != phead) {
		if (pcur->data == x) {
			return pcur;
		}
		pcur = pcur->next;
	}
	return NULL;
	return;
}


LT* LTMidfy( LT* pos,LTDataType x) {
	pos->data = x;
}

双向链表完结。链表完结!

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Arthas反编译与重新加载class

一、背景 因为其他研发部门同事给的产品jar包存在一个问题&#xff0c;就是http底层的超时时间默认为60s&#xff0c;但是最近调用外部接口同步数据&#xff0c;这个数据量太大导致超时超过60s&#xff0c;每次同步都不成功。但是客户目前对此情况特别不满意&#xff0c;需要紧…

将电脑D盘部分空间划分给C盘的方法

本文介绍在Windows电脑中&#xff0c;将D盘的部分空间分给C盘的方法。 最近&#xff0c;发现电脑中C盘的空间剩余不多了&#xff1b;而D盘由于当初分盘时划分的空间过多&#xff0c;导致其剩余空间很大且大概率以后都不会用上D盘中这些多余的空间了。因此&#xff0c;希望将D盘…

使用Word表格数据快速创建图表

实例需求&#xff1a;Word的表格如下所示&#xff0c;标题行有合并单元格。 现在需要根据上述表格数据&#xff0c;在Word中创建如下柱图。如果数据在Excel之中&#xff0c;那么创建这个图并不复杂&#xff0c;但是Word中就没用那么简单了&#xff0c;虽然Word中可以插入图表&a…

轻松拿捏C语言——【字符串函数】的使用及模拟实现

&#x1f970;欢迎关注 轻松拿捏C语言系列&#xff0c;来和 小哇 一起进步&#xff01;✊ &#x1f389;创作不易&#xff0c;请多多支持&#x1f389; &#x1f308;感谢大家的阅读、点赞、收藏和关注&#x1f495; &#x1f339;如有问题&#xff0c;欢迎指正 感谢 目录 一、…

向npm发布自己写的vue组件,使用vite创建项目

向npm发布自己写的vue组件&#xff0c;使用vite创建项目 创建项目 pnpm create vite输入项目名称 由于我的组件是基于 ant-design-vue和vue的&#xff0c;需要解析.vue文件&#xff0c;我又安装了下面4个。 然后执行 pnpm i安装依赖 vite.config.ts import { defineC…

ES基础概念

本文不介绍如何使用ES&#xff08;使用ES见&#xff1a;&#xff09; 1.ES生态圈 ES&#xff1a; Logstash&#xff1a;数据处理服务程序&#xff0c;解析转换加工数据&#xff1b; Kibana&#xff1a;数据展示、集群管理&#xff0c;数据可视化、ES管理与监控、报表等&#xf…

【QT+VS】如何在现有VS项目中添加Qt界面?【全网最详细】

0. 前置步骤 参考如下链接文章中的 前3个步骤(1:下载Qt;2:安装Qt;3:安装Qt插件),完成环境的配置和安装。 深耕AI:如何联合Qt,VS,C++,来开发一个电脑版软件(简单有趣,详细) 本文的基础项目链接为: c++工程+图像分割预测+mmdet+实例分割+最新工程+简洁易懂+新手…

方正畅享全媒体新闻采编系统 binary.do SQL注入漏洞复现

0x01 产品简介 方正畅享全媒体新闻生产系统是以内容资产为核心的智能化融合媒体业务平台,融合了报、网、端、微、自媒体分发平台等全渠道内容。该平台由协调指挥调度、数据资源聚合、融合生产、全渠道发布、智能传播分析、融合考核等多个平台组成,贯穿新闻生产策、采、编、发…

Mysql 备份恢复 mysqldump与xtrabackup备份

1.1 备份的原因 备份是数据安全的最后一道防线&#xff0c;对于任何数据丢失的场景&#xff0c;备份虽然不一定能恢复百分之百的数据 (取决于备份周期)&#xff0c;但至少能将损失降到最低。衡量备份恢复有两个重要的指标&#xff1a;恢复点目标(RPO) 和恢复时间目标(RTO)&…

1.freertos基础知识

1.freertos最核心的概念就是多线程&#xff0c;就是可以让两段代码同时进行。 2.针对不同的用户场景&#xff0c;freertos提供了4种交互方式&#xff1a;消息队列&#xff0c;任务通知&#xff0c;信号量&#xff0c;互斥锁 3.什么是API&#xff1f;API是应用程序编程接口&…

【退役之重学Java】关于B+树索引

一、为什么使用索引 一条数据可能有很多字段&#xff0c;数据量比较大&#xff0c;挨个查询效率极差故使用索引&#xff0c;提高查询性能和加快数据检索速度。同时还可以帮助优化排序、分组和连接操作&#xff0c;提高数据库系统的整体性能和响应速度。 二、为什么要用 B 树 B树…

HarmonyOS 鸿蒙应用开发 - 创建自定义组件

开发者定义的称为自定义组件。在进行 UI 界面开发时&#xff0c;通常不是简单的将系统组件进行组合使用&#xff0c;而是需要考虑代码可复用性、业务逻辑与UI分离&#xff0c;后续版本演进等因素。因此&#xff0c;将UI和部分业务逻辑封装成自定义组件是不可或缺的能力。 1、创…

Stable Diffusion简单食用方法

1.下载 1.1打开B站 哔哩哔哩 (゜-゜)つロ 干杯~-bilibili哔哩哔哩&#xff08;bilibili.com)是国内知名的视频弹幕网站&#xff0c;这里有及时的动漫新番&#xff0c;活跃的ACG氛围&#xff0c;有创意的Up主。大家可以在这里找到许多欢乐。https://www.bilibili.com/ 1.2搜索…

【CSS】input宽度根据内容自适应

目标&#xff1a;纯css实现以下功能 input 设置width:min-content | max-content&#xff1b;//无效 ∵ 文本输入框通常会被浏览器渲染为具有固定宽度的控件。 解决方法&#xff1a;设置input的宽高都为100%&#xff0c;让它随着父元素的宽高改变。 父元素如何改变呢&#xf…

可视化在医疗健康领域的巨大价值,该如何设计呢。

可视化设计在医疗健康领域具有以下价值&#xff1a; 数据展示与分析&#xff1a;可视化设计可以将医疗健康领域的大量数据以图表、图形等形式进行展示和分析&#xff0c;帮助医生、研究人员和决策者更直观地理解和解读数据&#xff0c;发现规律和趋势&#xff0c;从而做出科学决…

HTTP 错误 404.3 - Not Found 问题处理

问题描述 HTTP 错误 404.3 - Not Found 由于扩展配置问题而无法提供您请求的页面。如果该页面是脚本&#xff0c;请添加处理程序。如果应下载文件&#xff0c;请添加 MIME 映射。 解决对策

MATLAB构建一些简单的人工数据集

1. 构建一个2维3类的数据集 %高斯二维三类 1 3 mul[0,0]; % 均值 S1[.1 0;0 .1]; % 协方差 data1mvnrnd(mul, S1, 100); % 产生高斯分布数据 % 第二组数据 mu2[1.25 1.25]; S2[.1 0;0 .1]; data2mvnrnd(mu2,S2,100); % % 第三组数据 mu3[-1.25;1.25] S3[.1 0;0 .1] data3mvn…

企业级架构及本体论最新进展

本文主要探讨了企业级架构和本体论的新兴趋势&#xff0c;特别是DoDAF、IDEAS、UAF和NAF的发展历程、理论基础、模型构建以及与ArchiMate和语义网技术的关联。原文: The emerging landscape of Enterprise Architecture and Ontology 导言 动机和采用的方法 关注我的人都知道我…

ROCm上运行预训练BERT

14.10. 预训练BERT — 动手学深度学习 2.0.0 documentation (d2l.ai) 下载数据集 在d2l-zh/pytorch/data目录解压&#xff1a; ~/d2l-zh/pytorch/data$ unzip wikitext-2-v1.zip Archive: wikitext-2-v1.zipcreating: wikitext-2/inflating: wikitext-2/wiki.test.tokens …

Autodesk 3DS Max v2025 解锁版安装教程 (3D 建模软件)

前言 Autodesk 3ds Max 是一款功能强大的 3D 建模和动画解决方案&#xff0c;游戏开发人员、视觉效果艺术家和平面设计师使用它来创建庞大的世界、令人惊叹的场景和引人入胜的虚拟现实 (VR) 体验。 Autodesk 3DS MAX是业界使用最广泛的3D建模和动画软件程序之一&#xff0c;它…