数据结构之双向链表

目录

引言 

链表的分类 

双向链表的结构

双向链表的实现 

定义

创建新节点 

初始化 

打印 

尾插

头插 

判断链表是否为空

尾删 

头删

查找与修改 

指定插入

指定删除 

销毁 

顺序表和双向链表的优缺点分析

源代码 

dlist.h

dlist.c

test.c


引言 

数据结构之路在链表章节,前面介绍过单链表,今天我们来介绍最复杂的链表——双向链表(Double Linked List)

链表的分类 

虽然有这么多的链表的结构,但是我们实际中 最常用 还是 两种结构 单链表 双向带头循环链表
1. 无头单向非循环链表 :结构简单,一般不会单独用来存数据。实际中更多是作为其他数据结构的 子结构 ,如 哈希桶、图的邻接表 等等。另外这种结构在 笔试面试 中出现很多。
2. 带头双向循环链表 :结构最复杂,一般用在单独存储数据。实际中使用的链表数据结构,都是带头双向循环链表。另外这个结构虽然结构复杂,但是使用代码实现以后会发现结构会带来很多优势, 实现 反而 简单 了,后面我们代码实现了就知道了。

 前面我们讲的单链表,就是无头单向非循环链表,而现在讲的双向链表,就是带头双向循环链表。

双向链表的结构

注意: 这里的 “带头” 跟前面我们说的“头节点”是两个概念,实际前面的在单链表阶段称呼不严谨,但是为了同学们更好的理解就直接称为单链表的头节点。
带头链表里的头节点,实际为“哨兵位” ,哨兵位节点 不存储任何有效元素 ,只是站在这里“放哨的”
“哨兵位”存在的意义:
遍历循环链表避免死循环

双向链表的实现 

定义

与单链表不同,一个节点里有两个指针,分别指向前节点后节点,实现双向 

创建新节点 

创建新节点与单链表大致相同,抽离成函数方便创建节点

初始化 

双向链表与单链表很大区别,就是在于初始化创建哨兵位,而哨兵位不存储有效数据,所有这里存入-1,并让prev和next指针都指向自身

打印 

首先assert断言,因为phead指向哨兵位,一定不为空,其次因为双向循环链表里没有NULL,所有停止条件就看哨兵位,当cur指针的下一个为哨兵位时,就代表走到尾部

尾插

双向循环结构在空链表插入时,带来了极大的便利。因为prev指针的存在,使得找尾十分方便,不用遍历链表,直接访问哨兵位的上一个节点即可

如果是单链表,是不是还要讨论空链表的特殊情况啊?但是,在双向循环链表中,上述代码就可包含所有情况。因为哨兵位的存在,使得链表一定不为空

 

同时,因为哨兵位的存在,我们不用传二级指针了,只用对结构体进行操作。怎么样,有没有发现双向链表的巨大优势? 

运行结果

 

头插 

头插时,要注意的就是要先链接后一个节点再链接前一个节点

运行结果

判断链表是否为空

删除值得注意的是,不能删除哨兵位,要不然后续都无法对链表进行操作,所以专门写个函数来判断除了哨兵位链表是否为空,再用assert断言返回值  

如果phead和phead->next相等则为空,返回1,即为真 ;不相等,则不为空,返回0,即为假

尾删 

经历过单链表,双向链表的尾删就显得太简单了。找尾tail直接往phead的上一位,同时创建tailPrev,在释放尾部节点后,双向链接哨兵位


运行结果 

头删

同样,双向链表的头删也很简单。 找头first往phead的下一位,再创建second,在释放头部空间后,双向链接哨兵位

运行结果 

查找与修改 

双向链表的查找,找到返回地址,找不到返回NULL。要注意的是从哨兵位的下一位开始找,因为哨兵位是不存储有效数据的,直到重新找到哨兵位

运行结果 

查找到地址后,就可以对其解引用访问,进行修改

指定插入

 在pos前插入

在双向链表,找到pos的上一个节点的地址prev太简单了 

运行结果 

在这里可以复用指定插入函数,快速实现头插和尾插 

头插 

尾插

指定删除 

创建posPrev和posNext两个指针,释放掉pos的节点空间,再相互链接 

在pos删除 

运行结果 

在这里也可以复用指定删除函数,快速实现头删和尾删  

头删 

尾删

大家有没有发现,因为双向链表存在哨兵位链表不为空省去了很多关于空链表和空指针的讨论,一路下来浑身舒畅 

销毁 

双向链表的销毁,值得注意的是最后哨兵位也要释放掉,因为为了保持用一级指针的连贯性,所以我们选择最后手动在外部将链表指针置为NULL,实现半自动(和free函数的逻辑一致) 

运行结果 

这样我们就完成了对双向链表增删查改等功能的实现 

顺序表和双向链表的优缺点分析

