【C++】优先级队列(容器适配器)

简介: 本文介绍了C++ STL中的线性容器及其适配器,包括栈、队列和优先队列的设计与实现。详细解析了`deque`的特点和存储结构,以及如何利用`deque`实现栈、队列和优先队列。通过自定义命名空间和类模板,展示了如何模拟实现这些容器适配器,重点讲解了优先队列的内部机制,如堆的构建与维护方法。

前言

string vector list 这种线性结构是最基础的存储结构,C++(STL)container很好的帮助我们数据存储的问题。

容器适配器

介绍

  • 容器适配器是C++标准模板库(STL)中的一种设计模式,它允许将一个容器的接口转换为另一个接口,从而提供不同的操作和行为。
  • 容器适配器通常用于封装现有容器,以实现特定的数据结构特性,如栈(后进先出)、队列(先进先出)和优先队列(根据优先级排序)。

应用

  • 栈(stack):栈是一种后进先出的数据结构,其操作包括入栈(push)、出栈(pop)、查看栈顶元素(top)等。栈适配器可以基于多种底层容器实现,如vectordequelist.

  • 队列(queue):队列是一种先进先出的数据结构,其操作包括入队(push)、出队(pop)、查看队首元素(front)和查看队尾元素(back)。队列适配器同样可以基于dequelist实现,以适应不同的性能需求.

  • 优先队列(priority_queue):优先队列是一种特殊的队列,它根据元素的优先级进行排序。其底层容器通常是vectordeque,并通过堆算法维护元素的优先级顺序。优先队列适配器提供了插入和删除具有最高优先级元素的操作.

双重结束队列(双端队列(deque))

特点

  • 双端操作效率:支持在两端进行快速的插入和删除操作。
  • 随机访问:可以通过索引直接访问容器中的元素。
  • 无需预先分配固定大小:与vector不同,deque不需要在创建时指定大小,它可以根据需要动态增长。
  • 内存分配策略deque不需要像vector那样一次性分配大量内存,而是分散在内存中,这有助于减少内存碎片。

存储结构

双端队列底层是一段假象的连续空间,实际是分段连续的,为了维护其“整体连续”以及随机访问的假象,落 在了deque的迭代器身上,因此deque的迭代器设计就比较复杂,如下图所示:

Listvector deque对比

对比维度 Vector Deque List
内存连续性
随机访问性能 O(1) O(1) 但可能不如Vector O(n)
插入/删除性能 非末尾O(n) 两端O(1), 中间O(n) 两端及中间O(1)
内存重用效率 扩容时需移动元素 两端添加删除不需移动 不适用
内存分配模式 动态数组,连续内存 分段连续内存 非连续内存
迭代器失效 可能 不会 不会
支持的操作 [] 访问、.at() 等 [] 访问、.at() 等 [] 访问、.at() 等
内存管理开销 高(扩容时) 中等(两端操作)
适用场景 需要快速随机访问且元素数量稳定 需要两端快速插入删除,随机访问需求适中 频繁插入删除,不关心随机访问

栈(stack)

栈的介绍

函数说明 接口说明
stack() 构造空的栈
empty() 检测stack是否为空
size() 返回stack中元素的个数
top() 返回栈顶元素的引用
push() 将元素val压入stack中
pop() 将stack中尾部的元素弹出

栈的模拟实现

利用容器适配器的设计原理,很容易实现

  • 将栈放mystack的命名空间,以防止和库中冲突
  • 类模板设计container可以给缺省参数,默认deque(容器适配器)
  • 在里面利用deque的接口实现
namespace mystack
{
   
    template<class T, class Container = std::deque<T>>
    class stack
    {
   
    public:

        void push_back(const T& x)
        {
   
            _con.push_back(x);
        }
        void pop()
        {
   
            _con.pop_back();
        }
        size_t size()
        {
   
            return _con.size();
        }
        T& top()
        {
   
            return _con.back();
        }

        bool empty()
        {
   
            return _con.empty();
        }
    private:

        Container _con;
    };
}

队列

队列介绍

函数声明 接口说明
queue() 构造空的队列
empty() 检测队列是否为空,是返回true,否则返回false
size() 返回队列中有效元素的个数
front() 返回队头元素的引用
back() 返回队尾元素的引用
push() 在队尾将元素val入队列
pop() 将队头元素出队列

队列模拟实现

  • 将栈放myqueue的命名空间,以防止和库中冲突
  • 类模板设计container可以给缺省参数,默认deque(容器适配器)
  • 在里面利用deque的接口实现
