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

双向链表的实现

  • 一,双向链表的特点
  • 二,双向链表的结构
  • 三,双向链表的内容实现
    • 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.mzph.cn/news/80959.shtml

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

相关文章

day21算法

常见的七种查找算法&#xff1a; ​ 数据结构是数据存储的方式&#xff0c;算法是数据计算的方式。所以在开发中&#xff0c;算法和数据结构息息相关。今天的讲义中会涉及部分数据结构的专业名词&#xff0c;如果各位铁粉有疑惑&#xff0c;可以先看一下哥们后面录制的数据结构…

【C++学习】继承

目录 一、继承的概念及定义 1、继承的概念 2、继承的定义 2.1 定义格式 2.2 继承关系和访问限定符 2.3 继承基类成员访问方式的变化 二、基类和派生类对象赋值转换 三、继承中的作用域 四、派生类的默认成员函数 五、继承与友元 六、继承与静态成员 七、复杂的菱形…

SpringMvc第五战-【SpringMvcJSR303和拦截器】

前言&#xff1a; 小编阐述了springmvc 中的文件下载&#xff0c;以及jrebel的使用和文件下载以及多文件下载! 在本次小编将会介绍JSR303的概念&#xff0c;应用场景和在具体实例的使用&#xff1b;和拦截器的应用 一.JSR303的介绍 1.什么是JSR303&#xff1f; JSR是Java S…

Aztec的隐私抽象:在尊重EVM合约开发习惯的情况下实现智能合约隐私

1. 引言 Aztec的架构&#xff0c;不同于当前“通过EVM兼容执行环境”所实现的区块链水平扩容趋势。Aztec内部笑称其构建的为首个非zkEVM协议。 Aztec专注于实现&#xff1a; 成为理解和需要智能合约隐私的开发者的终极解决方案。 Aztec为开发者提供构建隐私优先app所需的网…

【微信小程序开发】宠物预约医疗项目实战-环境配置与Vant UI集成

第一章 宠物预约医疗项目实战-环境配置与Vant UI集成 文章目录 前言一、Vant UI是什么&#xff1f;二、使用步骤2.1 安装 node.js2.2 通过 npm 安装vant2.3 修改 app.json2.4 修改 project.config.json2.5 构建 npm 包2.6 使用组件全局引入和局部引入全局引入局部引入 前言 Va…

基于Java+SpringBoot+Vue+uniapp点餐小程序(亮点:协同过滤算法、会员系统,购物车结算、在线聊天)

校园点餐小程序 一、前言二、我的优势2.1 自己的网站2.2 自己的小程序&#xff08;小蔡coding&#xff09;2.3 有保障的售后2.4 福利 三、开发环境与技术3.1 MySQL数据库3.2 Vue前端技术3.3 Spring Boot框架3.4 微信小程序 四、功能设计4.1 系统功能结构设计4.2 主要功能描述 五…

SpringBoot整合Flowable

1. 配置 &#xff08;1&#xff09; 引入maven依赖 <dependency><groupId>org.flowable</groupId><artifactId>flowable-spring-boot-starter</artifactId><version>6.7.2</version></dependency><!-- MySQL连接 -->&l…

MySQL--MySQL索引事务

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

什么是接口自动化?为什么要做?和怎么做接口自动化?

服务端接口测试介绍 什么是服务端&#xff1f; 一般所说的服务端是指为用户在 APP 或 PC 使用的互联网功能提供数据服务的背后的一切。以天猫精灵智能音箱系列的产品链路为例&#xff0c;服务端便是网关&#xff08;包括网关在内&#xff09;之后的链路。 什么是接口&#xf…

【自然语言处理】【大模型】RWKV:基于RNN的LLM

相关博客 【自然语言处理】【大模型】RWKV&#xff1a;基于RNN的LLM 【自然语言处理】【大模型】CodeGen&#xff1a;一个用于多轮程序合成的代码大语言模型 【自然语言处理】【大模型】CodeGeeX&#xff1a;用于代码生成的多语言预训练模型 【自然语言处理】【大模型】LaMDA&a…

