当前位置: 首页 > news >正文

priority_queue(优先级队列)和仿函数

文章目录

  • 前言
  • 优先级队列
    • 优先级队列的实现
      • 优先级队列基本框架
      • 向上调整和向下调整
    • 建堆
  • 仿函数/函数对象
    • 仿函数的运用
  • 将仿函数融入优先级队列中

前言

本节小编会结合优先级队列去探讨仿函数的由来以及作用。

优先级队列

定义:优先级队列是一种特殊的队列,它会按照元素的优先级顺序出队,而不是按照先进先出(FIFO)的顺序。优先级队列通常基于堆(Heap)数据结构实现,分为最大堆和最小堆。

注意:优先级队列并不是一个先进先出的队列,它是由容器适配器适配出的一个堆结构。

优先级队列的实现

优先级队列基本框架

  • 先看下方的代码
  • 用命名空间封装防止和标准库中的优先级队列冲突
  • 定义三个模板参数
    • T:容器内对象类型
    • Container:容器适配器
    • Compare:仿函数(放后面讲解)
  • priority_queue()=default:让编译器生成默认构造(由于构造不是本章重点)

为什么我们的默认容器适配器要选用vector呢?

由于优先级队列通常基于堆数据结构实现,而堆需要一个随机访问容器std::vector 提供了随机访问特性,能够高效地支持堆操作,例如:

  • 访问堆顶元素:可以直接通过索引访问第一个元素。
  • 调整堆结构:在插入或删除元素时,需要对堆进行调整(如上浮或下沉操作),这需要随机访问元素。

std::vector 的连续内存布局使得这些操作非常高效,而其他容器(如 std::list 或 std::deque)无法提供这种高效的随机访问能力。

