C++:priority_queue模拟实现

C++:priority_queue模拟实现

    • 什么是priority_queue
    • 模拟实现
      • 向上调整算法
      • 向下调整算法
      • 插入与删除
    • 仿函数


什么是priority_queue

priority_queue称为优先级队列。优先级队列是一种特殊的队列,其中每个元素都有一个相关的优先级。元素的优先级决定了它们在队列中的顺序。具有较高优先级的元素在队列中被排在前面,而具有较低优先级的元素在队列中被排在后面。

可以简单理解为,priority_queue其实就是堆,如果作用于整型,可以让最大/最小的数字处于堆顶,而其也可以作用于其它类型,比如作用于string,那么可以根据字典序来排序。

接下来我带大家实现一下这个结构:


模拟实现

在STL中,preority_queue是一个容器适配器,因为其只要求了数据按照指定的顺序排列,但是没有对底层结构做要求,所以我们直接用其他容器做底层即可。

在此我使用了vector做底层,因为在优先级队列中,我们需要频繁地进行下标访问,使用deque也是可以的。

基本结构:

template <class T, class Container = vector<T>>
class preority_queue
{
private:Container _con;
};

模板参数:

T:这个优先级队列承载的元素类型
Container :底层容器类型,此处默认值为vector

我们看看preority_queue有哪些接口:
在这里插入图片描述
接下来我们一一实现:


top:
想要得到preority_queue优先级最高的元素,其实就是拿到vector的第一个元素_con[0]

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

size:
preority_queue的大小,就是vector的大小。

size_t size()
{return _con.size();
}

empty:
判断preority_queue是否为空,就是判断vector是否为空。

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

向上调整算法

现在假设我有以下堆结构:
在这里插入图片描述

我现在在堆尾部插入一个数据,如何将数据调整到合适的位置,保证这个结构依然满足堆的要求?
在这里插入图片描述
想要将其插入到指定位置,就要使用到向上调整算法:将最后一个节点向上调整到合适的位置

首先讲解一个公式:堆结构中父节点与子节点的下标关系

假设父节点的下标为parent,则其左子节点的下标为 2 * parent+1,右子节点的下标为 2 * parent+2。

由于大堆要保证每隔父亲节点大于两个子节点,而除去最后一个节点,其它的节点已经满足堆结构了,所以此处需要将最后一个节点不断地与其父亲节点比较,如果其比父亲节点大,就交换位置,然后继续和新的父亲节点比较,直到比当前的父亲节点小,或者到达堆顶为止。

图示:
请添加图片描述

vector中的效果(实际效果):
请添加图片描述

代码实现:

void AdjustUp(int child)
{int parent = (child - 1) / 2;while (child > 0){if (_con[child] > _con[parent]){Swap(_con[child], _con[parent]);child = parent;parent = (child - 1) / 2;}else{break;}}
}

代码详解:

  1. 定义父节点索引:
int parent = (child - 1) / 2;

根据完全二叉树的性质,节点i的父节点索引是(i - 1) / 2,所以计算出child节点的父节点索引。

  1. 进入循环:
while (child > 0)

child大于0时,继续执行循环。循环的目的是将child节点与其父节点进行比较,并根据需要进行交换。

  1. 比较child节点与其父节点的大小:
if (_con[child] < _con[parent])

如果child节点的值小于其父节点的值,说明需要进行交换。这是一个小堆的向上调整操作。如果想要实现大堆的向上调整,需要将判断条件改为>

  1. 交换节点值和更新索引:
Swap(_con[child], _con[parent]);
child = parent;
parent = (child - 1) / 2;

交换child节点和父节点的值,然后更新childparent的值,使其指向交换后的节点。

  1. 循环结束:
else
{break;
}

如果child节点大于等于其父节点,说明调整完成,跳出循环。

通过这个向上调整的操作,可以将新插入的元素调整到合适的位置,以保证堆的性质。


向下调整算法

如果堆中某个节点的值被修改,如何调整这个堆的结构保证其依然满足堆的要求?

当堆中的某个节点的值发生改变时(例如,该节点的值被修改),需要进行向下调整操作来保持堆的性质。

