【C++】优先级队列、仿函数和反向迭代器-创新互联
目录🌠作者:@阿亮joy.
天津ssl适用于网站、小程序/APP、API接口等需要进行数据传输应用场景,ssl证书未来市场广阔!成为创新互联建站的ssl证书销售渠道,可以享受市场价格4-6折优惠!如果有意向欢迎电话联系或者加微信:18980820575(备注:SSL证书合作)期待与您的合作!
🎆专栏:《吃透西嘎嘎》
🎇座右铭:每个优秀的人都有一段沉默的时光,那段时光是付出了很多努力却得不到结果的日子,我们把它叫做扎根
- 👉priority_queue 的介绍👈
- 👉priority_queue 的使用👈
- 👉仿函数👈
- 👉priority_queue 的模拟实现👈
- 👉反向迭代器👈
- 👉总结👈
👉priority_queue 的使用👈
- 优先队列是一种容器适配器,根据严格的弱排序标准,它的第一个元素总是它所包含的元素中大的。
- 此上下文类似于堆,在堆中可以随时插入元素,并且只能检索大堆元素(优先队列中位于顶部的元素)。
- 优先队列被实现为容器适配器,容器适配器即将特定容器类封装作为其底层容器类.priority_queue 提供一组特定的成员函数来访问其元素,元素从特定容器的尾部弹出,其称为优先队列的顶部。
- 底层容器可以是任何标准容器类模板,也可以是其他特定设计的容器类。容器应该可以通过随机访问迭代器访问,并支持以下操作:
- empty():检测容器是否为空
- size():返回容器中有效元素个数
- front():返回容器中第一个元素的引用
- push_back():在容器尾部插入元素
- pop_back():删除容器尾部元素
- 标准容器类 vector 和 deque 满足这些需求。默认情况下,如果没有为特定的 priority_queue 类实例化指定容器类,则使用 vector。
- 需要支持随机访问迭代器,以便始终在内部保持堆结构。容器适配器通过在需要时自动调用算法函数 make_heap、push_heap 和 pop_heap 来自动完成此操作。
优先级队列默认使用 vector 作为其底层存储数据的容器。在 vector 上又使用了向上调整算法和向下调整算法将 vector 中元素构造成堆的结构,因此 priority_queue 就是堆。所有需要用到堆的地方,都可以考虑使用priority_queue。注意:默认情况下 priority_queue 是大堆。
函数声明 | 接口说明 |
---|---|
priority_queue() | 构造一个空的优先级队列 |
priority_queue(first, last) | 迭代器区间构造优先级队列 |
empty( ) | 检测优先级队列是否为空,是返回 true,否则返回 false |
top( ) | 返回优先级队列中大元素(最小元素),即堆顶元素 |
push(x) | 在优先级队列中插入元素 x |
pop() | 删除优先级队列中大元素(最小元素),即堆顶元素 |
size() | 返回优先级队列中元素的个数 |
代码示例:
#include// 优先级队列priority的头文件
#includeusing namespace std;
#include// greater算法的头文件
int main()
{// 默认情况下,创建的是大堆,其底层是按照小于号比较的
priority_queuepq1;
pq1.push(3);
pq1.push(2);
pq1.push(8);
pq1.push(5);
pq1.push(1);
while (!pq1.empty())
{cout<< pq1.top()<< " ";
pq1.pop();
}
cout<< endl;
// 如果要创建小堆,将第三个模板参数换成greater比较方式,其底层是按照大于号比较的
priority_queue, greater>pq2;
pq2.push(3);
pq2.push(2);
pq2.push(8);
pq2.push(5);
pq2.push(1);
while (!pq2.empty())
{cout<< pq2.top()<< " ";
pq2.pop();
}
cout<< endl;
return 0;
}
如果在priority_queue中放自定义类型的数据,用户需要在自定义类型中提供 >或者< 的重载。
#include// 优先级队列priority的头文件
#include// greater算法的头文件
#includeusing namespace std;
class Date
{public:
Date(int year = 1900, int month = 1, int day = 1)
: _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;
};
void TestPriorityQueue()
{// 大堆,需要用户在自定义类型中提供<的重载
priority_queueq1;
q1.push(Date(2018, 10, 29));
q1.push(Date(2018, 10, 28));
q1.push(Date(2018, 10, 30));
while (!q1.empty())
{cout<< q1.top()<< " ";
q1.pop();
}
cout<< endl;
// 如果要创建小堆,需要用户提供>的重载
priority_queue, greater>q2;
q2.push(Date(2018, 10, 29));
q2.push(Date(2018, 10, 28));
q2.push(Date(2018, 10, 30));
while (!q2.empty())
{cout<< q2.top()<< " ";
q2.pop();
}
cout<< endl;
}
int main()
{TestPriorityQueue();
return 0;
}
优先级的队列使用起来没有什么难的,接下来我们来做一道题目,顺便回顾一下堆。
数组中的第K个大元素
给定整数数组 nums 和整数 k,请返回数组中第 k 个大的元素。
请注意,你需要找的是数组排序后的第 k 个大的元素,而不是第 k 个不同的元素。
你必须设计并实现时间复杂度为 O(n) 的算法解决此问题。
解决这道题有两种思路,第一种就是用数组的元素建大堆,然后 pop 掉 k - 1 个元素,此时堆顶的元素就是第 k 大的数。这种思路的时间复杂度为O(N + k * lgN)
,当 N 很大时,就会需要非常多的空间。这时候,我们可以考虑第二种思路就是用前 k 个数建只包含 k 个数的小堆,再遍历数组剩下的元素,比堆顶元素大则替换掉堆顶元素,再向下调整堆。这种思路的时间复杂度为O(k + (N - k) * lgk)
注:顶元素不允许修改,因为堆顶元素修改可以会破坏堆的特性。
class Solution
{public:
int findKthLargest(vector& nums, int k)
{// 建n个数的大堆
priority_queuepq(nums.begin(), nums.end());
// pop掉前k-1个大的数
while(--k) pq.pop();
return pq.top();
}
};
class Solution
{public:
int findKthLargest(vector& nums, int k)
{// 建k个数的小堆,从下标k开始遍历数组
// 如果num[i]大于堆顶的数据,那么nums[i]入堆
// 遍历结束,堆顶的数就是第k大的数
priority_queue, greater>pq(nums.begin(), nums.begin() + k);
for(size_t i = k; i< nums.size(); ++i)
{if(nums[i] >pq.top())
{pq.pop();
pq.push(nums[i]);
}
}
return pq.top();
}
};
👉仿函数👈仿函数也称为函数对象,它是具有operator()
的类对象,可以想函数一样来使用它。
#includeusing namespace std;
// 仿函数/函数对象
templateclass Less
{public:
templatebool operator()(const T& left, const T& right)
{return left< right;
}
};
templateclass Greater
{public:
templatebool operator()(const T& left, const T& right)
{return left >right;
}
};
int main()
{// 仿函数用起来就像是函数
// 有名对象
LesslessFunc;
cout<< lessFunc(1, 2)<< endl;
// lessFunc(1, 2)<==>lessFunc.operator()(1, 2);
// 匿名对象
cout<< Greater()(1, 2)<< endl;
return 0;
}
这样一看,好像仿函数也没有特别大的又是,和函数指针也没有什么大的区别嘛!其实不然,函数指针需要写函数的参数和返回值类型。所以仿函数用起来还是比函数指针舒服的。
#includeusing namespace std;
// 仿函数/函数对象
templateclass Less
{public:
templatebool operator()(const T& left, const T& right)
{return left< right;
}
};
templateclass Greater
{public:
templatebool operator()(const T& left, const T& right)
{return left >right;
}
};
templatevoid BubbleSort(T* a, int n, Compare com)
{for (int i = 0; i< n - 1; ++i)
{int exchange = 0;
for (int j = 1; j< n - i; ++j)
{ // if (a[i]< a[i - 1])
if (com(a[j], a[j - 1]))
{ swap(a[j], a[j - 1]);
exchange = 1;
}
}
if (exchange == 0)
break;
}
}
int main()
{LesslessFunc;
int arr[] = {2, 7,3, 1, 5, 9 ,10,20 };
BubbleSort(arr, sizeof(arr) / sizeof(arr[0]), lessFunc);
for (auto e : arr)
cout<< e<< " ";
cout<< endl;
BubbleSort(arr, sizeof(arr) / sizeof(arr[0]), Greater());
for (auto e : arr)
cout<< e<< " ";
cout<< endl;
return 0;
}
注:没有成员变量的类的大小是 1 个字节,所以传参时加不加引用都没有关系。如果参数用 const 修饰了,那么仿函数也要用 const 来修饰。
如果库提供的仿函数不符合我们的需求,我们可以自己写!
#include// 优先级队列priority的头文件
#include// greater算法的头文件
#includeusing namespace std;
class Date
{public:
Date(int year = 1900, int month = 1, int day = 1)
: _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;
};
struct PDateLess
{bool operator()(const Date* d1, const Date* d2)
{return *d1< *d2;
}
};
struct PDateGreater
{bool operator()(const Date* d1, const Date* d2)
{return *d1 >*d2;
}
};
void TestPriorityQueue()
{priority_queue, PDateLess>q3;
q3.push(new Date(2018, 10, 29));
q3.push(new Date(2018, 10, 28));
q3.push(new Date(2018, 10, 30));
while (!q3.empty())
{cout<< *q3.top()<< " ";
q3.pop();
}
cout<< endl;
priority_queue, PDateGreater>q4;
q4.push(new Date(2018, 10, 29));
q4.push(new Date(2018, 10, 28));
q4.push(new Date(2018, 10, 30));
while (!q4.empty())
{cout<< *q4.top()<< " ";
q4.pop();
}
cout<< endl;
}
int main()
{TestPriorityQueue();
return 0;
}
现在学习到的仿函数还只是冰山一角,以后还会有更多的仿函数要学!!!
👉priority_queue 的模拟实现👈// PriorityQueue.h
#pragma once
namespace Joy
{// 仿函数
templatestruct less
{bool operator()(const T& left, const T& right)
{ return left< right;
}
};
templatestruct greater
{bool operator()(const T& left, const T& right)
{ return left >right;
}
};
template, class Compare = less>class priority_queue
{public:
priority_queue()
: _con()
{}
templatepriority_queue(InputIterator first, InputIterator last)
: _con(first, last)
{ // 注意:这里要使用int!如果使用size_t,如果只有一个元素会出现Bug
int size = _con.size();
for (int i = (size - 2) / 2; i >= 0; --i)
adjust_down(i);
}
void push(const T& val)
{ _con.push_back(val);
adjust_up(_con.size() - 1);
}
void pop()
{ //swap(_con[0], _con[_con.size() - 1]);
swap(_con.front(), _con.back());
_con.pop_back();
adjust_down(0);
}
bool empty() const
{ return _con.empty();
}
size_t size() const
{ return _con.size();
}
const T& top() const
{ assert(!empty());
return _con[0];
}
private:
Container _con; // 底层容器
// 向上调整算法
void adjust_up(size_t child)
{ size_t parent = (child - 1) / 2;
while (child >0)
{
//if(_con[parent]< _con[child])
if (Compare()(_con[parent], _con[child])) // 匿名对象
{swap(_con[parent], _con[child]);
child = parent;
parent = (child - 1) / 2;
}
else
break;
}
}
void adjust_down(size_t parent)
{ size_t child = 2 * parent + 1;
size_t size = _con.size();
while (child< size)
{ // 找出较大的孩子
if (child + 1< size && Compare()(_con[child], _con[child + 1]))
++child;
if (Compare()(_con[parent], _con[child]))
{swap(_con[parent], _con[child]);
parent = child;
child = 2 * parent + 1;
}
else
break;
}
}
};
}
// Test.cpp
#include#includeusing namespace std;
#include "PriorityQueue.h"
int main()
{//priority_queuepq; 默认是大堆
//Joy::priority_queuepq;
Joy::priority_queue, greater>pq;
pq.push(3);
pq.push(2);
pq.push(8);
pq.push(5);
pq.push(1);
while (!pq.empty())
{cout<< pq.top()<< " ";
pq.pop();
}
cout<< endl;
return 0;
}
优先级队列的实现就不讲解了,和用C语言大的区别就是不需要自己造轮子了,直接使用 vector 适配出优先级队列。还有就是使用了仿函数,可以根据传入的仿函数生成大堆还是小堆。如果还有相关的实现细节不了解的话,可以看这篇文章:堆的实现!
👉反向迭代器👈方向迭代器其实也是一种适配器,它可以适配出各种容器的反向迭代器,其主要的是将正向迭代器进行了封装,反向迭代器 ++ 就复用正向迭代器的 - -,反向迭代器 - - 就复用正向迭代器的 ++。
#pragma once
templateclass ReverseInterator
{public:
typedef ReverseInteratorSelf;
ReverseInterator(Interator it)
: _it(it)
{}
Self& operator++()
{--_it;
return *this;
}
Self operator++(int)
{auto tmp = _it;
--_it;
return tmp;
}
Self& operator--()
{++_it;
return *this;
}
Self operator--(int)
{auto tmp = _it;
++_it;
return tmp;
}
Ref operator*()
{auto tmp = _it;
return *(--tmp);
}
// 返回当前对象的地址
Ptr operator->()
{return &(operator*());
}
bool operator!=(const Self& s) const
{return _it != s._it;
}
bool operator==(const Self& s) const
{return _it == s._it;
}
private:
Interator _it; // 对正向迭代器进行封装
};
因为rbegin()
是用end()
来构造的,rend()
使用begin()
来构造的,所以反向迭代器的解引用需要创建一个临时对象tmp
,再返回*(--tmp)
。
测试反向迭代器
注:以上的反向迭代器是用我们自己模拟实现的 list 来测试的!
👉总结👈本篇博客主要讲解了什么是优先级队列、优先级队列的使用和模拟实现、仿函数以及反向迭代器的实现等等。那么以上就是本篇博客的全部内容了,如果大家觉得有收获的话,可以点个三连支持一下!谢谢大家!💖💝❣️
你是否还在寻找稳定的海外服务器提供商?创新互联www.cdcxhl.cn海外机房具备T级流量清洗系统配攻击溯源,准确流量调度确保服务器高可用性,企业级服务器适合批量采购,新人活动首月15元起,快前往官网查看详情吧
当前文章:【C++】优先级队列、仿函数和反向迭代器-创新互联
浏览地址:http://myzitong.com/article/csoehd.html