C语言数据结构-双链表

双链表是什么

双向链表,又称为双链表,是链表的一种,它的每个数据结点中都有两个指针,分别指向直接后继和直接前驱。所以,从双向链表中的任意一个结点开始,都可以很方便地访问它的前驱结点和后继结点。

我们根据名字来判断双链表有两个链,也就是两个指针,两个指针分别指向当前节点的下一个链表以及上一个链表,就像下面这张图
在这里插入图片描述

双链表与单链表的区别

单链表相比双链表的随机访问性较差,不能随机查找,只能从第一个节点开始往后遍历,查找效率低下,单链表只能单向读取。
双链表随机访问性较单链表较高,可以随机查找,因为一个节点里面有两个指针分别指向上一个和下一个节点,双链表可以双向读取,灵活性较高。
就像下面这个结构体,这个就是双链表的一个节点里面的内容,存放两个地址,一个是前一个节点,另一个是后一个节点。

struct listnode
{LTdatatype data;struct listnode* next;struct listnode* pervious;
};

带头双链表

在这里插入图片描述
所谓带头双链表,带头链表⾥的头节点,实际为“哨兵位”,哨兵位节点不存储任何有效元素,只是站在这⾥“放哨的,这样做的目的是为了防止遍历链表出现死循环,以头节点当作一个哨兵位,当循环遍历链表的时候遇到哨兵位就停止。
这里篇文章我们主要讲带头双链表的实现

带头双链表的代码实现

单个节点申请空间

listnode* creative_space(LTdatatype x)
{listnode* newnode = (listnode*)malloc(sizeof(listnode));if (newnode == NULL){perror("malloc");exit(1);}newnode->data = x;newnode->next = newnode->pervious = newnode;return newnode;
}

这里我们用了malloc函数来申请空间,并且用了一个if判断语句来判断一下空间有没有申请成功。这里着重来讲一下这段代码

newnode->next = newnode->pervious = newnode;

我们想一下,我们要创建的是一个带头循环双链表所以我们在创建单个节点申请空间的时候需要把上一个节点的地址和下一个节点的地址都指向自己,这样才能形成一个环,这是一种初始化策略,有一个明确的状态,他既不是上一个节点的连续,也不是下一个节点的先导,来保证创建的新空间在还没有加入链表之前是独立的,同时也符合环形链表这个概念,而不是给节点赋值给NULL,这样就是不是循环了。
在这里插入图片描述

初始化链表

listnode* listnode_init()
{listnode* phead = creative_space(-1);return phead;
}

这里的初始化就是创建一个哨兵位,我们就直接调用的是申请节点空间,里面的参数我们传了一个-1这样做的目的是为了,创建一个哨兵位,一个带头节点,里面存放-1,我们假设双向链表里面存放的都是正数,我们放一个负数进去可以很好的分辨出哨兵位节点,而这个哨兵位节点是个自循环的。

尾插入

void listnode_push_back(listnode* phead, LTdatatype x)
{assert(phead);listnode* newnode = creative_space(x);newnode->pervious = phead->pervious;newnode->next = phead;phead->pervious->next = newnode;phead->pervious = newnode;
}

尾插画图思路
在这里插入图片描述
我们为了思路清晰,首先不动链表,先让新申请的空间与链表相连接手拉手起来,在去改变原来的尾节点的指向,指向我们新申请的空间 phead->pervious->next = newnode;就这一步,我们要先找到原来的尾节点,也就是phead->pervious在去改变他的下一个指向的地址,改变成新的节点,也就是pervious->next = newnode;然后在把头节点的上一个节点指向为新申请的空间。

头插入

void listnode_pushfront(listnode* phead, LTdatatype x)
{assert(phead);listnode* newnode = creative_space(x);newnode->next = phead->next;newnode->pervious = phead;phead->next->pervious = newnode;phead->next = newnode;
}

