今天,我们来学习一下优先队列。
一:优先队列的认识
首先,我们来认识一下什么是优先队列?它与我们之前写的队列有什么区别呢?
看看文档中的priority_queue说明:



优先级队列默认使用vector作为其底层存储数据的容器,在vector上又使用了堆算法将vector中元素构造成堆的结构,因此priority_queue就是堆,所有需要用到堆的位置,都可以考虑使用priority_queue。注意:默认情况下priority_queue是大堆。
其中,我们在c语言阶段也写过堆了,有兴趣的伙伴可以自行去了解:
简单认识priority_queue的接口
总括:有了我们之前对各种接口的认识,现在看到这些接口名字应该可以大致明白什么意思和如何使用了。
| 函数声明 | 接口说明 |
| priority_queue()/priority_queue(first, last) | 构造一个空的优先级队列 |
| empty( ) | 检测优先级队列是否为空,是返回true,否则返回false |
| top( ) | 返回优先级队列中最大(最小元素),即堆顶元素 |
| push(x) | 在优先级队列中插入元素x |
| pop() | 删除优先级队列中最大(最小)元素,即堆顶元素 |

好了,简单看了它们的接口后,现在我们来正式来模拟实现一下:
模拟实现
(一)priority_queue成员变量
1.由上面我们知道,priority_queue的底层容器是用vector或者deque来实现的。为了方便,我们不妨使用一个模板来实现,到我们使用时去决定调用vector还是deque自己决定即可。
namespace bai { template<class T, class Container = vector<T>, class Cmp = less<T>> private: Container _con; };
(二)priority_queue 构造函数
1.这里我们使用迭代器区间来进行初始化。即,用这个迭代器区间的元素插入到这个堆,然后再根据优先队列本质上是堆,来建成大堆。
2.而我们又知道迭代器的类型有多种。那么,这里利用模板来自由控制迭代器的类型。

C语言阶段的向下调整的思路代码:
![]()
向下调整部分
void Adjustdown(int parent)
{
Cmp com;
//父亲与孩子结点之间的关系
int child = parent * 2 + 1;
while (child<_con.size())
{
//由于我们上面用了类模板的比较,所以变更加灵活,
//主要看自己实现时控制的 是less还是greater
if (child + 1 < _con.size() && com(_con[child] , _con[child + 1]))
{
child=child +1;
}
if (com(_con[parent],_con[child] ))
{
//交换,更新
swap(_con[child], _con[parent]);
parent = child;
child = parent * 2 + 1;
}
else
{
break;
}
}
}
1.为什么int i = (_con.size() - 1 - 1) / 2?
答:我们在C语言阶段也有解释过,_con.size() - 1是最后一个元素的下标(即孩子),又因为i找的是父亲的结点。它与孩子结点的关系是:parent=(child-1)/2。
template<class Inputiterator> Priority_Queue(Inputiterator frist, Inputiterator last) { //插入迭代器区间元素 while (frist != last) { _con.push_back(*frist); frist++; } //建大堆 for (int i = (_con.size() - 1 - 1) / 2; i >= 0; i--) { Adjustdown(i); } }
无参构造函数:
为什么还要写一个无参构造函数呢?
因为我们上面使用了迭代器区间来初始化了,所以编译器就不会自动帮你生成一个 无参的构造函数,所以在我们后边要定义无参的优先队列变量得自己去实现。
Priority_Queue()
{}
(三)push插入部分
这部分的整体思路比较简单,因为底层是vector(这里也可以list)所以直接调用它们的接口插入元素。接入元素后,原来的大堆就可能变乱了,所以重新向上调整法建大堆。
void push(const T& val) { _con.push_back(val); Adjustup(_con.size() - 1); }
向上调整法
void Adjustup(int child)
{
Cmp com;
int parent = (child - 1) / 2;
while (child>0)
{
if (com(_con[parent], _con[child]))
{
swap(_con[parent], _con[child]);
child = parent;
parent = (child - 1) / 2;
}
else
{
break;
}
}
}
(四)pop删除部分
思路:看上面
void pop() { swap(_con[0], _con[_con.size() - 1]); _con.pop_back(); Adjustdown(0); }
(五)返回top堆顶元素
1.即第一个元素
const T& top() { return _con[0]; }
(六)返回size大小
int size()
{
return _con.size();
}
(七)判断空
bool empty()
{
return _con.empty();
}
好了,优先队列的模拟实现基本就完成了。
附上完整代码和测试代码:
namespace bai
{
template<class T, class Container = vector<T>, class Cmp = less<T>>
class Priority_Queue
{
private:
void Adjustdown(int parent)
{
Cmp com;
int child = parent * 2 + 1;
while (child<_con.size())
{
if (child + 1 < _con.size() && com(_con[child] , _con[child + 1]))
{
child=child +1;
}
if (com(_con[parent],_con[child] ))
{
swap(_con[child], _con[parent]);
parent = child;
child = parent * 2 + 1;
}
else
{
break;
}
}
}
void Adjustup(int child)
{
Cmp com;
int parent = (child - 1) / 2;
while (child>0)
{
if (com(_con[parent], _con[child]))
{
swap(_con[parent], _con[child]);
child = parent;
parent = (child - 1) / 2;
}
else
{
break;
}
}
}
public:
Priority_Queue()
{}
template<class Inputiterator>
Priority_Queue(Inputiterator frist, Inputiterator last)
{
while (frist != last)
{
_con.push_back(*frist);
frist++;
}
//
for (int i = (_con.size() - 1 - 1) / 2; i >= 0; i--)
{
Adjustdown(i);
}
}
void push(const T& val)
{
_con.push_back(val);
Adjustup(_con.size() - 1);
}
void pop()
{
swap(_con[0], _con[_con.size() - 1]);
_con.pop_back();
Adjustdown(0);
}
const T& top()
{
return _con[0];
}
int size()
{
return _con.size();
}
bool empty()
{
return _con.empty();
}
private:
Container _con;
};
void Test_priority_queue()
{
Priority_Queue<int, vector<int>,Greater<int>> pq;
pq.push(3);
pq.push(5);
pq.push(1);
pq.push(4);
//pq.pop();
while (!pq.empty())
{
cout << pq.top() << " ";
pq.pop();
}
cout << endl;
}
但是,文章还没有结束,我们还额往补充一些知识:
仿函数:
1.概念:一种行为类似函数的对象。它是通过定义一个类,并重载()运算符来实现,这样该类的对象就可以像函数一样使用被调用。
2.举例子:
1)简单的仿函数:

2)仿函数用于排序:

