进阶数据结构——双向循环链表

目录

  • 前言
  • 一、定义与结构
  • 二、特点与优势
  • 三、基本操作
  • 四、应用场景
  • 五、实现复杂度
  • 六、动态图解
  • 七、代码模版(c++)
  • 八、经典例题
  • 九、总结
  • 结语

前言

这一期我们学习双向循环链表。双向循环链表不同于单链表,双向循环链表是一种特殊的数据结构,它结合了双向链表和循环链表的特点。

在这里插入图片描述

一、定义与结构

双向循环链表中的每个节点都包含三个部分:数据域(存储数据)、前驱指针(指向前一个节点)和后继指针(指向下一个节点)。此外,链表的头节点和尾节点通过指针相互连接,形成一个闭环。这种结构使得链表可以从任意一个节点开始遍历整个链表。

二、特点与优势

双向访问:双向链表允许从任意节点向前或向后遍历,这使得在需要频繁访问链表前后节点的场景中,双向链表比单向链表更加高效。
循环特性:双向循环链表的头尾相连,形成一个环,这使得在处理需要循环访问所有节点的任务时,双向循环链表比单向循环链表更加方便。
灵活性:由于节点之间通过指针相互连接,双向循环链表在插入和删除节点时具有较高的灵活性。

三、基本操作

创建链表:首先需要初始化头节点,并设置其前驱指针和后继指针都指向自己,以形成闭环。然后,根据用户输入或其他数据源,依次插入节点。
遍历链表:可以从头节点或任意节点开始遍历整个链表。由于链表是循环的,因此遍历过程会一直进行,直到再次回到起始节点。
插入节点:在指定位置插入新节点时,需要调整新节点及其相邻节点的前驱和后继指针。
删除节点:删除指定节点时,需要调整其相邻节点的前驱和后继指针,并释放被删除节点的内存空间。
查询节点:根据节点位置或数据值查询节点时,需要从头节点开始遍历链表,直到找到目标节点或遍历完整个链表。

四、应用场景

双向循环链表在需要频繁访问链表前后节点的场景中非常有用。例如,在任务调度、缓存管理、图形界面元素管理等场景中,双向循环链表可以提供高效的数据访问和操作。

五、实现复杂度

虽然双向循环链表提供了更多的灵活性和功能,但其实现复杂度也相对较高。在插入和删除节点时,需要处理更多的指针操作,这可能会增加代码复杂性和出错风险。因此,在实现双向循环链表时,需要特别注意指针的正确性和内存管理。

六、动态图解

在这里插入图片描述

七、代码模版(c++)

