⭐本篇重点:
1 priority_queue的使用与底层原理
2 使用容器来适配 priority_queue
⭐本篇代码:c++学习 · 橘子真甜/c++-learning-of-yzc - 码云 - 开源中国 (gitee.com)
⭐标⭐是比较重要的部分
目录
一. priority_queue(优先级队列)的使用与原理
1.1 priority_queue的底层原理⭐
1.2 STL中 priority_queue的原型与函数接口
1.3 prioirty_queue的使用举例
二. 使用模板模拟实现priority_queue
2.1 priority_queue的代码框架
2.2 比较器的仿函数和默认适配容器
2.3 向下调整算法 adjustdown ⭐
2.4 向上调整算法adjustup⭐
2.5 push
2.6 pop
2.7 代码与测试1
2.8 测试2
三. 简单总结一下STL六大组件
四. 下篇内容: 模板的进阶使用
一. priority_queue(优先级队列)的使用与原理
1.1 priority_queue的底层原理⭐
priority_queue(优先级队列)的本质是一个二叉堆。对于堆的介绍和理解以及堆的常见用法,可以看我的这两篇文章:
6.1.数据结构-c/c++模拟实现堆上篇(向下,上调整算法,建堆,增删数据)_c++实现堆的向上调整-CSDN博客
6.1.数据结构-c/c++堆详解下篇(堆排序,TopK问题)_堆排序c++代码实现-CSDN博客
简单来说,堆是一个使用数组来表示完全二叉树的数据结构。堆分为大根堆,小根堆。堆常用于堆排序,处理TopK问题,频繁的插入和删除元素场景下获取最大,最小元素。
1.2 STL中 priority_queue的原型与函数接口
在文档中priority_queue的函数原型如下:
template <class T, class Container = vector<T>,
class Compare = less<typename Container::value_type> > class priority_queue;
其中,第一个参数是priority_queue存储的数据。
第二个参数是priority_queue的适配器(使用什么容器适配priority_queue)。
第三个参数是如何去比较这个优先级队列的比较函数
priority_queue的函数接口如下表:
函数接口 | 说明 |
push() | 在priority_queue中插入一个数据 |
pop() | 在priority_queue中删除一个数据 |
size() | 获取priority_queue中元素的个数 |
empty() | 判断priority_queue是否为空 |
top() | 获取priority_queue堆顶的数据 |
注意: 在STL中,priority_queue的默认是大根堆。
1.3 prioirty_queue的使用举例
举例代码1:
#define _CRT_SECURE_NO_WARNINGS 1
#include <iostream>
#include <vector>
#include <queue>
using namespace std;
void test1()
{
//1. 测试默认的大根堆 priority_queue
//数据类型是int,使用vector适配,比较函数设置为默认
priority_queue<int, vector<int>> pq1;
//在优先级队列中插入10个随机数字
for (int i = 0; i < 10; i++)
{
pq1.push(rand() % 10000);
}
//priority_queue没有迭代器。只能边删除边访问
while (!pq1.empty())
{
cout << pq1.top() << " ";
pq1.pop();
}
cout << endl;
}
int main()
{
srand((unsigned int)time(0));
test1();
}
测试结果:
举例代码2
通过第三个参数来控制大根堆,小根堆
#define _CRT_SECURE_NO_WARNINGS 1
#include <iostream>
#include <vector>
#include <queue>
using namespace std;
template<class T>
struct Compare
{
bool operator()(const T& a, const T& b)
{
return a > b;
}
};
void test1()
{
//2. 使用系统自带的greater生成小根堆
priority_queue<int, vector<int>, greater<int>> pq1;
//在优先级队列中插入10个随机数字
for (int i = 0; i < 10; i++)
{
pq1.push(rand() % 10000);
}
//priority_queue没有迭代器。只能边删除边访问
while (!pq1.empty())
{
cout << pq1.top() << " ";
pq1.pop();
}
cout << endl;
//2.使用仿函数生成自定义的比较函数
priority_queue<int, vector<int>, Compare<int>> pq2;
for (int i = 0; i < 10; i++)
{
pq2.push(rand() % 10000);
}
while (!pq2.empty())
{
cout << pq2.top() << " ";
pq2.pop();
}
cout << endl;
}
int main()
{
srand((unsigned int)time(0));
test1();
}
测试结果如下:
二. 使用模板模拟实现priority_queue
2.1 priority_queue的代码框架
根据优先级队列的原型和接口,我们可以写出其框架。代码如下
类模板中有三个参数,T是数据类型,Container是使用的适配器,Compare是比较器
#pragma once
#include <vector>
namespace YZC
{
//小于比较的仿函数
template<class T>
struct less
{};
//大于比较的仿函数
template<class T>
struct greater
{};
//priority_queue容器适配器
template<class T, class Container, class Compare>
class priority_queue
{
public:
void push(const T& x)
{}
T pop()
{}
T& top()
{}
void size()
{}
bool empty()
{}
private:
Container<T> _con; //适配的成员变量
};
}
2.2 比较器的仿函数和默认适配容器
在这里,使用vector作为默认的适配容器。使用less作为默认的比较器
代码如下:其中两个仿函数比较简单,less返回a < b,gerater返回a > b 即可
#pragma once
#include <vector>
namespace YZC
{
//小于比较的仿函数
template<class T>
struct less
{
bool operator()(const T& a, const T&b)
{
return a < b;
}
};
//大于比较的仿函数
template<class T>
struct greater
{
bool operator()(const T& a, const T& b)
{
return a > b;
}
};
//priority_queue容器适配器
template<class T, class Container = vector<T>, class Compare = less<T>>
class priority_queue
{
public:
void push(const T& x)
{}
T pop()
{}
T& top()
{}
void size()
{}
bool empty()
{}
private:
Container _con; //适配的成员变量
};
}
2.3 向下调整算法 adjustdown ⭐
为了保持堆结构的完整性,我们需要使用调整算法进行调整。其中一个是向下调整算法。该算法用于将一个左右子树都满足堆结构的根节点向下调整,让整个堆都满足堆结构。
调整算法的逻辑可以看我的这篇文章。6.1.数据结构-c/c++模拟实现堆上篇(向下,上调整算法,建堆,增删数据)_c++实现堆的向上调整-CSDN博客
简单的说,就是将根节点向下浮动,在不满足堆结构的情况下(大根堆交换子节点中大的数据,小根堆交换子节点中小的数据)
代码如下:部分难以理解的逻辑在注释中
void adjustdown(size_t root)
{
Compare com{}; //比较器,less是大根堆,greater是小根堆
size_t parent = root;
size_t child = root * 2 + 1; //默认是其左孩子
while (child < _con.size())
{
//child+1 < _con.size() 用于判断有无右孩子,使用com来获取比较的结果
//由于默认是less的大根堆,我们要获取大的孩子,当
if (child + 1 < _con.size() && com(_con[child], _con[child + 1]))
{
child++;
}
//这样child默认是较大的孩子
//此时如果父亲比孩子小,就需要交换
if (com(_con[parent], _con[child]))
{
//交换父子节点
swap(_con[parent], _con[child]);
//重新更新父子节点
parent = child;
child = parent * 2 + 1;
}
else
{
//说明这个结构是正确的不需要交换,直接跳出循环即可
break;
}
}
}
2.4 向上调整算法adjustup⭐
将插入到最后的节点,通过向上调整让整个优先级队列结构合法
代码如下:
void adjustup(size_t child)
{
Compare cmp{};
//将最后一个节点的数据向上移动
size_t parent = (child - 1) >> 1;
while (child > 0)
{
//默认使用仿函数less,子节点比父节点大就要交换
if (cmp(_con[parent], _con[child]))
{
swap(_con[parent], _con[child]);
child = parent;
parent = (child - 1) >> 1;
}
else
{
//说明整个结构是合法的
break;
}
}
}
2.5 push
priority_queue每一次插入数据都在尾部插入数据,所以我们需要插入数据后,对这个数据进行向上调整
代码如下:
void push(const T& x)
{
//1.插入数据
_con.push_back(x);
//2.向上调整
adjustup(static_cast<size_t>(_con.size() - 1);
}
2.6 pop
这个函数用于删除priority_queue堆顶的数据。根据二叉堆的结构,在删除堆顶数据之后需要重新调整整个堆。
所以我们的思路应该是:1 交换堆顶数据和最后一个数据(保证删除堆顶数据之后除了堆顶的左右子树都是合法堆结构)2 删除交换到堆尾的数据,然后对堆顶使用向下
代码如下:
T pop()
{
assert(_con.size() > 0); //有数据才能删除
T ret = _con[0]; //保存堆顶数据用于返回
//1.交换堆顶堆尾的数据
swap(_con[0], _con[_con.size() - 1]);
//2.删除堆尾数据
_con.pop_back();
//3.重新向下调整堆顶
adjustdown(0);
return ret;
}
2.7 top size empty
这三个函数比较简单:
T& top()
{
return _con[0];
}
void size()
{
return _con.size();
}
bool empty()
{
return _con.empty();
}
2.7 代码与测试1
头文件 test.h
#pragma once
#include <vector>
#include <cassert>
using namespace std;
namespace YZC
{
//小于比较的仿函数
template<class T>
struct less
{
bool operator()(const T& a, const T&b)
{
return a < b;
}
};
//大于比较的仿函数
template<class T>
struct greater
{
bool operator()(const T& a, const T& b)
{
return a > b;
}
};
//priority_queue容器适配器
template<class T, class Container = vector<T>, class Compare = less<T>>
class priority_queue
{
public:
void push(const T& x)
{
//1.插入数据
_con.push_back(x);
//2.向上调整
adjustup(static_cast<size_t>(_con.size() - 1));
}
T pop()
{
assert(_con.size() > 0); //有数据才能删除
T ret = _con[0]; //保存堆顶数据用于返回
//1.交换堆顶堆尾的数据
swap(_con[0], _con[_con.size() - 1]);
//2.删除堆尾数据
_con.pop_back();
//3.重新向下调整堆顶
adjustdown(0);
return ret;
}
T& top()
{
return _con[0];
}
void size()
{
return _con.size();
}
bool empty()
{
return _con.empty();
}
private:
void adjustdown(size_t root)
{
Compare cmp{}; //比较器,less是大根堆,greater是小根堆
size_t parent = root;
size_t child = root * 2 + 1; //默认是其左孩子
while (child < _con.size())
{
//child+1 < n 用于判断有无右孩子,使用com来获取比较的结果
//由于默认是less的大根堆,我们要获取大的孩子,当
if (child + 1 < _con.size() && cmp(_con[child], _con[child + 1]))
{
child++;
}
//这样child默认是较大的孩子
//此时如果父亲比孩子小,就需要交换
if (cmp(_con[parent], _con[child]))
{
//交换父子节点
swap(_con[parent], _con[child]);
//重新更新父子节点
parent = child;
child = parent * 2 + 1;
}
else
{
//说明这个结构是正确的不需要交换,直接跳出循环即可
break;
}
}
}
void adjustup(size_t child)
{
Compare cmp{};
//将最后一个节点的数据向上移动
if (child == 0) return; //
size_t parent = (child - 1) >> 1;
while (child > 0)
{
//默认使用仿函数less,子节点比父节点大就要交换
if (cmp(_con[parent], _con[child]))
{
swap(_con[parent], _con[child]);
child = parent;
parent = (child - 1) >> 1;
}
else
{
//说明整个结构是合法的
break;
}
}
}
Container _con; //适配的成员变量
};
void test1()
{
priority_queue<int> pq1{};
for (int i = 0; i < 10; i++)
pq1.push(rand() % 10000);
while (!pq1.empty())
{
cout << pq1.top() << " ";
pq1.pop();
}
cout << endl;
}
}
源文件 test.cpp
#define _CRT_SECURE_NO_WARNINGS 1
#include <iostream>
#include "test.h"
int main()
{
srand((unsigned int)time(0));
YZC::test1();
return 0;
}
测试结果如下:
2.8 测试2
使用less来实现一个小根堆
void test1()
{
priority_queue<int, vector<int>, greater<int>> pq1{};
for (int i = 0; i < 10; i++)
pq1.push(rand() % 10000);
while (!pq1.empty())
{
cout << pq1.top() << " ";
pq1.pop();
}
cout << endl;
}
测试使用deque做适配,并且使用string测试类类型
void test1()
{
//测试string类,并且使用deque
priority_queue<string, deque<string>, less<string>> pq1{};
for (int i = 0; i < 10; i++)
{
string s;
s += 'a' + rand() % 26;
pq1.push(s);
}
while (!pq1.empty())
{
cout << pq1.top() << " ";
pq1.pop();
}
cout << endl;
}
测试结果如下
三. 简单总结一下STL六大组件
容器:vector string list deque map set
迭代器:iterator const_iterator reverse_iterator reverse_const_iterator
适配器:stack queue priority_queue
仿函数:less greater
算法:find sort reverse
空间配置器:无
四. 下篇内容: 模板的进阶使用