向下调整的基本思想是将当前节点与其子节点进行比较,并根据堆的类型(大堆或小堆)选择合适的交换操作。如果当前节点的值小于(或大于)其子节点的值,那么需要将当前节点与其子节点中的较大(或较小)值进行交换。然后,继续向下调整交换后的子节点,直到满足堆的性质为止。

示意图如下:
请添加图片描述vector中的效果(实际效果):
请添加图片描述

代码如下:

void AdjustDown(int parent)
{int child = parent * 2 + 1;while (child < _con.size()){if (child + 1 < con.size() && _con[child + 1] > _con[child]){child++;}if (_con[child] > _con[parent]){Swap(_con[child], _con[parent]);parent = child;child = parent * 2 + 1;}else{break;}}
}

首先,计算待调整节点的左子节点下标child = parent * 2 + 1

然后,进入一个循环,判断child是否越界。如果child < size,则说明待调整节点有左子节点。

在循环中,首先判断是否存在右子节点,并且右子节点的值大于左子节点的值,如果满足这个条件,则将child更新为右子节点的下标。

接下来,判断child节点的值是否大于parent节点的值。如果满足这个条件,则交换childparent节点的值,并更新parentchild,再次计算child的值。

如果child节点的值不大于parent节点的值,则退出循环。

函数结束后,即可保证以parent节点为根的子树满足堆的性质。


插入与删除

push:
我们只需要将待插入元素放到末尾,然后将其向上调整即可:

void push(const T& x)
{_con.push_back(x);adjust_up(_con.size() - 1);
}

pop:
我们只需要将第一个元素与最后一个元素交换,然后删除最后一个元素,最后把刚刚交换上来的第一个元素向下调整即可:

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

但是至此我们的优先级队列还不是完全体,因为其只能固定是大堆/小堆,或者说优先级的比较方式是固定的,想要解决这个问题,我们就需要仿函数。


仿函数

仿函数本质上是一个类,它具有函数调用运算符(operator())的特性。仿函数的本质是一种可调用的对象,它可以像函数一样被调用。

我先为大家展示一个仿函数:

class Add 
{int operator()(int a, int b) {return a + b;}
};int main(){Add add;int result = add(3, 4); return 0;
}

在上面的示例中,我们定义了一个名为Add的类,它重载了函数调用运算符operator(),并实现了对两个整数相加的逻辑。在主函数中,我们创建了一个Add的实例add,并通过调用函数调用运算符对两个整数进行相加。

add本质上明明是一个类的对象,但是由于重载了运算符operator(),所以可以模仿函数的行为,完成函数的调用。

那么我们为什么不直接写一个函数,完成两个数字的加法,而是要搞仿函数这样的东西呢?

这就不得不提到被人诟病的C语言函数指针体系了,在C语言中,如果我们想将一个函数作为参数传给其他函数,那么我们就要在形参列表中准确写出函数指针的类型。但是函数指针经常会非常复杂,用起来很难受。

但是对象就不一样了,如果我们将仿函数的对象作为参数传递,此时的类型就非常好写了。

其次是内联函数的问题:

仿函数是定义在类中的,类中的成员函数默认为内联函数,此时短小的函数就会被展开。而如果是一般的函数,用inline修饰后,由于要在指定位置展开,此时就没有地址,没有函数指针了,所以内联函数无法作为参数传递。如果inline修饰的函数被作为函数指针传递,此时这个函数的inline就会失效,从而无法展开。而仿函数很好解决了这个问题

所以仿函数有两大优点:

  1. 避免了复杂的函数指针体系,我们可以很方便的将仿函数作为参数传递
  2. 仿函数可以作为参数传递的同时,还支持内联函数

对于我们的优先级队列,我们就可以使用仿函数作为一个模板参数。
用户使用优先级队列时,可以自己传入一个仿函数,从而制定自己的规则,按照自己设想的方法来排列数据。

比大小的仿函数:

template <class T>
struct less
{bool operator()(const T& x, const T& y){return x < y;}
};template <class T>
struct greater
{bool operator()(const T& x, const T& y){return x > y;}
};

完整版priority_queue

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

在模板参数中,我们传入了第二个参数,用于接受一个仿函数Compare ,这个仿函数用于决定后续比较时的优先级规则。