我们看到,双向链表的好处是如此巨大的,那是否就代表前面学的顺序表就很low呢?

不同点顺序表链表
存储空间上物理上一定连续逻辑上连续,但物理上不一定连续
随机访问支持O(1)不支持:O(N)
任意位置插入或者删除元素可能需要搬移元素,效率低O(N)只需修改指针指向
插入动态顺序表,空间不够时需要扩容没有容量的概念
应用场景元素高效存储+频繁访问任意位置插入和删除频繁

源代码 

dlist.h

#pragma once
#include<stdio.h>
#include<assert.h>
#include<stdlib.h>
#include<stdbool.h>typedef int DLDataType;
typedef struct DListNode
{struct DListNode* prev;struct DListNode* next;DLDataType data;
}DLNode;//初始化
DLNode* DLInit();
//打印
void DLPrint(DLNode* phead);
//销毁
void DLDestroy(DLNode* phead);//检测链表是否为空
bool DLEmpty(DLNode* phead);
//尾插
void DLPushBack(DLNode* phead, DLDataType x);
//尾删
void DLPopBack(DLNode* phead);
//头插
void DLPushFront(DLNode* phead, DLDataType x);
//头删
void DLPopFront(DLNode* phead);//查找
DLNode* DLFind(DLNode* phead, DLDataType x);//在pos前指定插入
void DLInsert(DLNode* pos, DLDataType x);
//在pos指定删除
void DLErase(DLNode* pos);

dlist.c

#define _CRT_SECURE_NO_WARNINGS 1
#include"dlist.h"DLNode* BuyDLNode(DLDataType x)
{DLNode* newnode = (DLNode*)malloc(sizeof(DLNode));if (newnode == NULL){perror("malloc fail");return NULL;}newnode->data = x;newnode->prev = NULL;newnode->next = NULL;return newnode;
}DLNode* DLInit()
{DLNode* phead = BuyDLNode(-1);phead->next = phead;phead->prev = phead;return phead;
}void DLPrint(DLNode* phead)
{assert(phead);DLNode* cur = phead;printf("Guard");while (cur->next != phead){cur = cur->next;printf("<==>%d", cur->data);}printf("\n");
}bool DLEmpty(DLNode* phead)
{assert(phead);return phead == phead->next;
}void DLPushBack(DLNode* phead, DLDataType x)
{assert(phead);DLInsert(phead, x);//DLNode* newnode = BuyDLNode(x);//DLNode* tail = phead->prev;////tail->next = newnode;//newnode->prev = tail;//phead->prev = newnode;//newnode->next = phead;
}void DLPopBack(DLNode* phead)
{assert(phead);assert(!DLEmpty(phead));DLErase(phead->prev);//DLNode* tail = phead->prev;//DLNode* tailPrev = tail->prev;////free(tail);//tailPrev->next = phead;//phead->prev = tailPrev;
}void DLPushFront(DLNode* phead, DLDataType x)
{assert(phead);DLInsert(phead->next, x);//DLNode* newnode = BuyDLNode(x);//DLNode* first = phead->next;//newnode->next = first;//first->prev = newnode;//phead->next = newnode;//newnode->prev = phead;
}void DLPopFront(DLNode* phead)
{assert(phead);assert(!DLEmpty(phead));DLErase(phead->next);//DLNode* first = phead->next;//DLNode* second = first->next;//second->prev = phead;//phead->next = second;//free(first);
}DLNode* DLFind(DLNode* phead, DLDataType x)
{assert(phead);DLNode* cur = phead->next;while (cur != phead){if (cur->data == x){return cur;}cur = cur->next;}return NULL;
}void DLInsert(DLNode* pos, DLDataType x)
{assert(pos);DLNode* newnode = BuyDLNode(x);DLNode* prev = pos->prev;prev->next = newnode;newnode->prev = prev;newnode->next = pos;pos->prev = newnode;
}void DLErase(DLNode* pos)
{assert(pos);DLNode* posPrev = pos->prev;DLNode* posNext = pos->next;posPrev->next = posNext;posNext->prev = posPrev;free(pos);
}void DLDestroy(DLNode* phead)
{assert(phead);DLNode* cur = phead->next;while (cur != phead){DLNode* next = cur->next;free(cur);cur = next;}free(phead);
}

test.c

