数据结构之双向链表

目录

引言 

链表的分类 

双向链表的结构

双向链表的实现 

定义

创建新节点 

初始化 

打印 

尾插

头插 

判断链表是否为空

尾删 

头删

查找与修改 

指定插入

指定删除 

销毁 

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

源代码 

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.hqwc.cn/news/168945.html

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

相关文章

Linux shell编程学习笔记24:函数定义和使用

为了实现模块化设计和代码重用&#xff0c;很多编程语言支持函数或过程&#xff0c;Linux shell也支持函数定义和调用。 Linux shell中的函数与其它编程语言很多有相似之处&#xff0c;也有自己独特之处。 1 函数的定义 1.1 标准格式 function 函数名(){语句或命令1……语句…

基于SpringBoot+Redis的前后端分离外卖项目-苍穹外卖(三)

员工分页查询和账号启用禁用功能 1. 员工分页查询1.1 需求分析和设计1.1.1 产品原型1.1.2 接口设计 1.2 代码开发1.2.1 设计DTO类1.2.2 封装PageResult1.2.3 Controller层1.2.4 Service层接口1.2.5 Service层实现类1.2.6 Mapper层 1.3 功能测试1.4 代码完善 2. 启用禁用员工账号…

基于ubuntu22.04手动安装openstack——2023.2版本(最新版)的问题汇总

前言&#xff1a;基本上按照openstack官方网站动手可以搭建成功&#xff08;如有需要私信发部署文档&#xff09;。 但是任然有些小问题&#xff0c;所以汇总如下。 第一个问题 问题&#xff1a; ubuntu搭建2023.2版本neutorn报错&#xff0c;ERROR neutron.plugins.ml2.driv…

c++求三个数的最小公倍数

答案&#xff1a; #include <iostream> using namespace std; int main() {int n1, n2, n3, max;cin >> n1 >> n2 >> n3;max (n1 > n2 > n3) ? n1 : n2;do{if (max % n1 0 && max % n2 0 && max % n3 0){cout << ma…

2.docker镜像的导入导出

目录 概述docker 常用命令下载导出导入镜像结束 概述 docker 常用命令 本章节使用到的命令&#xff0c;总结在此&#xff0c;后面有使用案例。 命令作用docker images显示镜像docker rmi $(docker images -q)删除系统上所有的镜像docker rmi -f强制删除多个镜像 &#xff1a…

第十三章《搞懂算法:神经网络是怎么回事》笔记

目前神经网络技术受到追捧&#xff0c;一方面是由于数据传感设备、数据通信技术和数据存储技术 的成熟与完善&#xff0c;使得低成本采集和存储海量数据得以成为现实;另一方面则是由于计算能力的大幅提升&#xff0c;如图形处理器(Graphics Processing Unit&#xff0c;GPU)在神…

VS c++多文件编译

前言&#xff1a;记录下我这个菜鸡学习的过程&#xff0c;如有错误恳请指出&#xff0c;不胜感激&#xff01; 1.简单多文件编译调试 文件目录&#xff1a; 编译&#xff1a; -g选项是告诉编译器生成调试信息&#xff0c;这样可以在程序崩溃或出现错误时更容易地进行调试。这…

图论11-欧拉回路与欧拉路径+Hierholzer算法实现

文章目录 1 欧拉回路的概念2 欧拉回路的算法实现3 Hierholzer算法详解4 Hierholzer算法实现4.1 修改Graph&#xff0c;增加API4.2 Graph.java4.3 联通分量类4.4 欧拉回路类 1 欧拉回路的概念 2 欧拉回路的算法实现 private boolean hasEulerLoop(){CC cc new CC(G);if(cc.cou…

19 异步通知

一、异步通知 1. 异步通知简介 阻塞和非阻塞两种方式都是需要应用程序去主动查询设备的使用情况。 异步通知类似于驱动可以主动报告自己可以访问&#xff0c;应用程序获取信号后会从驱动设备中读取或写入数据。 异步通知最核心的就是信号&#xff1a; #define SIGHUP 1 /* 终…

帝国cms中如何让外部链接直接从新窗口打开页面

<?php if($bqr[isurl]) { ?> <a href"<?$bqsr[titleurl]?>" target"_blank"> <?php } else { ?> <a href"<?$bqsr[titleurl]?>"> <?php } ?>

KCC@广州与 TiDB 社区联手—广州开源盛宴

10月21日&#xff0c;KCC广州与 TiDB 社区联手&#xff0c;在海珠区保利中悦广场 29 楼召开了一次难忘的开源盛宴。这不仅仅是 KCC广州的又一次线下见面&#xff0c;更代表着与 TiDB 社区及广州技术社区的首次深度合作。 活动的策划与组织由 KCC广州负责人 - 惠世冀、PingCAP 的…

Doris:多源数据目录(Multi-Catalog)

目录 1.基本概念 2.基本操作 2.1 查看 Catalog 2.2 新增 Catalog 2.3 切换 Catalog 2.4 删除 Catalog 3.元数据更新 3.1手动刷新 3.2定时刷新 3.3自动刷新 4.JDBC Catalog 4.1 上传mysql驱动包 4.2 创建mysql catalog 4.3. 读取mysql数据 1.基本概念 …