后续我们在比较parentchild大小的地方调用了仿函数:Compare()(_con[parent], _con[child])
这里并没有多写一对括号,Compare()是一个匿名对象,而后面的(_con[parent], _con[child])则是在调用operator()

当然,如果你认为这样可读性太差了,也可以在类中多定义一个compare类的对象cmp,后续直接cmp(_con[parent], _con[child])这样调用这个仿函数。


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

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

相关文章

【FTP讲解】

FTP讲解 1. 介绍2. 工作原理3. 传输模式4. 安全5. 设置FTP服务器6. FTP命令 1. 介绍 FTP&#xff08;File Transfer Protocol&#xff09;是“文件传输协议”的英文缩写&#xff0c;它是用于在网络上进行数据传输的一种协议。FTP是因特网上使用最广泛的协议之一&#xff0c;它…

Python数学建模之回归分析

1.基本概念及应用场景 回归分析是一种预测性的建模技术&#xff0c;数学建模中常用回归分析技术寻找存在相关关系的变量间的数学表达式&#xff0c;并进行统计推断。例如&#xff0c;司机的鲁莽驾驶与交通事故的数量之间的关系就可以用回归分析研究。回归分析根据变量的…

论文阅读:GamutMLP A Lightweight MLP for Color Loss Recovery

这篇文章是关于色彩恢复的一项工作&#xff0c;发表在 CVPR2023&#xff0c;其中之一的作者是 Michael S. Brown&#xff0c;这个老师是加拿大 York 大学的&#xff0c;也是 ISP 领域的大牛&#xff0c;现在好像也在三星研究院担任兼职&#xff0c;这个老师做了很多这种类似的工…

系统架构25 - 软件架构设计(4)

软件架构复用 软件产品线定义分类原因复用对象及形式基本过程 软件产品线 软件产品线是指一组软件密集型系统&#xff0c;它们共享一个公共的、可管理的特性集&#xff0c;满足某个特定市场或任务的具体需要&#xff0c;是以规定的方式用公共的核心资产集成开发出来的。即围绕…

九、OpenCV自带colormap

项目功能实现&#xff1a;每隔1500ms轮流自动播放不同风格图像显示&#xff0c;按下Esc键退出 按照之前的博文结构来&#xff0c;这里就不在赘述了 一、头文件 colormap.h #pragma once #include<opencv2/opencv.hpp> using namespace cv;class ColorMap { public:vo…

Mybatis开发辅助神器p6spy

Mybatis什么都好&#xff0c;就是不能打印完整的SQL语句&#xff0c;虽然可以根据数据来判断一二&#xff0c;但始终不能直观的看到实际语句。这对我们想用完整语句去数据库里执行&#xff0c;带来了不便。 怎么说呢不管用其他什么方式来实现完整语句&#xff0c;都始终不是Myb…

C++ 11新特性之并发

概述 随着计算机硬件的发展&#xff0c;多核处理器已经成为主流&#xff0c;对程序并发执行能力的需求日益增长。C 11标准引入了一套全面且强大的并发编程支持库&#xff0c;为开发者提供了一个安全、高效地利用多核CPU资源进行并行计算的新框架&#xff0c;极大地简化了多线程…

C#面:Static Nested Class 和 Inner Class 有什么不同

这是两种不同的类嵌套方式。 Static Nested Class &#xff1a; 是一个静态嵌套类&#xff0c;它是在外部类中定义的一个静态类。它可以访问外部类的静态成员和方法&#xff0c;但不能直接访问外部类的非静态成员和方法。静态嵌套类可以独立于外部类实例化&#xff0c;即可以…

《Linux 简易速速上手小册》第6章: 磁盘管理与文件系统(2024 最新版)

文章目录 6.1 磁盘分区与格式化6.1.1 重点基础知识6.1.2 重点案例&#xff1a;为新硬盘配置分区和文件系统6.1.3 拓展案例 1&#xff1a;创建交换分区6.1.4 拓展案例 2&#xff1a;使用 LVM 管理分区 6.2 挂载与卸载文件系统6.2.1 重点基础知识6.2.2 重点案例&#xff1a;挂载新…

近十年金融资产收益率