#define _CRT_SECURE_NO_WARNINGS 1
#include"dlist.h"TestDList1()
{DLNode* plist = DLInit();//尾插DLPushBack(plist, 1);DLPushBack(plist, 2);DLPushBack(plist, 3);DLPushBack(plist, 4);DLPushBack(plist, 5);//头插DLPushFront(plist, 1);DLPushFront(plist, 2);DLPushFront(plist, 3);DLPushFront(plist, 4);DLPushFront(plist, 5);//打印DLPrint(plist);
}TestDList2()
{DLNode* plist = DLInit();//尾插DLPushBack(plist, 1);DLPushBack(plist, 2);DLPushBack(plist, 3);DLPushBack(plist, 4);DLPushBack(plist, 5);//头删DLPopFront(plist);DLPopFront(plist);DLPopFront(plist);//打印DLPrint(plist);
}TestDList3()
{DLNode* plist = DLInit();//头插DLPushFront(plist, 1);DLPushFront(plist, 2);DLPushFront(plist, 3);DLPushFront(plist, 4);DLPushFront(plist, 5);//尾删DLPopBack(plist);DLPopBack(plist);DLPopBack(plist);//打印DLPrint(plist);
}TestDList4()
{DLNode* plist = DLInit();//头插DLPushFront(plist, 1);DLPushFront(plist, 2);DLPushFront(plist, 3);DLPushFront(plist, 4);DLPushFront(plist, 5);//查找与修改DLNode* pos = DLFind(plist, 4);if (pos != NULL){pos->data = 40;//在pos前指定插入DLInsert(pos, 100);}//打印DLPrint(plist);
}TestDList5()
{DLNode* plist = DLInit();//头插DLPushFront(plist, 1);DLPushFront(plist, 2);DLPushFront(plist, 3);DLPushFront(plist, 4);DLPushFront(plist, 5);//查找与修改DLNode* pos = DLFind(plist, 4);if (pos != NULL){pos->data = 40;//在pos指定删除DLErase(pos);}//打印DLPrint(plist);
}TestDList6()
{DLNode* plist = DLInit();//尾插DLPushBack(plist, 1);DLPushBack(plist, 2);//头插DLPushFront(plist, 1);DLPushFront(plist, 2);//打印DLPrint(plist);//销毁DLDestroy(plist);plist = NULL;
}int main()
{TestDList6();return 0;
}

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

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

相关文章

网络通信TCP、UDP详解

目录 IP 和端口 网络传输中的 2 个对象&#xff1a;server 和 client 两种传输方式&#xff1a;TCP/UDP TCP 和 UDP 原理上的区别 为何存在 UDP 协议 TCP/UDP 网络通信大概交互图 IP 和端口 所有的数据传输&#xff0c;都有三个要素 &#xff1a;源、目的、长度。 怎么表…

ZYNQ_project:IP_ram_pll_test

例化MMCM ip核&#xff0c;产生100Mhz&#xff0c;100Mhz并相位偏移180&#xff0c;50Mhz&#xff0c;25Mhz的时钟信号。 例化单口ram&#xff0c;并编写读写控制器&#xff0c;实现32个数据的写入与读出。 模块框图&#xff1a; 代码&#xff1a; module ip_top(input …

基于FPGA的PS端的Si5340的控制

1、功能 Si5340/41-D可以输出任意频率&#xff0c;当然有范围&#xff0c;100Hz1GHz。外部输入为24M或者4854M的XTAL&#xff0c;VCO在13500~14256Mhz之间&#xff0c;控制接口采用IIC或者SPI。 芯片架构图 2、IIC控制方式 3、直接上控制代码 使用米联客ZU3EG&#xff0c;将…

git使用笔记

0.记录使用经验 1.提交和push代码 git add .添加修改 git commit -m "提交日志" git push origin branch_name推送分支名称代码到远程服务器对应分支 1.1日常操作 git status查看仓库状态 git branch查看分支 git branch -a查看所有分支【包含远程】 git checkou…

如何从存档服务器上完全删除PDM用户

当创建新用户时使用“PDM 登录”类型&#xff08;如下图&#xff09;&#xff0c;PDM用户名和密码会存储于存档服务器的注册表中。 存档服务器的注册表位置如下&#xff1a; HKEY_LOCAL_MACHINE\SOFTWARE\SolidWorks\Applications\PDMWorks Enterprise\ArchiveServer\ConisioU…

在 Microsoft Word 中启用护眼模式

在 Microsoft Word 中启用护眼模式 在使用 Microsoft Word 365 或 Word 2019&#xff08;Windows&#xff09;版本时&#xff0c;启用护眼模式&#xff08;也称为“夜间模式”&#xff09;可以有效减轻屏幕亮度&#xff0c;有助于减少眼睛疲劳。以下是启用护眼模式的步骤&…

Linux centos系统中添加磁盘

为了学习与训练文件系统或磁盘的分区、格式化和挂载/卸载&#xff0c;我们需要为虚拟机添加磁盘。根据需要&#xff0c;可以添加多块不同大小的磁盘。具体操作讨论如下&#xff0c;供参考。 一、添加 1.开机前 有两个地方&#xff0c;可选择打开添加硬盘对话框 (1)双击左侧…

深度学习模型基于Python+TensorFlow+Django的垃圾识别系统