namespace myqueue
{
   
    template<class T, class Container = std::deque<T >>
    class queue
    {
   
    public:
        void push(const T& x)
        {
   
            _con.push_back(x);
        }
        void pop()
        {
   
            _con.pop_front();
        }
        size_t size()
        {
   
            return _con.size();
        }
        T& front()
        {
   
            return _con.front();
        }

        bool empty()
        {
   
            return _con.empty();
        }
    private:

        Container _con;
    };
}

优先级队列(priority_queue)

基本原理

  • 优先级队列通常在内部使用堆数据结构来维护元素的优先级。
  • 堆是一种完全二叉树,可以是最大堆或最小堆。
  • 在最大堆中,父节点的值总是大于或等于其子节点的值,而在最小堆中,父节点的值总是小于或等于其子节点的值。
  • 插入操作通过在堆的适当位置插入新元素并进行上调整(heapify-up)来维持堆的性质。
  • 删除操作则涉及到移除堆顶元素(优先级最高的元素)并进行下调整(heapify-down),以恢复堆的结构。

priority_queue介绍

函数声明 接口说明
priority_queue()/priority_queue(first, last) 构造一个空的优先级队列
empty( ) 检测优先级队列是否为空,是返回true,否则返回 false
top( ) 返回优先级队列中最大(最小元素),即堆顶元素
push(x) 在优先级队列中插入元素x
pop() 删除优先级队列中最大(最小)元素,即堆顶元素

优先级模拟实现

仿函数

  • 仿函数(Functor)是C++中的一个编程概念,它指的是一个类或结构体,通过重载函数调用运算符operator(),使得这个类或结构体的对象可以像函数一样被调用。
  • 仿函数可以包含状态,因为它们是对象,可以在构造函数中初始化状态,并在operator()中使用该状态。
  • 仿函数可以作为参数传递给其他函数,包括STL算法中的函数,从而提供灵活的编程模型.

==这个就是一个仿函数==

//小于
template<class T>
    class Less
    {
   
    public:
        bool operator()(const T& a,const T& b)
        {
   
            return a < b;
        }
    };
//大于
template<class T>
    class Greater
    {
   
    public:
        bool operator()(const T& x, const T& y)
        {
   
            return x > y;
        }
    };

priority_queue两个关键

向下建堆

  • 确定起始点:从最后一个非叶子节点开始向下建堆,这个节点也被称为堆的最后一个非叶子节点。在完全二叉树中,最后一个非叶子节点的索引可以通过 (n - 1 - 1) / 2 计算得到,其中 n 是数组的长度。
  • 执行向下调整:对每个非叶子节点执行向下调整操作,确保该节点与其子节点组成的子树满足堆的性质。向下调整的过程涉及到与子节点的比较和必要时的交换,直至到达堆的顶部或直到父节点不再违反堆的性质。
  • 迭代过程:从最后一个非叶子节点开始,逐步向上调整,直到根节点。每次调整后,更新当前节点的索引,以便进行下一次调整。
  • 完成建堆:重复步骤2和步骤3,直到根节点也满足堆的性质,此时整个数组就构建成了一个堆。
void AdjustDown(size_t parent)
{
   
    compare com;//仿函数

    size_t child = parent * 2 + 1;
    //if (child+1< _con.size() && _con[child] < _con[1+child])
    if (child + 1 < _con.size() && com(_con[child] ,_con[1 + child]))//和上面等价

    {
   
        ++child;
    }
    while (child <_con.size())
    {
   
        if (com(_con[parent], _con[child]))
        {
   
            std::swap(_con[child], _con[parent]);
            parent = child;
            child = parent * 2 + 1;
        }
        else
        {
   
            break;
        }
    }
}

向上建堆

  • 初始化堆大小**:设置堆的大小为数组的大小,即 n
  • 从最后一个非叶子节点开始向上调整:在完全二叉树中,最后一个非叶子节点的索引为 floor((n - 1) / 2)。从这个节点开始向上调整,确保每个节点都满足大根堆的性质。
  • 执行向上调整操作:对于每个非叶子节点,检查其与子节点的关系,并进行必要的交换,以确保父节点的值大于或等于其子节点的值。如果子节点中有一个或两个,选择较大的子节点与父节点进行比较。如果父节点的值小于子节点的值,交换它们的位置,并重新设置父节点为当前子节点,继续向上调整。
  • 重复步骤2和3:直到达到根节点,即堆的第一个元素。
