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

priority_queue的使用和模拟

(一) priority_queue的使用

priority_queue的文档介绍

它是优先级队列,其底层是堆,也是容器适配器。若忘记数据结构的堆可以去查看一下——堆。

又上图可知:它基于vector作为底层容器,第三个参数是仿函数(后面讲)

为什么底层容器是vector不是deque了?

        priority_queue选择vector作为默认底层容器,是因为堆操作对 “随机访问效率” 的强依赖 ——vector的连续内存布局能提供最高效的[]访问,而deque的分段内存虽然也能满足功能需求,但在高频随机访问场景下效率更低,且其优势无法被priority_queue利用。因此,vector是更 “划算” 的选择。

        当然,标准也允许通过模板参数指定deque作为底层容器(如std::priority_queue<int, std::deque<int>>),但实际中几乎不会这么做,因为性能不如vector。

操作:(重要的接口解释在图片中)

使用:

注意: 默认情况下priority_queue是大堆

void test1()
{vector<int> v{ 3,5,7,1,2,8,9,4,6,0 };priority_queue<int> q1;for (auto& e : v){q1.push(e);}while (!q1.empty()){cout << q1.top() << " ";q1.pop();}cout << endl;
}//测试结果:9 8 7 6 5 4 3 2 1 0

那如果想建立小堆怎么办?

使用仿函数控制,如下:

void test1()
{vector<int> v{ 3,5,7,1,2,8,9,4,6,0 };priority_queue<int,vector<int>,greater<int>> q2;for (auto& e : v){q2.push(e);}while (!q2.empty()){cout << q2.top() << " ";q2.pop();}cout << endl;
}//运行结果:0 1 2 3 4 5 6 7 8 9

使用基本还是跟之前也有,也是很容易上手的。

仿函数

它是一种通过重载operator()运算符,使对象能够像函数一样被调用的特殊类对象。

它替代了C语言的函数指针——函数指针可读性差,且易错,故出现了仿函数。

它常常被用在排序中

class Greater {
public:bool operator()(int a, int b) const {return a > b;}
};class Less {
public:bool operator()(int a, int b) const {return a < b;}
};void test2()
{vector<int> numbers = { 5, 2, 9, 1, 5, 6 };sort(numbers.begin(), numbers.end(), Greater());cout << "降序排序结果: ";for (int num : numbers) {cout << num << " ";}cout << endl;sort(numbers.begin(), numbers.end(), Less());cout << "升序排序结果: ";for (int num : numbers){cout << num << " ";}cout << endl;
}//运行结果:
//降序排序结果: 9 6 5 5 2 1
//升序排序结果: 1 2 5 5 6 9

发现可以改变其升序还是降序

Greater x1;
x1(1, 2);
Less x2;
x2(1, 2);

也可以这样我们能发现它就像函数一样被调用了。

注意:C++11 引入的Lambda 表达式本质上是匿名仿函数(这里博主没学到,后面在讲解吧)


(二) priority_queue的模拟

