双向链表的实现(增删查改)——最好理解的链表

news2024/11/19 19:43:44

双向链表的实现

  • 一,双向链表的特点
  • 二,双向链表的结构
  • 三,双向链表的内容实现
    • 3.1创建node节点
    • 3.2初始化
    • 3.3打印
    • 3.4插入
      • 3.4.1尾插
      • 3.4.2头插
      • 3.4.3在pos位置上插入
    • 3.5删除
      • 3.5.1尾删
      • 3.5.2头删
      • 3.5.3删除pos位置上的数据
  • 四,调试技巧(具体示例)
  • 五,总结

一,双向链表的特点

这里的双向链表就是单链表的升级版,链表有的共性他也有感兴趣的可以先看看单链表的内容链接: link

二,双向链表的结构

typedef int LTDataType;
typedef struct ListNode
{
	struct ListNode* prev;
	LTDataType date;
	struct ListNode* next;
}ListNode;

这里还是老生常谈,在写比较大一些的代码的时候我们还是要重新定义一下数据类型这样方便我们以后可以更方便的使用各种数据类型。
至于双向链表,就是它有两个指针既可以指向前面的数据也可以指向后面的数据,所以这里定义的时候就分成了前指针prev和后指针next,这里贴一张图方便理解双向链表。
在这里插入图片描述

三,双向链表的内容实现

// 创建返回链表的头结点.
ListNode* BuyLTNode(LTDataType x);
//初始化
ListNode* LTInit();
// 双向链表销毁
void ListDestory(ListNode* pHead);
// 双向链表打印
void ListPrint(ListNode* pHead);
// 双向链表尾插
void ListPushBack(ListNode* pHead, LTDataType x);
// 双向链表尾删
void ListPopBack(ListNode* pHead);
// 双向链表头插
void ListPushFront(ListNode* pHead, LTDataType x);
// 双向链表头删
void ListPopFront(ListNode* pHead);
// 双向链表在pos的前面进行插入
void ListInsert(ListNode* pos, LTDataType x);
// 双向链表删除pos位置的节点
void ListErase(ListNode* pos);

这里还要提醒一下,我们在写代码对我们的函数命名的时候尽量是按照它对应的英文单词取进行命名,这样有助于提升我们的代码质量和可读性。

3.1创建node节点

//创建节点
ListNode* BuyLTNode(LTDataType x)
{
	ListNode* node = (ListNode*)malloc(sizeof(ListNode));
	if (node == NULL)
	{
		perror("malloc failed");
		exit(-1);
	}
	node->prev = NULL;
	node->date = x;
	node->next = NULL;

	return node;
}

3.2初始化

//初始化
ListNode* LTInit()
{
	ListNode* phead = BuyLTNode(0);
	phead->next = phead;
	phead->prev = phead;
	return phead;
}

初始化是所有的开始,我们前面的顺序表,单链表都有进行初始化,后边我们要学习的栈和队列也是要有初始化的。

3.3打印

//打印
void ListPrint(ListNode* pHead)
{
	assert(pHead);
	ListNode* cur = pHead->next;
	printf("pHead<->");
	while (cur != pHead)
	{
		printf("%d<->", cur->date);
		cur = cur->next;
	}
	printf("\n");
}

打印这里是为了方便我们进行调试代码的。

3.4插入

这里我们的双向链表的插入一共分为了三种插入分为是尾插,头插,以及在特定位置下插入

3.4.1尾插

//尾插
void ListPushBack(ListNode* pHead, LTDataType x)
{
	ListNode* newnode = BuyLTNode(x);
	ListNode* tail = pHead->prev;
	newnode->prev = tail;
	tail->next = newnode;
	newnode->next = pHead;
	pHead->prev = newnode;
}

在这里插入图片描述
这里我们需要理解原本d3作为尾它的next是指向d1的,而d1的prev是指向d3的所以我们先记录下d1的prev的数据然后再把newnode的prev指向d3也就是tail,然后tail的next就指向的newnode,接着newnode的next就指向的头,头的prev就指向了newnode的尾。

3.4.2头插

