C++ 双向循环链表类模版实例详解

C++ 双向循环链表类模版实例详解

什么是双向循环链表?

双向循环链表(Doubly Linked Loop)是一种链式数据结构。相比于单向链表,它可以在两个方向上遍历,每个节点不仅保存了下一个节点的指针,还保存了上一个节点的指针。双向循环链表具有以下特点:

  • 双向循环链表的首尾节点连接起来,没有 NULL/None 节点。
  • 节点保存了指向上一节点和下一节点的指针,可以双向遍历。
  • 可以方便地实现插入、删除操作,但访问某个节点的时间复杂度依然是 O(n)。

如何实现 C++ 双向循环链表类模版?

下面是一个简单的 C++ 双向循环链表类模版实现。代码里有详细的注释,以及注明需要自己实现的方法。

template <typename T>
class DoublyLinkedList
{
private:
    struct Node
    {
        T data;            // 保存节点的数据
        Node *prev, *next; // 指向前一个节点和后一个节点的指针
        Node(T d = T(), Node *p = nullptr, Node *n = nullptr)
            : data(d), prev(p), next(n) {}
    };

    Node *head; // 链表头

public:
    // 构造函数,初始化链表头
    DoublyLinkedList() : head(nullptr)
    {
        // TODO: 实现构造函数
    }

    // 析构函数,删除链表所有节点
    ~DoublyLinkedList()
    {
        // TODO: 实现析构函数
    }

    // 获取链表的长度
    int size() const
    {
        // TODO: 实现 size 函数
        return 0;
    }

    // 在链表头部插入节点
    void insert_front(const T &d)
    {
        // TODO: 实现 insert_front 函数
    }

    // 在链表尾部插入节点
    void insert_back(const T &d)
    {
        // TODO: 实现 insert_back 函数
    }

    // 从链表头部删除节点
    void remove_front()
    {
        // TODO: 实现 remove_front 函数
    }

    // 从链表尾部删除节点
    void remove_back()
    {
        // TODO: 实现 remove_back 函数
    }

    // 打印整个链表
    void print() const
    {
        // TODO: 实现 print 函数
    }
};

实例说明一:使用双向循环链表实现 LRU 缓存

以下是使用上述双向循环链表类模版实现 LRU 缓存的完整代码示例。具体思路是用双向循环链表来维护缓存中各个元素的访问顺序,每次访问需要先找到元素到双向循环链表上的对应节点,然后将该节点移到链表头部,这样链表队尾的节点就是 LRU 元素,每次需要淘汰的就是队尾节点。

template <typename K, typename V>
class LRUCache
{
public:
    typedef std::pair<K, V> CacheEntry;

private:
    DoublyLinkedList<CacheEntry> cache_;
    std::unordered_map<K, typename DoublyLinkedList<CacheEntry>::Node *> map_;
    int capacity_;

    // 将元素移到链表头部
    void move_to_front(typename DoublyLinkedList<CacheEntry>::Node *node)
    {
        // 如果节点已经是头部,无需移动
        if (node == cache_.head)
            return;

        // 将节点从链表中摘除
        node->prev->next = node->next;
        node->next->prev = node->prev;

        // 将节点插入到链表头部
        node->next = cache_.head;
        node->prev = cache_.head->prev;
        node->prev->next = node;
        node->next->prev = node;
    }

public:
    LRUCache(int cap) : capacity_(cap) {}

    // 获取缓存元素的值
    V get(const K &key)
    {
        // 如果在 map_ 中没有找到对应节点,则返回空
        if (map_.find(key) == map_.end())
            return V();

        // 将对应节点移到链表头部,并返回节点的 value
        auto node = map_[key];
        move_to_front(node);
        return node->data.second;
    }

    // 插入(或更新)缓存元素
    void put(const K &key, const V &value)
    {
        // 如果元素已经存在,则先更新 value,然后将节点移到链表头部
        if (map_.find(key) != map_.end())
        {
            auto node = map_[key];
            node->data.second = value;
            move_to_front(node);
            return;
        }

        // 如果元素不存在,插入(或更新)到 map_ 和链表头部
        cache_.insert_front(std::make_pair(key, value));
        map_[key] = cache_.head;

        // 如果插入后缓存大小超过 capacity_,则淘汰队尾元素
        if (cache_.size() > capacity_)
        {
            auto node = cache_.head->prev;
            map_.erase(node->data.first);
            cache_.remove_back();
        }
    }

    // 打印缓存元素
    void print() const
    {
        cache_.print();
    }
};

实例说明二:使用双向循环链表实现高精度加法

以下是使用上述双向循环链表类模版实现高精度加法的完整代码示例。具体思路是将两个大整数的每一位按从低到高的顺序相加,在每一位相加后确定当前位的进位和留位。

template <int BASE>
class BigNumber
{
public:
    using Digit = unsigned char;
    using Node = typename DoublyLinkedList<Digit>::Node;

private:
    DoublyLinkedList<Digit> digits_;

public:
    BigNumber() { digits_.insert_front(0); }

    BigNumber(const BigNumber &rhs) { digits_ = rhs.digits_; }

    ~BigNumber() {}

