线性表之双向链表

链表花里胡哨,一应俱全


前言

在这之前,我们已经学习了单链表。我们发现这些链表都是一个接一个朝一个方向接下去,有时,我们想要查找某个结点的时候还得从头开始遍历查找,尽管我们已经学习了顺序表,查找某个数据确实很方便,但是要想插入某个数据就麻烦了。对于这两个缺点,我们今天再来学习一种链表,很好地弥补了这两个缺点。

一、什么是双向链表

在我们了解双向链表之前,我们来认识到底有多少种链表

如上图,我们用一幅图片展示链表之间的关系,根据排列组合可以知道,上面一共有2*2*2=8种,这8种链表挺像的但又不太像。我们之前学习的单链表全称是 不带头单向不循环链表。而我们今天所要学习的双向链表全称是 带头双向循环链表。你就根据文字意思比较一下,两者是不是互补的关系,那么我们将这两种链表学习了之后,相信其他链表也能够信手拈来了。

我们仅看文字可能无法直观地看出来什么,接下来我用几张对比图来看看这几种链表

说了半天,如何来实现双向链表呢?其实你别看它是双向的,实现起来反而比单链表更加简单,不信咱们接下来继续看。

二、双向链表的实现

与之前单链表的实现一样,我主要是用代码来展示实现,其中有一些注意事项我会拿出来着重强调一下。同样地,我们实现双向链表也需要建立三个文件LIst.c ,List.h ,test.c

List.h

#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#include<stdbool.h>//定义结点
typedef int LTDatatype;
typedef struct LTNode
{LTDatatype data;struct LTNode* next;struct LTNode* prev;
}LTNode;//双向链表的创建
//由于双向链表有一个特殊的地方:一定有一个哨兵位结点,因此不能从空指针开始插入来创建,因此我们得先创一个哨兵位结点//双向链表的创建
LTNode* Init();//尾插
void LTPushBack(LTNode* phead, LTDatatype x);//头插
void LTPushFront(LTNode* phead, LTDatatype x);//打印
void LTPrint(LTNode* phead);//判断链表是否为空
bool LTEmpty(LTNode* phead);//尾删
void LTPopBack(LTNode* phead);//头删
void LTPopFront(LTNode* phead);//查找
LTNode* LTFind(LTNode* phead, LTDatatype x);//在指定位置之后插入数据
void LTInsert(LTNode* pos, LTDatatype x);//在指定位置删除数据
void LTErase(LTNode* pos);//销毁
//对于参数是使用一级指针还是二级指针,是看该操作会不会影响那个参数
//一般指的都是头结点,这里是销毁操作,同样的也会将哨兵位结点销毁
void LTDesTroy(LTNode** pphead);void LTDesTroy2(LTNode* phead);

如上图所示,这是双向链表的头文件部分,首先不同之处是,结点的组成部分不一样,由于双向链表多了一个指向前驱结点的功能,因此也就多了一个指针域,其他部分与单链表大差不差。还有的就是双向链表的创建与单链表的创建有所区别,对于单链表,我们直接从空进行插入创建,对于双向链表,它本身就有一个结点,我们称它为“哨兵位”结点,它可以说是双向结点的一个标志(其实这是带头结点链表的一个标志,我们这里是在与单链表进行区别)然后我们之后在“哨兵位”结点后面进行操作,从而创建一个双向链表。至于链表最基本的增删改查功能,双向链表同样具备,下面就来给大家展示。

List.c

