【手撕数据结构】拿捏双向链表

news2024/11/26 22:45:46

目录

  • 链表介绍
  • 初始化链表
  • 销毁链表
  • 查找节点
  • 打印链表
  • 增加节点
    • 尾插
    • 头插
    • 在指定位置之后插入节点
  • 删除节点
    • 尾删
    • 头删
    • 删除指定位置节点
  • 链表判空

链表介绍

前面说到,链表的结构一共有八种:带头单向循环链表、带头单向非循环链表、带头双向循环链表、带头双向非循环链表、无头单向循环链表、无头单向非循环链表、无头双向循环链表、无头双向非循环链表。
 在这八种结构中,我们只挑两种来进行刨析,即无头单向非循环链表和带头双向循环链表。
在这里插入图片描述
 而今天我们要介绍的双向链表就是带头的,头结点head也被称为哨兵位,如果链表为空,则链表只有一个哨兵位。双向链表的结构为

typedef int LTDataType;	//方便以后修改双向链表存储的数据类型
typedef struct ListNode
{
	LTDataType val;
	struct ListNode* prev;
	struct ListNode* next;
}LTNode;

分别用一个变量存储数据,前驱指针prev指向前节点,next指针指向后节点,这样链表就可以进行随机访问。

初始化链表

LTNode* LTBuyNode(LTDataType x)		//尾插,头插等都需要创建新的节点,不妨封装一个函数,每个节点的next,prev指针都先指向自己,
{		
	LTNode* newnode = (LTNode*)malloc(sizeof(LTDataType));
	if (newnode == NULL)
	{
		perror("malloc failed");
		exit(1);
	}
	newnode->val = x;
	newnode->prev = newnode->next = newnode;
	return newnode;
}

因为插入都需要创建节点,所以封装一个函数
注意:这些节点的next指针和prev指针都指向自己,这是为了兼容空链表只有一个哨兵位时,和后面插入节点时利用这两个指向自己的指针调成被影响的节点的指针

//void LTInit(LTNode** pphead)
//{
//	assert(pphead);
//	*pphead = LTBuyNode(-1);
//}
//优化接口一致性版本
LTNode* LTInit(LTNode* phead)
{
	LTNode* newnode = LTBuyNode(-1);
	return newnode;
}

首先,我们要对链表进行初始化,上面说了空双向链表是只有一个哨兵位,所以我们创建一个节点作为哨兵位.

销毁链表

//void LTDestroy(LTNode** pphead)
//{
//	assert(pphead && *pphead);	//*pphead防止释放空链表
//	LTNode* pcur = (*pphead)->next;
//	while (pcur != *pphead)
//	{
//		LTNode* Next = pcur->next;
//		free(pcur);
//		pcur = NULL;
//		pcur = Next;
//	}
//	free(*pphead);
//	*pphead = NULL;
//}
//优化接口一致性版本
void LTDestroy(LTNode* pphead)
{
	assert(pphead);
	LTNode* pcur = pphead->next;
	while (pcur != pphead)
	{
		LTNode* Next = pcur->next;
		free(pcur);
		pcur = NULL;
		pcur = Next;
	}
	free(pphead);
	pphead = NULL;	//这里设置NULL了不影响实参,需要手动设置NULL,但是释放了空间是同一块空间,
}

销毁链表,从头结点的后一个结点处开始向后遍历并释放结点,直到遍历到头结点时,停止遍历并将头结点也释放掉。

查找节点

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

给定一个值,在链表中寻找与该值相同的结点,若找到了,则返回结点地址;若没有找到,则返回空指针(NULL)。如果为空链表,就直接返回NULL.这里因为双向楼面是循环的,用pcur从头结点(哨兵位下一个节点)开始遍历,如果回到哨兵位表示已经查找完一圈,没找到。

打印链表

void LTPrint(LTNode* phead)
{
	assert(phead);
	LTNode* pcur = phead->next;
	while (pcur != phead)
	{
		printf("%d->", pcur->val);
		pcur = pcur->next;
	}
}

打印双向链表时也是从头结点的后一个结点处开始向后遍历并打印,直到遍历哨兵位结点处时便停止遍历和打印(哨兵位结点数据不打印)。

增加节点

尾插

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

尾插,申请一个新结点,将节点插入到旧尾节点之后,改变旧尾节点的next指针和新尾节点的next指针和prev指针,改变哨兵为的prev指针

在这里插入图片描述

头插

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

头插,即申请一个新结点,将新结点插入在哨兵位后即可。
在这里插入图片描述

在指定位置之后插入节点

