当前位置: 首页 > news >正文

数据结构之双向链表

目录

引言 

链表的分类 

双向链表的结构

双向链表的实现 

定义

创建新节点 

初始化 

打印 

尾插

头插 

判断链表是否为空

尾删 

头删

查找与修改 

指定插入

指定删除 

销毁 

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

源代码 

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;
}

相关文章:

  • 在以BUF,字节存储区中,存放有n个带符号整数。试编写统计其中负偶数个数(假设≤9)并且显示。
  • 【Spring Boot 源码学习】初识 SpringApplication
  • 【开题报告】基于SpringBoot的高校实验室管理系统的设计与实现
  • Springboot项目部署及多环境开发
  • AlGaN/GaN HFET 五参数模型
  • deployment.yaml文件详解
  • Python Selenium元素定位方法详解
  • 【网络】TCP协议理论
  • idea报错java: 程序包com.alibaba.fastjson不存在,明明存在!
  • 操作系统——内存管理(一文搞懂操作系统的内存管理)
  • centos 7.9系统安装老版本jenkins,并解决插件问题
  • Java之“数字困境”:资产管理项目中的Bug追踪与启示
  • uniapp+uviewPlus+vue3+ts+pinia+vite+echarts 开发基础模板,开箱即用,非常顺手
  • HarmonyOS应用开发-首选项与后台通知管理
  • 智能PDU在现代智慧医院机房末端配电系统中的应用分析
  • angular组件开发
  • ComponentOne 2017 V2版本正式发布
  • Facebook AccountKit 接入的坑点
  • Laravel 菜鸟晋级之路
  • scala基础语法(二)
  • spring + angular 实现导出excel
  • Vue UI框架库开发介绍
  • 翻译--Thinking in React
  • 分享一个自己写的基于canvas的原生js图片爆炸插件
  • 解决jsp引用其他项目时出现的 cannot be resolved to a type错误
  • 警报:线上事故之CountDownLatch的威力
  • 来,膜拜下android roadmap,强大的执行力
  • 聊聊sentinel的DegradeSlot
  • 深入浏览器事件循环的本质
  • 数组大概知多少
  • 微服务核心架构梳理
  • 远离DoS攻击 Windows Server 2016发布DNS政策
  • 好程序员大数据教程Hadoop全分布安装(非HA)
  • 智能情侣枕Pillow Talk,倾听彼此的心跳
  • ​​​​​​​Installing ROS on the Raspberry Pi
  • ​LeetCode解法汇总1276. 不浪费原料的汉堡制作方案
  • # 再次尝试 连接失败_无线WiFi无法连接到网络怎么办【解决方法】
  • $分析了六十多年间100万字的政府工作报告,我看到了这样的变迁
  • (02)Cartographer源码无死角解析-(03) 新数据运行与地图保存、加载地图启动仅定位模式
  • (2015)JS ES6 必知的十个 特性
  • (a /b)*c的值
  • (附源码)ssm学生管理系统 毕业设计 141543
  • (转载)VS2010/MFC编程入门之三十四(菜单:VS2010菜单资源详解)
  • .net6使用Sejil可视化日志
  • .NET框架
  • /bin/bash^M: bad interpreter: No such file ordirectory
  • /usr/local/nginx/logs/nginx.pid failed (2: No such file or directory)
  • @AliasFor注解
  • @JoinTable会自动删除关联表的数据
  • [Docker]三.Docker 部署nginx,以及映射端口,挂载数据卷
  • [iOS开发]iOS中TabBar中间按钮凸起的实现
  • [iphone-cocos2d]关于Loading的若干处理和讨论
  • [JS入门到进阶] 哎,被vite小坑了一波,大家记得配置build.cssTarget为‘chrome61‘
  • [loj#115] 无源汇有上下界可行流 网络流
  • [Oh My C++ Diary]Main函数参数argc,argv如何传入