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

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

目录

  • 前言
  • 一、定义与结构
  • 二、特点与优势
  • 三、基本操作
  • 四、应用场景
  • 五、实现复杂度
  • 六、动态图解
  • 七、代码模版(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不用慌张,耐心差错,这样你的水平才能提升。
在这里插入图片描述

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

相关文章:

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

目录 前言一、定义与结构二、特点与优势三、基本操作四、应用场景五、实现复杂度六、动态图解七、代码模版&#xff08;c&#xff09;八、经典例题九、总结结语 前言 这一期我们学习双向循环链表。双向循环链表不同于单链表&#xff0c;双向循环链表是一种特殊的数据结构&…...

记录一次,PyQT的报错,多线程Udp失效,使用工具如netstat来检查端口使用情况。

1.问题 报错Exception in thread Thread-1: Traceback (most recent call last): File "threading.py", line 932, in _bootstrap_inner File "threading.py", line 870, in run File "main.py", line 456, in udp_recv IndexError: list…...

安装anaconda3 后 电脑如何单独运行python,python还需要独立安装吗?

安装anaconda3 后 电脑如何单独运行python&#xff0c;python还需要独立安装吗? 电脑第一此安装anaconda用于jupyter notebook使用。 但是在运行cmd的时候&#xff0c;输入python --version 显示未安装或跳转商店提示安装。 明明我可以运行python但是为什么cmd却说我没安装呢…...

电子电气架构 --- 汽车电子拓扑架构的演进过程

我是穿拖鞋的汉子&#xff0c;魔都中坚持长期主义的汽车电子工程师。 老规矩&#xff0c;分享一段喜欢的文字&#xff0c;避免自己成为高知识低文化的工程师&#xff1a; 简单&#xff0c;单纯&#xff0c;喜欢独处&#xff0c;独来独往&#xff0c;不易合同频过着接地气的生活…...

ASP.NET Core 中使用依赖注入 (DI) 容器获取并执行自定义服务

目录 一、ASP.NET Core 中使用依赖注入 (DI) 容器获取并执行自定义服务 1. app.Services 2. GetRequiredService() 3. Init() 二、应用场景 三、依赖注入使用拓展 1、使用场景 2、使用步骤 1. 定义服务接口和实现类 2. 注册服务到依赖注入容器 3. 使用依赖注入获取并…...

leetcode——验证二叉搜索树(java)

给你一个二叉树的根节点 root &#xff0c;判断其是否是一个有效的二叉搜索树。 有效 二叉搜索树定义如下&#xff1a; 节点的左子树只包含小于当前节点的数。 节点的右子树只包含 大于 当前节点的数。 所有左子树和右子树自身必须也是二叉搜索树。 示例 1&#xff1a; 输入…...

搜索引擎快速收录:关键词布局的艺术

本文来自&#xff1a;百万收录网 原文链接&#xff1a;https://www.baiwanshoulu.com/21.html 搜索引擎快速收录中的关键词布局&#xff0c;是一项既精细又富有策略性的工作。以下是对关键词布局艺术的详细阐述&#xff1a; 一、关键词布局的重要性 关键词布局影响着后期页面…...

VLN视觉语言导航基础

0 概述 视觉语言导航模型旨在构建导航决策模型 π π π&#xff0c;在 t t t时刻&#xff0c;模型能够根据指令 W W W、历史轨迹 τ { V 1 , V 2 , . . . , V t − 1 } \tau\{V_1,V_2,...,V_{t-1}\} τ{V1​,V2​,...,Vt−1​}和当前观察 V t { P t , R t , N ( V t ) } V_…...

4 Hadoop 面试真题

4 Hadoop 面试真题 1. Apache Hadoop 3.0.02. HDFS 3.x 数据存储新特性-纠删码Hadoop面试真题 1. Apache Hadoop 3.0.0 Apache Hadoop 3.0.0在以前的主要发行版本&#xff08;hadoop-2.x&#xff09;上进行了许多重大改进。 最低要求的Java版本从Java 7增加到Java 8 现在&…...

java练习(2)

回文数&#xff08;题目来自力扣&#xff09; 给你一个整数 x &#xff0c;如果 x 是一个回文整数&#xff0c;返回 true &#xff1b;否则&#xff0c;返回 false 。 回文数 是指正序&#xff08;从左向右&#xff09;和倒序&#xff08;从右向左&#xff09;读都是一样的整…...

vscode命令面板输入 CMake:build不执行提示输入

CMake&#xff1a;build或rebuild不编译了&#xff0c;弹出:> [Add a new preset] , 提示输入发现settings.jsons设置有问题 { "workbench.colorTheme": "Default Light", "cmake.pinnedCommands": [ "workbench.action.tasks.configu…...

Java中对消息序列化和反序列化并且加入到Spring消息容器中

--- 参考项目&#xff1a;苍穹外卖。 在对没有Java中的数据序列化时&#xff0c;比如说时间格式&#xff1a; 时间的格式是这种没有格式化的效果&#xff0c;因为在给前端返回数据时&#xff0c;返回的结果并没有序列化。 所以&#xff0c;需要对返回的数据序列化。 首先需…...

FFmpeg源码:av_base64_decode函数分析

一、引言 Base64&#xff08;基底64&#xff09;是一种基于64个可打印字符来表示二进制数据的表示方法。由于log2 646&#xff0c;所以每6个比特为一个单元&#xff0c;对应某个可打印字符。3个字节相当于24个比特&#xff0c;对应于4个Base64单元&#xff0c;即3个字节可由4个…...

【后端面试总结】mysql的group by怎么用

GROUP BY 是 SQL 中的一种用于对结果集进行分组的子句&#xff0c;常与聚合函数&#xff08;如 COUNT()、SUM()、AVG()、MAX() 和 MIN() 等&#xff09;一起使用。GROUP BY 的作用是基于一个或多个列对查询结果进行分组&#xff0c;然后可以对每个分组执行聚合操作。 以下是 G…...

计算机视觉和图像处理

计算机视觉与图像处理的最新进展 随着人工智能技术的飞速发展&#xff0c;计算机视觉和图像处理作为其中的重要分支&#xff0c;正逐步成为推动科技进步和产业升级的关键力量。 一、计算机视觉的最新进展 计算机视觉&#xff0c;作为人工智能的重要分支&#xff0c;主要研究如…...

一文读懂Python之random模块(31)

random模块是Python的内置标准库&#xff0c;用于生成各类随机数&#xff0c;可以用作生成网站初始登录密码和随机验证码。 一、random模块简介 random模块可以生成随机数&#xff0c;包括随机整数、浮点数、随机元素等。 二、random模块相关概念 随机数&#xff1a; 是指在…...

p1044 栈

两种递推细节不同 1,将1和n在序列末尾的情况单独放出来处理&#xff0c;因为dp[0]0&#xff1b; 2,将所有情况统一处理&#xff0c;这种情况就要要求dp[1]1; 这里的n在解题中可以看做是元素数量 思路是&#xff0c;根据出栈最后一个元素,统计它前面的元素数量的输出序列数和…...

吴恩达深度学习——超参数调试

内容来自https://www.bilibili.com/video/BV1FT4y1E74V&#xff0c;仅为本人学习所用。 文章目录 超参数调试调试选择范围 Batch归一化公式整合 Softmax 超参数调试 调试 目前学习的一些超参数有学习率 α \alpha α&#xff08;最重要&#xff09;、动量梯度下降法 β \bet…...

SQL NOW() 函数详解

SQL NOW() 函数详解 引言 在SQL数据库中&#xff0c;NOW() 函数是一个常用的日期和时间函数&#xff0c;用于获取当前的时间戳。本文将详细介绍 NOW() 函数的用法、参数、返回值以及在实际应用中的注意事项。 函数概述 NOW() 函数返回当前的日期和时间&#xff0c;格式为 Y…...

【JAVA基础】双亲委派

双亲委派可以简单理解为, 当收到加载请求时, 会依次向上加载 ; 只有当父类加载器无法完成加载请求时&#xff0c;子类加载器才会尝试自己去加载。 工作原理 类加载请求传递&#xff1a;当应用程序需要加载一个类时&#xff0c;比如通过ClassLoader.loadClass()方法&#xff0…...

刷题记录 HOT100回溯算法-6:79. 单词搜索

题目&#xff1a;79. 单词搜索 给定一个 m x n 二维字符网格 board 和一个字符串单词 word 。如果 word 存在于网格中&#xff0c;返回 true &#xff1b;否则&#xff0c;返回 false 。 单词必须按照字母顺序&#xff0c;通过相邻的单元格内的字母构成&#xff0c;其中“相邻…...

JavaScript系列(52)--编译优化技术详解

JavaScript编译优化技术详解 &#x1f680; 今天&#xff0c;让我们深入探讨JavaScript的编译优化技术。通过理解和应用这些技术&#xff0c;我们可以显著提升JavaScript代码的执行效率。 编译优化基础概念 &#x1f31f; &#x1f4a1; 小知识&#xff1a;JavaScript引擎通常…...

Ollama+DeepSeek本地大模型部署

1、Ollama 官网&#xff1a;https://ollama.com/ Ollama可以干什么&#xff1f; 可以快速在本地部署和管理各种大语言模型&#xff0c;操作命令和dokcer类似。 mac安装ollama&#xff1a; # 安装ollama brew install ollama# 启动ollama服务&#xff08;默认11434端口&#xf…...

在 WSL2 中重启 Ubuntu 实例

在 WSL2 中重启 Ubuntu 实例&#xff0c;可以按照以下步骤操作&#xff1a; 方法 1: 使用 wsl 命令 关闭 Ubuntu 实例: 打开 PowerShell 或命令提示符&#xff0c;运行以下命令&#xff1a; wsl --shutdown这会关闭所有 WSL2 实例。 重新启动 Ubuntu: 再次打开 Ubuntu&#x…...

【ts + java】古玩系统开发总结

src别名的配置 开发中文件和文件的关系会比较复杂&#xff0c;我们需要给src文件夹一个别名吧 vite.config.js import { defineConfig } from vite import vue from vitejs/plugin-vue import path from path// https://vitejs.dev/config/ export default defineConfig({pl…...

机器学习周报-文献阅读

文章目录 摘要Abstract 1 相关知识1.1 WDN建模1.2 掩码操作&#xff08;Masking Operation&#xff09; 2 论文内容2.1 WDN信息的数据处理2.2 使用所收集的数据构造模型2.2.1 Gated graph neural network2.2.2 Masking operation2.2.3 Training loss2.2.4 Evaluation metrics 2…...

LabVIEW微位移平台位移控制系统

本文介绍了基于LabVIEW的微位移平台位移控制系统的研究。通过设计一个闭环控制系统&#xff0c;针对微位移平台的通信驱动问题进行了解决&#xff0c;并提出了一种LabVIEW的应用方案&#xff0c;用于监控和控制微位移平台的位移&#xff0c;从而提高系统的精度和稳定性。 项目背…...

fpga系列 HDL:XILINX Vivado ILA FPGA 在线逻辑分析

ILA为内置逻辑分析仪&#xff0c;通过JTAG与FPGA连接&#xff0c;程序在真实硬件中运行&#xff0c;功能类似Quaruts的SignalTap II 。 ip创建ila 使用ila ip核 timescale 1ns / 1ps module HLSLED(input wire clk ,input wire rst_n ,output wire led);// reg led_o_i 1…...

刷题记录 贪心算法-2:455. 分发饼干

题目&#xff1a;455. 分发饼干 难度&#xff1a;简单 假设你是一位很棒的家长&#xff0c;想要给你的孩子们一些小饼干。但是&#xff0c;每个孩子最多只能给一块饼干。 对每个孩子 i&#xff0c;都有一个胃口值 g[i]&#xff0c;这是能让孩子们满足胃口的饼干的最小尺寸&a…...

Android --- CameraX讲解

预备知识 surface surfaceView SurfaceHolder surface 是什么&#xff1f; 一句话来说&#xff1a; surface是一块用于填充图像数据的内存。 surfaceView 是什么&#xff1f; 它是一个显示surface 的View。 在app中仍在 ViewHierachy 中&#xff0c;但在wms 中可以理解为…...