深入网络底层,了解Linux系统收发网络数据包的过程、原理、流程,附图文说明

深入网络底层&#xff0c;了解Linux系统收发网络数据包的过程、原理、流程&#xff0c;附图文说明。 Linux 服务器收到网络数据包&#xff0c;需要经过哪些处理&#xff0c;一步步将数据传给应用进程的呢&#xff1f;应用进程发送数据包时&#xff0c;Linux 又是如何操作将数据…

android studio platform使用体验分享(as无法跳转c/c++等native源码的福音,强烈推荐)

hi&#xff0c;粉丝朋友们&#xff1a; 大家好&#xff01;这些天粉丝朋友们分享了一下Android Studio for Platform 这个最新的google开发的阅读aosp源码的工具&#xff0c;特别适合做原生系统开发。具体官方介绍如下地址&#xff1a; 参考链接&#xff1a;https://developer.…

react的状态管理简单钩子方法

1.recoil useProvider文件: import { atom, useRecoilState } from recoil;const initState atom({key: initState,default: {state: [],}, })// 将业务逻辑拆分到一个单独文件中&#xff0c;方便进行状态管理 export interface StateProps {id: number;text: string;isFini…

异地远程访问本地SQL Server数据库【无公网IP内网穿透】

文章目录 1. 前言2. SeaFile云盘设置2.1 Owncould的安装环境设置2.2 SeaFile下载安装2.3 SeaFile的配置 3. cpolar内网穿透3.1 Cpolar下载安装3.2 Cpolar的注册3.3 Cpolar云端设置3.4 Cpolar本地设置 4. 公网访问测试5. 结语 1. 前言 现在我们身边的只能设备越来越多&#xff…

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

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

【探索Linux】—— 强大的命令行工具 P.8(进程地址空间)

阅读导航 前言一、内存空间分布二、什么是进程地址空间1. 概念2. 进程地址空间的组成 三、进程地址空间的设计原理1. 基本原理2. 虚拟地址空间 概念 大小和范围 作用 虚拟地址空间的优点 3. 页表 四、为什么要有地址空间五、总结温馨提示 前言 前面我们讲了C语言的基础知识&am…

HTTP协议(超级详细)

HTTP协议介绍 基本介绍&#xff1a; HTTP&#xff1a;超文本传输协议&#xff0c;是从万维网服务器传输超文本到本地浏览器的传送协议HTTP是一种应用层协议&#xff0c;是基于TCP/IP通信协议来传送数据的&#xff0c;其中 HTTP1.0、HTTP1.1、HTTP2.0 均为 TCP 实现&#xff0…

vue组件库开发,webpack打包,发布npm

做一个像elment-ui一样的vue组件库 那多好啊&#xff01;这是我前几年就想做的 但webpack真的太难用&#xff0c;也许是我功力不够 今天看到一个视频&#xff0c;早上6-13点&#xff0c;终于实现了&#xff0c;呜呜 感谢视频的分享-来龙去脉-大家可以看这个视频&#xff1a;htt…

【C语言】【数据存储】用%u打印char类型?用char存128?

1.题目一&#xff1a; #include <stdio.h> int main() {char a -128;printf("%u\n",a);return 0; }%u 是打印无符号整型 解题逻辑&#xff1a; 1. 原反补互换&#xff0c;截断 -128 原码&#xff1a;10000000…10000000 补码&#xff1a;11111111…10000000…

uniapp项目实践总结(十六)自定义下拉刷新组件

导语&#xff1a;在日常的开发过程中&#xff0c;我们经常遇到下拉刷新的场景&#xff0c;很方便的刷新游览的内容&#xff0c;在此我也实现了一个下拉刷新的自定义组件。 目录 准备工作原理分析组件实现实战演练内置刷新案例展示 准备工作 在components新建一个q-pull文件夹…