头插相比尾插要简单一些,因为不用改变原链表的尾节点和头节点的相连。
在这里插入图片描述
还是先不动原链表,先让新空间与原链表进行相连,在去改原链表的指向,与新节点相连。

newnode->next = phead->next;
newnode->pervious = phead;

这两行代码对应的就是图中绿色的标识。

phead->next->pervious = newnode;
phead->next = newnode;

这两行对应的是图中棕色的标识。

尾删除

void del_back(listnode* phead)
{assert(phead && phead->next != phead);listnode* del_temp = phead->pervious;listnode* new_back = del_temp->pervious;new_back->next = phead;phead->pervious = del_temp->pervious;free(del_temp);del_temp = NULL;
}

这里我们断言了一下头节点和头节点的下一个节点指向的是不是头节点本身也就是哨兵位,哨兵位是不能删除的,我们创建了一个del_temp临时变量来存储尾节点的地址,这样方便释放尾节点。

	listnode* del_temp = phead->pervious;listnode* new_back = del_temp->pervious;

这两行代码对应的是图中灰色的解释。

new_back->next = phead;
phead->pervious = del_temp->pervious;

这两行是对应绿色的解释,还有紫色的。
在这里插入图片描述

头删除

头删除相比尾删除简单一些我们值只需要把原始头节点存放起来,然后在把phead哨兵位和头节点的下一个节点联系起来在释放原始头节点就可以了。

void del_front(listnode* phead)
{assert(phead && phead->next != phead);listnode* del_temp = phead->next;listnode* new_front = del_temp->next;phead->next = new_front;new_front->pervious = phead;free(del_temp);del_temp = NULL;
}

在这里插入图片描述
这段代码对应的是图中绿色的解释

	listnode* del_temp = phead->next;listnode* new_front = del_temp->next;

这段是紫色的

	phead->next = new_front;new_front->pervious = phead;

查找链表

listnode* listnode_find(listnode* phead, LTdatatype x)
{listnode* pcur = phead->next;while (pcur!= phead){if (pcur->data == x){return pcur;}pcur = pcur->next;}return NULL;
}

这里我们用了遍历链表来查找元素,首先让pcur从跳过哨兵位的第一个节点开始,然后一直往后面遍历,如果找到了就返回pcur指向的节点,如果遍历链表一次后又回到了开始的起点,也就是哨兵位的后一个节点,就没有找到返回NULL。

在指定节点后面插入

下面是代码

void push_after_pos(listnode* pos, LTdatatype x)
{assert(pos);listnode* newnode = creative_space(x);newnode->next = pos->next;newnode->pervious = pos;pos->next->pervious = newnode;pos->next = newnode;
}

这里在pos节点后插入我们首先要调用查找节点函数,找到了节点之后才能插入,否则就没有办法在指定节点后插入,所以这里要断言一些pos不能为空。然后就是申请空间
在这里插入图片描述

删除pos节点

void del_pos(listnode* pos)
{assert(pos);pos->next->pervious = pos->pervious;pos->pervious->next = pos->next;free(pos);pos = NULL;
}

删除节点就是让删除节点的上一个节点与下一个节点的地址联系起来,在释放掉该节点就行了
在这里插入图片描述

释放链表空间

void del_listnode(listnode* phead)
{assert(phead);listnode* pcur = phead->next;while (pcur != phead){listnode* next = pcur->next;free(pcur);pcur = next;}free(phead);phead = NULL;
}

这里就是循环释放,注意在释放当前节点的时候要把当前节点的下一个节点的地址给存放起来,不然释放完当前地址后下一个地址就找不到了。

原码

