【数据结构与算法】之双向链表及其实现!

                                                                                个人主页:秋风起,再归来~

                                                                                            数据结构与算法                             

                                                                       个人格言:悟已往之不谏,知来者犹可追

                                                                                        克心守己,律己则安!

目录

1、双向链表的结构及概念

2、双向链表的实现

2.1 要实现的接口(List.h)

2.2 链表的初始化

2.3 链表的销毁

2.4 链表的打印

2.5 链表的尾插

2.6 链表的尾删

2.7 链表的头插

2.8 链表的头删

2.8 链表的查找

2.9 pos位置插入数据

2.10 pos位置删除数据

3、完整代码

List.h

List.c

Test.c(本人在实现双向链表时的测试代码) 

4、 完结散花


1、双向链表的结构及概念

我们这里要实现的数据结构是带头双向循环的链表(简称双向链表)

下面就是该链表的物理模型啦~

2、双向链表的实现

2.1 要实现的接口(List.h)

#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>typedef int LTDataType;
typedef struct ListNode
{struct ListNode* prev;//前驱指针LTDataType data;struct ListNode* next;//后驱指针
}LTNode;//链表的初始化
//void LTInit(LTNode** pphead);带参数的初始化
LTNode* LTInit();//链表销毁
void LTDestroy(LTNode* phead);//链表的打印
void LTPrint(LTNode* phead);//链表的尾插
void LTPushBack(LTNode* phead, LTDataType x);//链表的尾删
void LTPopBack(LTNode* phead);//链表的头插
void LTPushFront(LTNode* phead, LTDataType x);//链表的头删
void LTPopFront(LTNode* phead);//在双向链表中查找数据
LTNode* LTFind(LTNode* phead, LTDataType x);//在pos位置之后插入数据
void LTInsert(LTNode* pos, LTDataType x);//删除pos位置节点
void LTErase(LTNode* pos);

2.2 链表的初始化

注意:在初始化的时候一定要让头结点的prev指针和next指针都指向自己!

//链表的初始化
//void LTInit(LTNode** pphead);带参数的初始化
LTNode* LTInit()
{//初始化时创建一个带哨兵卫的头结点LTNode* phead = (LTNode*)malloc(sizeof(LTNode));if (phead == NULL){perror("malloc fail!\n");return NULL;}phead->next = phead->prev = phead;phead->data = -1;return phead;
}

2.3 链表的销毁

注意:我们一定是从链表的头结点(头结点中并没有有效数据的存储)的下一个位置开始销毁链表的!

//链表销毁
void LTDestroy(LTNode* phead)
{assert(phead);LTNode* pcur=phead->next;LTNode* next = NULL;//结束条件是当pcur不等于篇pheadwhile (pcur!=phead){next = pcur->next;free(pcur);pcur = next;}
}

并且我们在调用链表的销毁函数后依然要手动释放动态内存开辟的phead头结点 !

为尽量确保接口传递参数的一致性我们并没有传递头结点的地址,所以我们并不能在链表的销毁函数中free我们的头结点!

LTDestroy(plist);
//动态开辟的头结点需要手动释放
free(plist);
plist = NULL;

2.4 链表的打印

遍历链表打印头结点,循环结束的条件是pcur=phead,继续的条件是pcur!=phead

//链表的打印
void LTPrint(LTNode* phead)
{assert(phead);LTNode* pcur = phead->next;LTNode* next = NULL;//结束条件是当pcur不等于篇pheadwhile (pcur != phead){next = pcur->next;printf("%d->", pcur->data);pcur = next;}printf("\n");
}

2.5 链表的尾插

新节点的创建(单独封装成为一个函数)

//新节点的创建
LTNode* ListCreatNode(LTDataType x)
{LTNode* NewNode = (LTNode*)malloc(sizeof(LTNode));//开辟空间if (NewNode == NULL)//判断空间是否开辟成功{perror("malloc fail");return NULL;}NewNode->data = x;//赋值NewNode->next = NULL;//置空NewNode->prev = NULL;return NewNode;
}