#include<iostream>
using namespace std;template<typename T>
struct Node {T data;Node* next;Node* prev;Node(const T& value):data(value),next(NULL),prev(NULL){}
};template<class T>
class doubleLinkedNode {
private:Node<T>* m_dummyHead;int m_size;
public:doubleLinkedNode();~doubleLinkedNode();void push_front(const T& value);void push_back(const T& value);void insert_after(Node<T>* node, const T& value);void delete_node(Node<T>* node);void modify(Node<T>* node, const T& value);Node<T>* find(const T& value) const;void print()const;int size()const;bool empty()const;
};template<class T>
doubleLinkedNode<T>::doubleLinkedNode():m_size(0){m_dummyHead = new Node<T>(T());	//初始化虚拟头结点,自己指向自己m_dummyHead->next = m_dummyHead;	m_dummyHead->prev = m_dummyHead;
}template<class T>
doubleLinkedNode<T>::~doubleLinkedNode(){while (m_size > 0) {delete_node(m_dummyHead->next);}delete m_dummyHead;m_dummyHead = NULL;
}// m_dummyHead <=> m_dummyHead ->next
// m_dummyHead <=> newNode <=> m_dummyHead -> next
template<class T>
void doubleLinkedNode<T>::push_front(const T& value){Node<T>* newNode = new Node<T>(value);newNode->prev = m_dummyHead;newNode->next = m_dummyHead->next;	//要好好理解一下这里,为什么不直接是 m_dummyHead-next=newNodem_dummyHead->next->prev = newNode;m_dummyHead->next = newNode;++m_size;
}// m_dummyHead <=> m_dummyHead ->next
// m_dummyHead -> prev <=> newNode <=> m_dummyHead
template<class T>
void doubleLinkedNode<T>::push_back(const T& value){Node<T>* newNode = new Node<T>(value);newNode->prev = m_dummyHead->prev;newNode->next = m_dummyHead;		//一定要注意要改变的是newNode和m_dummyHead的前驱和后继节点,它们本身不变m_dummyHead->prev->next= newNode;m_dummyHead->prev = newNode;m_size++;
}//插入前:node <=> node->next
//插入后:node <=> newNode <=> node->next
template<class T>
void doubleLinkedNode<T>::insert_after(Node<T>* node,const T& value){if (!node || node == m_dummyHead)return;Node<T>* newNode = new Node<T>(value);	//这里有四个箭头代表四个方向,我们加入节点就是要处理好这四个箭头newNode->prev = node;		//这里处理的是newNode的  <-newNode->next = node->next;	//  newNode ->node->next->prev = newNode; //node->next  <-node->next = newNode;		//node ->m_size++;
}//插入前:node -> prev <=> node <=> node->next
//插入后:node -> prev <=> node->next
template<class T>
void doubleLinkedNode<T>::delete_node(Node<T>* node){if (!node || node == m_dummyHead)return;node->prev->next = node->next;node->next->prev = node->prev;delete node;m_size--;
}template<class T>
void doubleLinkedNode<T>::modify(Node<T>* node, const T& value){if (!node || node == m_dummyHead)return;node->data = value;
}template<class T>
Node<T>* doubleLinkedNode<T>::find(const T& value) const{Node<T>* curr = m_dummyHead->next;while (curr != m_dummyHead) {if (curr->data == value)return curr;curr = curr->next;}return NULL;
}template<class T>
void doubleLinkedNode<T>::print() const{Node<T>* curr = m_dummyHead->next;while (curr != m_dummyHead) {cout << curr->data << " ";curr = curr->next;}cout << endl;
}template<class T>
int doubleLinkedNode<T>::size() const{return m_size;
}template<class T>
bool doubleLinkedNode<T>::empty() const {return m_size == 0;
}int main() {doubleLinkedNode<int> dll;for (int i = 1; i <= 10; i++) {dll.push_back(i);}int M;cin >> M;while (M--) {int x;cin >> x;Node<int>* fn = dll.find(x);dll.delete_node(fn);dll.push_front(x);dll.print();}return 0;
}

八、经典例题

1. 小王子双链表