//头插
void ListPushFront(ListNode* pHead, LTDataType x)
{
	assert(pHead);
	ListNode* newnode = BuyLTNode(x);
	newnode->next = pHead->next;
	pHead->next->prev = newnode;
	pHead->next = newnode;
	newnode->prev = pHead;
}

在这里插入图片描述

我们还是画图分析,因为是头插,我们newnode的next就指向了phead的下面一个位置也就是d2,接着d2的prev就要指向newnode,头的next的位置就指向了newnode,newnode的prev就指向了头。

3.4.3在pos位置上插入

//在pos前面插入
void ListInsert(ListNode* pos, LTDataType x)
{
	assert(pos);
	ListNode* prevpos = pos->prev;
	ListNode* newnode = BuyLTNode(x);
	newnode->next = pos;
	pos->prev = newnode;
	newnode->prev = prevpos;
	prevpos->next = newnode;
}

这里我们就不具体分析了,我们重点说一下这里的技巧,在双向链表的插入中我们可以看到其实头插和尾插都是在指定位置上插入的特例,所以我们在写双向链表的插入的时候完全可以先写这个函数然后头插和尾插也就完成了,这里我们展示一下改造后的头尾插。

//尾插
void ListPushBack(ListNode* pHead, LTDataType x)
{
	ListInsert(pHead->prev, x);
}
//头插
void ListPushFront(ListNode* pHead, LTDataType x)
{
	ListInsert(pHead->next, x);
}

怎么样是不是非常的方便,那么举一反三我们在后面的删除中叶有这样的技巧。

3.5删除

3.5.1尾删

//尾删
void ListPopBack(ListNode* pHead)
{
	assert(pHead);
	assert(pHead->next);
	ListNode* tail = pHead->prev;
	ListNode* prevtail = tail->prev;
	prevtail->next = pHead;
	pHead->prev = prevtail;
	free(tail);
}

在这里插入图片描述
看图分析,我们删除了tail,记录前一个的位置也就是prevtail,让prevtail和phead再次变成循环就可以了。

3.5.2头删

//头删
void ListPopFront(ListNode* pHead)
{
	assert(pHead);
	assert(pHead->next);
	ListNode* first = pHead->next;
	ListNode* second = first->next;
	free(first);
	second->prev = pHead;
	pHead->next = second;
}

头删也时同样的记录下一个的数据在跟前面链接free掉第一个数据。

3.5.3删除pos位置上的数据

//删除pos的数据
void ListErase(ListNode* pos)
{
	assert(pos);
	ListNode* nextpos = pos->next;
	ListNode* prevpos = pos->prev;
	free(pos);
	prevpos->next = nextpos;
	nextpos->prev = prevpos;
}

同样的,尾删和头删也是包括在这一种删除中的,所以我们也可以进一步简化我们的代码。

//尾删
void ListPopBack(ListNode* pHead)
{
	ListErase(pHead->prev);
}
//头删
void ListPopFront(ListNode* pHead)
{
	ListErase(pHead->next);
}

四,调试技巧(具体示例)

我们在写大型的代码的时候有一种调试方法可以极大的简便的让我们去检查代码就是写一查一
例如我们写了尾插的代码。

void Text1()
{
	ListNode* plist = LTInit();
	ListPushBack(plist, 1);
	ListPushBack(plist, 2);
	ListPushBack(plist, 3);
	ListPushBack(plist, 4);
	ListPushBack(plist, 5);
	ListPrint(plist);
}
int main()
{
	Text1();
	return 0;
}

在这里插入图片描述

我们就可以用Text1去检查我们的尾插有没有错误。
同样的我们写完头插之后再写一个Text2去检查我们的函数有没有错误。把一个复杂的大型代码去分解为一个个小的单元会让我们的效率提升很多。

五,总结

双向链表虽然是单链表之后的内容,但是我们会发现因为有两个指针的原因他的所有的操作比单链表比起来更加的方便,更易于理解。总之,还是要自己上手操作才能加深自己的印象。数据结构重点就是:画图!!画图!!画图!!

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

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

相关文章