链表的尾插 (在为尾插接口中直接调用创建节点的函数)

//链表的尾插
void LTPushBack(LTNode* phead, LTDataType x)
{assert(phead);LTNode* newNode = ListCreatNode(x);//先创建一个新节点LTNode* tail = phead->prev;newNode->prev = tail;newNode->next = phead;tail->next = newNode;phead->prev = newNode;
}

2.6 链表的尾删

注意各个节点的指向!

//链表的尾删
void LTPopBack(LTNode* phead)
{//尾删的前提是双向链表不为空assert(phead && phead->next != phead);LTNode* tail = phead->prev;phead->prev = tail->prev;tail->prev->next=phead;free(tail);tail = NULL;
}

2.7 链表的头插

//链表的头插
void LTPushFront(LTNode* phead, LTDataType x)
{assert(phead);LTNode* newNode = ListCreatNode(x);//先创建一个新节点newNode->next = phead->next;newNode->prev = phead;phead->next->prev = newNode;phead->next = newNode;
}

2.8 链表的头删

//链表的头删
void LTPopFront(LTNode* phead)
{//头删的前提是双向链表不为空assert(phead && phead->next != phead);LTNode* start = phead->next;phead->next = start->next;start->next->prev = phead;free(start);start= NULL;
}

2.8 链表的查找

返回值是该指向该数据节点的结构体指针,如没有找到,直接返回空!

//在双向链表中查找数据
LTNode* LTFind(LTNode* phead, LTDataType x)
{assert(phead);LTNode* pcur = phead->next;LTNode* next = NULL;//结束条件是当pcur不等于篇pheadwhile (pcur != phead){if (pcur->data == x){return pcur;}next = pcur->next;pcur = next;}return NULL;
}

2.9 pos位置插入数据

//在pos位置之后插入数据
void LTInsert(LTNode* pos, LTDataType x)
{assert(pos);LTNode* newNode = ListCreatNode(x);//先创建一个新节点newNode->next = pos->next;newNode->prev = pos;pos->next->prev = newNode;pos->next = newNode;
}

2.10 pos位置删除数据

//删除pos位置节点
void LTErase(LTNode* pos)
{assert(pos);pos->prev->next = pos->next;pos->next->prev = pos->prev;free(pos);pos = NULL;
}

3、完整代码

List.h

#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>typedef int LTDataType;
typedef struct ListNode
{struct ListNode* prev;//前驱指针LTDataType data;struct ListNode* next;//后驱指针
}LTNode;//链表的初始化
//void LTInit(LTNode** pphead);带参数的初始化
LTNode* LTInit();//链表销毁
void LTDestroy(LTNode* phead);//链表的打印
void LTPrint(LTNode* phead);//链表的尾插
void LTPushBack(LTNode* phead, LTDataType x);//链表的尾删
void LTPopBack(LTNode* phead);//链表的头插
void LTPushFront(LTNode* phead, LTDataType x);//链表的头删
void LTPopFront(LTNode* phead);//在双向链表中查找数据
LTNode* LTFind(LTNode* phead, LTDataType x);//在pos位置之后插入数据
void LTInsert(LTNode* pos, LTDataType x);//删除pos位置节点
void LTErase(LTNode* pos);

List.c