#include<iostream>
using namespace std;template<typename T>
struct Node {T data;Node* next;Node* prev;Node(const T& value):data(value),next(NULL),prev(NULL){}
};template<class T>
class doubleLinkedNode {
private:Node<T>* m_dummyHead;int m_size;
public:doubleLinkedNode();~doubleLinkedNode();void push_front(const T& value);void push_back(const T& value);void insert_after(Node<T>* node, const T& value);void delete_node(Node<T>* node);void modify(Node<T>* node, const T& value);Node<T>* find(const T& value) const;void print()const;int size()const;bool empty()const;
};template<class T>
doubleLinkedNode<T>::doubleLinkedNode():m_size(0){m_dummyHead = new Node<T>(T());	//初始化虚拟头结点,自己指向自己m_dummyHead->next = m_dummyHead;	m_dummyHead->prev = m_dummyHead;
}template<class T>
doubleLinkedNode<T>::~doubleLinkedNode(){while (m_size > 0) {delete_node(m_dummyHead->next);}delete m_dummyHead;m_dummyHead = NULL;
}// m_dummyHead <=> m_dummyHead ->next
// m_dummyHead <=> newNode <=> m_dummyHead -> next
template<class T>
void doubleLinkedNode<T>::push_front(const T& value){Node<T>* newNode = new Node<T>(value);newNode->prev = m_dummyHead;newNode->next = m_dummyHead->next;	//要好好理解一下这里,为什么不直接是 m_dummyHead-next=newNodem_dummyHead->next->prev = newNode;m_dummyHead->next = newNode;++m_size;
}// m_dummyHead <=> m_dummyHead ->next
// m_dummyHead -> prev <=> newNode <=> m_dummyHead
template<class T>
void doubleLinkedNode<T>::push_back(const T& value){Node<T>* newNode = new Node<T>(value);newNode->prev = m_dummyHead->prev;newNode->next = m_dummyHead;		//一定要注意要改变的是newNode和m_dummyHead的前驱和后继节点,它们本身不变m_dummyHead->prev->next= newNode;m_dummyHead->prev = newNode;m_size++;
}//插入前:node <=> node->next
//插入后:node <=> newNode <=> node->next
template<class T>
void doubleLinkedNode<T>::insert_after(Node<T>* node,const T& value){if (!node || node == m_dummyHead)return;Node<T>* newNode = new Node<T>(value);	//这里有四个箭头代表四个方向,我们加入节点就是要处理好这四个箭头newNode->prev = node;		//这里处理的是newNode的  <-newNode->next = node->next;	//  newNode ->node->next->prev = newNode; //node->next  <-node->next = newNode;		//node ->m_size++;
}//插入前:node -> prev <=> node <=> node->next
//插入后:node -> prev <=> node->next
template<class T>
void doubleLinkedNode<T>::delete_node(Node<T>* node){if (!node || node == m_dummyHead)return;node->prev->next = node->next;node->next->prev = node->prev;delete node;m_size--;
}template<class T>
void doubleLinkedNode<T>::modify(Node<T>* node, const T& value){if (!node || node == m_dummyHead)return;node->data = value;
}template<class T>
Node<T>* doubleLinkedNode<T>::find(const T& value) const{Node<T>* curr = m_dummyHead->next;while (curr != m_dummyHead) {if (curr->data == value)return curr;curr = curr->next;}return NULL;
}template<class T>
void doubleLinkedNode<T>::print() const{Node<T>* curr = m_dummyHead->next;while (curr != m_dummyHead) {cout << curr->data << " ";curr = curr->next;}cout << endl;
}template<class T>
int doubleLinkedNode<T>::size() const{return m_size;
}template<class T>
bool doubleLinkedNode<T>::empty() const {return m_size == 0;
}int main() {doubleLinkedNode<int> dll;for (int i = 1; i <= 10; i++) {dll.push_back(i);}int M;cin >> M;while (M--) {int x;cin >> x;Node<int>* fn = dll.find(x);dll.delete_node(fn);dll.push_front(x);dll.print();}return 0;
}

九、总结

综上所述,双向循环链表是一种功能强大且灵活的数据结构,适用于需要频繁访问链表前后节点的场景。然而,其实现复杂度也相对较高,需要开发者具备扎实的编程基础和内存管理能力。

结语

当前进阶的数据结构比之前学的初级数据结构要复杂了,希望大家一定要动手敲一遍代码,敲完之后提交到例题里检查一下是否正确,出现bug不用慌张,耐心差错,这样你的水平才能提升。
在这里插入图片描述

希望大家可以一键三连,后续我们一起学习,谢谢大家!!!
在这里插入图片描述

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

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

相关文章

稀疏混合专家架构语言模型(MoE)

注&#xff1a;本文为 “稀疏混合专家架构语言模型&#xff08;MoE&#xff09;” 相关文章合辑。 手把手教你&#xff0c;从零开始实现一个稀疏混合专家架构语言模型&#xff08;MoE&#xff09; 机器之心 2024年02月11日 12:21 河南 选自huggingface 机器之心编译 机器之心…

