C++笔记:容器适配器:优先级队列(priority_queue)模拟实现

文章目录

    • 框架
    • (constructor)
    • size()、empty()、top()
    • push()
      • 声明
      • 参数
      • 实现
      • 代码
    • pop()
      • 声明
      • 实现
      • 代码
    • 仿函数与函数指针
      • 仿函数的定义
      • 仿函数实现回调
      • 函数指针实现回调
      • adjust_up 和 adjust_down 的改进
  • 完整代码

容器适配器(Container Adapter)是一种 C++ 中的抽象数据类型,它提供了一种在指定底层容器基础上进行封装,以实现特定功能的方式。容器适配器并不是独立的容器类型,而是建立在其他容器之上的封装,通过提供不同的接口或限制来满足特定需求。

priority_queue(优先级队列)就是一个容器适配器,是数据结构上的堆(Heap)的实现,库中的声明如下:

template <class T, class Container = vector<T>,class Compare = less<typename Container::value_type> > class priority_queue;
  • T:指数据元素的类型。
  • Container :指存储元素的内部基础容器对象的类型,默认以 vector 进行适配。
  • Compare :仿函数对象的类型。(什么是仿函数后面会说)

基础容器可以是库里有的,也可以是自己实现的,但都应当满足以下要求:

  1. 要求应可通过随机访问迭代器访问
  2. 支持以下内容 operations:
    • empty()
    • size()
    • front()
    • push_back()
    • pop_back()

框架

本次模拟实现只是,简单的模拟实现,旨在加深对 priority_queue 的理解和了解和使用仿函数,主要参考 C++98 版本的 priority_queue。进行模拟实现。

priority_queue 类的大致框架如下:

template <class T, class Container = vector<T>, class Compare = less<T> >
class priority_queue
{
public:// priority_queue 该提供以下接口priority_queue(const Container& ctnr = Container(), const Compare& comp = Compare());template <class InputIterator>priority_queue(InputIterator first, InputIterator last);bool empty() const;size_t size() const;const T& top() const;void push(const T& x);void pop();private:void adjust_up(size_t child);void adjust_down(size_t parent);private:Container _c;Compare _comp;
};

(constructor)

C++98版本的 priority_queue 的构造函数重载有两个版本,一个是全缺省的默认构造函数,另一个是迭代器区间构造函数。

// 全缺省默认构造
priority_queue(const Container& ctnr = Container(), const Compare& comp = Compare()): _c(ctnr), _comp(comp)
{}// 迭代器范围区间构造
template <class InputIterator>
priority_queue(InputIterator first, InputIterator last)// 先调用 Container 对象的 range constructor: _c(first, last)
{// 从最后一个非叶子结点开始向下调整成堆结构int count = size();int root = ((count - 2) >> 1);for (; root >= 0; --root){adjust_down(root);}
}

size()、empty()、top()

这三个不是重点,且实现起来比较简单。

size_t size() const
{return _c.size();
}bool empty() const
{return _c.empty();
}// 堆顶数据不可被修改,堆顶元素被修改会破坏堆的特性
const T& top() const
{return _c.front();
}

push()

声明

void push (const T& val);

参数

形参 val 是待插入对象
val 类型是 const T&,引用传参是为了降低传参消耗。

实现

  1. 先将 val 插入到堆的末尾,即最后一个孩子之后。
  2. 插入之后如果堆的性质遭到破坏,将新插入结点顺着双亲往上调整到合适位置。

代码

void push(const T& x)
{// 1. 对象插入数据_c.push_back(x);// 2. 向上调整adjust_up(_c.size() - 1);
}// 向上调整算法
void adjust_up(size_t child)
{size_t parent = (child - 1) / 2;while (child > 0){if (_c[parent] < _c[child]){swap(_c[parent], _c[child]);child = parent;parent = (child - 1) / 2;}else{break;}}
}

pop()

声明

void pop();

实现

  1. 将堆顶元素与堆中的最后一个元素进行交换。
  2. 删除堆中最后一个元素。
  3. 从堆顶元素位置开始向下调整到满足堆特性为止。

代码

void pop()
{if (empty())return;// 交换堆顶和末尾swap(_c[0], _c[_c.size() - 1]);// 删除末尾位置元素_c.pop_back();// 堆顶位置向下调整adjust_down(0);
}// 向下调整算法
void adjust_down(size_t parent)
{size_t child = (parent * 2) + 1;while (child < _c.size()){if (child + 1 < _c.size() && _c[child] < _c[child + 1]){child += 1;}if (_c[parent] < _c[child]){swap(_c[parent], _c[child]);parent = child;child = (parent * 2) + 1;}else{break;}}
}

仿函数与函数指针