#include"List.h"//链表的初始化
//void LTInit(LTNode** pphead);带参数的初始化
LTNode* LTInit()
{//初始化时创建一个带哨兵卫的头结点LTNode* phead = (LTNode*)malloc(sizeof(LTNode));if (phead == NULL){perror("malloc fail!\n");return NULL;}phead->next = phead->prev = phead;phead->data = -1;return phead;
}//链表销毁
void LTDestroy(LTNode* phead)
{assert(phead);LTNode* pcur=phead->next;LTNode* next = NULL;//结束条件是当pcur不等于篇pheadwhile (pcur!=phead){next = pcur->next;free(pcur);pcur = next;}
}//链表的打印
void LTPrint(LTNode* phead)
{assert(phead);LTNode* pcur = phead->next;LTNode* next = NULL;//结束条件是当pcur不等于篇pheadwhile (pcur != phead){next = pcur->next;printf("%d->", pcur->data);pcur = next;}printf("\n");
}//新节点的创建
LTNode* ListCreatNode(LTDataType x)
{LTNode* NewNode = (LTNode*)malloc(sizeof(LTNode));//开辟空间if (NewNode == NULL)//判断空间是否开辟成功{perror("malloc fail");return NULL;}NewNode->data = x;//赋值NewNode->next = NULL;//置空NewNode->prev = NULL;return NewNode;
}//链表的尾插
void LTPushBack(LTNode* phead, LTDataType x)
{assert(phead);LTNode* newNode = ListCreatNode(x);//先创建一个新节点LTNode* tail = phead->prev;newNode->prev = tail;newNode->next = phead;tail->next = newNode;phead->prev = newNode;
}//链表的尾删
void LTPopBack(LTNode* phead)
{//尾删的前提是双向链表不为空assert(phead && phead->next != phead);LTNode* tail = phead->prev;phead->prev = tail->prev;tail->prev->next=phead;free(tail);tail = NULL;
}//链表的头插
void LTPushFront(LTNode* phead, LTDataType x)
{assert(phead);LTNode* newNode = ListCreatNode(x);//先创建一个新节点newNode->next = phead->next;newNode->prev = phead;phead->next->prev = newNode;phead->next = newNode;
}//链表的头删
void LTPopFront(LTNode* phead)
{//头删的前提是双向链表不为空assert(phead && phead->next != phead);LTNode* start = phead->next;phead->next = start->next;start->next->prev = phead;free(start);start= NULL;
}//在双向链表中查找数据
LTNode* LTFind(LTNode* phead, LTDataType x)
{assert(phead);LTNode* pcur = phead->next;LTNode* next = NULL;//结束条件是当pcur不等于篇pheadwhile (pcur != phead){if (pcur->data == x){return pcur;}next = pcur->next;pcur = next;}return NULL;
}//在pos位置之后插入数据
void LTInsert(LTNode* pos, LTDataType x)
{assert(pos);LTNode* newNode = ListCreatNode(x);//先创建一个新节点newNode->next = pos->next;newNode->prev = pos;pos->next->prev = newNode;pos->next = newNode;
}//删除pos位置节点
void LTErase(LTNode* pos)
{assert(pos);pos->prev->next = pos->next;pos->next->prev = pos->prev;free(pos);pos = NULL;
}

Test.c(本人在实现双向链表时的测试代码) 

#define _CRT_SECURE_NO_WARNINGS#include"LIst.h"void TestList1()
{LTNode* plist;plist = LTInit();//初始化链表LTPushBack(plist,1);LTPushBack(plist,2);LTPushBack(plist,3);LTPushFront(plist, 4);LTPushFront(plist, 4);/*LTPopFront(plist);LTPopFront(plist);*/LTNode* pos=LTFind(plist, 2);printf("删除pos位置之前\n");LTPrint(plist);LTErase(pos);printf("删除pos位置之后\n");LTPrint(plist);//LTInsert(pos, 5);//LTPopBack(plist);//LTPopBack(plist);//LTPopBack(plist);LTDestroy(plist);//动态开辟的头结点需要手动释放free(plist);plist = NULL;
}int main()
{TestList1();return 0;
}

4、 完结散花

好了,这期的分享到这里就结束了~

如果这篇博客对你有帮助的话,可以用你们的小手指点一个免费的赞并收藏起来哟~

如果期待博主下期内容的话,可以点点关注,避免找不到我了呢~

我们下期不见不散~~

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

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

相关文章

spring高级篇(一)

1、ApplicationContext与BeanFactory BeanFactory是ApplicationContext的父级接口&#xff1a;&#xff08;citlaltu查看类关系图&#xff09; 在springboot的启动类中&#xff0c;我们通过SpringApplication.run方法拿到的是继承了ApplicationContext的ConfigurableApplicatio…

PyCharm 2024.1 发布:全面升级,助力高效编程!