基于Xml方式Bean的配置-Bean的延时加载

SpringBean的配置详解 Bean的延时加载 当lazy-init设置为true时为延时加载&#xff0c;也就是当Spring容器创建的时候&#xff0c;不会立即创建Bean实例&#xff0c;等待用到时再创建Bean实例并储存到单例池中&#xff0c;后续使用该Bean时直接从单例池中获取即可&#xff0c;…

文心一言 VS 讯飞星火 VS chatgpt (94)-- 算法导论9.2 3题

三、用go语言&#xff0c;给出 RANDOMIZED-SELECT的一个基于循环的版本。 文心一言&#xff1a; 在循环中实现 RANDOMIZED-SELECT 的一个版本可以如下所示。这个版本使用 Go 语言编写&#xff0c;利用随机化来选择一个元素&#xff0c;并在循环中不断地调整选择的元素&#x…

红黑树封装实现map和set

全文目录 map和set红黑树的封装红黑树的迭代器begin() 和 end()operator()operator--() map完整代码set完整代码改造后的红黑树代码 map和set map和set 是STL中的容器&#xff0c;两者底层的实现都是参考文档&#xff1a; map参考文档 set参考文档 两个容器底层都是去重的二叉…

Spring复杂对象的3中创建方法

复杂对象是相对于简单对象可以直接 new 出的对象。这种对象在 Spring 中不可以通过简单对象的创建方式来创建。下面我们将通过实现 FactoryBean 接口、实例工厂、静态工厂三种方法来创建。 FactoryBean 接口 Spring 提供 FactoryBean 接口并且提供了 getObject 方法是为了支持…

【Vue.js】生命周期与基本使用

&#x1f3ac; 艳艳耶✌️&#xff1a;个人主页 &#x1f525; 个人专栏 &#xff1a;《Spring与Mybatis集成整合》 《springMvc使用》 ⛺️ 生活的理想&#xff0c;为了不断更新自己 ! 目录 ​编辑 1.Vue是什么 2.Vue的特点及优势 3. 使用Vue的详细步骤 3.1.导入 3.2…

Vue3引入滑块验证组件-2分钟搞定

安装 npm install --save vue3-slide-verify登录页面引入 template 下 <template><div class"login"><el-card class"cover" v-if"loginUser.data.user"><slide-verifyref"block"slider-text"向右滑动-&…

实验室预约系统设计与实现

实验室预约系统的设计 摘 要 目前各大学的实验项目日益繁多&#xff0c;如何合理预约实验室&#xff0c;已经成为当今各个大学实验室课程预约的难题。因此&#xff0c;这个实验室预约系统就是研究实验室预约的相关问题。实验室预约系统的设计主要是基于B/S模型&#xff0c;在W…

MySQL--MySQL索引事务

事务的概念 事务指逻辑上的一组操作&#xff0c;组成这组操作的各个单元&#xff0c;要么全部成功&#xff0c;要么全部失败。 在不同的环境中&#xff0c;都可以有事务。对应在数据库中&#xff0c;就是数据库事务。 使用 &#xff08;1&#xff09;开启事务&#xff1a;start…

VHOST-SCSI代码分析(2)VHOST SCSI驱动分析

在HOST内核中创建/dev/vhost-scsi&#xff0c;并提供用户态相关接口&#xff0c;在文件driver/vhost/scsi.c中。 对于/dev/vhost-scsi的ioctl调用包含如下类型&#xff1a; &#xff08;1&#xff09;VRING相关的系统调用 &#xff08;2&#xff09;VHOST SCSI相关的系统调用 …

【重新定义matlab强大系列十三】直方图 bin 计数和分 bin 散点图

&#x1f517; 运行环境&#xff1a;Matlab &#x1f6a9; 撰写作者&#xff1a;左手の明天 &#x1f947; 精选专栏&#xff1a;《python》 &#x1f525; 推荐专栏&#xff1a;《算法研究》 #### 防伪水印——左手の明天 #### &#x1f497; 大家好&#x1f917;&#x1f91…

Web前端大作业html+css静态页面--掌****有限公司