#define  _CRT_SECURE_NO_WARNINGS
#include"List.h"//申请结点
LTNode* LTBuyNode(LTDatatype x)
{LTNode* newnode = (LTNode*)malloc(sizeof(LTNode));if (newnode == NULL){perror("malloc fail");exit(-1);}newnode->data = x;newnode->next = newnode->prev = newnode;
//对于双向链表来说,它的两个指针是不指向NULL,由于是循环的,我们绕一圈之后还是会指向它本身的
}//对于双向链表的初始化,其实就是创建一个哨兵位结点,如果没有哨兵位结点就是一个单链表了
LTNode* Init()
{LTNode* phead = LTBuyNode(-1);//一般哨兵位里面存放的是一些无效的数据return phead;
}//尾插
void LTPushBack(LTNode* phead, LTDatatype x)
{assert(phead);// phead phead->prev newnode;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);//phead newnode phead->nextnewnode->prev = phead;newnode->next = phead->next;phead->next = newnode;phead->next->prev = newnode;
}//打印
void LTPrint(LTNode* phead)
{//定义一个指针,来遍历打印链表LTNode* pcur = phead->next;//注意:遍历的指针要从第一个有效数据开始,即哨兵位结点后面的那个结点while (pcur !=phead)//注意:双向链表不会遇到NULL,但是我们遇到phead结点时,我们就认为这个遍历一圈了{printf("%d->", pcur->data);pcur = pcur->next;}printf("\n");
}//判断链表是否为空
bool LTEmpty(LTNode* phead)
{assert(phead);
//对于双向链表空的情况就是哨兵位结点的next指针指向它自己即为空return phead->next == phead;//当这个成立时返回true,否则返回false
}
//尾删
void LTPopBack(LTNode* phead)
{assert(phead);assert(!LTEmpty(phead));//判断该链表是否为空LTNode* del = phead->prev;//phead del->prev delphead->prev = del->prev;del->prev->next = phead;free(del);del = NULL;
}//头删
void LTPopFront(LTNode* phead)
{assert(phead);assert(!LTEmpty(phead));//phead del del->nextLTNode* del = phead->next;phead->next = phead->next->next;phead->next->next->prev = phead;free(del);del = NULL;
}//查找
LTNode* LTFind(LTNode* phead, LTDatatype x)
{assert(phead);LTNode* pcur = phead->next;while (pcur != phead){if (pcur->data == x){return pcur;}pcur = pcur->next;}return NULL;
}//在指定位置之后插入数据
void LTInsert(LTNode* pos, LTDatatype x)
{assert(pos);LTNode* newnode = LTBuyNode(x);//pos newnode pos->nextnewnode->prev = pos;newnode->next = pos->next;pos->next = newnode;pos->next->prev = newnode;
}//在指定位置删除数据
void LTErase(LTNode* pos)
{assert(pos);//pos->prev pos pos->nextpos->prev->next = pos->next;pos->next->prev = pos->prev;free(pos);pos = NULL;
}//销毁
void LTDesTroy(LTNode** pphead)
{assert(pphead && *pphead);//pphead 表示的是哨兵位结点的地址,*pphead表示的是哨兵位的内容LTNode* pcur = (*pphead)->next;while (pcur != *pphead){LTNode* NEXT = pcur->next;//对于删除操作要保存上一个数据,否则就找不到下一个了free(pcur);pcur = NEXT;}free(*pphead);*pphead = NULL;pcur = NULL;
}void LTDesTroy2(LTNode* phead)
{assert(phead);LTNode* pcur = phead->next;while (pcur != phead){LTNode* NEXT = pcur->next;free(pcur);pcur = NEXT;}free(phead);phead=pcur=NULL;
}

这里我们需要注意的是对于双向链表来说,如果要想一个指针来遍历一圈链表的话,我们的结束条件是 pcur->next==phead,而且这个pcur指针是从phead->next开始遍历的。因为对于双向链表来说它是不会指向NULL的,那么我们想要将这个链表遍历一遍的话就要转一圈,我们设立的“哨兵位结点”里面存放的并非有效的数据,故我们要从第一个有效数据的位置开始遍历即phead->next。

test.c

#define  _CRT_SECURE_NO_WARNINGS
#include"List.h"test()
{LTNode* plist = Init();LTPushBack(plist,1);LTPushBack(plist,2);LTPushBack(plist,3);LTPushBack(plist,4);LTPushBack(plist,5);LTPrint(plist);//LTNode* pos = LTFind(plist, 3);//LTInsert(pos, 111);//LTPrint(plist);//LTErase(pos);//LTPrint(plist);//if (pos == NULL)//{//	printf("没找到!");//}//else//{//	printf("找到啦!");//}
//	LTDesTroy(&plist);
//	LTPrint(plist);//下面这个销毁与上面的销毁的差别在于参数不同,一个传递的是二级指针,我们可以直接在函数中进行销毁操作,//另外一个传递的是一级指针,我们无法改变其哨兵位结点的内容,故最后我们得 手动将其哨兵位结点置为空LTDesTroy2(plist);plist = NULL;
}
int main()
{test();return 0;
}

