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日

相关文章

  • win10系统右键菜单项里没有“打开方式”选项的解决方法

    下面是详细的攻略。 问题描述 在win10系统中,右键菜单项里没有“打开方式”选项,导致无法通过该选项来选择打开文件的方式,特别是针对不同类型的文件。这可能会导致一些文件无法打开或者打开方式不正确,影响使用体验。 解决方法 方法一:修改注册表 打开注册表编辑器:按下Win+R组合键打开“运行”窗口,输入“regedit”并点击“确定”按钮。 进入注册表项:在…

    other 2023年6月27日
    00
  • Go语言基础变量的声明及初始化示例详解

    Go语言基础变量的声明及初始化示例详解 在Go语言中,变量是程序中最基础的元素之一,声明和初始化变量是编写任何程序时必不可少的步骤。本文将详细介绍Go语言中基础变量的声明和初始化方法,包含示例说明以帮助您更好地理解。 基础变量类型 在Go语言中,基础变量类型包括以下几种: 整型:int、int8、int16、int32、int64、uint、uint8、ui…

    other 2023年6月20日
    00
  • cod是什么意思?

    COD 是 Call of Duty (使命召唤)视频游戏系列的缩写,是一款著名的射击类游戏。 在游戏界和游戏玩家之间,COD 是一个非常常用的术语。玩家经常用它来讨论这款游戏,或者在社交媒体上分享他们玩这款游戏的经验。 一些示例: 1. COD 游戏系列 COD 是 Call of Duty 游戏系列的缩写。这个游戏系列从 2003 年以来一直存在,每年都…

    其他 2023年4月16日
    00
  • sqlserver中含有某字符串

    当然,我很乐意为您提供有关“SQL Server中含有某字符串”的完整攻略。以下是详细的步骤和两个示例: 1 SQL Server中含有某字符串的方法 在SQL Server中,您可以使用LIKE运算符和通配符来查找含某个字符串的值。LIKE运算符用于比较一个字符串与另一个字符串是否相似。通配符用于匹配一个字符串中的任字符。 以下是使用LIKE运算符和通配符…

    other 2023年5月6日
    00
  • vantdialog弹出框

    以下是“vant-dialog弹出框”的完整攻略: vant-dialog弹出框 vant-dialog是Vant组件库中的一个弹出框组件,可以用于在页面中弹出对话框,提示用户进行或展示信息。本攻略将详细讲解vant-dialog的使用方法,包括基本用法、API参数和示例说明等。 基本用法 vant-dialog的基本用法非常简单,只需要在Vue组件中引入v…

    other 2023年5月8日
    00
  • mysql中的join和where优先级顺序解读

    MySQL中的JOIN和WHERE优先级顺序解读 在MySQL中,使用JOIN关键字可以将多个表连接起来,而WHERE子句被用来过滤结果集。在使用JOIN和WHERE的时候,需要了解它们的优先级顺序,以确保查询语句能够得到正确的结果。 JOIN和WHERE的优先级 在MySQL中,JOIN的优先级高于WHERE,这意味着查询语句中的JOIN操作会先执行,然后…

    other 2023年6月28日
    00
  • Golang递归获取目录下所有文件方法实例

    Golang递归获取目录下所有文件方法实例 在Golang中要递归获取目录下所有文件,可以很方便地通过标准库中的filepath.Walk函数来实现,下面将详细讲解这个过程。 1. 使用filepath.Walk函数 filepath.Walk函数的定义如下: func Walk(root string, walkFn WalkFunc) error roo…

    other 2023年6月27日
    00
  • 易语言制作调试助手

    易语言制作调试助手攻略 简介 在本攻略中,我们将使用易语言制作一个调试助手。调试助手可以帮助程序员在开发过程中进行调试和测试,提高开发效率。我们将使用易语言的基本语法和功能来实现这个调试助手。 步骤 步骤一:创建主界面 打开易语言开发环境,创建一个新项目。 在主界面上添加一个文本框和一个按钮,用于输入和执行调试命令。 示例代码: // 创建主界面 Form …

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