数据结构-双链表-详解

news2024/9/22 15:50:59

数据结构-双链表-详解

  • 1.前言
  • 2.结构
    • 2.1双向
    • 2.2带头
    • 2.3循环
  • 3.实现
    • 3.1结构体
    • 3.2初始化与删除
      • 初始化
      • 删除
    • 3.3插入
      • 尾插
      • 头插
    • 3.4删除
      • 尾删
      • 头删
    • 3.4查找
    • 3.5pos位置的插入删除

1.前言

链表总共有八种:双向、单向;带头、不带头;循环、不循环(8 = 2 * 2 * 2)

在前两篇博客中,我讲了单链表,具体来说,是 单向、不带头、不循环 的链表。
这种链表结构简单,使用复杂,常常是其他数据结构的子结构,在OJ题中会大量出现。
而常见的链表还有一个,就是今天要讲的 双向、带头、循环 的链表。
这种链表的特点是结构复杂,使用简单。如果有人让你二十分钟内搓出一个链表,那么它将是最优解。

2.结构

2.1双向

双向是什么?就是前一个结点能访问后一个结点,后一个结点也可以访问前一个结点。
在这里插入图片描述

相比于单向,双向可以倒着走了。
缺点也有,就是结构体中需要再创建一个指针变量,占用了更多的内存。

2.2带头

这个头不是指头指针,而是哨兵位的头结点,这种头结点不存储有效数据。
使用它的好处是插入时不用判断NULL的情况。
缺点是哨兵位的头结点有时候意义不大,单链表一般都不带头。

2.3循环

这个比较简单,就是指一个结点的next指向了前面的结点。
可以是这样:
在这里插入图片描述

也可以是这样:
在这里插入图片描述

甚至可以是这样:

A

今天讲的是头指尾的循环。

3.实现

3.1结构体

List.h:

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

只比单链表多了个指针。

3.2初始化与删除

初始化

这里需要创造一个哨兵位的头结点,且需符合 双向、带头、循环 的特点。

ListGuard

List.c:

ListNode* LTInit()
{
	ListNode* ListGuard;
	ListGuard = (ListNode*)malloc(sizeof(ListNode));
	if (!ListGuard)
	{
		perror("LTInit::malloc");
		return NULL;
	}
	ListGuard->prev = ListGuard;
	ListGuard->next = ListGuard;
	return ListGuard;
}

删除

List.c:

void ListDestory(ListNode* pHead)
{
	assert(pHead);
	ListNode* cur = pHead->next;
	while (cur)
	{
		ListNode* tmp = cur->next;
		free(cur);
		cur = tmp;
	}
}

3.3插入

尾插

在单链表时,尾插需找尾,当链表为空时,还需传二级指针。
现在,不需要找尾,因为头结点的prev就是尾;不需传二级,且链表空不空方法一致,不用处理特殊情况。
思路:
在这里插入图片描述

这里需注意链接顺序,不然会丢失位置。
当然,如果创建临时变量,存储前后位置,那怎么弄都行。
代码挺简单的:
List.c:

void ListPushBack(ListNode* pHead, LTDataType x)
{
	assert(pHead);
	ListNode* NewListNode = BuyListNode(x);
	NewListNode->prev = pHead->prev;
	NewListNode->next = pHead;
	pHead->prev->next = NewListNode;
	pHead->prev = NewListNode;
}

头插

方法和尾插差不多:
List.c:

void ListPushFront(ListNode* pHead, LTDataType x)
{
	assert(pHead);
	ListNode* NewListNode = BuyListNode(x);
	NewListNode->next = pHead->next;
	NewListNode->prev = pHead;
	pHead->next->prev = NewListNode;
	pHead->next = NewListNode;
}

3.4删除

空链表当然不能删除,可以写个判空的函数:
List.c:

int ListEmpty(ListNode* pHead)
{
	assert(pHead);
	return pHead->next == pHead;
}

这里直接return条件判断的结果,大大简化代码。

尾删

List.c:

void ListPopBack(ListNode* pHead)
{
	assert(pHead);
	assert(!ListEmpty(pHead));
	pHead->prev = pHead->prev->prev;
	free(pHead->prev->next);
	pHead->prev->next = pHead;
}

头删

List.c:

void ListPopFront2(ListNode* pHead)
{
	assert(pHead);
	assert(!ListEmpty(pHead));
	ListErase(pHead->next);
}

3.4查找

List.c:

ListNode* ListFind(ListNode* pHead, LTDataType x)
{
	assert(pHead);
	ListNode* cur = pHead->next;
	if (cur != pHead && cur->data != x)
	{
		cur = cur->next;
	}
	else return cur;
}

3.5pos位置的插入删除

思路大差不差,这里直接给代码。

List.c:

void ListInsert(ListNode* pos, LTDataType x)
{
	assert(pos);
	ListNode* NewListNode = BuyListNode(x);
	NewListNode->next = pos;
	NewListNode->prev = pos->prev;
	pos->prev->next = NewListNode;
	pos->prev = NewListNode;
}
void ListErase(ListNode* pos)
{
	assert(pos);
	assert(!ListEmpty(pos));
	pos->next->prev = pos->prev;
	pos->prev->next = pos->next;
	free(pos);
	pos = NULL;
}

现在有个值得注意的点了,即这两个函数完全可以代替头插尾插头删尾删,因此,又可以减少工作量了,只需写出这两个函数,然后就能开始套用:

void ListPushBack2(ListNode* pHead, LTDataType x)
{
	assert(pHead);
	ListInsert(pHead, x);
}
void ListPopBack2(ListNode* pHead)
{
	assert(pHead);
	assert(!ListEmpty(pHead));
	ListErase(pHead->prev);
}
void ListPushFront2(ListNode* pHead, LTDataType x)
{
	assert(pHead);
	ListInsert(pHead->next,x);
}
void ListPopFront2(ListNode* pHead)
{
	assert(pHead);
	assert(!ListEmpty(pHead));
	ListErase(pHead->next);
}

希望本篇文章对你有所帮助!并激发你进一步探索数据结构和算法的兴趣!
本人仅是个C语言初学者,如果你有任何疑问或建议,欢迎随时留言讨论!让我们一起学习,共同进步!

相关文章:
数据结构-单链表-详解-1
数据结构-单链表-详解-2

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

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

相关文章

C++第四十四弹---Lambda表达式的妙用:高效解决编程中的匿名函数问题

✨个人主页: 熬夜学编程的小林 💗系列专栏: 【C语言详解】 【数据结构详解】【C详解】 目录 1 lambda表达式 1.1 C98中的一个例子 1.2 lambda表达式 1.3 lambda表达式语法 1.4 函数对象与lambda表达式 1 lambda表达式 1.1 C98中的一个例…

上门家政系统

上门家政系统是基于likeadmin开发的上门家政预约系统,提供全部前后台无加密源代码,拥有强大的地图定位、在线预约、系统派单、指定派单、下单支付、核销订单等功能模块,用户端和师傅端完美融合,随时随地都能接单;可自定…

vs中在工具箱添加自定义控件numberTextBox

在winform中没有numberTextBox控件,为了将numberTextBox添加到工具箱当中,于是参考以下博客封装了一个自定义numberTextBox控件: winform 自定义数值(数字)输入框_c#如何设置一个框输入值-CSDN博客 接下来介绍以下封装和引用的过程&#xf…

淘宝直通车投放优化(进阶版本)

相爱相杀何时休,只看今篇论英雄! 看目录上电梯,各去对应楼层耍。顶楼的风景最迷人。38719字诸君细读。 第一阶段:基础认识和推广(新手可从头看起) 第二阶段:明确原理与逻辑(运营/推广同学从此看做到心中有数) 第三阶段:实战中涅槃(知其然,知其所以然) 第一阶…

MySQL之DQL简单查询

1、结构化查询语言 1. 什么是SQL 结构化查询语言(Structured Query Language),后续通常简称SQL。SQL是用于存取数据以及查询、更新和管理关系数据库系统的标准语言。20世纪70年代由IBM公司开发,目前应用于各种关系型数据库。SQL是一套标准,…

黄金市场步入数据驱动的关键周:多空角力聚焦非农就业

一、市场情绪分化,黄金波动趋于平稳 本周黄金市场在经历了一系列波动后,价格维持在2500美元/盎司上方波动,显示出相对稳定的交易状态。Kitco新闻黄金调查显示,分析师对下周金价走势的看法分歧较大,而零售投资者看涨情绪…

TCP/IP网络编程:第18章聊天室

服务端&#xff1a;负责连接客户端&#xff0c;转发客户端的信息给其他客户 客户端&#xff1a;发送信息给服务端&#xff0c;接收服务端传来的其他客户的信息 服务端代码&#xff1a; #include <stdio.h> #include <stdlib.h> #include <unistd.h> #incl…

OpenCV开发笔记(八十):基于特征点匹配实现全景图片拼接

若该文为原创文章&#xff0c;转载请注明原文出处 本文章博客地址&#xff1a;https://hpzwl.blog.csdn.net/article/details/141790116 长沙红胖子Qt&#xff08;长沙创微智科&#xff09;博文大全&#xff1a;开发技术集合&#xff08;包含Qt实用技术、树莓派、三维、OpenCV…

YOLOS:大道至简,直接使用预训练ViT进行实时目标检测 | NeurIPS 2021

论文探索了在中型ImageNet-1k数据集上预训练的普通ViT到更具挑战性的COCO目标检测基准的可迁移性&#xff0c;提出了基于Vision Transformer的You Only Look at One Sequence(YOLOS)目标检测模型。在具有挑战性的COCO目标检测基准上的实验结果表明&#xff0c;2D目标检测可以以…