PyCharm 2024.1 发布&#xff1a;全面升级&#xff0c;助力高效编程&#xff01; 文章目录 PyCharm 2024.1 发布&#xff1a;全面升级&#xff0c;助力高效编程&#xff01;摘要引言 Hugging Face&#xff1a;模型和数据集的快速文档预览针对 JavaScript 和 TypeScript 的全行代…

深度学习-多尺度训练的介绍与应用

一、引言 在当今快速发展的人工智能领域&#xff0c;多尺度训练已经成为了一种至关重要的技术&#xff0c;特别是在处理具有复杂结构和不同尺度特征的数据时。这种技术在许多应用中发挥着关键作用&#xff0c;例如图像识别、自然语言处理和视频分析等。 多尺度训练的定义 多尺…

软考 - 系统架构设计师 - 嵌入式真题

问题 1&#xff1a; &#xff08;1&#xff09;.HTML 静态化&#xff1a;可以实现对系统经常访问的页面进行静态化以提高系统访问的效率&#xff0c;但系统页面通常需要数据库中的用户信息和用户选择来动态显示&#xff0c;因此不适合采用。 HTML 静态化&#xff1a; 将动态生成…

Python爬虫:requests模块的基本使用

学习目标&#xff1a; 了解 requests模块的介绍掌握 requests的基本使用掌握 response常见的属性掌握 requests.text和content的区别掌握 解决网页的解码问题掌握 requests模块发送带headers的请求掌握 requests模块发送带参数的get请求 1 为什么要重点学习requests模块&…

第23次修改了可删除可持久保存的前端html备忘录:增加了百度引擎

第22次修改了可删除可持久保存的前端html备忘录视频背景分离&#xff0c;增加了本地连接&#xff0c;增加了纯CSS做的折叠隐藏修改说明 <!DOCTYPE html> <html lang"zh"> <head><meta charset"UTF-8"><meta name"viewport…

WPF中DataGrid主从数据(父子数据)展示

在wpf中可以使用DataGrid控件,进行主从数据展示,也称父子数据展示。下面展示纯原生控件编码实现功能(样式自己可以根据需求进行修改)。 效果如下: 点击图标,展开和收缩可以自由的切换,也可以自己重新写一个样式,比如+,-或者类似图标的样式,都是可以的。 1.首先创建一…

【光伏企业】光伏项目怎么做才能提高效率?

一、精细化项目管理 项目规划&#xff1a;在项目启动前&#xff0c;进行充分的调研和规划&#xff0c;明确项目的目标、规模、预算和时间表&#xff0c;确保各项资源得到合理分配。 团队建设&#xff1a;组建一支高效、专业的项目团队&#xff0c;确保团队成员具备光伏领域的…

计算机视觉——图像特征提取D2D先描述后检测特征提取算法原理

概述 局部特征提取是计算机视觉中的一个重要任务&#xff0c;它旨在从图像中提取出能够代表图像局部结构和外观信息的特征。这些特征通常用于图像匹配、物体识别、三维重建、跟踪和许多其他应用。传统方法&#xff0c;如尺度不变特征变换&#xff08;SIFT&#xff09;&#xf…

浅谈Java JVM

Java虚拟机&#xff08;Java Virtual Machine&#xff0c;简称JVM&#xff09;是Java语言的核心组成部分&#xff0c;它是一个抽象的计算机&#xff0c;负责执行Java字节码指令。JVM是Java平台无关性的基石&#xff0c;它为Java代码提供了一个标准的运行环境&#xff0c;使Java…

【C/C++笔试练习】read函数、虚拟存储、用户态、线程特点、缺页处理、调度算法、进程优先级、锁的使用、创建进程、不用加减乘除做加法、三角形

文章目录 C/C笔试练习选择部分&#xff08;1&#xff09;read函数&#xff08;2&#xff09;虚拟存储&#xff08;3&#xff09;用户态&#xff08;4&#xff09;线程特点&#xff08;5&#xff09;缺页处理&#xff08;6&#xff09;调度算法&#xff08;7&#xff09;进程优先…