nampspace dhb
{template<class T,class Container=vector<T>,class Compare=Less<int>>class priority_queue{//......priority_queue()=defaultprivate:Container _con;}}

向上调整和向下调整

在堆里最基本的插入删除操作都是需要借助这些调整算法
故带大家快速回顾一下调整算法:

  • 向上调整算法
//以大堆为例
void adjust_up(size_t child)
{size_t parent=(child-1)/2;while(child>0){if(_con[parent]<_con[child]){std::swap(_con[parent],_con[child]);child=parent;parent=(child-1)/2;}else{break;}}
}

进一步实现插入操作

void push(const T& x)
{//先插入再调整_con.push_back();adjust_up(_con.size()-1);
}

向下调整算法

void adjust_down(size_t parent)
{size_t child=parent*2+1;while(child<_con.size()){if(_con[child+1]<_con.size()&&_con[child]<_con[child+1])child++;//找到最大孩子if(_con[parent]<_con[child]){std::swap(_con.[child],_con.[parent]);parent=child;child=parent*2+1;}else{break;}}	
}

进一步实现插入操作

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

为什么每次的插入删除操作后都要进行相关调整操作呢?

  • 对于插入操作来说每插入一个数都要看这个数是否满足堆结构,所以要依次和自己的父亲比较(其他的兄弟结点都已经满足树结构不用调整),即向上调整。
  • 而删除操作为什么不直接进行头删然后挪动数据呢?
    如果这样操作会有兄弟变父亲的现象,整个二叉树的结构会彻底破坏。
    所以我们采用先进行头尾交换再尾删,这样整体的二叉树结构没有被破坏,调整也只是一支走下去不用全部调整。

建堆

为什么一般采用向下调整算法建堆?

向上调整建堆时间复杂度较高O(n*logn)
因为越向下结点要走的深度越高有一半的结点要走整个树的深度
(结点最多的一层调整次数也最多)
而向下调整(结点最多的一层不需要调整)O(n)

这里的向下调整建堆会被运用于迭代器区间的构造中

template<class InpushIterator>
priority_queue(InpushIterator first,InpushIterator end):_con(first,last)
{//向下调整建堆for(size_t i=0,i<(_con.size-1-1)/2;i++){adjust_down(i);}	
}

仿函数/函数对象

仿函数的定义:仿函数是一种特殊的函数对象,它是一个类的对象,通过重载 operator(),使其能够像函数一样被调用。仿函数在 C++ 中广泛应用于标准库和自定义算法中,用于封装可调用的逻辑。

其实仿函数出现的意义就是来取代C语言中的函数指针,函数指针过于复杂难用,而且函数指针不能用于类模板中,类模板是传一个类型过来其类型可以不确定,但是函数指针的类型是确定的。
运用函数指针在C++11中提出了解决方案但在这里不做讲解,这里我们会提供另外一种解决方案:仿函数。

例如,以下两个函数的指针类型是不同的:

void f1(int);
int f2(double);

对应的函数指针类型分别为:

void (*pf1)(int);
int (*pf2)(double);

类模板在定义时是泛型的,其类型参数在编译时才被具体化。例如:

template <typename T>
class MyClass {T value;
};

在实例化 MyClass<int>MyClass<double> 时,T 才被具体化为 intdouble

仿函数的运用

我先根据定义写一个仿函数大伙就明白它为啥叫仿函数了。

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;}
};
int main()
{Less<int>  less;cout << less(3,2) << endl;return 0;
}

我们发现cout << less(3,2) << endl;这里对象less的调用就像函数一样,所以我们又称仿函数为函数对象。

运用仿函数调节比较大小逻辑
这里我们以冒泡排序为例讲解:

template<class T, class Compare>
void BubbleSort(T* a, int n, Compare com)
{int exchange = 0;for (int i = 0; i < n; i++){for (int j = 0; j < n - i - 1; j++){//if (a[j + 1] > a[j])if (com(a[j + 1], a[j]))//仿函数{exchange = 1;swap(a[j], a[j + 1]);}}if (exchange == 0){break;}}
}

这样if (com(a[j + 1], a[j]))修改冒泡排序后我们就可以通过传第三个模板参数来调节比大小的逻辑:

int a[] = { 1,2,3,2,5,7,1 };
BubbleSort(a, 7, Less<int>());
for (auto& e : a)
{cout << e << " ";
}
cout << endl;BubbleSort(a, 7, Greater<int>());
for (auto& e : a)
{cout << e << " ";
}
cout << endl;

在这里插入图片描述

将仿函数融入优先级队列中

好处?

  1. 封装性:仿函数是一种封装了可调用逻辑的对象。通过使用仿函数,可以将比较逻辑封装在一个类中,而不是直接传递一个函数指针。这种方式不仅提高了代码的可读性和可维护性,还可以携带状态信息。
  2. 灵活性:优先级队列的核心功能是根据元素的优先级顺序管理元素。默认情况下,优先级队列使用最大堆(即优先级最高的元素是最大的元素),但用户可能需要根据自己的需求定义不同的优先级规则。通过使用仿函数,用户可以自定义比较逻辑(一个类中我既可以比较它的对象大小,也可以比较它们的指针大小),从而使优先级队列能够适应各种不同的应用场景。
  • 注意:在库中传仿函数Less是生成大堆,greater是生成小堆(与字面意思相反)

具体实现:

#pragma once
#include<vector>
//库中的仿函数比较逻辑是反的
//传less大的优先级高(大堆),传greater小的优先级高 
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;}
};
namespace dhb
{	template<class T, class Container = std::vector<T>,class Compare=Less<T>>class priority_queue{public://强制生成默认构造priority_queue() = default;template<class InpushIterator>//迭代器区间构造priority_queue(InpushIterator first, InpushIterator last):_con(first, last){for (int i = (_con.size() - 1 - 1) / 2; i >= 0; i--){adjust_down(i);  }}void push(const T& x){//可以用算法库里的push_heap_con.push_back(x);	adjust_up(_con.size() - 1);}void pop(){std::swap(_con[0], _con[_con.size() - 1]);_con.pop_back();adjust_down(0);}const T& top(){return _con[0];}bool empty()const{return _con.empty();}private:void adjust_up(size_t child){Compare com;size_t parent = (child - 1) / 2;while (child > 0){if (com(_con[parent],_con[child])){std::swap(_con[child], _con[parent]);child = parent;parent = (child - 1) / 2;}else{break;}}}void adjust_down(size_t parent){Compare com;size_t child = parent * 2 + 1;while (child<_con.size())//需要保证右孩子存在{if (child + 1 < _con.size() && com(_con[child] , _con[child + 1]))child++;if (com(_con[parent] , _con[child])){std::swap(_con[child], _con[parent]);parent = child;child = parent * 2 + 1;}elsebreak;}}	private:Container _con;};
}
http://www.dtcms.com/a/326606.html

相关文章:

  • ArkUI中的自定义组件(一)
  • 用于计算的程序是部署在哪里,为什么可以这样?
  • 从 WebView2 迁移至 DotNetBrowser:第一部分
  • android 换肤框架详解2-LayoutInflater源码解析
  • 《零基础入门AI:深度学习基础核心概念解析(从激活函数到反向传播)》
  • 大模型提示词工程实践:提示词工程实践-引导大模型完成任务
  • 直播美颜SDK架构设计指南:美白滤镜的高效实现与跨平台适配
  • MySQL 基本语法
  • 【网络基础】深入理解 TCP/IP 协议体系
  • 秒懂边缘云|1分钟了解边缘安全加速 ESA
  • GCC C++实现Matlab矩阵计算和数学函数功能
  • 乡土诗性的多重奏鸣——儿歌《生我养我的小村庄》文学赏析
  • C5.3:发射极偏置和LED驱动电路
  • 26考研|西安电子科技大学优势学科、25考研复试线及就业质量分析报告
  • 力扣热题100-----322.零钱兑换
  • 事务的特性
  • 下一代防火墙组网方案
  • IoT/透过oc_lwm2m/boudica150 源码中的AT指令序列,分析NB-IoT接入华为云物联网平台IoTDA的工作机制
  • visual studio 2015 使用番茄助手(Visual Assist)给函数自动添加注释模板
  • WSL / Linux安装MySQL(以及注意事项)
  • 嵌入式学习的第四十八天-中断+OCP原则
  • Photoshop图层混合模式:实现图像元素透明度渐变过渡的终极指南
  • Effective C++ 条款36: 绝不重新定义继承而来的非虚函数
  • 数据结构:树与二叉树
  • ARM基础概念 day51
  • easyExcel嵌套子集合导出Excel
  • 2025第十六届蓝桥杯大赛青少组省赛C++真题(初级组和中级组)
  • MCU的设计原理
  • SNMP入门教程:Windows下编译
  • Linux811 YUM;SHELL:if else fi,for