【数据结构】双向链表(Doubly Linked List)

news/2024/10/6 18:18:52 标签: 数据结构, 链表

双向链表(Doubly Linked List)是一种链式数据结构,它的每个节点都包含三个部分:数据、指向前一个节点的指针(prev),以及指向下一个节点的指针(next)。与单向链表不同,双向链表允许从任意节点向前或向后遍历,提供了更灵活的操作方式。

双向链表的结构

双向链表的每个节点都有以下三个部分:

  1. 数据部分:存储节点中的实际数据。
  2. 前向指针 (prev):指向当前节点的前一个节点,头节点的 prevnullptr
  3. 后向指针 (next):指向当前节点的下一个节点,尾节点的 nextnullptr

这种双指针的结构允许我们高效地在链表中进行插入、删除等操作。

双向链表的操作

  1. 插入操作:在双向链表中插入一个新节点时,需要更新前后两个节点的指针,因此插入操作比单向链表略复杂,但仍然能在 O(1) 时间内完成(假设已经找到插入点)。
  2. 删除操作:删除一个节点时,需要修改前后节点的指针,也需要处理边界条件,如删除头节点或尾节点。
  3. 遍历操作:可以从任意节点开始,向前或向后遍历链表

C++ 实现

下面是一个简单的 C++ 双向链表实现,包含插入、删除、遍历等常用操作。

#include <iostream>

struct Node {
    int data;
    Node* prev;
    Node* next;

    // 构造函数
    Node(int value) : data(value), prev(nullptr), next(nullptr) {}
};

class DoublyLinkedList {
private:
    Node* head;

public:
    // 构造函数初始化空链表
    DoublyLinkedList() : head(nullptr) {}

    // 在链表头插入新节点
    void insertAtHead(int value) {
        Node* newNode = new Node(value);
        if (head != nullptr) {
            newNode->next = head;
            head->prev = newNode;
        }
        head = newNode;
    }

    // 在链表尾插入新节点
    void insertAtTail(int value) {
        Node* newNode = new Node(value);
        if (head == nullptr) {
            head = newNode;
            return;
        }
        Node* temp = head;
        while (temp->next != nullptr) {
            temp = temp->next;
        }
        temp->next = newNode;
        newNode->prev = temp;
    }

    // 删除指定值的节点
    void deleteNode(int value) {
        Node* temp = head;
        while (temp != nullptr && temp->data != value) {
            temp = temp->next;
        }
        if (temp == nullptr) {
            std::cout << "Node with value " << value << " not found.\n";
            return;
        }
        if (temp->prev != nullptr) {
            temp->prev->next = temp->next;
        } else {
            head = temp->next;
        }
        if (temp->next != nullptr) {
            temp->next->prev = temp->prev;
        }
        delete temp;
    }

    // 正向遍历链表
    void traverseForward() {
        Node* temp = head;
        while (temp != nullptr) {
            std::cout << temp->data << " ";
            temp = temp->next;
        }
        std::cout << std::endl;
    }

    // 反向遍历链表
    void traverseBackward() {
        if (head == nullptr) return;
        Node* temp = head;
        while (temp->next != nullptr) {
            temp = temp->next;
        }
        while (temp != nullptr) {
            std::cout << temp->data << " ";
            temp = temp->prev;
        }
        std::cout << std::endl;
    }

    // 析构函数:释放内存
    ~DoublyLinkedList() {
        Node* temp = head;
        while (temp != nullptr) {
            Node* next = temp->next;
            delete temp;
            temp = next;
        }
    }
};

int main() {
    DoublyLinkedList dll;
    dll.insertAtHead(10);
    dll.insertAtHead(20);
    dll.insertAtTail(30);
    dll.insertAtTail(40);

    std::cout << "Forward traversal: ";
    dll.traverseForward(); // 输出: 20 10 30 40

    std::cout << "Backward traversal: ";
    dll.traverseBackward(); // 输出: 40 30 10 20

    dll.deleteNode(10);
    std::cout << "After deleting 10, forward traversal: ";
    dll.traverseForward(); // 输出: 20 30 40

    return 0;
}

