数据结构(双向链表——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 动画…...
江苏艾立泰跨国资源接力:废料变黄金的绿色供应链革命
在华东塑料包装行业面临限塑令深度调整的背景下,江苏艾立泰以一场跨国资源接力的创新实践,重新定义了绿色供应链的边界。 跨国回收网络:废料变黄金的全球棋局 艾立泰在欧洲、东南亚建立再生塑料回收点,将海外废弃包装箱通过标准…...
Ascend NPU上适配Step-Audio模型
1 概述 1.1 简述 Step-Audio 是业界首个集语音理解与生成控制一体化的产品级开源实时语音对话系统,支持多语言对话(如 中文,英文,日语),语音情感(如 开心,悲伤)&#x…...
微信小程序云开发平台MySQL的连接方式
注:微信小程序云开发平台指的是腾讯云开发 先给结论:微信小程序云开发平台的MySQL,无法通过获取数据库连接信息的方式进行连接,连接只能通过云开发的SDK连接,具体要参考官方文档: 为什么? 因为…...
Map相关知识
数据结构 二叉树 二叉树,顾名思义,每个节点最多有两个“叉”,也就是两个子节点,分别是左子 节点和右子节点。不过,二叉树并不要求每个节点都有两个子节点,有的节点只 有左子节点,有的节点只有…...
【数据分析】R版IntelliGenes用于生物标志物发现的可解释机器学习
禁止商业或二改转载,仅供自学使用,侵权必究,如需截取部分内容请后台联系作者! 文章目录 介绍流程步骤1. 输入数据2. 特征选择3. 模型训练4. I-Genes 评分计算5. 输出结果 IntelliGenesR 安装包1. 特征选择2. 模型训练和评估3. I-Genes 评分计…...
JS手写代码篇----使用Promise封装AJAX请求
15、使用Promise封装AJAX请求 promise就有reject和resolve了,就不必写成功和失败的回调函数了 const BASEURL ./手写ajax/test.jsonfunction promiseAjax() {return new Promise((resolve, reject) > {const xhr new XMLHttpRequest();xhr.open("get&quo…...
R 语言科研绘图第 55 期 --- 网络图-聚类
在发表科研论文的过程中,科研绘图是必不可少的,一张好看的图形会是文章很大的加分项。 为了便于使用,本系列文章介绍的所有绘图都已收录到了 sciRplot 项目中,获取方式: R 语言科研绘图模板 --- sciRplothttps://mp.…...
Caliper 配置文件解析:fisco-bcos.json
config.yaml 文件 config.yaml 是 Caliper 的主配置文件,通常包含以下内容: test:name: fisco-bcos-test # 测试名称description: Performance test of FISCO-BCOS # 测试描述workers:type: local # 工作进程类型number: 5 # 工作进程数量monitor:type: - docker- pro…...
Xcode 16 集成 cocoapods 报错
基于 Xcode 16 新建工程项目,集成 cocoapods 执行 pod init 报错 ### Error RuntimeError - PBXGroup attempted to initialize an object with unknown ISA PBXFileSystemSynchronizedRootGroup from attributes: {"isa">"PBXFileSystemSynchro…...
在Spring Boot中集成RabbitMQ的完整指南
前言 在现代微服务架构中,消息队列(Message Queue)是实现异步通信、解耦系统组件的重要工具。RabbitMQ 是一个流行的消息中间件,支持多种消息协议,具有高可靠性和可扩展性。 本博客将详细介绍如何在 Spring Boot 项目…...