作者新游戏1.0

目录 前言 正文 忘记 前言 今天&#xff0c;作者要创作一个关于末日丧尸的游戏&#xff0c;开整&#xff01; 正文 首先&#xff0c;我们创作一个标题 cout<<"作者新游戏!\n"; cout<<"1:查看\n";cout<<"2:提示\n";cout…

PVE 中 Debian 虚拟机崩溃后,硬盘数据怎么恢复

问题 在 PVE 中给 Debian 虚拟机新分配硬盘后&#xff0c;通过 Debian 虚拟机开启 Samba 共享该硬盘。如果这个 Debian 虚拟机崩溃后&#xff0c;怎么恢复 Samba 共享硬盘数据。 方法 开启 Samba 共享相关知识&#xff1a;挂载硬盘和开启Samba共享。 新建一个虚拟机&#xf…

【黄啊码】DeepSeek提示词大道至简版

1.1 有效提问的五个黄金法 法则一&#xff1a;明确需求 错误示例&#xff1a; Γ帮我写点东西」 正确姿势&#xff1a; Γ我需要一封求职邮件&#xff0c;应聘新媒体运营岗位&#xff0c;强调B年公众号运营经验 法则二&#xff1a;提供背景 错误示例 &#xff1a; Γ分析这个…

Baklib如何改变内容管理平台的未来推动创新与效率提升

内容概要 在信息爆炸的时代&#xff0c;内容管理平台成为了企业和个人不可或缺的工具。它通过高效组织、存储和发布内容&#xff0c;帮助用户有效地管理信息流。随着技术的发展&#xff0c;传统的内容管理平台逐渐暴露出灵活性不足、易用性差等局限性&#xff0c;这促使市场需…

想品客老师的第天:类

类是一个优化js面向对象的工具 类的声明 //1、class User{}console.log(typeof User)//function//2、let Hdclass{}//其实跟1差不多class Stu{show(){}//注意这里不用加逗号&#xff0c;对象才加逗号get(){console.log(后盾人)}}let hdnew Stu()hd.get()//后盾人 类的原理 类…

Java 大视界 -- Java 大数据在自动驾驶中的数据处理与决策支持(68)

&#x1f496;亲爱的朋友们&#xff0c;热烈欢迎来到 青云交的博客&#xff01;能与诸位在此相逢&#xff0c;我倍感荣幸。在这飞速更迭的时代&#xff0c;我们都渴望一方心灵净土&#xff0c;而 我的博客 正是这样温暖的所在。这里为你呈上趣味与实用兼具的知识&#xff0c;也…

基于springboot私房菜定制上门服务系统设计与实现(源码+数据库+文档)

私房菜定制上门服务系统目录 目录 基于springbootvue私房菜定制上门服务系统设计与实现 一、前言 二、系统功能设计 三、系统实现 1、管理员功能实现 &#xff08;1&#xff09;菜品管理 &#xff08;2&#xff09;公告管理 &#xff08;3&#xff09; 厨师管理 2、用…

Node.js常用知识

Nodejs 总结Node.js基础知识&#xff0c;便于定期回顾 1、fs 文件写入 1、require(‘fs’) 2、fs.writeFile() 3、fs.appendFile() 4、fs.createwriteStream&#xff08;&#xff09; //流式写入 ws.write() 文件读取 1、fs.readFile(‘’,(err,data)>{ }) const …

C#属性和字段(访问修饰符)

不同点逻辑性/灵活性存储性访问性使用范围安全性属性(Property)源于字段,对字段的扩展,逻辑字段并不占用实际的内存可以被其他类访问对接收的数据范围做限定,外部使用增加了数据的安全性字段(Field)不经过逻辑处理占用内存的空间及位置大部分字段不能直接被访问内存使用不安全 …

15JavaWeb——Maven高级篇

