【C语言】双向链表详解

文章目录

  • 关于双向链表
  • 双向链表的初始化
  • 双向链表的打印
  • 双向链表方法调用 - 尾删为例
  • 双向链表的查找 - 指定位置之后插入为例
  • 双向链表结束 - 链表的销毁
  • 小结及整体代码实现


关于双向链表

首先链表有8种基本分法 在这里插入图片描述

其中在笔者之前文章种详细介绍的 单链表不带头单项不循环链表
而今天笔者要介绍的就是 带头双向循环链表 双向链表

我们可以把链表理解为下图

在这里插入图片描述
所以我们双向链表的结构往往是这样的
在这里插入图片描述

typedef int LTDataType;
//定义双向链表节点的结构
typedef struct ListNode
{int data;struct ListNode* next;struct ListNode* prev;
}LTNode;

双向链表的初始化

第一步我们需要进行双向链表的初始化,双向链表的初始化需要注意的一个问题是,传进去的参数是一级指针还是二级指针,这本应该后面大致写完了再讨论,但是这里提前说了

在笔者后面的方法中,例如尾插,头插,尾删,头删等都是用一级指针,这是因为双向链表有一个好处,我们存在一个哨兵位,这个节点是初始化时创立的,不需要改变,同时有存储着前后节点的地址,只需要通过成员名调用即可,这里呈现一下会出现的方法参数

//打印
void LTPrint(LTNode* phead);//尾插
void LTPushBack(LTNode* phead, LTDataType x);//插入数据之前,链表必须初始化到只有一个头结点的情况
//头插
void LTPushFront(LTNode* phead, LTDataType x);//尾删
void LTPopBack(LTNode* phead);
//头删
void LTPopFront(LTNode* phead);//查找
LTNode* LTFind(LTNode* phead, LTDataType x);
//在pos之后插入数据
void LTInsert(LTNode* pos, LTDataType x);

笔者这里据大多数关于双向链表的地址传参都是一级指针,这在一定程度上,保证了接口的一致性,为后来调用双向链表数据结构者降低了理解成本,这也就是我们在设计程序时需要注意的一点

笔者这里通过二级指针传参和无传参的方法各创建了一个初始化方法
大家可以看到两种方法连语句都基本一致,但是后者明显会比前者好,因为在以后不论是我们还是其他人通过一览所有的方法名后,不需要刻意去记初始化需要传参二级指针,直接空着使用就行了

//申请节点
LTNode* LTBuyNode(LTDataType x)
{LTNode* newnode = (LTNode*)malloc(sizeof(LTNode));if (newnode == NULL){perror("malloc newnode");exit(1);}newnode->data = x;newnode->next = newnode->prev = newnode;//指向自己return newnode;
}//初始化1
void LTInit(LTNode** pphead)
{//该双向链表创建一个哨兵位*pphead = LTBuyNode(-1);
}
//初始化2
LTNode* newLTInit()
{return LTBuyNode(-1);
}

双向链表的打印

双向链表的打印比单链表好理解太多了,
因为存在一个无意义的 哨兵位 用来记录开头,链接末尾,
所以只需要新创建一个新的节点用来帮忙定位当前位置,如果这个节点的值跟 哨兵位 一样,便退出

代码如下

//打印
void LTPrint(LTNode* phead)
{LTNode* pcur = phead->next;while (pcur != phead){printf("%d->",pcur->data);pcur = pcur->next;}printf("\n");
}

双向链表方法调用 - 尾删为例

大家可以先看一下下图,我们在双向链表的末尾放上新节点

在这里插入图片描述

为了保证双向链表在使用时不发生意外,
我们先将 newnodeprevnext 对接好 d3 和 头节点phead
这样做只是给新的节点的前后向确定好,不影响原来的双向链表

再将 d3next 对准 newnode
pheadprev 对准 newnode

下面代码呈现

//尾插
void LTPushBack(LTNode* phead, LTDataType x)
{assert(phead);LTNode* newnode = LTBuyNode(x);newnode->prev = phead->prev;newnode->next = phead;phead->prev->next = newnode;phead->prev = newnode;
}

双向链表的查找 - 指定位置之后插入为例

在这里插入图片描述

指定位置之后插入,除了插入这个重要点,就是指定位置这个大头了,那么我们该如何实现找到指定位置呢

同样因为 哨兵位 的存在,我们新定义节点来一个一个对比下去,知道等于 哨兵位 为止,代表整个双向链表中都没有这个要查找的数据

