双向链表详解及C++实现

一、引言

链表是一种常见的数据结构,它允许动态地分配内存空间,并通过指针(或引用)将数据元素连接在一起。双向链表作为链表的一种,除了拥有普通链表的特性外,每个节点还包含两个指针:一个指向前一个节点(prev),另一个指向后一个节点(next)。这种结构使得双向链表在插入、删除以及遍历等操作上都具有独特的优势。

二、双向链表的基本结构

双向链表的基本结构通常包括一个节点(Node)类和一个双向链表(DoublyLinkedList)类。节点类用于存储数据元素以及指向前后节点的指针,而双向链表类则用于管理这些节点,提供插入、删除、遍历等操作的方法。

1. 节点类(Node)

节点类通常包含两个指针(prev和next)和一个数据元素(data)。在C++中,节点类可以定义如下:

template <typename T>
class Node {
public:T data;Node* prev;Node* next;Node(T data) : data(data), prev(nullptr), next(nullptr) {}
};

这里使用了模板类(template class),使得节点类可以存储任意类型的数据。

2. 双向链表类(DoublyLinkedList)

双向链表类需要包含一些基本操作,如插入节点、删除节点、遍历链表等。以下是一个简单的双向链表类的实现:

template <typename T>
class DoublyLinkedList {
private:Node<T>* head;  // 指向头节点的指针Node<T>* tail;  // 指向尾节点的指针int size;       // 链表的大小// 辅助函数,用于在指定位置插入节点void insertAt(int position, T data) {// ... 实现细节将在后文给出 ...}// 辅助函数,用于删除指定位置的节点void deleteAt(int position) {// ... 实现细节将在后文给出 ...}public:DoublyLinkedList() : head(nullptr), tail(nullptr), size(0) {}// 插入节点到链表末尾void append(T data) {insertAt(size, data);}// 插入节点到链表开头void prepend(T data) {insertAt(0, data);}// 删除指定位置的节点void removeAt(int position) {if (position < 0 || position >= size) {throw std::out_of_range("Invalid position");}deleteAt(position);}// 删除指定值的节点(如果存在多个,只删除第一个)void remove(T data) {Node<T>* current = head;while (current != nullptr) {if (current->data == data) {removeAt(currentIndex(current));return;}current = current->next;}}// 遍历链表并打印节点数据void printList() {Node<T>* current = head;while (current != nullptr) {std::cout << current->data << " ";current = current->next;}std::cout << std::endl;}// 获取链表大小int getSize() const {return size;}// 检查链表是否为空bool isEmpty() const {return size == 0;}// ... 其他可能的方法,如查找、反转等 ...
};

三、双向链表的操作实现

接下来,我们将详细实现双向链表类中的一些关键操作。

1. 插入节点(insertAt)

在指定位置插入节点时,我们需要考虑几种情况:插入到链表开头、插入到链表末尾以及插入到链表中间。以下是insertAt方法的实现:

template <typename T>
void DoublyLinkedList<T>::insertAt(int position, T data) {if (position < 0 || position > size) {throw std::out_of_range("Invalid position");}Node<T>* newNode = new Node<T>(data);if (size == 0) {  // 链表为空,新节点既是头节点也是尾节点head = tail = newNode;} else if (position == 0) {  // 插入到链表开头newNode->next = head;head->prev = newNode;head = newNode;} else if (position == size) {  // 插入到链表末尾newNode->prev = tail;tail->next = newNode;tail = newNode;} else {  // 插入到链表中间Node<T>* current = head;for (int i = 0; i < position - 1; ++i) {current = current->next;}newNode->prev = current;newNode->next = current->next;current->next->prev = newNode;current->next = newNode;}++size;
}

2. 删除节点(deleteAt)

删除指定位置的节点时,我们需要考虑删除头节点、删除尾节点以及删除中间节点的情况。以下是deleteAt方法的实现:

template <typename T>
void DoublyLinkedList<T>::deleteAt(int position) {if (position < 0 || position >= size) {throw std::out_of_range("Invalid position");}Node<T>* target = head;if (position == 0) {  // 删除头节点head = head->next;if (head != nullptr) {head->prev = nullptr;} else {tail = nullptr;  // 如果链表为空,则尾节点也为空}} else if (position == size - 1) {  // 删除尾节点target = tail;tail = tail->prev;if (tail != nullptr) {tail->next = nullptr;} else {head = nullptr;  // 如果链表为空,则头节点也为空}} else {  // 删除中间节点for (int i = 0; i < position; ++i) {target = target->next;}target->prev->next = target->next;target->next->prev = target->prev;}delete target;--size;
}

四、测试代码

下面是一个简单的测试程序,用于演示双向链表的用法:

#include <iostream>int main() {DoublyLinkedList<int> list;list.append(1);list.append(2);list.prepend(0);list.insertAt(1, 100);list.printList();  // 输出: 0 100 1 2std::cout << "Size: " << list.getSize() << std::endl;  // 输出: 4std::cout << "Is empty? " << (list.isEmpty() ? "Yes" : "No") << std::endl;  // 输出: Nolist.removeAt(1);  // 删除索引为1的节点(值为100)list.printList();  // 输出: 0 1 2list.remove(1);  // 删除值为1的节点list.printList();  // 输出: 0 2return 0;
}

五、总结

本文详细介绍了双向链表的基本结构、操作实现以及C++代码示例。双向链表通过引入两个指针(prev和next)来实现对前后节点的访问,从而在插入、删除和遍历等操作上提供了更高的灵活性。在实际应用中,双向链表常用于需要频繁进行节点插入和删除的场景,如LRU(最近最少使用)缓存淘汰算法等。通过掌握双向链表的基本原理和实现方法,读者可以更好地理解和应用这种数据结构。

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

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

相关文章

Leetcode 剑指 Offer II 082.组合总和 II

题目难度: 中等 原题链接 今天继续更新 Leetcode 的剑指 Offer&#xff08;专项突击版&#xff09;系列, 大家在公众号 算法精选 里回复 剑指offer2 就能看到该系列当前连载的所有文章了, 记得关注哦~ 题目描述 给定一个可能有重复数字的整数数组 candidates 和一个目标数 tar…

能耗监控与管理平台

在当今社会&#xff0c;随着工业化、城市化的快速发展&#xff0c;能源消耗问题日益凸显&#xff0c;节能减排已成为全社会共同关注的焦点。在这个背景下&#xff0c;一款高效、智能的能耗监控与管理平台显得尤为重要。 一、HiWoo Cloud平台的概念 HiWoo Cloud是一款集数据采…

六大维度全面焕新升级!麒麟信安服务器操作系统V3.6.1引领未来计算

昨日&#xff0c;openEuler 24.03 LTS 正式发布&#xff0c;麒麟信安作为openEuler社区重要贡献者和参与者&#xff0c;充分发挥自身在国产操作系统领域的技术优势&#xff0c;在打造安全可靠、极致体验的操作系统上与社区共同努力&#xff0c;同步推出服务器操作系统V3.6.1&am…

OpenGL3.3_C++_Windows(7)

演示 最终演示效果 ​​​​ 冯氏光照 光照原理&#xff1a;对于向量相乘默认为点乘&#xff0c;如果*lightColor(1.0f, 1.0f, 1.0f);白光&#xff0c;值不变物体的颜色显示原理&#xff1a;不被物体吸收的光反射&#xff0c;也就是由白光分解后的一部分&#xff0c;因此&…

【bugfix】解决Redis缓存键清理问题

前言 在Spring Boot应用中集成Redis作为缓存存储时&#xff0c;合理配置RedisTemplate是确保数据正确存储和检索的关键。本文将通过对比分析一段初始存在问题的Redis配置代码及其修正后的版本&#xff0c;探讨如何正确处理Redis键前缀&#xff0c;以避免清理缓存时遇到的问题。…

Cask ‘oraclexxx‘ is unavailable: No Cask with this name exists.

brew search oracle-jdk或brew search --cask oracle-jdk 原因&#xff1a;Homebrew官方仓库不再维护多个旧版本的OracleJDK 不推荐使用Homebrew环境安装JDK //指定版本安装 brew install --cask temurin17 //设置 JAVA_HOME 环境变量 //找到安装的JDK 版本的路径 /usr/lib…

探索测试分享

1. “器” 项目中的实践——我们是怎么做的 本章将带你身历其境的感受到思想和方法是如何具体使用在项目里的 1.如何挖掘探索性测试的探索点&#xff0c;在任何阶段都可以利用探索测试策略找到可探索的点&#xff0c;发现产品中的bug&#xff0c;或明显或隐含。 “器”的应用…

分布式管理

一、基本概念 分布式管理是指在一个由多个独立计算机节点组成的分布式系统中&#xff0c;通过对这些节点的资源、服务、数据进行统一的协调、控制和优化&#xff0c;以实现整个系统的高效、稳定、可靠运行。 二、核心原理 无中心化&#xff1a;分布式系统没有一个中心节点来…

ArcGIS JSAPI 高级教程 - ArcGIS Maps SDK for JavaScript - 锐化效果

ArcGIS JSAPI 高级教程 - ArcGIS Maps SDK for JavaScript - 锐化效果 核心代码完整代码在线示例ArcGIS Maps SDK for JavaScript 从 4.29 开始增加 RenderNode 类,可以添加数据以及操作 FBO(ManagedFBO); 通过操作 FBO,可以通过后处理实现很多效果,官方提供了几个示例,…

利用74HC165实现8路并行输入口的扩展

代码&#xff1a; #include <mega16.h>// Declare your global variables here #define hc165_clk PORTB.0 #define hc165_lp PORTB.1 #define hc165_out PINB.2unsigned char read_hc165(void) {unsigned char data0,i,temp0x80;hc165_lp0;hc165_lp1; for(i0;i<7;i)…

汇编:内联汇编和混合编程

C/C内联汇编 C/C 内联汇编&#xff08;Inline Assembly&#xff09;是一种在C或C代码中嵌入汇编语言指令的方法&#xff0c;以便在不离开C/C环境的情况下利用汇编语言的优势进行性能优化或执行特定的硬件操作。以下是一些详细的说明和示例&#xff0c;展示如何在C和C代码中使用…

c++ 中 namespace包的 全局变量 c++ 中 static 全局变量 会给初值吗

在 C 中&#xff0c;命名空间&#xff08;namespace&#xff09;中的全局变量会被自动初始化为零值&#xff08;zero-initialized&#xff09;&#xff0c;除非显式地为其指定初始值。与静态全局变量类似&#xff0c;命名空间中的全局变量在程序启动时会被初始化&#xff0c;其…

zookeeper介绍 和 编译踩坑

zookeeper 分布式协调服务 ZooKeeper原理及介绍 - 鹿泉 - 博客园 Zookeeper是在分布式环境中应用非常广泛&#xff0c;它的优秀功能很多&#xff0c;比如分布式环境中全局命名服务&#xff0c;服务注册中心&#xff0c;全局分布式锁等等。 本项目使用其分布式服务配置中心&am…

Java:访问权限修饰符

文章目录 一、访问权限修饰符二、权限修饰符的分类 一、访问权限修饰符 访问权限修饰符&#xff0c;就是控制类中的属性和方法的被访问范围。 二、权限修饰符的分类 作用范围&#xff1a;private < 空着不写 < protected < public 修饰符同类同包不同类不同包下子类…

Minecraft模组开发(fabric)之准备工作

Minecraft模组开发&#xff08;fabric&#xff09;之准备工作 最近心血来潮想开发个Minecraft的模组&#xff0c;一边学习一边开发&#xff0c;顺带着将一些步骤、学习心得整理下来。之所以选择fabric&#xff0c;是因为自己的光影包使用的是iris-fabric&#xff0c;所以就想着…

深度学习-使用 Bash 脚本

在深度学习领域&#xff0c;Bash 脚本通常用于管理和自动化训练模型、数据预处理、后处理以及环境设置等任务。虽然 Bash 脚本本身并不直接参与深度学习的计算&#xff0c;但它在简化工作流程和提高效率方面扮演着重要角色。以下是一些使用 Bash 脚本的理由&#xff1a; 自动化…

Vue41-vc实例与vm实例

一、 vc实例与vm实例的区别 vc实例与vm实例&#xff0c;99%结构都是类似的&#xff0c;仅2点不同&#xff1a; el属性data的书写格式 1-1、 el属性 vc有的功能vm都有&#xff0c;但是vm能通过el决定为哪个容器服务&#xff0c;但是vc却不行&#xff01; 1-2、data的书写格式

【PythonCode】力扣Leetcode26~30题Python版

【PythonCode】力扣Leetcode26~30题Python版 前言 力扣Leetcode是一个集学习、刷题、竞赛等功能于一体的编程学习平台&#xff0c;很多计算机相关专业的学生、编程自学者、IT从业者在上面学习和刷题。 在Leetcode上刷题&#xff0c;可以选择各种主流的编程语言&#xff0c;如C…

XML 编辑器:功能、选择与使用技巧

XML 编辑器&#xff1a;功能、选择与使用技巧 简介 XML&#xff08;可扩展标记语言&#xff09;是一种用于存储和传输数据的标记语言。由于其灵活性和广泛的应用&#xff0c;XML编辑器成为开发者、数据管理者和内容创作者的重要工具。本文将探讨XML编辑器的功能、选择标准以及…

python,django好的get和post请求

获得get请求 df request.GET.get("dades")获得post请求 文件settings.py关闭csrf MIDDLEWARE [ ‘django.middleware.security.SecurityMiddleware’, ‘django.contrib.sessions.middleware.SessionMiddleware’, ‘django.middleware.common.CommonMiddleware’…