void AdjustUp(int child)
{
   
    compare com;

    int parent = (child - 1) / 2;
    while (child > 0)
    {
   
        if (com(_con[parent] , _con[child]))
        {
   
            std::swap(_con[parent], _con[child]);
            child = parent;
            parent = (child - 1) / 2;
        }
        else
        {
   
            break;
        }
    }

}

初始化数据

迭代器初始化

  • 模板嵌套给,迭代器初始化

  • 依次push数据,在进行堆的建立

template<class InputIterator>
void push_back(InputIterator first, InputIterator last)
    {
   
        while (first != last)
        {
   
            _con.push_back(*first);
            ++first;
        }

        //向下建堆
        for (int i = (_con.size() - 2) / 2; i >= 0; i--)
        {
   
            AdjustDown(i);
        }
    }

pop数据

  • 将一个个数据和最后一个数据进行交换(目的:保持当前堆的结构)
  • pop出数据,将第一个数据进行向下调整
void pop()
{
   
    swap(_con[0], _con[_con.size() - 1]);

    _con.pop_back();

    AdjustDown(0);
}

push数据

  • 将数据进行尾插入,进行==向上调整==
void push(const T& x)
{
   
    _con.push_back(x);

    AdjustUp(_con.size() - 1);
}

priority_queue operators

  • top数据,返回首个数据;
  • 其他常见操作,采取容器适配器设计模式的操作
namespace mypriority_queue
{
   
    template<class T, class container = std::vector<T>,class compare = Less<T>>

    class priority_queue
    {
   
    public:
        const T& top()
        {
   
            return _con[0];
        }
        size_t szie()
        {
   
            return _con.size();
        }

        bool empty()
        {
   
            return _con.empty();
        }

    private:
        container _con;
    };

}

源码(优先级队列)

namespace mypriority_queue
{
   
    template<class T>
    class Less
    {
   
    public:
        bool operator()(const T& a,const T& b)
        {
   
            return a < b;
        }
    };

    template<class T>
    class Greater
    {
   
    public:
        bool operator()(const T& x, const T& y)
        {
   
            return x > y;
        }
    };

    template<class T, class container = std::vector<T>,class compare = Less<T>>

    class priority_queue
    {
   


        void AdjustDown(size_t parent)
        {
   
            compare com;

            size_t child = parent * 2 + 1;
            //if (child+1< _con.size() && _con[child] < _con[1+child])
            if (child + 1 < _con.size() && com(_con[child] ,_con[1 + child]))

            {
   
                ++child;
            }
            while (child <_con.size())
            {
   
                if (com(_con[parent], _con[child]))
                {
   
                    std::swap(_con[child], _con[parent]);
                    parent = child;
                    child = parent * 2 + 1;
                }
                else
                {
   
                    break;
                }
            }
        }

        void AdjustUp(int child)
        {
   
            compare com;

            int parent = (child - 1) / 2;
            while (child > 0)
            {
   
                if (com(_con[parent] , _con[child]))
                {
   
                    std::swap(_con[parent], _con[child]);
                    child = parent;
                    parent = (child - 1) / 2;
                }
                else
                {
   
                    break;
                }
            }

        }

    public:
        template<class InputIterator>
        void push_back(InputIterator first, InputIterator last)
        {
   
            while (first != last)
            {
   
                _con.push_back(*first);
                ++first;
            }

            //向下建堆
            for (int i = (_con.size() - 2) / 2; i >= 0; i--)
            {
   
                AdjustDown(i);
            }
        }

        void pop()
        {
   
            swap(_con[0], _con[_con.size() - 1]);

            _con.pop_back();

            AdjustDown(0);
        }

        const T& top()
        {
   
            return _con[0];
        }

        void push(const T& x)
        {
   
            _con.push_back(x);

            AdjustUp(_con.size() - 1);
        }
        size_t szie()
        {
   
            return _con.size();
        }

        bool empty()
        {
   
            return _con.empty();
        }

    private:
        container _con;
    };

}

向下建堆
for (int i = (_con.size() - 2) / 2; i >= 0; i--)
{
AdjustDown(i);
}
}

    void pop()
    {
        swap(_con[0], _con[_con.size() - 1]);

        _con.pop_back();

        AdjustDown(0);
    }

    const T& top()
    {
        return _con[0];
    }

    void push(const T& x)
    {
        _con.push_back(x);

        AdjustUp(_con.size() - 1);
    }
    size_t szie()
    {
        return _con.size();
    }

    bool empty()
    {
        return _con.empty();
    }

private:
    container _con;
};

}
~~~