//查找
LTNode* LTFind(LTNode* phead, LTDataType x)
{LTNode* pcur = phead->next;while (pcur != phead){if (pcur->data == x)return pcur;pcur = pcur->next;}return NULL;
}

既然知道如何找到指定位置,那么接下来就是插入了,
我们通过指定位置的节点信息,按照前面尾插的方法就行了,也是很简单的

//在pos之后插入数据
void LTInsert(LTNode* pos, LTDataType x)
{assert(pos);LTNode* newnode = LTBuyNode(x);newnode->next = pos->next->next;newnode->prev = pos;pos->next->prev = newnode;pos->next = newnode;
}

双向链表结束 - 链表的销毁

因为我们前面有提到为保证接口的一致性我们同意设定为一级指针,但这样我们能做的就是将除 哨兵位 以外的节点全部置为空,但是外部所创建的节点,即我们一开始所初始化的点无法在内部置为空,所以出去后仍然需要置为空

//销毁
void LTDestory(LTNode* phead)
{assert(phead);LTNode* pcur = phead;while (pcur != phead){LTNode* next = pcur->next;free(pcur);pcur = next;}free(phead);phead = NULL;pcur = NULL;//这点无所谓,跳出方法,就会为空
}

这是内部的销毁,出去后,在置为空一次,即

LTDestory(plist);
plist = NULL;

小结及整体代码实现

有一说一,双向链表真的很好理解,而且写方法时,往往不用循环就能很容易解出来,比单链表和数组方便许多,大家如果想要自己实现双向链表,尽量没完成一次方法时,调试检验一下,方便找到错误

下面为大家展现一下笔者的代码

“List.h”

#pragma once#define _CRT_SECURE_NO_WARNINGS  1
#pragma warning(disable:6031)#include<stdio.h>
#include<stdlib.h>
#include<assert.h>typedef int LTDataType;
//定义双向链表节点的结构
typedef struct ListNode
{int data;struct ListNode* next;struct ListNode* prev;
}LTNode;//声明双向链表中提供的方法
//尽量都用一级指针,降低调用方的理解成本,保持接口的一致性
//初始化
//void LTInit(LTNode** pphead);
LTNode* newLTInit();//打印
void LTPrint(LTNode* phead);//尾插
void LTPushBack(LTNode* phead, LTDataType x);//插入数据之前,链表必须初始化到只有一个头结点的情况
//头插
void LTPushFront(LTNode* phead, LTDataType x);//尾删
void LTPopBack(LTNode* phead);
//头删
void LTPopFront(LTNode* phead);//查找
LTNode* LTFind(LTNode* phead, LTDataType x);
//在pos之后插入数据
void LTInsert(LTNode* pos, LTDataType x);

“List.c”

#include"List.h"//申请节点
LTNode* LTBuyNode(LTDataType x)
{LTNode* newnode = (LTNode*)malloc(sizeof(LTNode));if (newnode == NULL){perror("malloc newnode");exit(1);}newnode->data = x;newnode->next = newnode->prev = newnode;//指向自己return newnode;
}//初始化
void LTInit(LTNode** pphead)
{//该双向链表创建一个哨兵位*pphead = LTBuyNode(-1);
}LTNode* newLTInit()
{return LTBuyNode(-1);
}//销毁
void LTDestory(LTNode* phead)
{assert(phead);LTNode* pcur = phead;while (pcur != phead){LTNode* next = pcur->next;free(pcur);pcur = next;}free(phead);phead = NULL;pcur = NULL;//这点无所谓,跳出方法,就会为空
}//打印
void LTPrint(LTNode* phead)
{LTNode* pcur = phead->next;while (pcur != phead){printf("%d->",pcur->data);pcur = pcur->next;}printf("\n");
}//尾插
void LTPushBack(LTNode* phead, LTDataType x)
{assert(phead);LTNode* newnode = LTBuyNode(x);newnode->prev = phead->prev;newnode->next = phead;phead->prev->next = newnode;phead->prev = newnode;
}//头插
void LTPushFront(LTNode* phead, LTDataType x)
{assert(phead);LTNode* newnode = LTBuyNode(x);newnode->prev = phead;newnode->next = phead->next;phead->next->prev = newnode;phead->next = newnode;
}//尾删
void LTPopBack(LTNode* phead)
{//链表必须有效且不为空assert(phead && phead->next != phead);phead->prev->prev->next = phead;phead->prev = phead->prev->prev;
}
//头删
void LTPopFront(LTNode* phead)
{assert(phead && phead->next != phead);phead->next->next->prev = phead;phead->next = phead->next->next;
}//查找
LTNode* LTFind(LTNode* phead, LTDataType x)
{LTNode* pcur = phead->next;while (pcur != phead){if (pcur->data == x)return pcur;pcur = pcur->next;}return NULL;
}//在pos之后插入数据
void LTInsert(LTNode* pos, LTDataType x)
{assert(pos);LTNode* newnode = LTBuyNode(x);newnode->next = pos->next->next;newnode->prev = pos;pos->next->prev = newnode;pos->next = newnode;
}
//在pos之前插入数据
void LTInsertAfter(LTNode* pos, LTDataType x)
{assert(pos);LTNode* newnode = LTBuyNode(x);newnode->next = pos;newnode->prev = pos->prev;pos->prev->next = newnode;pos->prev = newnode;
}
//删除pos节点
void LTErase(LTNode* pos)
{assert(pos);pos->next->prev = pos->prev;pos->prev->next = pos->next;free(pos);pos = NULL;
}