    BigNumber operator+(const BigNumber &rhs) const
    {
        BigNumber res;
        Node *n1 = digits_.head->next, *n2 = rhs.digits_.head->next, *nr = res.digits_.head;

        int carry = 0;
        while (n1 || n2)
        {
            // 取出当前位
            Digit d1 = n1 ? n1->data : 0, d2 = n2 ? n2->data : 0;

            // 计算当前位的和以及新的进位
            Digit sum = d1 + d2 + carry;
            carry = sum / BASE;
            sum %= BASE;

            // 新建一个节点保存当前位的值,插入到 res 中
            nr = res.digits_.insert_back(sum);
            if (n1)
                n1 = n1->next;
            if (n2)
                n2 = n2->next;
        }

        // 如果还有进位,新建一个节点保存进位的值,插入到 res 中
        if (carry)
            res.digits_.insert_back(carry);

        return res;
    }

    void print() const
    {
        Node *n = digits_.head->prev;
        while (n && n != digits_.head)
        {
            std::cout << static_cast<int>(n->data);
            n = n->prev;
        }
        std::cout << std::endl;
    }
};

本站文章如无特殊说明,均为本站原创,如若转载,请注明出处:C++ 双向循环链表类模版实例详解 - Python技术站

(0)
上一篇 2023年6月27日
下一篇 2023年6月27日

相关文章

  • JavaScript基础心法 数据类型

    JavaScript基础心法:数据类型 JavaScript是一种动态弱类型语言,变量的类型会根据赋值自动推导,因此了解JavaScript中的数据类型是编写高质量代码的基础。本文将详细介绍JavaScript中的数据类型,以及常用操作。 数据类型 JavaScript中的数据类型可分为两大类:原始类型和对象类型。 原始类型 原始类型包括字符串、数字、布尔值…

    other 2023年6月27日
    00
  • JavaSE基础篇—MySQL三大范式—数据库设计规范

    JavaSE基础篇—MySQL三大范式—数据库设计规范 MySQL是常见的关系数据库管理系统,是一种常用的数据库语言。而无论在何种情况下,一个优秀的数据库设计规范都是不可或缺的。本文将解析MySQL三大范式,为你提供一份可靠的数据库设计规范。 什么是MySQL三大范式 MySQL三大范式是关系数据库中的基本规则,确保数据库表的行动规范。据说,这些范式存在是为…

    其他 2023年3月28日
    00
  • 白夜追凶一家五口谁杀的

    白夜追凶一家五口谁杀的 最近在网上火爆一部国产剧《白夜追凶》,故事情节紧凑,悬疑丛生,随着剧情发展,一个家庭惨案的真相浮出水面,“五口之家”的死因,嫌疑人纷至沓来,真正的凶手究竟是谁? 具体情景 “五口之家”住在高档小区中一处高层公寓,一天晚上,他们中的四口发生了离奇死亡,死因各异,而最后仅有的一个幸存者——临时回家的女儿,成为了所有人仅有的希望,在公安机关…

    其他 2023年3月29日
    00
  • 分析Windows和Linux动态库

    下面就为您提供完整的“分析Windows和Linux动态库”的攻略。 一、动态库介绍 动态库,也称为共享库,是一种可重用的代码库,里面包含多个函数或类等。动态库与静态库的不同在于,静态库连接到编译后的程序中,而动态库则在程序运行时加载。动态库可以被多个程序共享,可以节省内存,也方便应用程序更新。动态库的后缀通常为.so(在Linux中)或.dll(在Wind…

    other 2023年6月26日
    00
  • ASP.NET MVC下基于异常处理的完整解决方案总结

    ASP.NET MVC是一款优秀的Web开发框架,异常处理是网站开发中一个重要的环节,本文将详细讲解基于异常处理的完整解决方案。 异常处理的必要性 异常指的是程序在运行期间发生的错误,例如数据验证失败、业务逻辑错误等。如果不对异常进行处理,就会导致网站出现意外的错误、崩溃等问题。因此,异常处理是网站开发中不可忽视的环节。 异常处理的解决方案 异常处理的解决方…

    other 2023年6月26日
    00
  • vi中全选的命令或者快捷方式

    以下是关于在Vi中全选的命令或者快捷方式的完整攻略,包括定义、使用方法、示例说明和注意事项。 定义 Vi是一种文本编辑器,常用于Linux和Unix系统中。在Vi中,全是指选中整个文本内容。Vi中全选的命令或快捷方式可以帮助用户快速选中整个文本内容。 使用方法 是在Vi中全选的命令或快捷方式的方法: 进入Vi编辑器。 按下Esc键,确保处于令模式。 输入以下…

    other 2023年5月8日
    00
  • rest和restful以及它们之间的区别

    REST和RESTful以及它们之间的区别 REST和RESTful是Web服务中常用的两个术语,它们之间有一定的区别。本文将详细讲解REST和RESTful的概念、特点以及它们之间的区别,并提供两个示例说明。 REST的概念和特点 REST(Representational State Transfer)是一种基于HTTP协议的Web服务架构风格。它一种轻…

    other 2023年5月8日
    00
  • Intellij IDEA如何修改配置文件位置

    当我们在使用IntelliJ IDEA开发项目时,可能需要修改一些配置文件的位置,以便更好地适应项目的需求。下面就来详细讲解如何修改IntelliJ IDEA的配置文件位置。 1. 修改配置文件位置的前提条件 在修改IntelliJ IDEA的配置文件位置前,需要确保已经安装好了IntelliJ IDEA,并且熟悉基本的使用方法。同时,需要对配置文件的内容和…

    other 2023年6月25日
    00
合作推广
合作推广
分享本页
返回顶部