这里Greater仿函数类重载了()运算符,它返回一个布尔值,用于告诉sort函数如何比较两个元素,从而实现降序排序。
好了,现在我们再以日期的比较再次认识仿函数:
class Date
{
public:
Date(int year = 1999, int month = 7, int day = 10)
:_year(year)
,_month(month)
,_day(day)
{}
bool operator<(const Date& d1)const
{
return (_year < d1._year)
|| (_year == d1._year && _month < d1._month)
|| (_year == d1._year && _month == d1._month && _day < d1._day);
}
bool operator ==(const Date& d1)const
{
return (_year == d1._year)
&& (_month == d1._month)
&& (_day == d1._day);
}
bool operator>(const Date& d1)const
{
return (_year > d1._year)
|| (_year == d1._year && _month > d1._month)
|| (_year == d1._year && _month == d1._month && _day > d1._day);
}
friend ostream& operator<<(ostream& out,const Date& d);
private:
int _year;
int _month;
int _day;
};
ostream& operator<<(ostream& _cout,const Date&d)
{
_cout << d._year << "-" << d._month << "-" << d._day << endl;
return _cout;
}
//仿函数的用法
struct LessDate
{
bool operator()(const Date* p1,const Date* p2)
{
return (*p1) < (*p2);
}
};
void test_Date()
{
Priority_Queue<Date*, vector<Date*>,LessDate> pq;
pq.push(new Date(1999, 1, 1));
pq.push(new Date(1999, 1, 5));
pq.push(new Date(1999, 1, 3));
pq.push(new Date(1999, 1, 2));
while (!pq.empty())
{
cout << *pq.top() << " ";
pq.pop();
}
cout << endl;
}
我们发现如果没有加入仿函数的话,日期的比较是错误的
在C++ 中,通常自定义的日期类如果没有重载比较运算符,那么直接比较对象时,比较的是对象在内存中的地址。但一般情况下,我们会重载比较运算符(如 == 、 < 、 > 等)来按照日期的实际值进行比较,比如按照年、月、日的顺序依次比较
![]()

好了,本次分享就先到这里了,希望大家都有所进步!
最后,到了本次鸡汤环节:
图片文字与大家共勉!


4319

被折叠的 条评论
为什么被折叠?



