这里写目录标题
- 一.优先级队列
- 1.优先级队列的介绍
- 2.priority_queue的定义与使用
- 二.仿函数/函数对象
- 三.优先级队列的模拟实现
一.优先级队列
1.优先级队列的介绍
1)注意优先级队列和队列不是一个东西,队列是容器,优先级队列是一种容器适配器,不提供迭代器。(了解容器适配器,请点击这篇文章:【C++】容器适配器)
2)优先级队列默认使用vector作为其底层存储数据的容器,在vector上又使用了堆算法将vector中的元素构造成堆的结构,因此priority_queue就是堆,所有需要用到堆的位置,都可以考虑使用priority_queue。
3)优先级队列:
less->大堆,默认优先级高的元素先输出;
greater->小堆,默认优先级小的元素先输出。
4)注意priority_queue默认构建大堆。原因:运行下面的代码,向堆中插入数据后,默认使用向上调整算法,构建大堆,我们依次取top(栈顶元素),输出的结果是降序,即优先级高的元素先输出。
对堆的数据结构还不熟悉或忘了的宝子们可以点击这篇博客进行学习——>【数据结构】堆的实现
2.priority_queue的定义与使用
priority_queue的各个成员函数及其功能如下:
方式一:不指定底层容器和内部需要构造的堆结构。(默认使用vector作为底层容器,构造大堆结构)
priority_queue<int> pq;
方式二:使用vector作为底层容器,内部构造大堆结构。
priority_queue<int,vector<int>,less<int>> pq1;
方式三:使用vector作为底层容器,内部构造小堆结构。[使用方式如下图]
priority_queue<int,vector<int>,greater<int>> pq2;
二.仿函数/函数对象
1.在C语言中,我们是用函数指针实现函数的传递。但是函数指针的形式有些累赘,那么C++引入了仿函数(又称函数对象)。
仿函数(函数对象)的定义:如果我们在一个类中重载了()运算符。那么该类的实例化对象就可以像调用函数一样去调用。具体实现过程的代码如下:
#include<iostream>
using namespace std;
//仿函数/函数对象
namespace nn
{
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 Compare>
void bubble_sort(T* a, int n,Compare com)
{
for (int i = 0; i < n-1; i++)
{
int flag = 0;
for (int j = 1; j < n - i; j++)
{
if (com(a[j], a[j-1]))//com是类的实例化对象,com()相当于运算符重载函数,operator>,operator<
{
swap(a[j-1], a[j]);
flag = 1;
}
}
if (flag == 0)
{
break;
}
}
}
int main()
{
nn::less<int> lessFunc;//我们在less类中重载了()运算符,就可以用该类的实例化对象当作函数来使用
nn::greater<int> greaterFunc;//我们在less类中重载了()运算符,就可以用该类的实例化对象当作函数来使用
int a[] = { 2, 3, 4, 5, 6, 1, 2, 4, 9 };
bubble_sort(a, sizeof(a) / sizeof(a[0]), lessFunc);
for (auto e : a)
{
cout << e << " ";
}
cout << endl;
bubble_sort(a, sizeof(a) / sizeof(a[0]), greaterFunc);
for (auto e : a)
{
cout << e << " ";
}
cout << endl;
return 0;
}
2.上面的less和greater类是我们自己实现的,库中也有对应的类,头文件是functional,这里给个C++标准库less类的解释。
三.优先级队列的模拟实现
priority_queue的底层实际上是堆结构,实现priority_queue之前,我们得先知道堆这个数据结构的算法,详情请点击【数据结构】堆的实现
- 升序建大堆——>小于号——>less
- 降序建小堆——>大于号——>greater
#define _CRT_SECURE_NO_WARNINGS 1
namespace nn
{
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>>//优先级队列默认建大堆,less——>小于号
class priority_queue
{
public:
//构造函数
priority_queue()
{}
//迭代器构造
template<class InputIterator>
priority_queue(InputIterator first, InputIterator last)
:_con(first,last)
{
//向下调整建堆,时间复杂度O(N)
for (size_t i = (_con.size() - 1 - 1) / 2; i >= 0; i--)
{
adjust_down(i);
}
}
void adjust_up(size_t child)
{
Compare com;//定义一个仿函数对象,可以用作函数
size_t parent = (child - 1) / 2;
while (child > 0)
{
//if (_con[parent] < _con[child])
if(com(_con[parent],_con[child]))
{
swap(_con[child], _con[parent]);
child = parent;
parent = (child - 1) / 2;
}
else
{
break;
}
}
}
void adjust_down(size_t parent)
{
Compare com;
size_t minchild = parent * 2 + 1;
while (minchild < _con.size())
{
if (minchild + 1 < _con.size() && com(_con[minchild],_con[minchild + 1]))
{
minchild = minchild + 1;
}
//if (_con[parent] < _con[minchild])
if(com(_con[parent],_con[minchild]))
{
swap(_con[minchild], _con[parent]);
parent = minchild;
minchild = 2 * parent + 1;
}
else
{
break;
}
}
}
//一边push一边向上调整
void push(const T& x)
{
_con.push_back(x);
adjust_up(_con.size() - 1);
}
//pop的是栈顶元素,交换栈顶元素和最后一个元素,删除最后一个元素,然后用向下调整算法(时间复杂度logN,因为已经满足左右子树是堆,所以可以直接用向下调整算法)
void pop()
{
swap(_con[0],_con[_con.size() - 1]);
_con.pop_back();
adjust_down(0);
}
const T& top() const
{
return _con[0];
}
size_t size() const
{
return _con.size();
}
bool empty() const
{
return _con.empty();
}
private:
Container _con;
};
}
如果在priority_queue中放自定义类型的数据,用户需要在自定义类型中提供>,<的运算符重载。
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;
};
//对Date*重载两个类
struct PDateLess
{
bool operator()(const Date* x, const Date* y)
{
return *x < *y;
}
};
struct PDateGreater
{
bool operator()(const Date* x, const Date* y)
{
return *x > *y;
}
};
void TestPriorityQueue()
{
// 大堆,需要用户在自定义类型中提供<的重载
priority_queue<Date> q1;
q1.push(Date(2018, 10, 29));//匿名对象
q1.push(Date(2018, 10, 28));
q1.push(Date(2018, 10, 30));
cout << q1.top() << endl;
// 如果要创建小堆,需要用户提供>的重载
priority_queue<Date, vector<Date>, greater<Date>> q2;
q2.push(Date(2018, 10, 29));
q2.push(Date(2018, 10, 28));
q2.push(Date(2018, 10, 30));
cout << q2.top() << endl;
//那如果改成传日期对象的地址呢?
//答:重新重载两个类比较日期的大小
priority_queue<Date*, vector<Date*>, PDateLess> q3;
q3.push(new Date(2018, 10, 29));//匿名对象,new返回的是动态开辟空间的地址
q3.push(new Date(2018, 10, 28));
q3.push(new Date(2018, 10, 30));
cout << *q3.top() << endl;
priority_queue<Date*, vector<Date*>, PDateGreater> q4;
q4.push(new Date(2018, 10, 29));
q4.push(new Date(2018, 10, 28));
q4.push(new Date(2018, 10, 30));
cout << *q4.top() << endl;
}