测试文件就不展示了,没有什么参考价值,能够独自理解并写出上述方法名,一个简简单单的调试自然不在话下

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

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

相关文章

【饿了么笔试题汇总】[全网首发]2024-04-12-饿了么春招笔试题-三语言题解(CPP/Python/Java)

&#x1f36d; 大家好这里是KK爱Coding &#xff0c;一枚热爱算法的程序员 ✨ 本系列打算持续跟新饿了么近期的春秋招笔试题汇总&#xff5e; &#x1f4bb; ACM银牌&#x1f948;| 多次AK大厂笔试 &#xff5c; 编程一对一辅导 &#x1f44f; 感谢大家的订阅➕ 和 喜欢&#x…

MySQL视图的语法以及限制

语法 创建&#xff1a;create view view_name as select 语句; mysql能够通过创建视图的方式来创建一个虚拟表&#xff0c;它内容由select 语句决定。 并且创建的视图的变化会影响到主表&#xff0c;主表的变化也会影响视图。 删除: drop view view_name; 其实我们能够发现&am…

2023全国青少年信息素养大赛总决赛C++小学组真题

2023 全国青少年信息素养大赛总决赛C小学组真题 第一题 给定一个五位数x&#xff0c;你需要重复做以下操作: 把数的各个数位进行由大到小排序和由小到大排序&#xff0c;得到的最大值和最小值&#xff0c;进行求差后作为新的x。 可以证明&#xff0c;在经过有限次操作后&…

mybatis05:复杂查询:(多对一,一对多)

mybatis05&#xff1a;复杂查询&#xff1a;&#xff08;多对一&#xff0c;一对多&#xff09; 文章目录 mybatis05&#xff1a;复杂查询&#xff1a;&#xff08;多对一&#xff0c;一对多&#xff09;前言&#xff1a;多对一 &#xff1a; 关联 &#xff1a; 使用associatio…

GPT-5将在6月发布前进行「红队进攻测试」

“GPT-5将在6月发布”的消息刷屏了AI朋友圈。这则消息之所以被无数人相信并转发&#xff0c;是因为已经有不少技术人员在社交平台上晒出了「红队进攻测试」邀请。 基于 GPT系列庞大的用户体量和影响力&#xff0c;OpenAI 将更加重视GPT-5 的安全性&#xff0c;作为GPT-5上市前的…

DVWA靶场的下载与搭建

目录 什么是靶场 DVWA靶场下载 下载地址 安装 什么是靶场 靶场就是人为提供的带有安全漏洞的服务&#xff0c;每一个学习者都可以在本地快速搭建来实操&#xff0c;回溯漏洞的发生原理以及操作方式。DVWA靶场呢就是一个可以通过浏览器访问的拥有可视化页面的web靶场。 DVW…

前端图片详解(最全面、最新)

前言 当我们在做前端性能优化的时候&#xff0c;总是会离不开图片&#xff0c;尤其在首次内容绘制&#xff08;FCP&#xff09;和最大内容绘制 (LCP)中&#xff0c;图片显得格外关键&#xff0c;而我发现关于图片格式的文章&#xff0c;一般不全&#xff0c;或者是偏旧。 所以…

Bitmap OOM

老机器Bitmap预读仍然OOM&#xff0c;无奈增加一段&#xff0c;终于不崩溃了。 if (Build.VERSION.SDK_INT < 21)size 2; 完整代码&#xff1a; Bitmap bitmap; try {//Log.e(Thread.currentThread().getStackTrace()[2] "", surl);URL url new URL(surl);…

数据结构--链式栈