void LTInsert(LTNode* pos, LTDataType x)
{
	assert(pos);
	LTNode* newnode = LTBuyNode(x);
	newnode->next = pos->next;
	newnode->prev = pos;
	pos->next->prev = newnode;
	pos->next = newnode;
}

我们只需申请一个新结点插入到指定位置结点和其后一个结点之间即可。

删除节点

尾删

void LTPopBack(LTNode* phead)
{
	assert(phead);
	assert(LTEmpty(phead));
	LTNode* del = phead->prev;
	del->prev->next = phead;
	phead->prev = del->prev;
	free(del);
	del = NULL;
}

头删

头删,即释放哨兵位的后一个结点,并建立哨兵位节点与被删除结点的后一个结点之间的双向关系即可。
在这里插入图片描述

void LTPopFront(LTNode* phead)
{
	assert(phead);
	assert(LTEmpty(phead));
	LTNode* del = phead->next;
	phead->next = del->next;
	del->next->prev = phead;
	free(del);
	del = NULL;
}

删除指定位置节点

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

删除指定位置结点,释放掉目标结点后,建立该结点前一个结点和后一个结点之间的双向关系即可。

链表判空

bool LTEmpty(LTNode* phead)
{
	assert(phead);
	return phead->next != phead;
}

链表判空,即判断头结点的前驱或是后驱指向的是否是自己即可(因为是循环)

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

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

相关文章

绿色算力|暴雨服务器用芯片筑起“十四五”转型新篇章

面对全球气候变化、技术革新以及能源转型的新形势,发展低碳、高效的绿色算力不仅是顺应时代的要求,更是我国建设数字基础设施和展现节能减碳大国担当的重要命题,在此背景下也要求在提升算力规模和性能的同时,积极探索推动算力基础…

计算机网络参考模型与5G协议

目录 OSI七层参考模型OSI模型vsTCP/IP模型TCP/IP协议族的组成 OSI七层参考模型 分层功能应用层网络服务与最终用户的一个接口表示层数据的表示,安全,压缩会话层建立,管理,终止会话传输层定义传输数据的协议端口号,以及流控和差错校验网络层进行逻辑地址寻址,实现不同网路之间的…

泛型新理解

1.创建三个类,并写好对应关系 package com.jmj.gulimall.study;public class People { }package com.jmj.gulimall.study;public class Student extends People{ }package com.jmj.gulimall.study;public class Teacher extends People{ }2.解释一下这三个方法 pub…

麻省理工学院 - MIT - 线性代数学习笔记

学习视频地址 文章目录 1.01方程组的几何解释2.02矩阵消元3.03乘法和逆矩阵乘法逆 4.04矩阵A的LU分解5.05转置,置换,向量空间置换转置向量空间 6.06列空间和零空间7.07求解Ax0:主变量,特解 1.01方程组的几何解释 对于二元方程组&…

重生之我们在ES顶端相遇第6 章- Dynamic Mapping(动态映射)

思维导图 前言 在第5章,我们说完 ES 常用字段类型。但是,并未跟大家解释,为什么不设置 Mapping,写入的字符串,默认就可以全文搜索。例如 PUT /test4/_doc/1 {"name": "hello world" } GET /test…

Qt开发网络嗅探器01

引言 随着互联网的快速发展和普及,人们对网络性能、安全和管理的需求日益增 长。在复杂的网络环境中,了解和监控网络中的数据流量、安全事件和性能 问题变得至关重要。为了满足这些需求,网络嗅探器作为一种重要的工具被 广泛应用。 网络嗅探…

IoTDB 分段查询语句详解:GROUP BY + 时序语义

GROUP BY 查询子句的时序语义展开,IoTDB 支持的分段方式总结! 存储的数据通过分析来发挥价值,当一组被存储的数据通过查询得到分析后的结果时,这些数据才真正在数据库中实现了价值闭环。 在关系型数据库中,GROUP BY 子…

微信小程序数组绑定使用案例(二)

一、数组事件绑定&#xff0c;事件传递数据 1.wxml <text>姓名&#xff1a;{{name}} </text> <block wx:for"{{list}}"><button bind:tap"nameClick2" data-name"{{item}}">修改:{{item}}</button> </block&…

【BUG】已解决:xlrd.biffh.XLRDError: Excel xlsx file; not supported

已解决&#xff1a;xlrd.biffh.XLRDError: Excel xlsx file&#xff1b; not supported 目录 已解决&#xff1a;xlrd.biffh.XLRDError: Excel xlsx file&#xff1b; not supported 【常见模块错误】 错误原因 解决办法&#xff1a; 欢迎来到英杰社区https://bbs.csdn.net/…

