数据结构(双向链表——c语言实现)
双向链表相比于单向链表的优势:
1. 双向遍历的灵活性
-
双向链表:由于每个节点都包含指向前一个节点和下一个节点的指针,因此可以从头节点遍历到尾节点,也可以从尾节点遍历到头节点。这种双向遍历的灵活性使得在某些算法和操作中,双向链表能够提供更高效的解决方案。
-
单向链表:只能从头节点开始顺序遍历到尾节点,无法直接访问前驱节点,因此在某些需要双向遍历的场合下,单向链表显得不够灵活。
2. 插入和删除操作的便捷性
-
双向链表:在双向链表中插入或删除一个节点时,只需要修改相邻节点的前后指针,而不需要遍历整个链表来查找前驱或后继节点。这大大提高了插入和删除操作的效率。
-
单向链表:在插入或删除节点时,通常需要遍历链表以找到插入或删除位置的前一个节点,这增加了操作的复杂性。
3. 适用于复杂操作
-
双向链表:由于可以方便地访问前驱和后继节点,双向链表在实现一些复杂操作时(如反转链表、合并链表等)变得更加简单和直观。
-
单向链表:由于只能单向遍历,因此在实现这些复杂操作时可能需要更多的辅助变量和步骤。
4. 内存开销的考虑
-
双向链表:每个节点需要额外存储一个指向前驱节点的指针,因此相对于单向链表,双向链表占用更多的内存空间。然而,这种额外的内存开销通常是可以接受的,特别是在需要频繁访问前驱节点的场合下。
-
单向链表:由于每个节点只存储一个指向后继节点的指针,因此内存开销相对较小。但在需要访问前驱节点的场合下,可能需要通过额外的操作或数据结构来实现。
双向链表:
双向链表跟单向链表最大的区别就在于它的节点里面多了一个指向前一个节点的指针,我们还是约定头结点不存数据,同样还是多文件封装的形式。
#ifndef _DOUBLELINK_H
#define _DOUBLELINK_H#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h> //fabs头文件#define OUT(A) {printf("%.2f ",A);}
typedef float Type;
#define PRE 0.000001 //精度typedef struct node{Type data; //存值struct node *front; //前指针struct node *rear; //后指针
}list;
//头节点不存数据//创建
list *create_link();
//判空
int empty_link(list *l);
//求长度
int length_link(list *l);
//遍历
void traverse_link(list *l);
//首插
void head_insert_link(list *l,Type data);
//尾插
void tail_insert_link(list *l,Type data);
//查找
list *search_link(list *l,Type data);
//修改
void update_link(list *l,Type oldData,Type newData);
//删除
void delete_link(list *l,Type data);
//初始化
void init_link(list *l);
//回收
void recycle_link(list **l);#endif
双向链表的结构体里有三个成员,存储数据的变量data,指向上一个节点的指针front和指向下一个节点的指针rear;在这里浮点型数据是有精度的,它只是无限接近于我们输入的值,并不是完全相同,例如输入的是1.2,但是实际存储的数据为1.20000005;因此我将PRE宏定义为精度,这里是精确到6位小数,便于我们后续的查找中使用。
#include "doublelink.h"//创建
list *create_link()
{list *l = (list *)malloc(sizeof(list));if(NULL == l){perror("create malloc");return NULL;}l->front = NULL;l->rear = NULL;return l;
}
//判空
int empty_link(list *l)
{if(l == NULL){puts("list is NULL");return -1;}return NULL == l->rear?0:1;
}
//求长度
int length_link(list *l)
{
#if 0if(l->rear == NULL) //递归求长度return 0;return 1+length_link(l->rear);
#elseif(l == NULL){puts("list is NULL");return -1;}int n = 0;while(l->rear){n++;l = l->rear;}return n;
#endif
}
//遍历
void traverse_link(list *l)
{if(l == NULL){puts("list is NULL");return;}while(l->rear){l = l->rear;OUT(l->data);}puts("");
}
//首插
void head_insert_link(list *l,Type data)
{if(l == NULL){puts("list is NULL");return;}list *p = (list *)malloc(sizeof(list));if(NULL == p){perror("create malloc");return;}p->data = data;p->front = l;p->rear = l->rear;l->rear = p;if(NULL != p->rear)p->rear->front = p;
}
//尾插
void tail_insert_link(list *l,Type data)
{if(l == NULL){puts("list is NULL");return;}list *p = (list *)malloc(sizeof(list));if(NULL == l){perror("create malloc");return;}while(l->rear){l = l->rear;}p->data = data;l->rear = p;p->front = l;p->rear = NULL;
}
//查找
list *search_link(list *l,Type data)
{if(l == NULL){puts("list is NULL");return NULL;}while(l->rear){l = l->rear;float t = l->data - data;if(fabs(t)<PRE)return l;}return NULL;
}
//修改
void update_link(list *l,Type oldData,Type newData)
{
#if 1if(l == NULL){puts("list is NULL");return;}while(l->rear){l = l->rear;if(oldData == l->data){l->data = newData;}}
#elselist *p = search_link(l,oldData);p->data = newData;
#endif
}
//删除
void delete_link(list *l,Type data)
{
#if 1if(l == NULL){puts("list is NULL");return;}while(l->rear){if(data == l->rear->data){list *p = l->rear;if(NULL != l->rear->rear)l->rear->rear->front = l;l->rear = l->rear->rear;free(p);}elsel = l->rear;}
#elselist *p;while(p=search_link(l,data)){p->front->rear = p->rear;if(p->rear)p->rear->front = p->front;free(p);}
#endif
}
//初始化
void init_link(list *l)
{if(l == NULL){puts("list is NULL");return;}
#if 1while(l->rear){list *p = l->rear;if(NULL != l->rear->rear)l->rear->rear->front = l;l->rear = l->rear->rear;free(p);}
#elsewhile(l->rear){list *p = l->rear;//这里不需要去管要删除节点后一个的前指针,因为所有的都要删除l->rear = p->rear;free(p);}
#endif
}
//回收
void recycle_link(list **l)
{if(l == NULL){puts("list is NULL");return;}init_link(*l);free(*l);*l = NULL;
}
创建:list *create_link()
创建函数的返回值还是节点的地址,创建头节点之后要让它的两个指针都指向NULL,因为这时只有一个头结点,没有其他节点。
判空:int empty_link(list *l)
双向链表的判空跟单向链表是一样的,只需要判断头节点的下一个节点是否为空(NULL)即可,空函数返回0,非空函数返回1。
求长度:int length_link(list *l)
求长度只需要在遍历的时候使用一个变量来统计节点的数量即可;在这里我使用了两种方法来求长度,一种就是遍历计数;另外一种是使用递归函数来求长度,每次调用自己的时候都+1,就变成了有几个节点就是几个1相加 0,这样也可以求长度,而且代码更加简洁。
遍历:void traverse_link(list *l)
使用循环来遍历,让L每次往后移动一个节点,直到最后一个节点为止,每次循环都将节点的数据输出,就实现了遍历。
首插(头插):void head_insert_link(list *l,Type data)
头部插入我们需要做的事情有给节点data赋值,给节点指针指向,断链;首先创建节点,创建完成之后给data赋值;然后让新节点的front指向头节点,让rear指向头节点的下一个节点,让头节点的rear指向新节点;最后就是让新节点的下一个节点的front指向新节点,但是在这里需要注意一个问题,如果原链表是空链表,那么就不能对新节点的下一个进行p->rear->front = p;操作,因为这时它为空,所以在这里就需要做一个判断,如果新节点的下一个节点不为空,那么才让它的front指向新节点,否则就不需要执行这一步操作。
尾插:void tail_insert_link(list *l,Type data)
尾部插入相对头部插入就要简单一些,首先是给新节点的data赋值,然后循环遍历找到最后一个节点,让最后一个节点的rear指向新节点,让新节点的front指向最后这个节点,最后让新节点的rear指向NULL即可。
查找:list *search_link(list *l,Type data)
查找需要返回节点的地址,因此函数为list *类型,同样使用循环的方式,但是在这里不建议使用data == l->data的方式来寻找所需要的节点,因为浮点型数据存储并不是完全与输入的数据相等,因此我们将查找的data与链表节点中的data做一个差,根据这个差值来判断是否是我们寻找的数据,这里就用到了我一开始定义的精度,差值在精度范围内就说明这个数据是我们要寻找的数据;在这里因为差值可能为负数,因此我使用fabs这个函数来将差值转化为绝对值,只要绝对值小于我们的精度,那就返回这个节点的地址,要是循环遍历结束都没有找到这个数据,那就返回一个NULL。
在这里我使用的是fabs函数而不是abs函数是因为abs在将浮点型数据取绝对值的时候只能将它的整数部分取绝对值,而不能将整个浮点型数据取绝对值,但是fabs就能将整个浮点型数据取绝对值,这一点需要宝子们注意。
abs
函数和fabs
函数关键的区别:
-
数据类型:
-
abs
函数用于计算整数的绝对值。它的参数和返回值都是整数类型(int
)。 -
fabs
函数用于计算浮点数的绝对值。它的参数是浮点类型(double
),返回值也是double
类型。
-
-
头文件:
-
要使用
abs
函数,你需要包含stdlib.h
头文件。 -
要使用
fabs
函数,你需要包含math.h
头文件。
-
-
用途:
-
当你的数据是整数时,使用
abs
函数。 -
当你的数据是浮点数时,使用
fabs
函数。
-
修改:void update_link(list *l,Type oldData,Type newData)
数据的修改就很简单啦,通过循环遍历找到要修改的数据,将它直接修改为要更改的值即可,在这里可以偷懒调用之前的查找函数来帮助我们找到要修改的节点,然后直接修改内容就行,这样就不用再写一遍遍历啦。
删除:void delete_link(list *l,Type data)
删除节点要做的有两件,释放节点和断链;断链也就是更改删除节点前后节点的指针指向,在这里把要删除的节点叫做目标节点;既然是删除节点,那就需要使用一个指针来指向这个节点,然后让目标节点的前一个节点的rear指向目标节点的下一个节点,在这里同样需要判断目标节点的下一个节点是否为空,如果非空就需要让该节点的front指向目标节点的前一个节点。
初始化:void init_link(list *l)
双向链表的初始化和单向链表类似,只需要将每一个节点空间都释放掉即可,同样是通过循环遍历的方式;同样的也可以调用之前的删除函数,但是在这里我们可以不需要去管节点的前指针front,因为我们每一个节点都需要删除,将空间释放掉之后front也就不存在了。
回收:void recycle_link(list **l)
回收函数的传参传的同样是链表头节点的地址,首次是初始化,然后再将头节点也释放,因此可以调用之前的初始化函数,然后再让这个指针指向NULL即可。
测试(主函数):
#include "doublelink.h"
int main(int agrc,char *agrv[])
{list *p = create_link();puts("尾插");tail_insert_link(p,1.25);tail_insert_link(p,1.2);tail_insert_link(p,1.1);traverse_link(p);puts("头插");head_insert_link(p,1.5);head_insert_link(p,1.10);head_insert_link(p,1.4);traverse_link(p);printf("length=%d\n",length_link(p));puts("将1.10更改为1.6");update_link(p,1.10,1.6);traverse_link(p);puts("删除1.2");delete_link(p,1.2);traverse_link(p);puts("查找1.50,利用找到的节点将它修改为2.00");list *q = search_link(p,1.50);q->data = 2.00;traverse_link(p);puts("初始化");init_link(p);printf("length=%d\n",length_link(p));puts("回收");recycle_link(&p);printf("p=%p\n",p);return 0;
}
相关文章:

数据结构(双向链表——c语言实现)
双向链表相比于单向链表的优势: 1. 双向遍历的灵活性 双向链表:由于每个节点都包含指向前一个节点和下一个节点的指针,因此可以从头节点遍历到尾节点,也可以从尾节点遍历到头节点。这种双向遍历的灵活性使得在某些算法和操作中&a…...

【新人系列】Python 入门(十一):控制结构
✍ 个人博客:https://blog.csdn.net/Newin2020?typeblog 📝 专栏地址:https://blog.csdn.net/newin2020/category_12801353.html 📣 专栏定位:为 0 基础刚入门 Python 的小伙伴提供详细的讲解,也欢迎大佬们…...

群核科技首次公开“双核技术引擎”,发布多模态CAD大模型
11月20日,群核科技在杭州举办了第九届酷科技峰会。现场,群核科技首次正式介绍其技术底层核心:基于GPU高性能计算的物理世界模拟器。并对外公开了两大技术引擎:群核启真(渲染)引擎和群核矩阵(CAD…...

【AI大模型引领变革】探索AI如何重塑软件开发流程与未来趋势
文章目录 每日一句正能量前言流程与模式介绍【传统软件开发 VS AI参与的软件开发】一、传统软件开发流程与模式二、AI参与的软件开发流程与模式三、AI带来的不同之处 结论 AI在软件开发流程中的优势、挑战及应对策略AI在软件开发流程中的优势面临的挑战及应对策略 结论 后记 每…...

linux 常用命令指南(存储分区、存储挂载、docker迁移)
前言:由于目前机器存储空间不够,所以‘斥巨资’加了一块2T的机械硬盘,下面是对linux扩容的一系列操作,包含了磁盘空间的创建、删除;存储挂载;docker迁移;anaconda3迁移等。 一、存储分区 1.1 …...

用pyspark把kafka主题数据经过etl导入另一个主题中的有关报错
首先看一下我们的示例代码 import os from pyspark.sql import SparkSession import pyspark.sql.functions as F """ ------------------------------------------Description : TODO:SourceFile : etl_stream_kafkaAuthor : zxxDate : 2024/11/…...

Redis的过期删除策略和内存淘汰机制以及如何保证双写的一致性
Redis的过期删除策略和内存淘汰机制以及如何保证双写的一致性 过期删除策略内存淘汰机制怎么保证redis双写的一致性?更新策略先删除缓存后更新数据库先更新数据库后删除缓存如何选择?如何保证先更新数据库后删除缓存的线程安全问题? 过期删除策略 为了…...

异常处理:import cv2时候报错No module named ‘numpy.core.multiarray‘
问题描述 执行一个将视频变成二值视频输出时候,报错。No module named numpy.core.multiarray,因为应安装过了numpy,所以比较不解。试了卸载numpy和重新安装numpy多次操作,也进行了numpy升级的操作,但是都没有用。 解…...
C++手写PCD文件
前言 一般pcd读写只需要调pcl库接口,直接用pcl的结构写就好了 这里是不依赖pcl库的写入方法 主要是开头写一个header 注意字段大小,类型不要写错 结构定义 写入点需要与header中定义一致 这里用的RoboSense的结构写demo 加了个1字节对齐 stru…...

优选算法(双指针)
1.双指针介绍 双指针算法是一种常用的算法思想,特别适用于处理涉及阵列、链表或字符串等线性数据结构的问题。通过操作两个一个指针来进行导航或操作数据结构,双指针可以最大程度优化解决方案的效率。提高效率并减少空间复杂度。 在Java中使用双指针的核…...

【保姆级】Mac上IDEA卡顿优化
保姆级操作,跟着操作即可~~~ 优化内存 在你的应用程序中,找到你的idea 按住control键+单击 然后点击“显示包内容” </...

python实战案例----使用 PyQt5 构建简单的 HTTP 接口测试工具
python实战案例----使用 PyQt5 构建简单的 HTTP 接口测试工具 文章目录 python实战案例----使用 PyQt5 构建简单的 HTTP 接口测试工具项目背景技术栈用户界面核心功能实现结果展示完整代码总结 在现代软件开发中,测试接口的有效性与响应情况变得尤为重要。本文将指导…...
pytest 接口串联场景
在编写接口测试时,如果有多个接口需要串联在一起调用,并且这些接口共同构成了一个业务场景,通常可以使用以下几种方法来组织代码,使其更具可读性和维护性。以下是一些规范的建议: 1. 使用 pytest 的 fixture 来管理接…...

Springboot项目搭建(2)-用户详细信息查询
1. 提要信息 1.1 java四类八种 在Java中,四类指的是Java中的基本数据类型和引用数据类型: 基本数据类型:Java提供了八种基本数据类型,包括整数型、浮点型、字符型和布尔型。引用数据类型:指向对象的引用,…...

Stable Diffusion的加噪和去噪详解
SD模型原理: Stable Diffusion概要讲解Stable diffusion详细讲解Stable Diffusion的加噪和去噪详解Diffusion ModelStable Diffusion核心网络结构——VAEStable Diffusion核心网络结构——CLIP Text EncoderStable Diffusion核心网络结构——U-NetStable Diffusion中…...
解决 Gradle 报错:`Plugin with id ‘maven‘ not found` 在 SDK 开发中的问题
在 SDK 开发过程中,使用 Gradle 构建和发布 SDK 是常见的任务。在将 SDK 发布为 AAR 或 JAR 包时,你可能会使用 apply plugin: maven 来发布到本地或远程的 Maven 仓库。但是,随着 Gradle 版本的更新,特别是从 Gradle 7 版本开始&…...

EMD-KPCA-Transformer多变量回归预测!分解+降维+预测!多重创新!直接写核心!
EMD-KPCA-Transformer多变量回归预测!分解降维预测!多重创新!直接写核心! 目录 EMD-KPCA-Transformer多变量回归预测!分解降维预测!多重创新!直接写核心!效果一览基本介绍程序设计参…...
前端 px、rpx、em、rem、vh、vw计量单位的区别
目录 一、px 二、rpx 三、em 四、rem 五、vh和vw 六、rpx 和 px之间的区别 七、px 与 rem 的区别 一、px px(像素): 1、相对单位,代表屏幕上的一个基本单位,逻辑像素。 2、不会根据屏幕尺寸或分辨率自动调整大…...
OceanBase数据库产品与工具介绍
OceanBase:蚂蚁集团自主研发的分布式关系数据库 1、什么是 OceanBase? OceanBase 是由蚂蚁集团完全自主研发的企业级分布式关系数据库,始创于 2010 年。它具有以下核心特点: 数据强一致性:在分布式架构下确保数据强…...

学习threejs,对模型多个动画切换展示
👨⚕️ 主页: gis分享者 👨⚕️ 感谢各位大佬 点赞👍 收藏⭐ 留言📝 加关注✅! 👨⚕️ 收录于专栏:threejs gis工程师 文章目录 一、🍀前言1.1 ☘️THREE.AnimationMixer 动画…...
挑战杯推荐项目
“人工智能”创意赛 - 智能艺术创作助手:借助大模型技术,开发能根据用户输入的主题、风格等要求,生成绘画、音乐、文学作品等多种形式艺术创作灵感或初稿的应用,帮助艺术家和创意爱好者激发创意、提高创作效率。 - 个性化梦境…...

eNSP-Cloud(实现本地电脑与eNSP内设备之间通信)
说明: 想象一下,你正在用eNSP搭建一个虚拟的网络世界,里面有虚拟的路由器、交换机、电脑(PC)等等。这些设备都在你的电脑里面“运行”,它们之间可以互相通信,就像一个封闭的小王国。 但是&#…...

Spark 之 入门讲解详细版(1)
1、简介 1.1 Spark简介 Spark是加州大学伯克利分校AMP实验室(Algorithms, Machines, and People Lab)开发通用内存并行计算框架。Spark在2013年6月进入Apache成为孵化项目,8个月后成为Apache顶级项目,速度之快足见过人之处&…...
Java - Mysql数据类型对应
Mysql数据类型java数据类型备注整型INT/INTEGERint / java.lang.Integer–BIGINTlong/java.lang.Long–––浮点型FLOATfloat/java.lang.FloatDOUBLEdouble/java.lang.Double–DECIMAL/NUMERICjava.math.BigDecimal字符串型CHARjava.lang.String固定长度字符串VARCHARjava.lang…...

Springcloud:Eureka 高可用集群搭建实战(服务注册与发现的底层原理与避坑指南)
引言:为什么 Eureka 依然是存量系统的核心? 尽管 Nacos 等新注册中心崛起,但金融、电力等保守行业仍有大量系统运行在 Eureka 上。理解其高可用设计与自我保护机制,是保障分布式系统稳定的必修课。本文将手把手带你搭建生产级 Eur…...
MySQL用户和授权
开放MySQL白名单 可以通过iptables-save命令确认对应客户端ip是否可以访问MySQL服务: test: # iptables-save | grep 3306 -A mp_srv_whitelist -s 172.16.14.102/32 -p tcp -m tcp --dport 3306 -j ACCEPT -A mp_srv_whitelist -s 172.16.4.16/32 -p tcp -m tcp -…...

HarmonyOS运动开发:如何用mpchart绘制运动配速图表
##鸿蒙核心技术##运动开发##Sensor Service Kit(传感器服务)# 前言 在运动类应用中,运动数据的可视化是提升用户体验的重要环节。通过直观的图表展示运动过程中的关键数据,如配速、距离、卡路里消耗等,用户可以更清晰…...

Mysql中select查询语句的执行过程
目录 1、介绍 1.1、组件介绍 1.2、Sql执行顺序 2、执行流程 2.1. 连接与认证 2.2. 查询缓存 2.3. 语法解析(Parser) 2.4、执行sql 1. 预处理(Preprocessor) 2. 查询优化器(Optimizer) 3. 执行器…...

springboot整合VUE之在线教育管理系统简介
可以学习到的技能 学会常用技术栈的使用 独立开发项目 学会前端的开发流程 学会后端的开发流程 学会数据库的设计 学会前后端接口调用方式 学会多模块之间的关联 学会数据的处理 适用人群 在校学生,小白用户,想学习知识的 有点基础,想要通过项…...
JavaScript基础-API 和 Web API
在学习JavaScript的过程中,理解API(应用程序接口)和Web API的概念及其应用是非常重要的。这些工具极大地扩展了JavaScript的功能,使得开发者能够创建出功能丰富、交互性强的Web应用程序。本文将深入探讨JavaScript中的API与Web AP…...