一.链式栈的栈顶在哪里? 二.链栈的结构: typedef struct LSNode{ int data; struct LSNode* next; }LSNode ,*PLStack; //链栈的节点.由于栈顶在第一个数据节点,所以不需要top指针 三.链式栈的实现: //初始化LSNode* p (LSNode*)malloc(sizeof(LSNode));assert(p ! NULL)…

Nifi同步过程中报错create_time字段找不到_实际目标表和源表中没有这个字段---大数据之Nifi工作笔记0066

很奇怪的问题,在使用nifi的时候碰到的,这里是用NIFI,把数据从postgresql中同步到mysql中, 首先postgresql中的源表,中是没有create_time这个字段的,但是同步的过程中报错了. 报错的内容是说,目标表中有个create_time字段,这个字段是必填的,但是传过来的flowfile文件中,的数据没…

Kali中间人攻击

中间人攻击 中间人攻击&#xff08;Man-in-the-Middle Attack&#xff0c;简称MITM&#xff09;是一种网络安全攻击&#xff0c;其中攻击者插入自己&#xff08;作为“中间人”&#xff09;在通信的两个端点之间&#xff0c;以窃取或篡改通过的数据。攻击者可以监视通信&#x…

Composer 安装与使用

文章目录 Composer的主要特点&#xff1a;Composer 的安装Windows 平台Linux 平台Mac OS 系统 Composer 的使用require 命令update 命令remove 命令search 命令show 命令 基本约束精确版本范围通配符波浪号 ~折音号 ^ 版本稳定性 Composer 是PHP编程语言的一个依赖管理工具。它…

【R语言从0到精通】-3-R统计分析(列联表、独立性检验、相关性检验、t检验)

上两次教程集中学习了R语言的基本知识&#xff0c;那么我们很多时候使用R语言是进行统计分析&#xff0c;因此对于生物信息学和统计科学来说&#xff0c;R语言提供了简单优雅的方式进行统计分析。教程参考《Rlearning》 3.1 描述性统计分析 3.1.1 载入数据集及summary函数 我…

广州南沙番禺联想SR530服务器主板传感器故障维修

今日分享一例广州市南沙区联想ThinkSystem SR530服务器sensor sysbrd vol故障问题维修案例&#xff1b; 服务器型号是&#xff1a;Lenovo thinksystem sr530 g6服务器 服务器所在位置&#xff1a;广东省广州市南沙区 服务器故障问题&#xff1a;机房异常停电&#xff0c;来电后…

HarmonyOS开发学习:【DevEco Device Tool 安装配置(问题全解)】

本文介绍如何在Windows主机上安装DevEco Device Tool工具。 坑点总结&#xff1a; 国内部分网络环境下&#xff0c;安装npm包可能会很慢或者超时&#xff0c;推荐使用国内npm源&#xff08;如淘宝源、华为源等&#xff09;&#xff1b;serialport这个npm包安装的过程中需要编…

透视晶圆制造黑匣子:RFID赋能智能生产,构建晶圆盒全程精准追溯体系

透视晶圆制造黑匣子&#xff1a;RFID赋能智能生产&#xff0c;构建晶圆盒全程精准追溯体系 应用背景 在全球半导体产业链中&#xff0c;晶圆盒作为承载硅片的重要载体&#xff0c;其生产过程的精细化管理和追溯显得至关重要。近年来&#xff0c;一种名为RFID&#xff08;Radi…

Fast-lio2运行时如何显示轨迹线

修改对应设备的.yaml文件&#xff0c;以velodyne为例&#xff1a; 将 path_en参数改为true即可&#xff0c;运行其他设备&#xff0c;修改对应的参数

mysql面试题 1

为什么要使用数据库 数据保存在内存 优点&#xff1a; 存取速度快缺点&#xff1a; 数据不能永久保存 数据保存在文件 优点&#xff1a; 数据永久保存缺点&#xff1a;1、速度比内存操作慢&#xff0c;频繁的IO操作。2、查询数据不方便 数据保存在数据库 数据永久保存使用SQL语…

跟TED演讲学英文:The inside story of ChatGPT‘s astonishing potential by Greg Brockman

The inside story of ChatGPT’s astonishing potential Link: https://www.ted.com/talks/greg_brockman_the_inside_story_of_chatgpt_s_astonishing_potential Speaker: Greg Brockman Date:April 2023 文章目录 The inside story of ChatGPTs astonishing potentialIntro…

path环境变量的作用

当我把一个运行文件的路径加入到了path环境变量&#xff0c;就可以在cmd命令行随时使用运行。 在path中有两个path上面的是用户的path&#xff0c;下面的是计算机的path