【学习笔记】无人机系统(UAS)的连接、识别和跟踪(三)-架构模型和概念

引言 3GPP TS 23.256 技术规范&#xff0c;主要定义了3GPP系统对无人机&#xff08;UAV&#xff09;的连接性、身份识别、跟踪及A2X&#xff08;Aircraft-to-Everything&#xff09;服务的支持。 3GPP TS 23.256 技术规范&#xff1a; 【免费】3GPPTS23.256技术报告-无人机系…

Ubuntu 中默认的 root 用户密码

场景&#xff1a;想要切换root用户&#xff0c;发现得输入密码&#xff0c;以为是以前设置过然后一直尝试都是错误【认证失败】最后发现根本没设置过root用户&#xff0c;默认会随机生成root用户的密码&#x1f605; Ubuntu 中默认的 root 密码是随机的&#xff0c;即每次开机都…

Golang | Leetcode Golang题解之第263题丑数

题目&#xff1a; 题解&#xff1a; var factors []int{2, 3, 5}func isUgly(n int) bool {if n < 0 {return false}for _, f : range factors {for n%f 0 {n / f}}return n 1 }

数据结构->线性结构->顺序存储->静态链表

一、思路 链表由节点组成。 1、分析需求&#xff0c;画图&#xff1a; 2、定义学生结构体&#xff0c;包含姓名、年龄、性别和下一个学生的指针&#xff1a; #include <stdio.h> #define N 20// 定义性别枚举类型&#xff0c;固定值&#xff0c;不是男就是女 typedef e…

基于Centos7搭建rsyslog服务器

一、配置rsyslog可接收日志 1、准备新的Centos7环境 2、部署lnmp环境 # 安装扩展源 wget -O /etc/yum.repos.d/epel.repo http://mirrors.aliyun.com/repo/epel-7.repo# 安装扩展源 yum install nginx -y# 安装nginx yum install -y php php-devel php-fpm php-mysql php-co…

JVM:GraalVM

文章目录 一、介绍1、什么是GraalVM&#xff1a;2、GraalVM版本 二、两种使用模式 一、介绍 1、什么是GraalVM&#xff1a; GraalVM是Oracle官方推出的一款高性能JDK&#xff0c;使用它享受比OpenJDK或者OracleJDK更好的性能。GraalVM的官网地址&#xff1a;https://www.graa…

我在高职教STM32——串口通信(4)

大家好,我是老耿,高职青椒一枚,一直从事单片机、嵌入式、物联网等课程的教学。对于高职的学生层次,同行应该都懂的,老师在课堂上教学几乎是没什么成就感的。正因如此,才有了借助 CSDN 平台寻求认同感和成就感的想法。在这里,我准备陆续把自己花了很多心思的教学设计分享…

十年前的老电脑能装win10吗_十年前的老电脑用U盘安装win10教程

十年前的老电脑能装win10吗&#xff1f;十年前的老电脑只要满足win10最低要求的配置都可以安装win10。安装win10方法很多&#xff0c;有一键重装方法、U盘安装、硬盘安装等方式&#xff0c;但最靠谱的方式还是U盘安装。十年前的老电脑用U盘安装win10首先要将u盘制作成u盘启动盘…

Flutter中GetX的用法(超详细使用指南之路由依赖管理篇)

目录 1.前言 2.GetX 依赖管理概述 1.GetX 依赖管理的基本概念 2.与其他依赖管理工具的比较 3. 基础依赖注入 1.Get.put 2.Get.lazyPut 3.Get.putAsync 4.高级依赖注入 1.使用Get.create 2.依赖生命周期管理 5. 参考资料 1.前言 今天这篇博客主要介绍Getx的三大功能…

【AI学习】LLaMA 系列模型的进化(二)

在前面LLaMA 系列模型的进化&#xff08;一&#xff09;中学习了LLama模型的总体进化发展&#xff0c;再来看看其中涉及的一些重要技术。 PreLayerNorm Layer Norm有Pre-LN和Post-LN两种。Layer Normalization&#xff08;LN&#xff09;在Transformer架构中的放置位置对模型…

Java实战中如何使用多线程(线程池)及其为什么使用?

这个话题在入行之前就想过很多次&#xff0c;很多8古文或者你搜索的结果都是告诉你什么提高高并发或者是一些很高大上的话&#xff0c;既没有案例也没有什么公式去证明&#xff0c;但是面试中总是被问到&#xff0c;也没有实战经历&#xff0c;所以面试时一问到多线程的东西就无…