通过掌握大类资产的历年收益率数据&#xff0c;做基于数据的投资&#xff0c;提高胜率和收益率。 下面是同花顺梳理的2014至2023大类金融资产收益率&#xff1a; 基于这个数据&#xff0c;我们再统计两项指标&#xff1a; 1. 每种资产在近十年的投资胜率&#xff08;收益率为…

牛客2024年情人节比赛 娱乐报告

前言 挺欢乐的比赛&#xff0c;有趣 欢迎关注 珂朵莉 牛客周赛专栏 珂朵莉 牛客小白月赛专栏 A. 第二杯半价 思路: 模拟 分奇偶进行讨论 t int(input())for _ in range(t):n, x list(map(int, input().split()))if n % 2 1:print (n//2 * (x (x 1) // 2) x)else:pr…

Fabric自动化部署

Fabric自动化部署是一种基于Python的自动化部署工具&#xff0c;它可以帮助开发人员自动化地执行一系列部署任务&#xff0c;如代码推送、服务器配置更新、文件传输等。 Fabric自动化部署的核心是一个名为fabfile.py的Python文件&#xff0c;其中定义了要执行的部署任务。通过…

力扣-345. 反转字符串中的元音字母

文章目录 力扣题目代码 力扣题目 给你一个字符串 s &#xff0c;仅反转字符串中的所有元音字母&#xff0c;并返回结果字符串。 元音字母包括 ‘a’、‘e’、‘i’、‘o’、‘u’&#xff0c;且可能以大小写两种形式出现不止一次。 示例 1&#xff1a; 输入&#xff1a;s …

RIDERS: Radar-Infrared Depth Estimation for Robust Sensing

RIDERS: 恶劣天气及环境下鲁棒的密集深度估计 论文链接&#xff1a;https://arxiv.org/pdf/2402.02067.pdf 作者单位&#xff1a;浙江大学, 慕尼黑工业大学 代码链接&#xff1a;https://github.com/MMOCKING/RIDERS 1. 摘要&#xff08;Abstract&#xff09; 恶劣的天气条件, …

Ubuntu+Anaconda 常用指令记录

Anaconda 使用指令记录 1 创建环境 conda create -n name pythonx.x(python版本自己指定)例如 conda create --name myenv: 创建名为"myenv"的新环境。 conda activate myenv: 激活名为"myenv"的环境。 conda deactivate: 退出当前环境。 2 删除环境 c…

python 自我检测题--part 1

1. Which way among them is used to create an event loop ? Window.mainloop() 2. Suppose we have a set a {10,9,8,7}, and we execute a.remove(14) what will happen ? Key error is raised. The remove() method removes the specified element from the set. Th…

imazing怎么连接苹果手机

imazing怎么连接苹果手机 要连接苹果手机&#xff0c;您可以选择使用数据线或无线网络&#xff08;Wi-Fi&#xff09;两种方式。以下是具体的步骤&#xff1a; 使用数据线连接&#xff1a; 准备工具&#xff1a;确保您的Mac或Windows电脑已经安装了iMazing软件&#xff0c;并且…

流畅的Python(九)-符合Python风格的对象

一、核心要义 接续第一章&#xff0c;说明如何实现很多Python类型中常见的特殊方法 1. 支持使用生成对象其他表示形式的内置函数(如repr(),bytes()等) 2. 使用类方法&#xff0c;实现备选构造方法 3.扩展内置的format()函数和str.format()方法使用的格式微语言 4.实现只读…

寒假作业2024.2.14

一&#xff0e;选择题 1.变量的指针&#xff0c;其含义是指该变量的 B 。 A&#xff09;值 B&#xff09;地址 C&#xff09;名 D&#xff09;一个标志 2.已有定义int k2;int *ptr1,*ptr2;且ptr1和ptr2均已指向变量k&#xff…

Linux命令行全景指南:从入门到实践,掌握命令行的力量

目录 知识梳理思维导图&#xff1a; linux命令入门 为什么要学Linux命令 什么是终端 什么是命令 关于Linux命令的语法 tab键补全 关于命令提示符 特殊目录 常见重要目录 /opt /home /root /etc /var/log/ man命令 shutdown命令 history命令 which命令 bash…