Maven高级 Web开发讲解完毕之后&#xff0c;我们再来学习Maven高级。其实在前面的课程当中&#xff0c;我们已经学习了Maven。 我们讲到 Maven 是一款构建和管理 Java 项目的工具。经过前面 10 多天 web 开发的学习&#xff0c;相信大家对于 Maven 这款工具的基本使用应该没什…

100.1 AI量化面试题:解释夏普比率(Sharpe Ratio)的计算方法及其在投资组合管理中的应用,并说明其局限性

目录 0. 承前1. 夏普比率的基本概念1.1 定义与计算方法1.2 实际计算示例 2. 在投资组合管理中的应用2.1 投资组合选择2.2 投资组合优化 3. 夏普比率的局限性3.1 统计假设的限制3.2 实践中的问题 4. 改进方案4.1 替代指标4.2 实践建议 5. 回答话术 0. 承前 如果想更加全面清晰地…

从0开始使用面对对象C语言搭建一个基于OLED的图形显示框架(基础图形库实现)

目录 基础图形库的抽象 抽象图形 抽象点 设计我们的抽象 实现我们的抽象 测试 抽象线 设计我们的抽象 实现我们的抽象 绘制垂直的和水平的线 使用Bresenham算法完成任意斜率的绘制 绘制三角形和矩形 矩形 三角形 实现 绘制圆&#xff0c;圆弧和椭圆 继续我们的…

文件系统分析

文件系统与磁盘管理详解 一、存储设备基础 1. 存储设备类型对比 设备类型特点典型接口应用场景机械硬盘依赖磁头机械读写&#xff0c;转速影响性能&#xff08;5400/7200/10000rpm&#xff09;&#xff0c;价格低容量大SATA/SAS冷数据存储、备份固态硬盘无机械结构&#xff…

基于Python的药物相互作用预测模型AI构建与优化(上.文字部分)

一、引言 1.1 研究背景与意义 在临床用药过程中,药物相互作用(Drug - Drug Interaction, DDI)是一个不可忽视的重要问题。当患者同时服用两种或两种以上药物时,药物之间可能会发生相互作用,从而改变药物的疗效、增加不良反应的发生风险,甚至危及患者的生命安全。例如,…

1.Template Method 模式

模式定义 定义一个操作中的算法的骨架&#xff08;稳定&#xff09;&#xff0c;而将一些步骤延迟&#xff08;变化)到子类中。Template Method 使得子类可以不改变&#xff08;复用&#xff09;一个算法的结构即可重定义&#xff08;override 重写&#xff09;该算法的某些特…

16.[前端开发]Day16-HTML+CSS阶段练习(网易云音乐五)

完整代码 网易云-main-left-rank&#xff08;排行榜&#xff09; <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta http-equiv"X-UA-Compatible" content"IEedge"><meta name&q…

使用Pygame制作“走迷宫”游戏

1. 前言 迷宫游戏是最经典的 2D 游戏类型之一&#xff1a;在一个由墙壁和通道构成的地图里&#xff0c;玩家需要绕过障碍、寻找通路&#xff0c;最终抵达出口。它不但简单易实现&#xff0c;又兼具可玩性&#xff0c;还能在此基础上添加怪物、道具、机关等元素。本篇文章将展示…

Python之Excel操作 - 写入数据

我们将使用 openpyxl 库&#xff0c;它是一个功能强大且易于使用的库&#xff0c;专门用于处理 Excel 文件。 1. 安装 openpyxl 首先&#xff0c;你需要安装 openpyxl 库。你可以使用 pip 命令进行安装&#xff1a; pip install openpyxl创建一个文件 example.xlsx&#xff…

C++解决输入空格字符串的三种方法

一.gets和fgets char * gets ( char * str ); char * fgets ( char * str, int num, FILE * stream ); 1. gets 是从第⼀个字符开始读取&#xff0c;⼀直读取到 \n 停⽌&#xff0c;但是不会读取 \n &#xff0c;也就是读取到的内容 中没有包含 \n ,但是会在读取到的内…