#pragma oncetemplate <class T>
class Less
{
public:bool operator()(const T& x, const T& y) const  {return x < y;}
};template <class T>
class Greater
{
public:bool operator()(const T& x, const T& y) const {return x > y;}
};namespace Yu
{template <class T, class Container = std::vector<T>, class Compare = Less<T> >class priority_queue{private:// 向下调整(用于删除堆顶元素后)void Adjustdown(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[parent], _con[child]);parent = child;child = parent * 2 + 1;}else{break;  }}}// 向上调整(用于插入新元素后)void AdjustUp(size_t 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:priority_queue() {}// 迭代器范围构造函数(建堆)template <class InputIterator>priority_queue(InputIterator first, InputIterator last){// 先插入所有元素while (first != last){_con.push_back(*first);++first;}// 从最后一个非叶子节点开始向下调整(建堆)for (int i = (_con.size() - 1 - 1) / 2; i >= 0; --i) // 用int避免无符号下溢,i >= 0确保根节点被调整{Adjustdown(i);}}bool empty() const{return _con.empty();}size_t size() const{return _con.size();}const T& top() const{return _con[0];}// 插入元素后向上调整void push(const T& x){_con.push_back(x);AdjustUp(_con.size() - 1);  }// 删除堆顶元素后向下调整void pop(){std::swap(_con[0], _con[_con.size() - 1]);  // 交换堆顶和最后一个元素_con.pop_back();  // 移除最后一个元素(原堆顶)if (!_con.empty()){Adjustdown(0);  // 对新堆顶向下调整}}private:Container _con;  };
}

小细节:

若向上调整和向下调整忘记了可以看——博主之前的博客堆

template <class T, class Container = std::vector<T>, class Compare = Less<T> >

第一个参数:class T

  • 含义:指定优先队列中存储的元素类型。
  • 作用:决定了队列能存放什么类型的数据。

第二个参数:class Container = std::vector<T>

  • 含义:指定优先队列的底层容器类型

第三个参数:class Compare = Less<T>
含义:指定优先级比较规则(仿函数类型),默认使用自定义的Less<T>仿函数。
作用:决定堆的类型(大顶堆 / 小顶堆):

  • Less<T>(默认):比较规则为x < y,构造大顶堆(每次弹出最大元素)。
  • Greater<T>:比较规则为x > y,构造小顶堆(每次弹出最小元素)。

通过修改 Compare 所指定的仿函数类型,无需修改堆的核心调整逻辑,就能快速切换堆的类型

调用如下:

// 大顶堆(默认,使用 Less<T>)
Yu::priority_queue<int> pq1; 
// 底层为大顶堆,弹出最大元素// 小顶堆(显式指定 Greater<T>)
Yu::priority_queue<int, std::vector<int>, Greater<int>> pq2; 
// 底层为小顶堆,弹出最小元素

for (int i = (_con.size() - 1 - 1) / 2; i >= 0; --i)

(size() - 1 - 1)/2:为最后一个非叶子节点

因为最后一个节点索引是size-1,其父节点是(size-1-1)/2)

注意:

如果在priority_queue中放自定义类型的数据,用户需要在自定义类型中提供> 或者< 的重载。

如:日期类的比较大小,如下:

Yu::priority_queue<Date> q1;
q1.push(Date(2025, 8, 7));
q1.push(Date(2025, 1, 25));
q1.push(Date(2025, 11, 22));
cout << q1.top() << endl;

就得增加自定义类型的operator>>和operator<<

class Date
{
public:Date(int year = 2025, int month = 8, int day = 7): _year(year), _month(month), _day(day){}bool operator<(const Date& d)const{return (_year < d._year) ||(_year == d._year && _month < d._month) ||(_year == d._year && _month == d._month && _day < d._day);}bool operator>(const Date& d)const{return (_year > d._year) ||(_year == d._year && _month > d._month) ||(_year == d._year && _month == d._month && _day > d._day);}friend ostream& operator<<(ostream& _cout, const Date& d){_cout << d._year << "-" << d._month << "-" << d._day;return _cout;}
private:int _year;int _month;int _day;
};

priority_queue的完整代码——gitee

(三) 反向迭代器

允许从容器的最后一个元素开始,依次访问到第一个元素。(博主懒所以花了个简单点的)

由上图可知

正向迭代器:

  • begin :指向容器第一个元素( list  因是链表,图示中  begin  与  end  初始位置关联 )
  • end :指向容器尾后位置(不存储元素,标记遍历结束 )

反向迭代器:

  • rbegin :指向容器最后一个有效元素(反向遍历的“起始” )
  • rend :指向容器第一个元素的前一个位置(反向遍历的“结束” )

而库中的反向rbegin为正向end位置,rend为中序begin位置,为什么这样设计了?

  • 接口统一:保持正向与反向迭代器遍历代码结构一致,如正向用 while (it != end) ,反向用 while (it != rend) ,方便使用。
  • 算法兼容:利于标准库通用算法,能在正向和反向遍历容器时正常工作,避免为反向遍历单独设计算法。
  • 实现便利:为不同容器实现反向迭代器提供统一规则,降低实现和维护难度,如 list 用前驱指针、 vector 通过偏移量计算来实现。

总之,他们是镜像对称——反向迭代器和正向迭代器为对称关系

模拟

反向迭代器不直接操作容器,而是通过封装一个正向迭代器 _it 实现功能

#pragma oncenamespace Yu
{template<class Iterator, class Ref, class Ptr>struct ReverseIterator{typedef ReverseIterator<Iterator, Ref, Ptr> Self;Iterator _it;ReverseIterator(Iterator it):_it(it){}Ref operator*(){Iterator tmp = _it;return *(--tmp);}Ptr operator->(){return &(operator*());}Self& operator++(){--_it;return *this;}Self& operator--(){++_it;return *this;}bool operator!=(const Self& s) const{return _it != s._it;}};
}

