【数据结构】链表的分类和双向链表

news2024/11/16 11:31:33

本篇是基于上篇单链表所作,推荐与上篇配合阅读,效果更加

http://t.csdnimg.cn/UhXEj

1.链表的分类

链表的结构非常多样,以下情况组合起来就有8种(2 x 2 x 2)链表结构:
我们一般叫这个头为哨兵位
我们上回讲的单链表就是不带头单项不循环链表。
今天我们要讲带头双向循环的链表。
不过在次之前容我先为大家画一画8种链表结构:

1.带头单向循环链表:

2.带头单向不循环链表

3.带头双向循环链表

4.带头双向不循环链表

5.不带头单向循环链表

6.不带头单向不循环链表

7.不带头双向循环链表

8.不带头双向不循环链表

虽然有这么多的链表的结构,但是我们实际中最常用还是两种结构: 单链表 双向带头循环链表
1. 无头单向非循环链表:结构简单,⼀般不会单独用来存数据。实际中更多是作为其他数据结构的子结构,如哈希桶、图的邻接表等等。另外这种结构在笔试面试中出现很多。
2. 带头双向循环链表:结构最复杂,⼀般用在单独存储数据。实际中使用的链表数据结构,都是带头双向循环链表。另外这个结构虽然结构复杂,但是使用代码实现以后会发现结构会带来很多优势,实现反而简单了。

2.双向带头循环链表

我们还是经典三个文件:

我们先定义头文件所需要的函数

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

//定义双向链表中节点的结构
typedef int LTDataType;
typedef struct ListNode {
	LTDataType data;
	struct ListNode* prev;
	struct ListNode* next;
}LTNode;

//注意,双向链表是带有哨兵位的,插入数据之前链表中必须要先初始化一个哨兵位
//void LTInit(LTNode** pphead);
LTNode* LTInit();
//void LTDesTroy(LTNode** pphead);
void LTDesTroy(LTNode* phead);   //推荐一级(保持接口一致性)

void LTPrint(LTNode* phead);

//不需要改变哨兵位,则不需要传二级指针
//如果需要修改哨兵位的话,则传二级指针
void LTPushBack(LTNode* phead, LTDataType x);
void LTPushFront(LTNode* phead, LTDataType x);

//头删、尾删
void LTPopBack(LTNode* phead);
void LTPopFront(LTNode* phead);

//查找
LTNode* LTFind(LTNode* phead, LTDataType x);

//在pos位置之后插入数据
void LTInsert(LTNode* pos, LTDataType x);
//删除pos位置的数据
void LTErase(LTNode* pos);

首先我们还是得先定义节点,由于是双向链表,所以节点内存在两个节点的地址,一个是前驱节点的(指向其前一个节点),一个是尾结点的(指向其后一个节点)

这一段代码,是为了确保数据类型

我们节点定义成这样:

接下来又是完成各个功能:增,删,查,改。但是,由于我们长线的是带头的链表,所以我们需要对头初始化

3.初始化

我们先定义初始化函数,然后写函数:

void LTInit(LTNode** pphead);
void ltinit(ltnode** pphead) {
	*pphead = (ltnode*)malloc(sizeof(ltnode));
	if (*pphead == null) {
		perror("malloc fail!");
		exit(1);
	}
	(*pphead)->data = -1;
	(*pphead)->next = (*pphead)->prev = *pphead;
}

和上回写单链表差不多,检测开辟是否成功,成功就接着给数据赋值,由于此时只有一个节点,即哨兵节点,且是循环链表,所以存放的前驱和尾节点就是哨兵节点自己

所以我们可以得出,如果哨兵节点的next指针或者prev指针指向自己,说明当前链表为空。

4.创建新的节点

我们写法和上次差不多

LTNode* LTBuyNode(LTDataType x) {
	LTNode* newnode = (LTNode*)malloc(sizeof(LTNode));
	if (newnode == NULL) {
		perror("malloc fail!");
		exit(1);
	}
	newnode->data = x;
	newnode->next = newnode->prev = newnode;

	return newnode;
}

只是这回,多了一个前驱节点,我们定义时间默认前驱和后去节点指向的是本身。

