【数据结构】——双链表(增删查改)

 

目录

前言:

一:双链表的定义

​编辑 二:双向链表的实现

2.1:链表的构造

2.2:创建头节点

2.3:创建节点 

2.4:链表的尾插 

2.5:链表的打印

2.6:链表的尾删

2.7:链表的头插

2.8:链表的头删

2.9:链表的查找 

2.10:在目标位置前面插入

2.11:删除目标位置结点

2.12:链表的销毁

 总代码:

test.c

List.c

 List.h


 

前言:

双链表的引入是因为单链表要访问某个结点的前驱结点时,只能从头开始遍历,访问后驱结点的复杂度为O(1),访问前驱结点的复杂度为O(n)。为了克服上述缺点,引入了双链表。

双链表的引进,对于链表的操作有了极大的遍历;

一:双链表的定义

链表由单向的链变成了双向链。

双向链表(double linked list)是在单链表的每个结点中再设置一个指向其前驱结点的指针域。 

 二:双向链表的实现

2.1:链表的构造

包含了一个数据域,两个指针域(指向前后驱节点)

// 带头+双向+循环链表增删查改实现
typedef int LTDataType;
typedef struct ListNode
{LTDataType data;		//数据struct ListNode* next;	//下一个指针域struct ListNode* prev;	//上一个指针域
}ListNode;

 

2.2:创建头节点

双向链表一般都是带头节点的,在链表中,带了头节点对于链表分割这一问题有了简单化;

动态开辟出一块空间,前后指针都指向自己;

//初始化链表头头节点
ListNode* ListInit()
{ListNode* phead = (ListNode*)malloc(sizeof(ListNode));assert(phead);phead->data = -1;phead->next = phead;phead->prev = phead;return phead;
}

2.3:创建节点 

这里置NULL,和单链表的置NULL,是一样的意思,对后续的操作提供便利;

// 创建返回链表的结点.
ListNode* ListCreate(LTDataType x)
{ListNode* newnode = (ListNode*)malloc(sizeof(ListNode));assert(newnode);newnode->data = x;newnode->next = NULL;newnode->prev = NULL;return newnode;
}

2.4:链表的尾插 

单链表的尾插还需要考虑是否存在第一个节点,这里直接插入即可;

注意操作顺序