# include<stdio.h>
# include<stdlib.h>
# include<assert.h>
typedef int LTdatatype;
struct listnode
{LTdatatype data;struct listnode* next;struct listnode* pervious;
};
typedef struct listnode listnode;listnode* listnode_init();
listnode* creative_space(LTdatatype x);
void listnode_pushfront(listnode* phead, LTdatatype x);
void listnode_push_back(listnode* phead, LTdatatype x);
void printlist(listnode* phead);
void del_back(listnode* phead);
void del_front(listnode* phead);
listnode* listnode_find(listnode* phead, LTdatatype x);
void push_after_pos(listnode* pos, LTdatatype x);
void del_pos(listnode* pos);
void del_listnode(listnode* phead);
//初始化
listnode* listnode_init()
{listnode* phead = creative_space(-1);return phead;
}
//申请空间
listnode* creative_space(LTdatatype x)
{listnode* newnode = (listnode*)malloc(sizeof(listnode));if (newnode == NULL){perror("malloc");exit(1);}newnode->data = x;newnode->next = newnode->pervious = newnode;return newnode;
}
void printlist(listnode* phead)
{assert(phead);listnode* pcur = phead->next;while (pcur != phead){printf("%d ", pcur->data);pcur = pcur->next;}
}
//尾插
void listnode_push_back(listnode* phead, LTdatatype x)
{assert(phead);listnode* newnode = creative_space(x);newnode->pervious = phead->pervious;newnode->next = phead;phead->pervious->next = newnode;phead->pervious = newnode;
}
//头插
void listnode_pushfront(listnode* phead, LTdatatype x)
{assert(phead);listnode* newnode = creative_space(x);newnode->next = phead->next;newnode->pervious = phead;phead->next->pervious = newnode;phead->next = newnode;
}
//尾删
void del_back(listnode* phead)
{assert(phead && phead->next != phead);listnode* del_temp = phead->pervious;listnode* new_back = del_temp->pervious;new_back->next = phead;phead->pervious = del_temp->pervious;free(del_temp);del_temp = NULL;
}
//头删
void del_front(listnode* phead)
{assert(phead && phead->next != phead);listnode* del_temp = phead->next;listnode* new_front = del_temp->next;phead->next = new_front;new_front->pervious = phead;free(del_temp);del_temp = NULL;
}
//查找  
listnode* listnode_find(listnode* phead, LTdatatype x)
{listnode* pcur = phead->next;while (pcur!= phead){if (pcur->data == x){return pcur;}pcur = pcur->next;}return NULL;
}
//在pos之后插入
void push_after_pos(listnode* pos, LTdatatype x)
{assert(pos);listnode* newnode = creative_space(x);newnode->next = pos->next;newnode->pervious = pos;pos->next->pervious = newnode;pos->next = newnode;
}
//删除pos节点
void del_pos(listnode* pos)
{assert(pos);pos->next->pervious = pos->pervious;pos->pervious->next = pos->next;free(pos);pos = NULL;
}
void del_listnode(listnode* phead)
{assert(phead);listnode* pcur = phead->next;while (pcur != phead){listnode* next = pcur->next;free(pcur);pcur = next;}free(phead);phead = NULL;
}
void test()
{listnode* plist = listnode_init();listnode_pushfront(plist, 3);listnode_pushfront(plist, 4);listnode_push_back(plist, 44);listnode_pushfront(plist, 3);printlist(plist);printf("\n");del_back(plist);printlist(plist);del_front(plist);printf("\n");printlist(plist);listnode* find = listnode_find(plist, 3);push_after_pos(find, 55);printf("\n");printlist(plist);del_pos(find);printf("\n");printlist(plist);del_listnode(plist);plist = NULL;
}
int main()
{test();return 0;
}

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

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

相关文章

使用mapinfo软件的在线地图插件运行错误解决

使用mapinfo软件的在线地图插件运行错误解决 一、如何解决win10/win11家庭版运行MapInfo中的在线地图插件报错【unexpected error&#xff1b;quitting】问题&#xff1f;二、如何解决在线地图切换地图源时的报错问题&#xff1f; 一、如何解决win10/win11家庭版运行MapInfo中的…