这个文件是对我们所写的代码进行测试用的,这里面有一个我们需要注意一下:在上面的销毁这一操作中,我们即可以传递一级指针,我们也可以传递二级指针。有时候我们如果想要接口(函数方法)一致的话(统一使用一级指针),那么我们要手动的将“哨兵位”结点置为NULL。因为,我们传递一级指针的时候,并不会修改头结点的值。


总结

这节内容,可以说是上一节内容的一个补充,在之后的数据学习中,链表可谓学习过程中的一个基础,我们学习的单链表与双向链表,可以说把链表的几个特点都包含进去了,希望我们能够熟练掌握这两种链表,从而触类旁通。

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

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

相关文章

免费PDF页面提取小工具

下载地址 https://download.csdn.net/download/woshichenpi/89922797 使用说明&#xff1a;PDF页面提取工具 1. 启动应用程序 双击程序的启动图标或者通过命令行运行程序。 2. 选择PDF文件 在应用程序窗口中找到“选择PDF”按钮并点击它。在弹出的文件选择对话框中&#x…

Windows server 2003服务器的安装

Windows server 2003服务器的安装 安装前的准备&#xff1a; 1.镜像SN序列号 图1-1 Windows server 2003的安装包非常人性化 2.指定一个安装位置 图1-2 选择好安装位置 3.启动虚拟机打开安装向导 图1-3 打开VMware17安装向导 图1-4 给虚拟光驱插入光盘镜像 图1-5 输入SN并…

Linux系统安装Redis详细操作步骤(二进制发布包安装方式)

安装方式介绍 在Linux系统中&#xff0c;安装软件的方式主要有四种&#xff0c;这四种安装方式的特点如下&#xff1a; 安装方式特点二进制发布包安装软件已经针对具体平台编译打包发布&#xff0c;只要解压&#xff0c;修改配置即可rpm安装软件已经按照redhat的包管理规范进…

Redis 集群 总结

前言 相关系列 《Redis & 目录》&#xff08;持续更新&#xff09;《Redis & 集群 & 源码》&#xff08;学习过程/多有漏误/仅作参考/不再更新&#xff09;《Redis & 集群 & 总结》&#xff08;学习总结/最新最准/持续更新&#xff09;《Redis & 集群…

计算机网络:网络层 —— IPv4 地址与 MAC 地址 | ARP 协议

文章目录 IPv4地址与MAC地址的封装位置IPv4地址与MAC地址的关系地址解析协议ARP工作原理ARP高速缓存表 IPv4地址与MAC地址的封装位置 在数据传输过程中&#xff0c;每一层都会添加自己的头部信息&#xff0c;最终形成完整的数据包。具体来说&#xff1a; 应用层生成的应用程序…

Java--反射机制

前言&#xff1a; 反射与之前的知识的区别 1.面向对象中创建对象&#xff0c;调用指定结构(属性、方法)等功能&#xff0c;可以不使用反射&#xff0c;也可以使用反射。请问有什么区别? 不使用反射&#xff0c;我们需要考虑封装性。比如:出了自定义类之后&#xff0c;就不能…

WPF+MVVM案例实战(六)- 自定义分页控件实现

文章目录 1、项目准备2、功能实现1、分页控件 DataPager 实现2、分页控件数据模型与查询行为3、数据界面实现 3、运行效果4、源代码获取 1、项目准备 打开项目 Wpf_Examples&#xff0c;新建 PageBarWindow.xaml 界面、PageBarViewModel.cs ,在用户控件库 UserControlLib中创建…

电池的主被动均衡

只有串联的电池需要进行电压均衡&#xff0c;并联的电池由于电压一致&#xff0c;所以并不需要进行均衡&#xff1a; 被动均衡有一个很明显的特征就是会看到很多大电阻&#xff0c;串联在MOS和电池之间&#xff1a;下图中的保护板就是被动均衡板子以及它的原理图&#xff1a; …

软硬件开发面试问题大汇总篇——针对非常规八股问题的提问与应答

软硬件开发&#xff0c;从微控制器编程到复杂的嵌入式系统开发&#xff0c;离不开下位机、操作系统、上位机等&#xff0c;涵盖范围很广。 如何快速一行代码操作硬件寄存器 直接操作硬件寄存器的代码通常依赖于特定平台和编程语言。在 C 或 C 中&#xff0c;常见的方法是使用指…