// 双向链表尾插
void ListPushBack(ListNode* phead, LTDataType x)
{assert(phead);ListNode* newnode = ListCreate(x);struct ListNode* tail = phead->prev;// phead tail  newnode//newnode->prev = phead->prev;//newnode->next = phead;//phead->prev->next = newnode;//phead->prev = newnode;//注意前后顺序newnode->prev = tail;tail->next = newnode;newnode->next = phead;phead->prev = newnode;}

2.5:链表的打印

这里的关键就是从哪里开始?如何判断结束(因为是循环)?

我们可以从头结点的下一个开始打印,当遇到头结点即是结束;

// 双向链表打印
void ListPrint(ListNode* phead)
{assert(phead);struct ListNode* cur = phead->next;printf("哨兵位<=>");while (cur != phead){printf("%d<=>", cur->data);cur = cur->next;}printf("\n");
}

 

2.6:链表的尾删

要多一个断言判断:如果只有一个头指针就不用操作了;

保存尾结点的前驱,释放尾结点即可

// 双向链表尾删
void ListPopBack(ListNode* phead)
{assert(phead);assert(phead->next != phead);	//只有头指针不删struct ListNode* cur = phead->prev;struct ListNode* curPrev = cur->prev;	//尾节点的上一个phead->prev = curPrev;curPrev->next = phead;free(cur);cur = NULL;
}

 

2.7:链表的头插

和尾插操作相似,定义头节点的下一个结点,进行链接即可;

注意顺序;

// 双向链表头插
void ListPushFront(ListNode* phead, LTDataType x)
{assert(phead);ListNode* newnode = ListCreate(x);struct ListNode* cur = phead->next;newnode->next = cur;	cur->prev = newnode;newnode->prev = phead;phead->next = newnode;
}

 

2.8:链表的头删

删除操作一般都需要判断一下是否只有头节点。判断双向链表的条件是:phead->next != phead;

// 双向链表头删
void ListPopFront(ListNode* phead)
{assert(phead);assert(phead->next != phead);	//只有头指针不删ListNode* cur = phead->next;ListNode* next = cur->next;phead->next = next;next->prev = phead;free(cur);cur = NULL;
}

2.9:链表的查找 

找到该结点后,返回的是指针,而不是数据,返回该指针位置,方便后续操作

// 双向链表查找
ListNode* ListFind(ListNode* phead, LTDataType x)
{assert(phead);ListNode* cur = phead->next;while (cur != phead){if (cur->data == x)return cur;cur = cur->next;}return NULL;
}

2.10:在目标位置前面插入

// 双向链表在pos的前面进行插入
void ListInsert(ListNode* pos, LTDataType x)
{assert(pos);	//检查pos位置是否有效ListNode* newnode = ListCreate(x);newnode->next = pos;	//将newnode节点next prev 链接前后节点newnode->prev = pos->prev;pos->prev->next = newnode;pos->prev = newnode;
}

 

2.11:删除目标位置结点

// 双向链表删除pos位置的节点
void ListErase(ListNode* pos)
{assert(pos);ListNode* posPrev = pos->prev;ListNode* next = pos->next;posPrev->next = next;next->prev = posPrev;free(pos);pos = NULL;
}

 

2.12:链表的销毁

// 双向链表销毁
void ListDestory(ListNode* phead)
{assert(phead);ListNode* cur = phead->prev;while (cur != phead)		//将除了头结点的都销毁{ListNode* curPrev = cur->prev;free(cur);cur = curPrev;}free(phead);    //再释放头结点//phead = NULL;
}

 总代码:

test.c

#define _CRT_SECURE_NO_WARNINGS 1
#include"List.h"void test1()
{ListNode* plist = NULL;plist = ListInit();ListPushBack(plist, 1);ListPushBack(plist, 2);ListPushBack(plist, 3);ListPushBack(plist, 4);ListPrint(plist);ListPopBack(plist);ListPrint(plist);ListPopBack(plist);ListPrint(plist);ListPopBack(plist);ListPrint(plist);ListPopBack(plist);ListPrint(plist);}void test2()
{ListNode* plist = NULL;plist = ListInit();ListPrint(plist);//ͷListPushFront(plist, 6);ListPrint(plist);//ͷɾListPopFront(plist);ListPrint(plist);}void test3()
{ListNode* plist = NULL;plist = ListInit();ListPushBack(plist, 1);ListPushBack(plist, 2);ListPushBack(plist, 3);ListPushBack(plist, 4);ListPrint(plist);//βɾListPopBack(plist);ListPopBack(plist);ListPopBack(plist);ListNode* pos = ListFind(plist,1);ListInsert(pos, 666);ListPrint(plist);ListErase(pos);ListPrint(plist);ListDestory(plist);
}
int main()
{//test1();//test2();test3();return 0;
}

List.c

#define _CRT_SECURE_NO_WARNINGS 1
#include"List.h"//初始化链表头头节点
ListNode* ListInit()
{ListNode* phead = (ListNode*)malloc(sizeof(ListNode));assert(phead);phead->data = -1;phead->next = phead;phead->prev = phead;return phead;
}// 创建返回链表的头结点.
ListNode* ListCreate(LTDataType x)
{ListNode* newnode = (ListNode*)malloc(sizeof(ListNode));assert(newnode);newnode->data = x;newnode->next = NULL;newnode->prev = NULL;return newnode;
}// 双向链表尾插
void ListPushBack(ListNode* phead, LTDataType x)
{assert(phead);ListNode* newnode = ListCreate(x);struct ListNode* tail = phead->prev;// phead tail  newnode//newnode->prev = phead->prev;//newnode->next = phead;//phead->prev->next = newnode;//phead->prev = newnode;//注意前后顺序newnode->prev = tail;tail->next = newnode;newnode->next = phead;phead->prev = newnode;}// 双向链表打印
void ListPrint(ListNode* phead)
{assert(phead);struct ListNode* cur = phead->next;printf("哨兵位<=>");while (cur != phead){printf("%d<=>", cur->data);cur = cur->next;}printf("\n");
}// 双向链表尾删
void ListPopBack(ListNode* phead)
{assert(phead);assert(phead->next != phead);	//只有头指针不删struct ListNode* cur = phead->prev;struct ListNode* curPrev = cur->prev;	//尾节点的上一个phead->prev = curPrev;curPrev->next = phead;free(cur);cur = NULL;
}// 双向链表头插
void ListPushFront(ListNode* phead, LTDataType x)
{assert(phead);ListNode* newnode = ListCreate(x);struct ListNode* cur = phead->next;newnode->next = cur;	cur->prev = newnode;newnode->prev = phead;phead->next = newnode;
}// 双向链表头删
void ListPopFront(ListNode* phead)
{assert(phead);assert(phead->next != phead);	//只有头指针不删ListNode* cur = phead->next;ListNode* next = cur->next;phead->next = next;next->prev = phead;free(cur);cur = NULL;
}// 双向链表查找
ListNode* ListFind(ListNode* phead, LTDataType x)
{assert(phead);ListNode* cur = phead->next;while (cur != phead){if (cur->data == x)return cur;cur = cur->next;}return NULL;
}// 双向链表在pos的前面进行插入
void ListInsert(ListNode* pos, LTDataType x)
{assert(pos);	//检查pos位置是否有效ListNode* newnode = ListCreate(x);newnode->next = pos;	//将newnode节点next prev 链接前后节点newnode->prev = pos->prev;pos->prev->next = newnode;pos->prev = newnode;
}// 双向链表删除pos位置的节点
void ListErase(ListNode* pos)
{assert(pos);ListNode* posPrev = pos->prev;ListNode* next = pos->next;posPrev->next = next;next->prev = posPrev;free(pos);pos = NULL;
}// 双向链表销毁
void ListDestory(ListNode* phead)
{assert(phead);ListNode* cur = phead->prev;while (cur != phead)		//将除了头节点的都销毁{ListNode* curPrev = cur->prev;free(cur);cur = curPrev;}free(phead);//phead = NULL;
}

 List.h

#pragma once
#include<stdio.h>
#include<assert.h>
#include<stdlib.h>// 带头+双向+循环链表增删查改实现
typedef int LTDataType;
typedef struct ListNode
{LTDataType data;		//数据struct ListNode* next;	//下一个指针域struct ListNode* prev;	//上一个指针域
}ListNode;// 创建返回链表的头结点.
ListNode* ListCreate(LTDataType x);//初始化链表头头节点
ListNode* ListInit();// 双向链表尾插
void ListPushBack(ListNode* pHead, LTDataType x);// 双向链表打印
void ListPrint(ListNode* pHead);// 双向链表尾删
void ListPopBack(ListNode* pHead);// 双向链表头插
void ListPushFront(ListNode* pHead, LTDataType x);// 双向链表头删
void ListPopFront(ListNode* pHead);// 双向链表查找
ListNode* ListFind(ListNode* pHead, LTDataType x);// 双向链表在pos的前面进行插入
void ListInsert(ListNode* pos, LTDataType x);// 双向链表删除pos位置的节点
void ListErase(ListNode* pos);// 双向链表销毁
void ListDestory(ListNode* pHead);

 以上就是我对【数据结构|双向链表|增删改查】的介绍,不足之处,还望指点。

 

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

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

相关文章

[计算机网络]网络层概述

呼,写了这么久终于重新开始啦! 自己落下了太多东西了.....是时候应该重新拾掇起来了. 关于后面的代码项目,我的想法是vilas.js仍然使用js来进行编写,但是后续其他的项目会开始尝试使用ts来进行书写了. 就算是前端也需要点规范吧..... 0.写在前面 这篇文章要和大家道个歉,首…

2023年中职“网络安全“—Linux系统渗透提权③

2023年中职"网络安全"—Linux系统渗透提权③ Linux系统渗透提权任务环境说明&#xff1a;1. 使用渗透机对服务器信息收集&#xff0c;并将服务器中SSH服务端口号作为flag提交&#xff1b;2. 使用渗透机对服务器信息收集&#xff0c;并将服务器中主机名称作为flag提交…

​软考-高级-系统架构设计师教程(清华第2版)【第13章 层次式架构设计理论与实践(P466~495)-思维导图】​

软考-高级-系统架构设计师教程&#xff08;清华第2版&#xff09;【第13章 层次式架构设计理论与实践&#xff08;P466~495&#xff09;-思维导图】 课本里章节里所有蓝色字体的思维导图

Figma 插件学习(一)

一.插件介绍 插件在文件中运行&#xff0c;执行一个或多个用户操作&#xff0c;并允许用户自定义其体验或创建更高效的工作流程。 插件通过专用插件API与Figma的编辑器交互。还可以利用外部Web API。 1.插件API 插件API支持读写功能&#xff0c;允许查看、创建和修改文件的…

打破传统束缚,释放服务潜能:本地生活服务商聚合系统引领行业新风向!

本地生活服务商聚合系统是一种集合多平台、多项目的创新型服务系统&#xff0c;它打破了传统服务商系统的一对一限制&#xff0c;为创业者和运营商带来了诸多优势。小多将深入探讨本地生活服务商聚合系统的优势。 随着互联网的快速发展&#xff0c;本地生活服务也迎来了蓬勃的发…

el-tree 与table表格联动

html部分 <div class"org-left"><el-input v-model"filterText" placeholder"" size"default" /><el-tree ref"treeRef" class"filter-tree" :data"treeData" :props"defaultProp…

彻底删除的文件如何恢复?分享正确方法!

“求救&#xff01;我在清理电脑的过程中&#xff0c;把一些比较久远的文件彻底删除了。但是我突然想起好像有些非常重要的数据也一同被删掉了&#xff0c;这可怎么办&#xff1f;有方法恢复彻底删除的文件么&#xff1f;” 在日常使用电脑的过程中&#xff0c;很多用户或许都会…

机器学习-笔记

绪论 参考期刊 ICCV 偏向视觉CVPR 偏向MLIAAA AI原理ICML 参考链接 CSDN 机器学习知识点全面总结 课堂内容学习-0912-N1 对于特征提取&#xff0c;简而言之就是同类聚得紧&#xff0c;异类分得开&#xff1b;   detection研究的是样本二分类问题&#xff0c;即分为正样本…

Rockdb简介

背景 最近在使用flink的过程中&#xff0c;由于要存储的状态很大&#xff0c;所以使用到了rockdb作为flink的后端存储&#xff0c;本文就来简单看下rockdb的架构设计 Rockdb设计 Rockdb采用了LSM的结构&#xff0c;它和hbase很像&#xff0c;不过严格的说&#xff0c;基于LS…

设计模式-行为型模式-责任链模式

一、什么是责任链模式 责任链模式是一种设计模式。在责任链模式里&#xff0c;很多对象由每一个对象对其下家的引用而连接起来形成一条链。请求在这个链上传递&#xff0c;直到链上的某一个对象决定处理此请求。发出这个请求的客户端并不知道链上的哪一个对象最终处理这个请求&…

VS2019编译安装GDAL(C++)程序库

一、GDAL简介 GDAL&#xff0c;全称Geospatial Data Abstraction Library&#xff0c;即地理空间数据抽象库&#xff0c;是一个在X/MIT许可协议下读写空间数据的开源库&#xff0c;可以通过命令行工具来进行数据的转换和处理。而在调用中我们常用的OGR&#xff08;OpenGIS Simp…

MATLAB中std函数用法

目录 语法 说明 示例 矩阵列的标准差 三维数组的标准差 指定标准差权重 矩阵行的标准差 数组页的标准差 排除缺失值的标准差 标准差和均值 标准差 std函数的功能是得到标准差。 语法 S std(A) S std(A,w) S std(A,w,"all") S std(A,w,dim) S std(A…

Android---Gradle 构建问题解析

想必做 Android App 开发的对 Gradle 都不太陌生。因为有 Android Studio 的帮助&#xff0c;Android 工程师使用 Gradle 的门槛不算太高&#xff0c;基本的配置都大同小异。只要在 Android Studio 默认生成的 build.gradle 中稍加修改&#xff0c;就都能满足项目要求。但是&am…

面试题c/c++ --STL 算法与数据结构

1.6 STL 模板 模板底层实现&#xff1a;编译器会对函数模板进行两次编译&#xff0c; 在声明的地方对模板代码本身进行编译&#xff0c; 在调用的地方对参数替换后的代码进行编译。 模板传参分析 模板重载 vector 是动态空间&#xff0c; 随着元素的加入&#xff0c; 它的内…

Apache Airflow (十二) :PythonOperator

&#x1f3e1; 个人主页&#xff1a;IT贫道_大数据OLAP体系技术栈,Apache Doris,Clickhouse 技术-CSDN博客 &#x1f6a9; 私聊博主&#xff1a;加入大数据技术讨论群聊&#xff0c;获取更多大数据资料。 &#x1f514; 博主个人B栈地址&#xff1a;豹哥教你大数据的个人空间-豹…

内网穿透的应用-如何在Docker中部署MinIO服务并结合内网穿透实现公网访问本地管理界面

文章目录 前言1. Docker 部署MinIO2. 本地访问MinIO3. Linux安装Cpolar4. 配置MinIO公网地址5. 远程访问MinIO管理界面6. 固定MinIO公网地址 前言 MinIO是一个开源的对象存储服务器&#xff0c;可以在各种环境中运行&#xff0c;例如本地、Docker容器、Kubernetes集群等。它兼…

HTTPS流量抓包分析中出现无法加载key

HTTPS流量抓包分析(TLSv1.2)&#xff0c;这篇文章分析的比较透彻&#xff0c;就不班门弄斧了 https://zhuanlan.zhihu.com/p/635420027 写个小问题&#xff1a;RSA密钥对话框加载rsa key文件的时候注意不要在中文目录下&#xff0c;否则会提示&#xff1a;“Enter the passwor…

单张图像3D重建:原理与PyTorch实现

近年来&#xff0c;深度学习&#xff08;DL&#xff09;在解决图像分类、目标检测、语义分割等 2D 图像任务方面表现出了出色的能力。DL 也不例外&#xff0c;在将其应用于 3D 图形问题方面也取得了巨大进展。 在这篇文章中&#xff0c;我们将探讨最近将深度学习扩展到单图像 3…

【MySql】13- 实践篇(十一)

文章目录 1. 自增主键为什么不是连续的&#xff1f;1.1 自增值保存在哪儿&#xff1f;1.2 自增值修改机制1.2.1 自增值的修改时机1.2.2 自增值为什么不能回退? 1.3 自增锁的优化1.3.1 自增锁设计历史 2. Insert语句为何很多锁?2.1 insert … select 语句2.2 insert 循环写入2…

以“防方视角”观Shiro反序列化漏洞

为方便您的阅读&#xff0c;可点击下方蓝色字体&#xff0c;进行跳转↓↓↓ 01 案例概述02 攻击路径03 防方思路 01 案例概述 这篇文章来自微信公众号“潇湘信安”&#xff0c;记录的某师傅如何发现、利用Shiro反序列化漏洞&#xff0c;又是怎样绕过火绒安全防护实现文件落地、…