小细节:

template<class Iterator, class Ref, class Ptr>

第一个参数:class Iterator

  • 底层依赖的正向迭代器类型,反向迭代器通过操作这个正向迭代器实现功能。

第二个参数:class Ref

  • 反向迭代器解引用(operator*)返回的引用类型(如 int& 或 const int&),用于控制元素是否可修改。

第三个参数:class Ptr

  • 反向迭代器箭头操作(operator->)返回的指针类型(如 int* 或 const int*),配合指针访问元素成员。

Ref operator*() {Iterator tmp = _it;   return *(--tmp); }

其底层正向迭代器_it存储的是 “目标元素的下一个位置”(如rbegin()的_it对应正向end())。通过--tmp让临时迭代器回退一位,才能正确指向反向遍历的当前有效元素。

operator++()和operator--()

反向迭代器的++就是正向迭代器的--,而反向迭代器的--就是正向迭代器的++。

Ptr operator->() {return &(operator*()); }

  • 功能:当迭代器指向的元素是自定义类型时,通过 -> 访问其成员(如 (*it).name 等价于 it->name)。
  • 逻辑:通过解引用操作(operator*)获取元素引用,再取地址得到指针。

调用

typedef ReverseIterator<iterator, T&, T*> reverse_iterator;
typedef ReverseIterator<const_iterator, const T&, const T*> const_reverse_iterator;reverse_iterator rbegin()
{return reverse_iterator(end());
}reverse_iterator rend()
{return reverse_iterator(begin());
}const_reverse_iterator rbegin()const
{return const_reverse_iterator(end());
}const_reverse_iterator rend()const
{return const_reverse_iterator(begin());
}

小细节:

由上面的图片可知:

rbegin():返回反向遍历的 “起始点”,指向容器最后一个有效元素。

  • 实现上用容器的正向迭代器end()(尾后位置)初始化,配合ReverseIterator内部的--操作,刚好指向最后一个元素。

rend():返回反向遍历的 “终止点”(第一个元素的前一个位置)。

  • 实现上用正向迭代器begin()(第一个元素位置)初始化,作为反向遍历结束的标志(类似正向遍历的end())。

完整代码——博主的gitee


以上就是priority_queue,仿函数和反向迭代器的知识点了,后续的完善工作我们将留待日后进行。希望这些知识能为你带来帮助!如果觉得内容实用,欢迎点赞支持~ 若发现任何问题或有改进建议,也请随时与我交流。感谢你的阅读

http://www.lryc.cn/news/613380.html

相关文章:

  • Kotlin中String的==相等比较符
  • C语言sprintf、strcmp、strcpy、strcat函数详解:字符串操作的核心工具
  • 「日拱一码」045 机器学习-因果发现算法
  • 力扣238:除自身之外数组的乘积
  • LeetCode算法日记 - Day 4: 三数之和、四数之和
  • 力扣300:最长递增子序列
  • 优选算法 力扣 LCR 179. 查找总价格为目标值的两个商品 双指针降低时间复杂度 C++题解 每日一题
  • Cesium粒子系统模拟风场动态效果
  • 【Zephyr】02_从零教你开发芯片级ADC驱动(HAL层篇)
  • 第三章:【springboot】框架介绍MyBatis
  • 恒虚警检测(CFAR)仿真:杂波边缘与多目标场景分析
  • 在新建word中使用以前文件中的列表样式
  • java中override和overload的区别
  • Java 大视界 -- Java 大数据在智能安防门禁系统中的人员行为分析与异常事件预警(385)
  • AR技术:制造业质量控制的“智能革新”
  • Redis最新安装教程(WindowsLinux)
  • Kubernetes(k8s)之Service服务
  • SpringBoot的优缺点
  • 【更新被拒绝,因为推送的一个分支的最新提交落后于其对应的远程分支。】
  • VLMEvalKit使用记录
  • 公开致歉声明
  • P1690 贪婪的 Copy
  • idea工具maven下载报错:PKIX path building failed,配置忽略SSL检查
  • 量子计算入门 | 量子力学的发展
  • 如何将普通HTTP API接口改造为MCP服务器
  • list类
  • SQL注入攻击基础
  • Cookie和Session是什么?有什么区别?
  • 如何开发一个运行在windows系统服务器上的服务
  • 跨学科视域下的深层语义分析与人类底层逻辑一致性探索