WORFBENCH:一个创新的评估基准,目的是全面测试大型语言模型在生成复杂工作流 方面的性能。

2024-10-10,由浙江大学和阿里巴巴集团联合创建的WORFBENCH&#xff0c;一个用于评估大型语言模型&#xff08;LLMs&#xff09;生成工作流能力的基准测试。它包含了一系列的测试和评估协议&#xff0c;用于量化和分析LLMs在处理复杂任务时分解问题和规划执行步骤的能力。WORFBE…

智慧停车场导航系统架构及反向寻车系统解决方案

一、系统概述&#xff1a; 随着当前室内定位导航技术在大型公共场所如政务中心、商业综合体、车站中的应用越来越多&#xff0c;人们对智慧停车场的需求也日益凸显出来&#xff0c;并且智慧停车场对大型公共场所智慧化的整体建设起到重要作用。如何更有效提高停车效率&#xf…

如何加密电脑磁盘?电脑本地磁盘加密方法介绍

随着信息技术的不断发展&#xff0c;电脑磁盘加密已经成为保护个人隐私和数据安全的重要手段。本文将介绍几种常见的电脑本地磁盘加密方法&#xff0c;帮助用户保护自己的数据安全。 文件夹只读加密专家 文件夹只读加密专家不仅可以加密电脑中的文件夹&#xff0c;还可以加密保…

Android 13 SystemUI 隐藏下拉快捷面板部分模块(wifi,bt,nfc等)入口

frameworks/base/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSFactoryImpl.java createTileInternal(tileSpec)方法注释想隐藏的模块即可。

【C++进阶篇】——STL的简介

【C进阶篇】——STL的简介 1.什么是STL STL(standard template libaray-标准模板库)&#xff1a;是C标准库的重要组成部分&#xff0c;不仅是一个可复用的组件库&#xff0c;而且是一个包罗数据结构与算法的软件框架。 2.STL的版本 原始版本 Alexander Stepanov、Meng Lee 在…

redis集群配置

一、Redis集群的三种方式 Redis集群提供了三种分布式方案&#xff1a;主从模式&#xff1a;一个主节点和一个或多个从节点&#xff0c;主节点负责写操作&#xff0c;从节点负责读操作&#xff0c;实现读写分离&#xff0c;分担主节点的压力。哨兵模式&#xff1a;哨兵系统用于监…

【每日一题】LeetCode - 盛最多水的容器

给定一个长度为 n 的整数数组 height。有 n 条垂线&#xff0c;第 i 条线的两个端点是 (i, 0) 和 (i, height[i])。要求找出其中的两条线&#xff0c;使得它们与 x 轴共同构成的容器可以容纳最多的水。 输入示例&#xff1a; height [1,8,6,2,5,4,8,3,7]输出&#xff1a; 4…

Python依赖库的几种离线安装方法

Python依赖库的几种安装方法 python经常需要安装一些依赖库&#xff0c;但是有时候环境可以连通python源&#xff0c;有时不能连通需要离线安装&#xff08;安装单个库包或者整个库环境&#xff09;&#xff0c;使用pip的如下方法可以相对简单解决问题。 一、如何copy一个pyt…

Linux 端口占用 kill被占用的端口 杀掉端口

1、yum install lsof 2、输入netstat -tln,查看系统当前所有被占用端口 3、根据端口查询进程,输入lsof -i :9555,切记不要忘了添加冒号 4、 既然知道进程号了,那杀死当前进程就简单多了,直接 kill -9 PID 回车

如何通过企业架构蓝图引导企业实现数字化转型:构建与实施的全方位指南

在当今迅速变化的商业环境中&#xff0c;企业进行数字化转型已成为提升竞争力、优化运营的必要手段。企业架构蓝图&#xff08;EA Blueprint&#xff09;作为指导企业数字化转型的战略工具&#xff0c;不仅提供了系统化的设计和规划路径&#xff0c;还帮助企业在技术与业务目标…

【读书笔记·VLSI电路设计方法解密】问题26:什么是漏电流问题

功耗现已成为半导体行业面临的主要技术难题。在当前基于CMOS的VLSI电路中,有两种主要的功耗来源:动态功耗和静态功耗。动态功耗来源于晶体管的切换以及芯片上数百万逻辑门输出端的电容反复充电和放电,是芯片为产生有效输出所消耗的能量。静态功耗则指即使在晶体管关闭时也会…