当前位置: 首页 > news >正文

数据结构(双向链表——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函数关键的区别

  1. 数据类型

    • abs函数用于计算整数的绝对值。它的参数和返回值都是整数类型(int)。

    • fabs函数用于计算浮点数的绝对值。它的参数是浮点类型(double),返回值也是double类型。

  2. 头文件

    • 要使用abs函数,你需要包含stdlib.h头文件。

    • 要使用fabs函数,你需要包含math.h头文件。

  3. 用途

    • 当你的数据是整数时,使用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语言实现)

双向链表相比于单向链表的优势&#xff1a; 1. 双向遍历的灵活性 双向链表&#xff1a;由于每个节点都包含指向前一个节点和下一个节点的指针&#xff0c;因此可以从头节点遍历到尾节点&#xff0c;也可以从尾节点遍历到头节点。这种双向遍历的灵活性使得在某些算法和操作中&a…...

【新人系列】Python 入门(十一):控制结构

✍ 个人博客&#xff1a;https://blog.csdn.net/Newin2020?typeblog &#x1f4dd; 专栏地址&#xff1a;https://blog.csdn.net/newin2020/category_12801353.html &#x1f4e3; 专栏定位&#xff1a;为 0 基础刚入门 Python 的小伙伴提供详细的讲解&#xff0c;也欢迎大佬们…...

群核科技首次公开“双核技术引擎”,发布多模态CAD大模型

11月20日&#xff0c;群核科技在杭州举办了第九届酷科技峰会。现场&#xff0c;群核科技首次正式介绍其技术底层核心&#xff1a;基于GPU高性能计算的物理世界模拟器。并对外公开了两大技术引擎&#xff1a;群核启真&#xff08;渲染&#xff09;引擎和群核矩阵&#xff08;CAD…...

【AI大模型引领变革】探索AI如何重塑软件开发流程与未来趋势

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

linux 常用命令指南(存储分区、存储挂载、docker迁移)

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

用pyspark把kafka主题数据经过etl导入另一个主题中的有关报错