原牛角源码(修罗bbs)全站程序打包带数据库备份

原牛角源码(修罗bbs)全站程序打包带数据库备份&#xff0c;牛角源码全站数据全站文件、插件打包分享给大家&#xff0c;有兴趣的可以搭建玩玩&#xff01; conf文件夹中自己配置conf.php里面的数据库链接文件&#xff0c;默认管理账号&#xff1a;admin&#xff0c;密码&#…

代码随想录算法训练营Day60|LC84 柱状图中最大的矩形

一句话总结&#xff1a;完结撒花&#xff01; 原题链接&#xff1a;84 柱状图中最大的矩形 与接雨水的单调栈做法类似。代码如下&#xff1a; class Solution {public int largestRectangleArea(int[] heights) {int[] newHeight new int[heights.length 2];System.arrayco…

Ansible初识以及安装

1. Ansible应用简述&#xff1a; Ansible基于python语言实现&#xff0c;由Paramiko和PyYAML两个关键模块构建。具有独特的设计理念&#xff1a; 1&#xff09;安装部署简单 2&#xff09;管理主机便捷&#xff0c;支持多主机并行管理 3&#xff09;避免在被管理主机上安装客户…

SpringBoot集成Sharding-jdbc(水平分表)

SpringBoot集成Sharding-jdbc&#xff08;水平分表&#xff09; 1.Sharding-jdbc的应用场景2.实际使用2.0 项目层级2.1 导入依赖2.2 application.yml配置2.3 dao层2.4 对应的mybatis的xml文件2.5 Service层2.6 pojo2.7 controller2.8 多线程配置 1.Sharding-jdbc的应用场景 其…

【管理咨询宝藏78】MBB大型城投集团核心能力建设分析报告

本报告首发于公号“管理咨询宝藏”&#xff0c;如需阅读完整版报告内容&#xff0c;请查阅公号“管理咨询宝藏”。 【管理咨询宝藏78】MBB大型城投集团核心能力建设分析报告 【格式】PDF版本 【关键词】战略规划、商业分析、管理咨询、MBB顶级咨询公司 【强烈推荐】 这是一套…

什么是CPU与GPU,它们之间有什么关系

什么是CPU与GPU&#xff0c;它们之间有什么关系一、CPU1. 核心功能2. 工作原理3. 组成部分4. 发展历程5. 性能指标6. 架构种类7. 发展趋势8. 应用领域 二、GPU三、CPU与GPU的关系 什么是CPU与GPU&#xff0c;它们之间有什么关系 一、CPU CPU&#xff0c;全称是“Central Proc…

吃鸡游戏msvcp140.dll丢失的解决方法

msvcp140.dll 是一个与 Microsoft Visual C Redistributable 相关的动态链接库&#xff08;DLL&#xff09;文件&#xff0c;是 Windows 操作系统中众多应用程序正常运行所必需的关键组件之一。以下是对 msvcp140.dll 文件的总体介绍和msvcp140.dll丢失的多个解决方案分享。 *…

ROS仿真小车(一)—— urdf模型+rviz可视化

文章目录 前言一、创建功能包二、urdf文件三、launch文件四、图形化显示五、RVIZ可视化总结参考文献 前言 ROS学习过程记录&#xff0c;从零开始仿真一辆小车&#xff0c;之后会实现运动控制、雷达、相机等 部分代码已上传至本人的GitHub&#xff0c;如果需要请自行下载&…

算法题解记录18+++搜索二维矩阵Ⅱ

本题可以说是运用二分查找的典例&#xff0c;即使是面对矩阵&#xff0c;只要是它保持“排序好”这样的结构&#xff0c;就一定能采用二分查找法。【你知道的&#xff0c;对于排序好的数组&#xff0c;二分查找几乎是最优秀的算法】 当然&#xff0c;答案提供的是“Z字形查找法…

实在Agent:超自动化智能体的革命(附导引指南)