代码说明

  1. Node 结构体:每个节点包含 dataprevnext 三个成员变量,用于存储节点的数据和链表的双向连接。
  2. DoublyLinkedList
    • insertAtHead:在链表头插入节点,注意要更新新头节点和原头节点的指针。
    • insertAtTail:遍历链表到末尾,然后在尾部插入节点。
    • deleteNode:找到目标节点,修改前后节点的指针,使链表跳过该节点。
    • traverseForwardtraverseBackward:分别用于正向和反向遍历链表
  3. 内存管理链表析构函数会遍历链表并释放所有节点,避免内存泄漏。

应用场景

双向链表广泛应用于需要双向遍历或频繁插入、删除操作的场景,比如:

  • 浏览器历史记录:允许用户前进和后退浏览网页。
  • 音乐播放列表:可以向前或向后切换歌曲。
  • 内存管理:操作系统的内存块分配常常使用双向链表进行管理。

通过使用双向链表,可以提高程序处理数据的灵活性和效率。在 C++ 中实现双向链表,既考验了对指针操作的掌握,也有助于理解动态数据结构的原理。


http://www.niftyadmin.cn/n/5691979.html

相关文章

手机sd卡数据被清空怎么恢复原状?高效、可行的恢复策略

在数字化时代&#xff0c;手机SD卡作为我们存储重要数据的“数字仓库”&#xff0c;其安全性与稳定性直接关系到我们日常生活的便捷与信息安全。然而&#xff0c;不慎操作或系统故障导致的SD卡数据清空&#xff0c;常常让人措手不及&#xff0c;焦虑万分。面对这一挑战&#xf…

C语言 | Leetcode C语言题解之第456题132模式

题目&#xff1a; 题解&#xff1a; int upper_bound(int* vec, int vecSize, int target) {int low 0, high vecSize - 1;if (vec[high] > target) {return -1;}while (low < high) {int mid (high - low) / 2 low;int num vec[mid];if (num > target) {low m…

github项目——系统设计入门

今天的github趋势&#xff0c;有几个项目印象感觉很有意思&#xff0c;之后可能会用的上&#xff0c;记录一下 系统设计入门 书籍教程类项目&#xff0c;有中文文档&#xff0c;刚好需要。 https://github.com/donnemartin/system-design-primer/blob/master/README-zh-Hans.md…

ThreadLocal、InheritableThreadLocal、TransmittableThreadLocal原理及Demo

1.ThreadLocal 1.1 原理 1.2 Demo 1.3 应用场景 2.InheritableThreadLocal 2.1 原理 2.2 Demo 2.3 应用场景 3.TransmittableThreadLocal 3.1 原理 3.2 Demo 3.3应用场景 1.ThreadLocal 1.1 原理 造成ThreadLocal内存泄露的主要原因是&#xff1a; key是弱引用&…

【Taro】做项目过程中的一些问题记录

待更新~ React is declared but its value is never read. taro 中 &#xff0c;eslint 中使用 import React from “react”; 报错&#xff1a; React is declared but its value is never read. 解决办法&#xff1a; tsconfig 中 改为&#xff1a; {"compilerOptions…

jvisualvm学习

系列文章目录 JavaSE基础知识、数据类型学习万年历项目代码逻辑训练习题代码逻辑训练习题方法、数组学习图书管理系统项目面向对象编程&#xff1a;封装、继承、多态学习封装继承多态习题常用类、包装类、异常处理机制学习集合学习IO流、多线程学习仓库管理系统JavaSE项目员工…

《Windows PE》4.1导入表

导入表顾名思义&#xff0c;就是记录外部导入函数信息的表。这些信息包括外部导入函数的序号、名称、地址和所属的DLL动态链接库的名称。Windows程序中使用的所有API接口函数都是从系统DLL中调用的。当然也可能是自定义的DLL动态链接库。对于调用方&#xff0c;我们称之为导入函…

无需VPN!大厂力作:免费AI对口型神器登场,让你的视频制作更简单!

大家好&#xff0c;我是Shelly&#xff0c;一个专注于输出AI工具和科技前沿内容的AI应用教练&#xff0c;体验过300款以上的AI应用工具。关注科技及大模型领域对社会的影响10年。关注我一起驾驭AI工具&#xff0c;拥抱AI时代的到来。 &#xff08;偶尔会因为推荐工具&#xff…