// adjust_up 
if (_c[parent] < _c[child])// adjust_down 
if (child + 1 < _c.size() && _c[child] < _c[child + 1])
if (_c[parent] < _c[child])

这几句代码写死了,priority_queue 实现的堆只能是大堆,但是通过直接修改源代码的方式来进行大小堆的切换是不现实的,更推荐的做法是通过回调函数控制比较逻辑,C语言采用函数指针实现,C++更喜欢使用仿函数(函数对象)。

仿函数的定义

仿函数(Functor)是一种行为类似函数的对象,类中重载了函数调用运算符 operator(),通过这种方式,对象就可以像函数一样被调用。

class less
{
public:bool operator()(int x, int y){return x < y;}
};
int main()
{// 实例化对象 lessfuncless lessfunc;// 调用成员函数cout << lessfunc(1, 2) << endl;cout << lessfunc.operator()(1, 2) << endl;return 0;
}

乍一看我们会认为lessfunc是函数名,但其实它是一个对象,它通过运算符重载让这个对象能够仿造函数的形式来使用。

仿函数实现回调

现在有一个 A 类,类中有一个成员方法 func() 作用是比较两个整型数据的大小,func() 通过使用仿函数回调 less 类对象、greater 类对象中的方法来实现 func() 中比较逻辑的控制,做法如下:

class less
{
public:bool operator()(int x, int y){return x < y;}
};class greater
{
public:bool operator()(int x, int y){return x > y;}
};template<class Compare>
class A
{
public:// 功能:比较两个int数据的大小,返回比较结果void func(int xx, int yy){Compare com;cout << com(xx, yy) << endl;}
};int main()
{A<less> a1;a1.func(100, 200);A<less> a2;a2.func(100, 200);return 0;
}

函数指针实现回调

现在有一个 A 类,类中有一个成员方法 func() 作用是比较两个整型数据的大小,func() 通过函数指针回调全局函数 lessfc、greaterfc 来控制 func() 的比较逻辑,做法如下:

bool lessfc(int x, int y)
{return x < y;
}bool greaterfc(int x, int y)
{return x > y;
}// A 类回调 lessfc、greater
class A
{
public:A(bool(*pf)(int, int)):_pf(pf){}// 控制 xx,yy 的比较逻辑void func(int xx, int yy){cout << _pf(xx, yy) << endl;}private:bool(*_pf)(int, int);
};int main()
{A a(lessfc);// 比较大小cout << a.func(100, 200) << endl;A a(greaterfc);// 比较大小cout << a.func(100, 200) << endl;return 0;
}

相较于仿函数实现,函数指针实现有几个缺陷:

  1. 函数指针类型写起来复杂。
  2. 函数指针只能通过函数参数的形式来传递,这就被迫要求实现构造函数,同时还要用一个成员变量来保存传递进来的函数指针,以便使用。

adjust_up 和 adjust_down 的改进