文章目录 前言 一、效果图 二、代码展示 1.html 2.css部分 总结 前言 对于大一新生来说&#xff0c;期末考试和专业结课作业可能会带来很大的压力。特别是涉及到网页设计和编写的作业&#xff0c;可能让人感到无从下手。例如&#xff0c;web实训大作业、网页期末作业、web课程与…

四、C#—变量,表达式,运算符(2)

&#x1f33b;&#x1f33b; 目录 一、表达式1.1 什么是表达式1.2 表达式的基本组成 二、运算符2.1 算术运算符2.1.1 使用 / 运算符时的注意事项2.1.2 使用%运算符时的注意事项 2.2 赋值运算符2.2.1 简单赋值运算符2.2.2 复合赋值运算符 2.3 关系运算符2.4 逻辑运算符2.4.1 逻辑…

nexus 5x 刷机记录

1. 参考链接 https://d1ag0n.asia/archives/nexus5x%E5%88%B7android81root https://github.com/r0ysue/AndroidSecurityStudy/blob/master/FRIDA/A01/README.md 2. 下载工具 adb ,fastboot 官网的下载地址 &#xff1a; https://developer.android.com/studio/releases/pla…

高级视频和直播应用程序:Challenge 1.1.8 源码

您是否正在寻找高级视频和直播应用程序&#xff1f; 那么挑战就是您的完美选择。终极视频和直播挑战平台。凭借其尖端功能&#xff0c;Challenge 为用户和所有者提供了独特且引人入胜的体验。 通过购买挑战代码&#xff0c;您将可以使用以下令人兴奋的功能&#xff1a; 故事&…

学术团体的机器人相关分会和机器人相关大赛的说明

1. 中国机械工程学会 &#xff08;机器人分会&#xff09; 2017年成立&#xff0c;地点 华中科技大学 &#xff1a;中国机械工程学会机器人分会在汉成立 (huanqiu.com) 链接&#xff1a;中国机械工程学会 (cmes.org) 侧重点&#xff1a;工业机械臂、工厂和物流相关的移动机…

第 363 场 LeetCode 周赛题解

A 计算 K 置位下标对应元素的和 模拟 class Solution { public:int pop_cnt(int x) {//求x的二进制表示中的1的位数int res 0;for (; x; x >> 1)if (x & 1)res;return res;}int sumIndicesWithKSetBits(vector<int> &nums, int k) {int res 0;for (int i…

做一个有灵魂的软件测试员

有没有觉得自己每天的工作千篇一律&#xff0c;每天一上班就盼着下班&#xff1f; 一个月似乎能令自己开心的时间也就是发工资的那一天&#xff1f; 自己的工作生活总感觉被人牵着走&#xff0c;兜兜转转过了一年又一年&#xff1f; 测试员的工作性质决定了与重复、枯燥和乏…

自定义实现简易版ArrayList

文章目录 1.了解什么是顺序表2.实现哪些功能3.初始化ArrayList4.实现功能接口遍历顺序表判断顺序表是否已满添加元素指定下标添加元素自定义下标不合法异常判断顺序表是否为空查找指定元素是否存在查找指定元素返回下标获取指定下标的元素顺序表为空异常修改指定下标元素的值删…

【深度学习实验】线性模型(三):使用Pytorch实现简单线性模型:搭建、构造损失函数、计算损失值

目录 一、实验介绍 二、实验环境 1. 配置虚拟环境 2. 库版本介绍 三、实验内容 0. 导入库 1. 定义线性模型linear_model 2. 定义损失函数loss_function 3. 定义数据 4. 调用模型 5. 完整代码 一、实验介绍 使用Pytorch实现 线性模型搭建构造损失函数计算损失值 二、…

5-1 Dataset和DataLoader

Pytorch通常使用Dataset和DataLoader这两个工具类来构建数据管道。 Dataset定义了数据集的内容&#xff0c;它相当于一个类似列表的数据结构&#xff0c;具有确定的长度&#xff0c;能够用索引获取数据集中的元素。 而DataLoader定义了按batch加载数据集的方法&#xff0c;它是…