欢迎大家点赞、收藏、关注、评论啦 &#xff0c;由于篇幅有限&#xff0c;只展示了部分核心代码。 文章目录 一项目简介 二、功能三、系统四. 总结 一项目简介 要使用Python、TensorFlow和Django构建一个垃圾识别系统&#xff0c;您可以按照以下步骤进行操作&#xff1a; 安装…

Learn runqlat in 5 minutes

内容预告 learn X in 5 系列第一篇. 本篇主要介绍进程时延统计方式和 rawtracepoint. runqlat "高负载场景下应用为何卡顿", "进程 A 为什么得不到调度". 当我们在工作生活中产生这样的疑问, 目标进程的调度时延是一个不错的观测切入点. runqlat 可以帮…

2022最新版-李宏毅机器学习深度学习课程-P50 BERT的预训练和微调

模型输入无标签文本&#xff08;Text without annotation&#xff09;&#xff0c;通过消耗大量计算资源预训练&#xff08;Pre-train&#xff09;得到一个可以读懂文本的模型&#xff0c;在遇到有监督的任务是微调&#xff08;Fine-tune&#xff09;即可。 最具代表性是BERT&…

在线生成二维码--支持彩色二维码和包含Logo

具体请前往&#xff1a;在线二维码生成工具--可将网址等内容生成为指定大小&#xff0c;指定颜色的彩色二维码,同时支持添加Logo

数据结构:Map和Set(2):相关OJ题目

目录 136. 只出现一次的数字 - 力扣&#xff08;LeetCode&#xff09; 771. 宝石与石头 - 力扣&#xff08;LeetCode&#xff09; 旧键盘 (20)__牛客网 (nowcoder.com) 138. 随机链表的复制 - 力扣&#xff08;LeetCode&#xff09; 692. 前K个高频单词 - 力扣&#xff08…

linux_day02

1、链接&#xff1a;LN 一个点表示当前工作目录&#xff0c;两个点表示上一层工作目录&#xff1b; 目录的本质&#xff1a;文件&#xff08;该文件储存目录项&#xff0c;以链表的形式链接&#xff0c;每个结点都是目录项&#xff0c;创建文件相当于把目录项添加到链表中&…

【Unity之UI编程】编写一个面板交互界面需要注意的细节

&#x1f468;‍&#x1f4bb;个人主页&#xff1a;元宇宙-秩沅 &#x1f468;‍&#x1f4bb; hallo 欢迎 点赞&#x1f44d; 收藏⭐ 留言&#x1f4dd; 加关注✅! &#x1f468;‍&#x1f4bb; 本文由 秩沅 原创 &#x1f468;‍&#x1f4bb; 收录于专栏&#xff1a;Uni…

devops完整搭建教程(gitlab、jenkins、harbor、docker)

devops完整搭建教程&#xff08;gitlab、jenkins、harbor、docker&#xff09; 文章目录 devops完整搭建教程&#xff08;gitlab、jenkins、harbor、docker&#xff09;1.简介&#xff1a;2.工作流程&#xff1a;3.优缺点4.环境说明5.部署前准备工作5.1.所有主机永久关闭防火墙…

[PHP]Kodexplorer可道云 v4.47

KodExplorer可道云&#xff0c;原名芒果云&#xff0c;是基于Web技术的私有云和在线文件管理系统&#xff0c;由上海岱牧网络有限公司开发&#xff0c;发布于2012年6月。致力于为用户提供安全可控、可靠易用、高扩展性的私有云解决方案。 用户只需通过简单环境搭建&#xff0c;…

数据库安全:Hadoop 未授权访问-命令执行漏洞.

数据库安全&#xff1a;Hadoop 未授权访问-命令执行漏洞. Hadoop 未授权访问主要是因为 Hadoop YARN 资源管理系统配置不当&#xff0c;导致可以未经授权进行访问&#xff0c;从而被攻击者恶意利用。攻击者无需认证即可通过 RESTAPI 部署任务来执行任意指令&#xff0c;最终完…

SQL SELECT INTO 语句

SQL SELECT INTO 语句 使用 SQL&#xff0c;您可以将信息从一个表中复制到另一个表中。 SELECT INTO 语句从一个表中复制数据&#xff0c;然后将数据插入到另一个新表中。 SQL SELECT INTO 语法 我们可以把所有的列都复制到新表中&#xff1a; SELECT * INTO newtable [IN ex…

【Git】说说Git中开发测试的使用Git分支Git标签的使用场景

一、Git的使用场景 1、四个环境以及各自的功能特点 dev环境&#xff1a;开发环境&#xff0c;外部用户无法访问&#xff0c;开发人员使用&#xff0c;版本变动很大。test环境&#xff1a;测试环境&#xff0c;外部用户无法访问&#xff0c;专门给测试人员使用的&#xff0c;版本…