Windows系统通过WSL2安装Ubuntu22.04系统及图形化界面

1.通过WSL2安装Ubuntu22.04系统及图形化界面 WSL&#xff08;Windows Subsystem for Linux&#xff09;是一个为Windows用户设计的兼容层&#xff0c;它允许用户在Windows10和Windows11操作系统上直接运行GNU/Linux环境。WSL提供了一个微软开发的Linux兼容内核接口&#xff0c…

MySQL中的时间与当前时间相差8个小时,两步解决。

前言&#xff1a; 有朋友后台私信问&#xff0c;我服务器中的MySQL时间与本地时间差了8个小时&#xff0c;我在给表中字段设置根据时间戳自动更新时会出现时间与当前时间不符的原因。灰常简单&#xff0c;一起来看看吧。 第一步&#xff1a; 在MySQL中&#xff0c;输入命令&…

基于asp.net软件缺陷跟踪系统设计与实现

系统分析 本章讲述了本软件缺陷跟踪系统的前期分析方法和分析结论。 3.1 可靠性分析 在软件研发企业或研发团队中&#xff0c;开发人员和测试人员所应用的软件缺陷管理的主要方法流程是&#xff1a; 1&#xff0e;测试人员发现软件存在的缺陷&#xff0c;填写缺陷报告&…

【正点原子K210连载】第三十四章 image图像滤波实验 摘自【正点原子】DNK210使用指南-CanMV版指南

第三十四章 image图像滤波实验 在上一章节中&#xff0c;介绍了image模块中元素绘制方法给的使用&#xff0c;本章将继续介绍image模块中图像滤波方法的使用。通过本章的学习&#xff0c;读者将学习到image模块中图像滤波的使用。 本章分为如下几个小节&#xff1a; 34.1 imag…

Transformer直接预测完整数学表达式,推理速度提高多个数量级

前言 来自 Mata AI、法国索邦大学、巴黎高师的研究者成功让 Transformer 直接预测出完整的数学表达式。 转载自丨机器之心 符号回归&#xff0c;即根据观察函数值来预测函数数学表达式的任务&#xff0c;通常涉及两步过程&#xff1a;预测表达式的「主干」并选择数值常数&am…

jfif怎么改成jpg格式?这几种转换方法请务必学会!

jfif怎么改成jpg格式&#xff1f;JFIF&#xff0c;作为一种相对不常见的图像存储格式&#xff0c;其在实际应用中确实存在一系列不容忽视的局限&#xff0c;首要问题在于&#xff0c;当尝试将JFIF图片转换为其他格式时&#xff0c;往往会伴随着图像压缩的副作用&#xff0c;这意…

Vue学习笔记 一

Vue学习笔记 1、Vue基础指令 1.1 什么是Vue? Vue.js 是一套响应式的 JavaScript 开发库。Vue.js 自问世以来所受关注度不断提高,在现在的市场上,Vue.js 是非常流行的 JavaScript 技术开发框架之一。 Vue是一款国产前端框架,它的作者尤雨溪(Evan You)是一位美籍华人,…

Android使用addr2line分析Native Crash

NDK提供的工具将函数地址解析为具体的函数名和行数才能进一步分析问题。 常用的地址转换工具有addr2line、ndk-stack等&#xff0c;个人比较喜欢addr2line&#xff0c;所以接下来介绍下该工具的基本使用方式 日常使用过程中&#xff0c;只需要关注-C -f -e三个参数即可 // -…

LaViT:这也行,微软提出直接用上一层的注意力权重生成当前层的注意力权重 | CVPR 2024

Less-Attention Vision Transformer利用了在多头自注意力&#xff08;MHSA&#xff09;块中计算的依赖关系&#xff0c;通过重复使用先前MSA块的注意力来绕过注意力计算&#xff0c;还额外增加了一个简单的保持对角性的损失函数&#xff0c;旨在促进注意力矩阵在表示标记之间关…

注意力机制(Attention mechanism)(中篇)

模型的输入是一组向量&#xff0c;它可以是文字&#xff0c;可以是语音&#xff0c;可以是图。而输出有三种可能性&#xff0c; 第一种可能性是每一个向量都有一个对应的标签。如图1所示&#xff0c;当模型看到输入是4个向 量的时候&#xff0c;它就要输出4个标签。如果是回归问…

React项目通过jsmind实现思维导图以及相关功能

jsMind jsMind 是一个用于显示和编辑思维导图的纯 JavaScript 类库。它基于 Canvas 和 SVG 进行设计&#xff0c;能够在现代浏览器中高效地运行。jsMind 以 BSD 协议开源&#xff0c;这意味着可以在遵守该协议的前提下&#xff0c;将其嵌入到任何项目中使用。 功能特点 jsMi…