《由浅入深学习SAP财务》:第2章 总账模块 - 2.6 定期处理 - 2.6.6 年初操作:科目余额结转

2.6.6 年初操作&#xff1a;科目余额结转 在使用事务代码 FAGLB03 查询科目余额时&#xff0c;可以看到按期间的发生额清单。其中&#xff0c;第一行称为“余额结转”&#xff0c;该行的累计余额代表上年度遗留下来的余额&#xff0c;也就是年初余额。对于资产负债表科目而言&a…

【教学类-52-05】20240417动物数独(4宫格)黏贴卡片需要至少几张?难度1-9 打印版

作品展示&#xff1a; 背景需求&#xff1a; 实际打印的是以下代码生成的动物数独&#xff08;2*2&#xff09;学具 【教学类-52-03】20240412动物数独&#xff08;4宫格&#xff09;难度1-9 打印版-CSDN博客文章浏览阅读1.1k次&#xff0c;点赞30次&#xff0c;收藏17次。【教…

排序:冒泡排序,直接插入排序,简单选择排序,希尔排序,快速排序,堆排序,二路归并排序

目录 一.冒泡排序 代码如下 冒泡排序时间复杂度分析 二.直接插入排序 直接插入排序时间复杂度分析 直接插入排序优化&#xff1a;折半插入排序 三.简单选择排序 简单选择排序优化&#xff1a;双向选择排序 选择排序时间复杂度 双向选择排序时间复杂度 四.希尔排序 希…

2.2 iHRM人力资源 - 主页权限认证、Vux共享用户资料

iHRM人力资源 - 主页权限认证、主页内容展示 2.IHRM人力资源 - 登录-CSDN博客 文章目录 iHRM人力资源 - 主页权限认证、主页内容展示一、主页权限认证1.1 主页权限认证分析1.2 主页权限认证 - permission.js1.2.1 进度条部分1.2.2 token 认证 二、Vuex共享用户资料2.1 需求分析…

day02|最小花费爬梯子

最小花费爬梯子 比如 有一个数组 【2 5 20】我们直接选择从1号梯子&#xff08;从零编号&#xff09;跳两格就出去了。 算法原理 我们可以得出楼顶其实是数组的最后一个元素的下一个位置。对于最值问题我们可以尝试使用dpdp我们首先应该定义状态方差的含义&#xff0c;一般以…

Linux的重要命令(二)+了解Linux目录结构

目录 一.Linux的目录结构 二.查看文件内容命令 1.cat 命令 2.more 命令 3.less 命令 4.head 命令 5.tail 命令 6.拓展 head 和 tail 的其他用法 ​编辑 三.统计文件内容的命令-wc ​编辑 四.检索和过滤文件内容的命令-grep ​编辑 ​编辑 五.压缩命令 gzip 和 bz…

android studio 网络请求okhttp3、okgo

一、在build.gradle文件里添加 implementation com.squareup.okhttp3:okhttp:4.9.0 implementation com.squareup.okhttp3:okhttp:3.12.0 implementation com.squareup.okio:okio:1.17.4 implementation com.lzy.net:okgo:3.0.4 implementation com.alibaba:fastjson:1.2.57 i…

windows下已经创建好了虚拟环境,但是切换不了的解决方法

用得多Ubuntu&#xff0c;今天用Windows重新更新anaconda出问题&#xff0c;重新安装之后&#xff0c;打开pycharm发现打开终端之后&#xff0c;刚开始是ps的状态&#xff0c;后面试了网上改cmd的方法&#xff0c;终端变成c盘开头了 切换到虚拟环境如下&#xff1a;目前的shell…

实现iOS App代码混淆

简介 在开发iOS应用程序时&#xff0c;保护代码安全是至关重要的。代码混淆是一种常用的技术&#xff0c;可以增加逆向工程的难度&#xff0c;防止他人对代码的篡改和盗用。本文将介绍如何实现iOS App代码混淆的步骤和操作方法。 整体流程 下面是实现iOS App代码混淆的整体流…