在自动化技术的浪潮中&#xff0c;实在智能推出了实在Agent&#xff08;智能体&#xff09;&#xff0c;一款基于大语言模型和屏幕语义理解技术的超自动化智能体。它通过自然对话交互&#xff0c;将复杂工作简化为一句话指令&#xff0c;自动规划并执行工作任务&#xff0c;极大…

C++奇迹之旅:深入理解赋值运算符重载

文章目录 &#x1f4dd;赋值运算符重载&#x1f320; 运算符重载&#x1f309;特性 &#x1f320; 赋值运算符重载&#x1f320;传值返回&#xff1a;&#x1f320;传引用赋值&#xff1a;&#x1f309;两种返回选择&#x1f309;赋值运算符只能重载成类的成员函数不能重载成全…

unity学习(86)——细节优化

东西已经做出来了&#xff0c;现在需要的是优化&#xff0c;说得简单&#xff0c;做起来难。 1.122包的优化&#xff0c;避免重复创建&#xff01; 2.为何会出现一边动&#xff0c;一边不动的情况。重复登录后依旧是unity可以看到移动&#xff0c;但是exe那边看不到移动&#…

关于图像YUV格式分类和排布方式的全学习

【学习笔记】关于图像YUV格式分类和排布方式的全学习_yuv图像-CSDN博客 下图是将多个yuv420p图像(A和B)&#xff0c;拼接成一个画面的思路 A大小:416*64 B大小:416*208 将A和B合并到一个416*416的尺寸上&#xff0c;代码如下 //整合char * ptd;ptd (char * ) malloc (416*41…

C#通用类库封装实战

数据库查询 特性方式获取数据库列的别名 数据库更新 使用简单工厂配置的方式

矽塔SA8321 单通道 2.7-12.0V 持续电流 3.0A H 桥驱动芯片

描述 SA8321是为消费类产品&#xff0c;玩具和其他低压或者电池供电的运动控制类应用提供了一个集成的电机驱动器解决方案。此器件能够驱动一个直流无刷电机&#xff0c;由一个内部电荷泵生成所需的栅极驱动电压电路和4个功率 NMOS组成H桥驱动&#xff0c;集成了电机正转/反…

2024上海国际半导体制造设备材料与核心部件展览会

2024上海国际半导体制造设备材料与核心部件展览会 2024 Shanghai International Semiconductor Manufacturing Equipment Materials and Core Components Exhibition 时间&#xff1a;2024年11月18日-20日 地点&#xff1a;上海新国际博览中心 详询主办方陆先生 I38&#…

2024蓝桥杯嵌入式模板代码详解

文章目录 一、STM32CubeMx配置二、LED模板代码三、LCD模板代码 一、STM32CubeMx配置 打开STM32CubeMx&#xff0c;选择【File】->【New Project】&#xff0c;进入芯片选择界面&#xff0c;搜索到蓝桥杯官方的芯片型号&#xff0c;并点击收藏&#xff0c;下次直接点击收藏就…

【LeetCode: 39. 组合总和 + 递归】

&#x1f680; 算法题 &#x1f680; &#x1f332; 算法刷题专栏 | 面试必备算法 | 面试高频算法 &#x1f340; &#x1f332; 越难的东西,越要努力坚持&#xff0c;因为它具有很高的价值&#xff0c;算法就是这样✨ &#x1f332; 作者简介&#xff1a;硕风和炜&#xff0c;…

比特币减半倒计时:NFT 生态将受到怎样的影响?

BTC 减半倒计时仅剩不到 1 天&#xff0c;预计在 4 月 20 日迎来减半。当前区块奖励为 6.25 BTC&#xff0c;减半后区块奖励为 3.125 BTC&#xff0c;剩余区块为 253。比特币减半无疑是比特币发展史上最重要的事件之一&#xff0c;每当这一事件临近&#xff0c;整个加密社区都充…