但是既然我们都这么写了一个创建新节点的函数,那么我们可不可以用这个函数直接去进行哨兵节点的创建?

答案是肯定的,我们首先先改变以下我们定义的函数,

LTNode* LTInit();

然后调用创建新节点的函数,得到哨兵节点

LTNode* LTInit() {
	LTNode* phead = LTBuyNode(-1);
	return phead;
}

5.头插和尾插

注意:头插,是把新的节点插在第一个节点前,不是哨兵节点前

头插和尾插,头删和尾删的思路整体和单链表一致,我就不详细说明了,直接上代码

定义函数:

void LTPushBack(LTNode* phead, LTDataType x);
void LTPushFront(LTNode* phead, LTDataType x);

函数代码示例:

//尾插
void LTPushBack(LTNode* phead, LTDataType x) {
	assert(phead);
	LTNode* newnode = LTBuyNode(x);
	//phead phead->prev(ptail)  newnode
	newnode->next = phead;
	newnode->prev = phead->prev;

	phead->prev->next = newnode;
	phead->prev = newnode;
}
//头插
void LTPushFront(LTNode* phead, LTDataType x) {
	assert(phead);

	LTNode* newnode = LTBuyNode(x);
	//phead newnode phead->next
	newnode->next = phead->next;
	newnode->prev = phead;

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

不懂的你们可以再看看图:

6.头删和尾删

定义函数:

void LTPopBack(LTNode* phead);
void LTPopFront(LTNode* phead);

函数代码示例:

//尾删
void LTPopBack(LTNode* phead) {
	assert(phead);
	//链表为空:只有一个哨兵位节点
	assert(phead->next != phead);

	LTNode* del = phead->prev;
	LTNode* prev = del->prev;

	prev->next = phead;
	phead->prev = prev;

	free(del);
	del = NULL;
}
//头删
void LTPopFront(LTNode* phead) {
	assert(phead);
	assert(phead->next != phead);

	LTNode* del = phead->next;
	LTNode* next = del->next;

	//phead del next
	next->prev = phead;
	phead->next = next;

	free(del);
	del = NULL;
}

7.查找

整体思路还是遍历,和单链表十分相似

定义函数:

LTNode* LTFind(LTNode* phead, LTDataType x);

函数代码示例:

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

8.在pos位置之后插入数据

这个和单恋表的也很相似,多了一个prev指针而已,写的时候要注意顺序,函数定义我就不写了

函数代码示例:

//在pos位置之后插入数据
void LTInsert(LTNode* pos, LTDataType x) {
	assert(pos);
	LTNode* newnode = LTBuyNode(x);
	//pos newnode pos->next
	newnode->next = pos->next;
	newnode->prev = pos;

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

9.删除pos位置的数据

这个和单链表还是一样的,遍历整个表,然后相爱指针指向的地址,然后释放内存

函数代码示例:

void LTErase(LTNode* pos) {
	assert(pos);

	//pos->prev pos  pos->next
	pos->next->prev = pos->prev;
	pos->prev->next = pos->next;

	free(pos);
	pos = NULL;
}

10.打印

这个其实是用来看每个节点中间的数据的,我们可以通过前驱节点或者尾节点实现正序或逆序打印,这一步也是遍历然后看哨兵节点是否是下一位,是就中断,我这里之举一种例子,另一种只要将next改成prev

函数代码示例:

void LTPrint(LTNode* phead) {
	//phead不能为空
	assert(phead);
	LTNode* pcur = phead->next;
	while (pcur != phead)
	{
		printf("%d->", pcur->data);
		pcur = pcur->next;
	}
	printf("\n");
}

11.销毁

这个链表的销毁和点链表不大一样,因为存在哨兵节点,所以我们要分开释放内存

函数代码示例:

void LTDesTroy(LTNode* phead) {
	//哨兵位不能为空
	assert(phead);

	LTNode* pcur = phead->next;
	while (pcur != phead)
	{
		LTNode* next = pcur->next;
		free(pcur);
		pcur = next;
	}
	//链表中只有一个哨兵位
	free(phead);
	phead = NULL;
}

最后还是一如既往的测试环节就交给大家了。推荐阅读完http://t.csdnimg.cn/UhXEj

然后再阅读这个

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

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

相关文章

LeetCode做题总结 226. 翻转二叉树

226. 翻转二叉树 代码1 报错代码2 报错代码3 正确。 代码1 报错 class Solution {public TreeNode invertTree(TreeNode root) {// TreeNode _root root; // 这是在保证// _root preOrderTree(root);// return root;root preOrderTree(root);return root;}public TreeNode …

MATLAB中uibutton函数用法

目录 语法 说明 示例 创建普通按钮 创建状态按钮 设置和访问按钮属性值 编写响应以下操作的代码&#xff1a;点击按钮 uibutton函数的功能是创建普通按钮或状态按钮组件。 语法 btn uibutton btn uibutton(parent) btn uibutton(style) btn uibutton(parent,style…

【方法论】费曼学习方法

费曼学习方法是由诺贝尔物理学奖得主理查德费曼提出的一种学习方法。这种方法强调通过将所学的知识以自己的方式解释给别人来提高学习效果。 费曼学习方法的步骤如下&#xff1a; 选择一个概念&#xff1a;选择一个要学习的概念或主题。 理解和学习&#xff1a;用自己的方式学…

全栈工程师

很多人说&#xff0c;全栈工程师是程序猿的终极目标。过去&#xff0c;笔者也一直往这个方面在努力&#xff0c;前后端代码都能写&#xff0c;测试也做一做&#xff0c;原型也画一画。但随着深耕it行业越来越久&#xff0c;笔者觉得条条大路通罗马&#xff0c;作为骡马的我们&a…

HCIA-HarmonyOS设备开发认证-1.HarmonyOS简介

目录 前言目标一、HarmonyOS简介1.1、初识HarmonyOS1.2、HarmonyOS典型应用场景 二、HarmonyOS架构与安全2.1、HarmonyOS架构2.1.1 内核层2.1.2 系统服务层2.1.3 框架层2.1.4 应用层 2.2、HarmonyOS安全 3、HarmonyOS关键特性4、HarmonyOS生态5、思考题坚持就有收获 前言 本章…

web安全学习笔记【08】——算法1

思维导图在最后 #知识点&#xff1a; 1、Web常规-系统&中间件&数据库&源码等 2、Web其他-前后端&软件&Docker&分配站等 3、Web拓展-CDN&WAF&OSS&反向&负载均衡等 ----------------------------------- 1、APP架构-封装&原生态&…

大语言模型推理提速:TensorRT-LLM 高性能推理实践

作者&#xff1a;顾静 TensorRT-LLM 如何提升 LLM 模型推理效率 大型语言模型&#xff08;Large language models,LLM&#xff09;是基于大量数据进行预训练的超大型深度学习模型。底层转换器是一组神经网络&#xff0c;这些神经网络由具有 self-attention 的编码器和解码器组…

Docker安装Clickhouse详细教程

简介 ClickHouse是一种列式数据库管理系统&#xff0c;专门用于高性能数据分析和数据仓库应用。它是一个开源的数据库系统&#xff0c;最初由俄罗斯搜索引擎公司Yandex开发&#xff0c;用于满足大规模数据分析和报告的需求。 特点 开源的列式存储数据库管理系统&#xff0c;…

好书推荐丨豆瓣评出9.2高分!Python编程入门就看蟒蛇书

目录 写在前面 内容简介 业内专家推荐 编辑推荐 资源丰富 作者介绍 Q&A 粉丝福利 写在后面 写在前面 在这日新月异的科技新时代&#xff0c;编程如同一把万能钥匙&#xff0c;为无数人打开了通向无限可能的大门。而在众多编程语言中&#xff0c;Python无疑是最耀…

工业阀门3D数字化展厅降低投入成本

大型机械设备的销售面临诸多挑战&#xff0c;其中最突出的问题之一是展示区域的限制。与家用小汽车不同&#xff0c;大型机械设备的展示需要大面积的场地&#xff0c;同时还需要展示各种型号和功能&#xff0c;这使得在一个城市内实现网格布点覆盖整座城市的难度加大。 为了解决…

DataStream API(转换算子)

目录 源算子 转换算子 1&#xff0c;基本转换算子 1.1映射&#xff08;map&#xff09; 1.2过滤&#xff08;filter&#xff09; 1.3扁平映射&#xff08;flatMap&#xff09; 2&#xff0c;聚合算子 2.1按键分区&#xff08;keyBy&#xff09; 2.2简单聚合 3&#x…

医学图像跨模态转换:配准方法生成对图 + 扩散模型 + 成对配对方法

医学图像跨模态转换&#xff1a;配准方法生成对图 扩散模型 成对配对方法 MRI 到 CT 的高精度转换X 光、核磁共振 MRI、CT 区别最关键的配准方法读后启发&#xff1a;Fundus 转 OCT &#xff08;只是猜想&#xff0c;不一定&#xff09; 数据图像预处理5 个图像转换算法评估图…

数据建模分析与数仓维度建模规范

前言&#xff1a;数据建模是将定义现实世界的数据抽象成模型的过程&#xff0c;以便更好的分析&#xff0c;管理和操作数据实现需求。数据建模在数仓管理、数据库设计、算法模型训练等领域都有着重要的作用。在不同的领域&#xff0c;数据建模的关注点不同&#xff1a;在数据库…

K8S搭建(centos)七、节点加入集群

天行健&#xff0c;君子以自强不息&#xff1b;地势坤&#xff0c;君子以厚德载物。 每个人都有惰性&#xff0c;但不断学习是好好生活的根本&#xff0c;共勉&#xff01; 文章均为学习整理笔记&#xff0c;分享记录为主&#xff0c;如有错误请指正&#xff0c;共同学习进步。…

53-JS之BOM,打开,关闭窗口,screen对象,history对象,location对象,工作区尺寸,滚动距离

1.简介 BOM(Browser Object Model)---浏览器对象模型,提供JS当中对浏览器的各种操作对象 1.1BOM与DOM 2.打开窗口window.open(URL,name,features) 2.1 URL字符串:地址网址文件源 2.2name:指target属性,规定在哪个窗口打开新的url链接 blank:打开一个新窗口 _parent…

[足式机器人]Part2 Dr. CAN学习笔记- 最优控制Optimal Control Ch07-4 轨迹追踪

本文仅供学习使用 本文参考&#xff1a; B站&#xff1a;DR_CAN Dr. CAN学习笔记 - 最优控制Optimal Control Ch07-4 轨迹追踪 1. 目标误差控制-误差的调节2. 稳态非零值控制3. 输入增量控制 1. 目标误差控制-误差的调节 2. 稳态非零值控制 3. 输入增量控制

Wpf 使用 Prism 实战开发Day14

备忘录接口增删&#xff08;CURD&#xff09;改查实现 一.添加备忘录控制器&#xff08;MemoController&#xff09; 备忘录控制器&#xff08;MemoController&#xff09;和待办事项控制器 &#xff08;ToDoController&#xff09;功能实现差不多一样。基本套路就是&#xff1…

用艺术陪伴困境群体活动在庐阳区双岗街道万小店社区开展

用艺术陪伴困境群体活动在庐阳区双岗街道万小店社区开展 1月23日上午9时&#xff0c;王莉老师带领“一欣工作室”的七位小朋友冒着严寒&#xff0c;来到位于万小店社区和煦园小区的合肥市庐阳区为民社会工作服务中心&#xff0c;慰问陪伴中心的兄弟姐妹。 大家一起唱歌、一起表…

【pyqt6】用pyqt做一个点菜小程序

用pyqt做一个点菜小程序 前言1.pyqt62. 功能介绍3.程序实现 前言 在本文中&#xff0c;我们将使用 PyQt6&#xff08;Python的GUI库&#xff09;创建一个简单的点菜小程序。该程序允许用户从菜单中选择菜品&#xff0c;将其添加到订单中&#xff0c;并通过点击“下单”按钮查看…

springboot121编程训练系统设计与实现

简介 【毕设源码推荐 javaweb 项目】基于springbootvue 的编程训练系统设计与实现 适用于计算机类毕业设计&#xff0c;课程设计参考与学习用途。仅供学习参考&#xff0c; 不得用于商业或者非法用途&#xff0c;否则&#xff0c;一切后果请用户自负。 看运行截图看 第五章 第四…