首先看一下我们的示例代码 import os from pyspark.sql import SparkSession import pyspark.sql.functions as F """ ------------------------------------------Description : TODO&#xff1a;SourceFile : etl_stream_kafkaAuthor : zxxDate : 2024/11/…...

Redis的过期删除策略和内存淘汰机制以及如何保证双写的一致性

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

异常处理:import cv2时候报错No module named ‘numpy.core.multiarray‘

问题描述 执行一个将视频变成二值视频输出时候&#xff0c;报错。No module named numpy.core.multiarray&#xff0c;因为应安装过了numpy&#xff0c;所以比较不解。试了卸载numpy和重新安装numpy多次操作&#xff0c;也进行了numpy升级的操作&#xff0c;但是都没有用。 解…...

C++手写PCD文件

前言 一般pcd读写只需要调pcl库接口&#xff0c;直接用pcl的结构写就好了 这里是不依赖pcl库的写入方法 主要是开头写一个header 注意字段大小&#xff0c;类型不要写错     结构定义 写入点需要与header中定义一致 这里用的RoboSense的结构写demo 加了个1字节对齐 stru…...

优选算法(双指针)

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

【保姆级】Mac上IDEA卡顿优化

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

python实战案例----使用 PyQt5 构建简单的 HTTP 接口测试工具

python实战案例----使用 PyQt5 构建简单的 HTTP 接口测试工具 文章目录 python实战案例----使用 PyQt5 构建简单的 HTTP 接口测试工具项目背景技术栈用户界面核心功能实现结果展示完整代码总结 在现代软件开发中&#xff0c;测试接口的有效性与响应情况变得尤为重要。本文将指导…...

pytest 接口串联场景

在编写接口测试时&#xff0c;如果有多个接口需要串联在一起调用&#xff0c;并且这些接口共同构成了一个业务场景&#xff0c;通常可以使用以下几种方法来组织代码&#xff0c;使其更具可读性和维护性。以下是一些规范的建议&#xff1a; 1. 使用 pytest 的 fixture 来管理接…...

Springboot项目搭建(2)-用户详细信息查询

1. 提要信息 1.1 java四类八种 在Java中&#xff0c;四类指的是Java中的基本数据类型和引用数据类型&#xff1a; 基本数据类型&#xff1a;Java提供了八种基本数据类型&#xff0c;包括整数型、浮点型、字符型和布尔型。引用数据类型&#xff1a;指向对象的引用&#xff0c…...

Stable Diffusion的加噪和去噪详解

SD模型原理&#xff1a; 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 开发过程中&#xff0c;使用 Gradle 构建和发布 SDK 是常见的任务。在将 SDK 发布为 AAR 或 JAR 包时&#xff0c;你可能会使用 apply plugin: maven 来发布到本地或远程的 Maven 仓库。但是&#xff0c;随着 Gradle 版本的更新&#xff0c;特别是从 Gradle 7 版本开始&…...

EMD-KPCA-Transformer多变量回归预测!分解+降维+预测!多重创新!直接写核心!

EMD-KPCA-Transformer多变量回归预测&#xff01;分解降维预测&#xff01;多重创新&#xff01;直接写核心&#xff01; 目录 EMD-KPCA-Transformer多变量回归预测&#xff01;分解降维预测&#xff01;多重创新&#xff01;直接写核心&#xff01;效果一览基本介绍程序设计参…...

前端 px、rpx、em、rem、vh、vw计量单位的区别

目录 一、px 二、rpx 三、em 四、rem 五、vh和vw 六、rpx 和 px之间的区别 七、px 与 rem 的区别 一、px px&#xff08;像素&#xff09;&#xff1a; 1、相对单位&#xff0c;代表屏幕上的一个基本单位&#xff0c;逻辑像素。 2、不会根据屏幕尺寸或分辨率自动调整大…...

OceanBase数据库产品与工具介绍

OceanBase&#xff1a;蚂蚁集团自主研发的分布式关系数据库 1、什么是 OceanBase&#xff1f; OceanBase 是由蚂蚁集团完全自主研发的企业级分布式关系数据库&#xff0c;始创于 2010 年。它具有以下核心特点&#xff1a; 数据强一致性&#xff1a;在分布式架构下确保数据强…...

学习threejs,对模型多个动画切换展示

&#x1f468;‍⚕️ 主页&#xff1a; gis分享者 &#x1f468;‍⚕️ 感谢各位大佬 点赞&#x1f44d; 收藏⭐ 留言&#x1f4dd; 加关注✅! &#x1f468;‍⚕️ 收录于专栏&#xff1a;threejs gis工程师 文章目录 一、&#x1f340;前言1.1 ☘️THREE.AnimationMixer 动画…...

装饰模式(Decorator Pattern)重构java邮件发奖系统实战

前言 现在我们有个如下的需求&#xff0c;设计一个邮件发奖的小系统&#xff0c; 需求 1.数据验证 → 2. 敏感信息加密 → 3. 日志记录 → 4. 实际发送邮件 装饰器模式&#xff08;Decorator Pattern&#xff09;允许向一个现有的对象添加新的功能&#xff0c;同时又不改变其…...

React Native 开发环境搭建(全平台详解)

React Native 开发环境搭建&#xff08;全平台详解&#xff09; 在开始使用 React Native 开发移动应用之前&#xff0c;正确设置开发环境是至关重要的一步。本文将为你提供一份全面的指南&#xff0c;涵盖 macOS 和 Windows 平台的配置步骤&#xff0c;如何在 Android 和 iOS…...

Vue3 + Element Plus + TypeScript中el-transfer穿梭框组件使用详解及示例

使用详解 Element Plus 的 el-transfer 组件是一个强大的穿梭框组件&#xff0c;常用于在两个集合之间进行数据转移&#xff0c;如权限分配、数据选择等场景。下面我将详细介绍其用法并提供一个完整示例。 核心特性与用法 基本属性 v-model&#xff1a;绑定右侧列表的值&…...

Robots.txt 文件

什么是robots.txt&#xff1f; robots.txt 是一个位于网站根目录下的文本文件&#xff08;如&#xff1a;https://example.com/robots.txt&#xff09;&#xff0c;它用于指导网络爬虫&#xff08;如搜索引擎的蜘蛛程序&#xff09;如何抓取该网站的内容。这个文件遵循 Robots…...

关于 WASM:1. WASM 基础原理

一、WASM 简介 1.1 WebAssembly 是什么&#xff1f; WebAssembly&#xff08;WASM&#xff09; 是一种能在现代浏览器中高效运行的二进制指令格式&#xff0c;它不是传统的编程语言&#xff0c;而是一种 低级字节码格式&#xff0c;可由高级语言&#xff08;如 C、C、Rust&am…...

QT: `long long` 类型转换为 `QString` 2025.6.5

在 Qt 中&#xff0c;将 long long 类型转换为 QString 可以通过以下两种常用方法实现&#xff1a; 方法 1&#xff1a;使用 QString::number() 直接调用 QString 的静态方法 number()&#xff0c;将数值转换为字符串&#xff1a; long long value 1234567890123456789LL; …...

MySQL账号权限管理指南:安全创建账户与精细授权技巧

在MySQL数据库管理中&#xff0c;合理创建用户账号并分配精确权限是保障数据安全的核心环节。直接使用root账号进行所有操作不仅危险且难以审计操作行为。今天我们来全面解析MySQL账号创建与权限分配的专业方法。 一、为何需要创建独立账号&#xff1f; 最小权限原则&#xf…...

Java 二维码

Java 二维码 **技术&#xff1a;**谷歌 ZXing 实现 首先添加依赖 <!-- 二维码依赖 --><dependency><groupId>com.google.zxing</groupId><artifactId>core</artifactId><version>3.5.1</version></dependency><de…...

以光量子为例,详解量子获取方式

光量子技术获取量子比特可在室温下进行。该方式有望通过与名为硅光子学&#xff08;silicon photonics&#xff09;的光波导&#xff08;optical waveguide&#xff09;芯片制造技术和光纤等光通信技术相结合来实现量子计算机。量子力学中&#xff0c;光既是波又是粒子。光子本…...

蓝桥杯 冶炼金属

原题目链接 &#x1f527; 冶炼金属转换率推测题解 &#x1f4dc; 原题描述 小蓝有一个神奇的炉子用于将普通金属 O O O 冶炼成为一种特殊金属 X X X。这个炉子有一个属性叫转换率 V V V&#xff0c;是一个正整数&#xff0c;表示每 V V V 个普通金属 O O O 可以冶炼出 …...