目录
相关文章
|
14天前
|
存储 人工智能 弹性计算
阿里云弹性计算_加速计算专场精华概览 | 2024云栖大会回顾
2024年9月19-21日,2024云栖大会在杭州云栖小镇举行,阿里云智能集团资深技术专家、异构计算产品技术负责人王超等多位产品、技术专家,共同带来了题为《AI Infra的前沿技术与应用实践》的专场session。本次专场重点介绍了阿里云AI Infra 产品架构与技术能力,及用户如何使用阿里云灵骏产品进行AI大模型开发、训练和应用。围绕当下大模型训练和推理的技术难点,专家们分享了如何在阿里云上实现稳定、高效、经济的大模型训练,并通过多个客户案例展示了云上大模型训练的显著优势。
|
18天前
|
存储 人工智能 调度
阿里云吴结生:高性能计算持续创新,响应数据+AI时代的多元化负载需求
在数字化转型的大潮中,每家公司都在积极探索如何利用数据驱动业务增长,而AI技术的快速发展更是加速了这一进程。
|
9天前
|
并行计算 前端开发 物联网
全网首发!真·从0到1!万字长文带你入门Qwen2.5-Coder——介绍、体验、本地部署及简单微调
2024年11月12日,阿里云通义大模型团队正式开源通义千问代码模型全系列,包括6款Qwen2.5-Coder模型,每个规模包含Base和Instruct两个版本。其中32B尺寸的旗舰代码模型在多项基准评测中取得开源最佳成绩,成为全球最强开源代码模型,多项关键能力超越GPT-4o。Qwen2.5-Coder具备强大、多样和实用等优点,通过持续训练,结合源代码、文本代码混合数据及合成数据,显著提升了代码生成、推理和修复等核心任务的性能。此外,该模型还支持多种编程语言,并在人类偏好对齐方面表现出色。本文为周周的奇妙编程原创,阿里云社区首发,未经同意不得转载。
|
14天前
|
人工智能 运维 双11
2024阿里云双十一云资源购买指南(纯客观,无广)
2024年双十一,阿里云推出多项重磅优惠,特别针对新迁入云的企业和初创公司提供丰厚补贴。其中,36元一年的轻量应用服务器、1.95元/小时的16核60GB A10卡以及1元购域名等产品尤为值得关注。这些产品不仅价格亲民,还提供了丰富的功能和服务,非常适合个人开发者、学生及中小企业快速上手和部署应用。
|
21天前
|
缓存 监控 Linux
Python 实时获取Linux服务器信息
Python 实时获取Linux服务器信息
|
4天前
|
云安全 存储 弹性计算
|
6天前
|
云安全 人工智能 自然语言处理
|
9天前
|
人工智能 自然语言处理 前端开发
用通义灵码,从 0 开始打造一个完整APP,无需编程经验就可以完成
通义灵码携手科技博主@玺哥超carry 打造全网第一个完整的、面向普通人的自然语言编程教程。完全使用 AI,再配合简单易懂的方法,只要你会打字,就能真正做出一个完整的应用。本教程完全免费,而且为大家准备了 100 个降噪蓝牙耳机,送给前 100 个完成的粉丝。获奖的方式非常简单,只要你跟着教程完成第一课的内容就能获得。
|
25天前
|
自然语言处理 数据可视化 前端开发
从数据提取到管理:合合信息的智能文档处理全方位解析【合合信息智能文档处理百宝箱】
合合信息的智能文档处理“百宝箱”涵盖文档解析、向量化模型、测评工具等,解决了复杂文档解析、大模型问答幻觉、文档解析效果评估、知识库搭建、多语言文档翻译等问题。通过可视化解析工具 TextIn ParseX、向量化模型 acge-embedding 和文档解析测评工具 markdown_tester,百宝箱提升了文档处理的效率和精确度,适用于多种文档格式和语言环境,助力企业实现高效的信息管理和业务支持。
3984 5
从数据提取到管理:合合信息的智能文档处理全方位解析【合合信息智能文档处理百宝箱】
|
4天前
|
人工智能 C++ iOS开发
ollama + qwen2.5-coder + VS Code + Continue 实现本地AI 辅助写代码
本文介绍在Apple M4 MacOS环境下搭建Ollama和qwen2.5-coder模型的过程。首先通过官网或Brew安装Ollama,然后下载qwen2.5-coder模型,可通过终端命令`ollama run qwen2.5-coder`启动模型进行测试。最后,在VS Code中安装Continue插件,并配置qwen2.5-coder模型用于代码开发辅助。
290 4