template<class T>
class less
{
public:bool operator()(const T& x, const T& y){return x < y;}
};template<class T>
class greater
{
public:bool operator()(const T& x, const T& y){return x > y;}
};
template <class T, class Container = vector<T>, class Compare = less<T> >
class priority_queue
{
public:// push()...// pop()...
private:void adjust_up(size_t child){size_t parent = (child - 1) / 2;while (child > 0){//if (_c[parent] < _c[child])if (_comp(_c[parent], _c[child])){swap(_c[parent], _c[child]);child = parent;parent = (child - 1) / 2;}else{break;}}}void adjust_down(size_t parent)
{size_t child = (parent * 2) + 1;while (child < _c.size()){//if (child + 1 < _c.size() && _c[child] < _c[child + 1])if (child + 1 < _c.size() && _comp(_c[child], _c[child + 1])){child += 1;}//if (_c[parent] < _c[child])if (_comp(_c[parent], _c[child])){swap(_c[parent], _c[child]);parent = child;child = (parent * 2) + 1;}else{break;}}
}private:Container _c;Compare _comp;
};

完整代码

namespace ljh
{#include<vector>template<class T>class less{public:bool operator()(const T& x, const T& y){return x < y;}};template<class T>class greater{public:bool operator()(const T& x, const T& y){return x > y;}};template <class T, class Container = vector<T>, class Compare = less<T> >class priority_queue{public:priority_queue(const Container& ctnr = Container(), const Compare& comp = Compare()): _c(ctnr), _comp(comp){}template <class InputIterator>priority_queue(InputIterator first, InputIterator last)// 先调用 Container 对象的 range constructor: _c(first, last){// 从最后一个非叶子结点开始向下调整成堆结构int count = size();int root = ((count - 2) >> 1);for (; root >= 0; --root){adjust_down(root);}}bool empty() const{return _c.empty();}size_t size() const{return _c.size();}// 堆顶数据不可被修改,堆顶元素被修改会破坏堆的特性const T& top() const{return _c.front();}void push(const T& x){// 1. 对象插入数据_c.push_back(x);// 2. 向上调整adjust_up(_c.size() - 1);}void pop(){if (empty())return;// 交换堆顶和末尾swap(_c[0], _c[_c.size() - 1]);// 删除末尾位置元素_c.pop_back();// 堆顶位置向下调整adjust_down(0);}private:void adjust_up(size_t child){size_t parent = (child - 1) / 2;while (child > 0){//if (_c[parent] < _c[child])if (_comp(_c[parent], _c[child])){swap(_c[parent], _c[child]);child = parent;parent = (child - 1) / 2;}else{break;}}}void adjust_down(size_t parent){size_t child = (parent * 2) + 1;while (child < _c.size()){//if (child + 1 < _c.size() && _c[child] < _c[child + 1])if (child + 1 < _c.size() && _comp(_c[child], _c[child + 1])){child += 1;}//if (_c[parent] < _c[child])if (_comp(_c[parent], _c[child])){swap(_c[parent], _c[child]);parent = child;child = (parent * 2) + 1;}else{break;}}}private:Container _c;Compare _comp;};
}

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

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

相关文章

【每日一题】牛客网——链表的回文结构

✨专栏&#xff1a;《Java SE语法》 | 《数据结构与算法》 | 《C生万物》 ❤️感谢大家点赞&#x1f44d;&#x1f3fb;收藏⭐评论✍&#x1f3fb;&#xff0c;您的三连就是我持续更新的动力❤️ &#x1f64f;小杨水平有限&#xff0c;欢迎各位大佬指点&#xff0c;相互学习…

Leetcode 718 最长重复子数组

题意理解&#xff1a; 给两个整数数组 nums1 和 nums2 &#xff0c;返回 两个数组中 公共的 、长度最长的子数组的长度 。 如&#xff1a; nums1 [1,2,3,2,1], nums2 [3,2,1,4,7] 则最长重复子序列为&#xff1a; 321 长度为3 这里采用动态规划解决这个问题。最长公共子序…

centos7更新yum安装docker-ce使用阿里源

centos7更新yum安装docker-ce使用阿里源 centos7更新yum安装docker-ce使用阿里源240209版 #!/bin/bash ## 卸载之前的docke sudo yum remove docker \docker-client \docker-client-latest \docker-common \docker-latest \docker-latest-logrotate \docker-logrotate \dock…

OpenCV-38 图像金字塔

目录 一、图像金字塔 1. 高斯金字塔 2. 拉普拉斯金字塔 一、图像金字塔 图像金字塔是图像中多尺度表达的一种&#xff0c;最主要用于图像的分割&#xff0c;是一种以多分辨率来解释图像的有效但概念简单的结构。简单来说&#xff0c;图像金字塔是同一图像不同分辨率的子图…

2000-2021年县域指标统计数据库

2000-2021年县域统计数据库 1、时间&#xff1a;2000-2021年 2、来源&#xff1a;县域统计年鉴 3、范围&#xff1a;2500县 5、指标&#xff1a; 地区名称、年份、行政区域代码、所属城市、所属省份、行政区域土地面积平方公里、乡及镇个数个、乡个数个、镇个数个、街道办…

2024.2.6 模拟实现 RabbitMQ —— 数据库操作

目录 引言 选择数据库 环境配置 设计数据库表 实现流程 封装数据库操作 针对 DataBaseManager 单元测试 引言 硬盘保存分为两个部分 数据库&#xff1a;交换机&#xff08;Exchange&#xff09;、队列&#xff08;Queue&#xff09;、绑定&#xff08;Binding&#xff0…

调用讯飞火星AI大模型WebAPI

调用讯飞火星AI大模型 记录一次调用讯飞AI大模型的过程 官方文档 首先&#xff0c;去官网申请资格&#xff0c;获得必要秘钥等 再编写url&#xff0c;该url存在编码要求&#xff0c;具体看官网url编写 具体代码如下&#xff1a; getWebsocketUrl() {return new Promise((resol…

vivado仿真时使用的代码与实际不一致的解决办法

前言 在使用仿真软件时经常会遇到实际需要时间较长&#xff0c;而仿真需要改写实际代码运行时间的问题&#xff0c;在vivado软件中找到了解决办法 代码部分 这里使用一个最简单的例子来说明一下&#xff0c;学过FPGA的朋友肯定可以看出来就是一个简单的计数器使LED每500ms交…

使用Express 构建高效的Web应用程序

一、使用环境变量管理配置信息 在开发Web应用程序时&#xff0c;通常需要配置信息&#xff0c;例如数据库连接字符串、API密钥等。为了安全起见&#xff0c;我们建议将这些敏感信息存储在环境变量中&#xff0c;而不是硬编码在应用程序代码中。Express.js提供了process.env对象…

力扣:53. 最大子数组和

解题思路&#xff1a; 1.先把数组为空和数组的长度为1时的特殊情况分别开来。声明一个sum变量用于计算数组中的连续子数组的总和值 。在声明一个guo变量用于一种接收sum中的前i-1的总和。另一种接收sum中前i的总和&#xff0c;主要根据sum的值来判断是接收的哪一种。在声明一个…

【MySQL】:分组查询、排序查询、分页查询、以及执行顺序

&#x1f3a5; 屿小夏 &#xff1a; 个人主页 &#x1f525;个人专栏 &#xff1a; MySQL从入门到进阶 &#x1f304; 莫道桑榆晚&#xff0c;为霞尚满天&#xff01; 文章目录 &#x1f4d1;前言一. 分组查询1.1 语法1.2 where与having区别1.3 注意事项:1.4 案例: 二. 排序查询…

LRU Cache

目录 一、认识LRU Cache 二、LRU Cache实现 一、认识LRU Cache LRU是Least Recently Used的缩写&#xff0c;意思是最近最少使用&#xff0c;是一种Cache替换算法 狭义的Cache指的是位于CPU和主存间的快速RAM&#xff0c; 通常它不像系统主存那样使用DRAM技术&#xff0c;而…

vue三种路由守卫详解

在 Vue 中&#xff0c;可以通过路由守卫来实现路由鉴权。Vue 提供了三种路由守卫&#xff1a;全局前置守卫、全局解析守卫和组件内的守卫。 全局前置守卫 通过 router.beforeEach() 方法实现&#xff0c;可以在路由跳转之前进行权限判断。在这个守卫中&#xff0c;可以根据用…

Vue-自定义属性和插槽(五)

目录 自定义指令 基本语法 (全局&局部注册) 指令的值 练习&#xff1a;v-loading 指令封装 总结&#xff1a; 插槽&#xff08;slot&#xff09; 默认插槽 插槽 - 后备内容&#xff08;默认值&#xff09; 具名插槽 具名插槽基本语法: 具名插槽简化语法: 作…

pytorch花式索引提取topk的张量

文章目录 pytorch花式索引提取topk的张量问题设定代码实现索引方法gather方法验证 补充知识expand方法gather方法randint pytorch花式索引提取topk的张量 问题设定 或者说&#xff0c;有一个(bs, dim, L)的大张量&#xff0c;索引的index形状为(bs, X)&#xff0c;想得到一个(…

HTML世界之第二重天

目录 一、HTML 格式化 1.HTML 文本格式化标签 2.HTML "计算机输出" 标签 3.HTML 引文, 引用, 及标签定义 二、HTML 链接 1.HTML 链接 2.HTML 超链接 3.HTML 链接语法 4.文本链接 5.图像链接 6.锚点链接 7.下载链接 8.Target 属性 9.Id 属性 三、HTML …

王树森《RNN Transformer》系列公开课

本课程主要介绍NLP相关&#xff0c;包括RNN、LSTM、Attention、Transformer、BERT等模型&#xff0c;以及情感识别、文本生成、机器翻译等应用 ShusenWang的个人空间-ShusenWang个人主页-哔哩哔哩视频 (bilibili.com) &#xff08;一&#xff09;NLP基础 1、数据处理基础 数…

Spring Boot 笔记 007 创建接口_登录

1.1 登录接口需求 1.2 JWT令牌 1.2.1 JWT原理 1.2.2 引入JWT坐标 1.2.3 单元测试 1.2.3.1 引入springboot单元测试坐标 1.2.3.2 在单元测试文件夹中创建测试类 1.2.3.3 运行测试类中的生成和解析方法 package com.geji;import com.auth0.jwt.JWT; import com.auth0.jwt.JWTV…

day2-理解 linux 云计算

1.解释服务器是什么&#xff1b; 服务器是一种高性能计算机&#xff0c;它的主要功能是提供计算服务和资源给其他计算机使用。在网络环境中&#xff0c;服务器扮演着重要的角色&#xff0c;它们可以存储和管理大量的数据&#xff0c;处理网络请求&#xff0c;提供应用程序运行…

【Spring】公司为什么禁止在SpringBoot项目中使用@Autowired注解

目录 前言 说明 依赖注入的类型 2.1 基于构造器的依赖注入 2.2 基于 Setter 的依赖注入 2.3 基于属性的依赖注入 基于字段的依赖注入缺陷 3.1 不允许声明不可变域 3.2 容易违反单一职责设计原则 3.3 与依赖注入容器紧密耦合 3.4 隐藏依赖关系 总结 参考文档 前言 …