priority_queue模拟实现
1 仿函数
默认比较方式less:
显式修改比较方式为greater:
1. 默认比较方式是less,建大堆,堆顶元素是最大的,遍历顺序就是从大到小
2. 另一个比较方式是greater,建小堆,堆顶元素是最小的,遍历顺序就是从小到大
2 模拟实现
priority_queue的底层就是 堆,所以模拟实现最重要的是实现堆的调整。
2.1 堆的向下调整算法(以大堆为例)
2.1.1 向下调整算法流程
在大堆中,堆的向下调整算法,必须保证以当前节点为根节点的二叉树为大堆。
以下面这个堆举例:
因为这 整个堆是无序的,所以需要 将下面的那个堆建成大堆
经过上面的调整,图中红色方框的堆已经变成大堆了。
接下来要调整以4为根节点的堆。
经过上面的调整,图中红色方框的堆已经变成大堆了。
接下来要调整以1为根节点的堆。
刚才调整的是以6为根节点的堆,所以绿色框中的堆变成了一个大堆。
但是由于之前以9为根节点的堆也调整成了一个大堆,所以红色框中的堆也是一个大堆。
接下来要调整以3为根节点的堆。
到这里,全部调整完毕,整个堆都变成了一个大堆。
调整的全部过程如下:
2.1.2 向下调整代码
void AdjustDown(int parent)
{
size_t child = parent * 2 + 1;
//找到左右孩子中大的那一个
while (child < _con.size())
{
if (child + 1 < _con.size() && _con[child] < _con[child + 1])
{
++child;
}
if (_con[parent] < _con[child])
{
std::swap(_con[parent], _con[child]);
parent = child;
child = parent * 2 + 1;
}
else
{
break;
}
}
}
注意:向下调整是从父节点开始调整,所以传值的时候需要注意 最小的parent应该是最后一个结点下标的父节点。
1. 父节点如何确定?
因为堆是二叉树,所以父节点的确定就是 parent = (child - 1) / 2;
2. 子节点如何确定?
child = parent * 2 + 1;
下面是迭代器构造向下调整建堆的过程:
priority_queue(InputIterator first, InputIterator last, const Compare& comFunc = Compare())
{
//1.将数据放入堆中
while (first != last)
{
_con.push_back(*first);
++first;
}
//2.建堆
for (int i = (_con.size() - 1 - 1) / 2; i >= 0; -- i)
{
//传入下标, 进行向下调整 -- 向下调整是传入parent
AdjustDown(i);
}
}
3. 为什么for (int i = (_con.size() - 1 - 1) / 2; i >= 0; -- i)
中i = (_con.size() - 1 - 1) / 2
?
因为
parent = (child - 1) / 2;
而_con.size() - 1
表示child
的下标,将_con.size() - 1
看作是child
就行了.
4. 下面这句代码是什么意思?
if (child + 1 < _con.size() && _con[child] < _con[child + 1])
{
++child;
}
找到孩子结点中大的那个进行调整建大堆
5. child + 1 < _con.size() && _con[child] < _con[child + 1]
中?两个表达式可以交换位置吗?
不能,这样写是为了防止child + 1越界,如果先执行
_con[child] < _con[child + 1]
可能导致越界访问。
6.如果想要通过向下调整算法建小堆该怎么写代码?
只需要将比较过程中的小于改为大于即可。这样是写死的方法, 后面可以通过调用不同的仿函数来实现不同的建堆。
2.2 堆的向上调整算法(以大堆为例)
假设我们现在已经有一个大堆, 我们需要在堆的末尾插入数据,然后对其进行调整,使其仍然保持大堆的结构。
1. 将要插入的值和父节点的值进行比较
2. 如果插入的值比父节点的值大,则交换位置。
3. 继续比较当前节点和新的父节点的值;如果大,则交换位置。
建大堆的代码:
void AdjustUp(int child)
{
int parent = (child - 1) / 2;
while (child > 0)
{
//把大的往上调
if (_con[parent] < _con[child])
{
std::swap(_con[parent], _con[child]);
//child往上调
child = parent;
parent = (child - 1) / 2;
}
else
{
break;
}
}
}
2.3 push(const T& x)
进入堆的流程:
1. 在最后插入x
2. 利用向上调整算法将x调整到合适的位置
void push(const T& x)
{
//1.将x放入到末尾
_con.push_back(x);
//2.向上调整 -- 最后一个元素
AdjustUp(_con.size() - 1);
}
2.4 pop()
因为出堆是出的堆顶元素,也就是下标为0的元素。 如果直接删掉堆顶元素,后面就不好调整了。所以要先将堆顶元素和最后一个元素 交换,然后出掉交换后的堆顶元素,再对交换到堆顶的最后一个元素进行 向下调整。
出堆的流程:
1. 堆顶元素和堆最后一个元素交换
2. 出掉交换后的堆顶元素
3. 将交换后的最后一个元素向下调整到合适的位置。
void pop()
{
if (empty())
return;
//1.将堆顶元素和堆底元素交换
std::swap(_con[0], _con[_con.size() - 1]);
_con.pop_back();
AdjustDown(0);
}
2.5 仿函数修改调整算法
之前的向上调整和向下调整都是写死的创建大堆,如果想要创建小堆,要自己去改内部的代码,这是很不好的。所以我们需要添加仿函数来控制建大堆还是小堆。
7.使用仿函数的原理是什么?
由用户显式的传递一个仿函数(less或者greater),然后根据传入的内容去调用不同的仿函数。
仿函数的本质是类对象重载了operator(),就让调用类像调用函数一样,所以称为仿函数。
经过仿函数修改的AdjustDown代码:
void AdjustDown(int parent)
{
size_t child = parent * 2 + 1;
//找到左右孩子中大的那一个
while (child < _con.size())
{
//if (child + 1 < _con.size() && _con[child] < _con[child + 1])
if (child + 1 < _con.size() && _comFunc(_con[child], _con[child + 1]))
{
++child;
}
//if (_con[parent] < _con[child])
if (_comFunc(_con[parent], _con[child]))
{
std::swap(_con[parent], _con[child]);
parent = child;
child = parent * 2 + 1;
}
else
{
break;
}
}
}
修改后的AdjustUp代码
void AdjustUp(int child)
{
int parent = (child - 1) / 2;
while (child > 0)
{
//把大的往上调
//if (_con[parent] < _con[child])
if (_comFunc(_con[parent], _con[child]))
{
std::swap(_con[parent], _con[child]);
//child往上调
child = parent;
parent = (child - 1) / 2;
}
else
{
break;
}
}
}
修改后的测试:
传递less,建大堆
传递greater,建小堆
2.3 全部代码
priority_queue.h文件
#pragma once
#include <iostream>
#include <vector>
#include <queue>
using std::cout;
using std::endl;
using std::istream;
using std::ostream;
namespace zyy
{
//建大堆
template <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;
}
};
template <class T, class Container = std::vector<T>, class Compare = less<T>>
class priority_queue
{
public:
//创造一个空的priority_queue,利用缺省参数,如果不传,就是默认的less了
priority_queue(const Compare& comFunc = Compare())
:_comFunc(comFunc)
{}
//构造函数 -- 迭代器构造
template<class InputIterator>
priority_queue(InputIterator first, InputIterator last, const Compare& comFunc = Compare())
{
//1.将数据放入堆中
while (first != last)
{
_con.push_back(*first);
++first;
}
//2.建堆
for (int i = (_con.size() - 1 - 1) / 2; i >= 0; -- i)
{
//传入下标, 进行向下调整 -- 向下调整是传入parent
AdjustDown(i);
}
}
void AdjustDown(int parent)
{
size_t child = parent * 2 + 1;
//找到左右孩子中大的那一个
while (child < _con.size())
{
//if (child + 1 < _con.size() && _con[child] < _con[child + 1])
if (child + 1 < _con.size() && _comFunc(_con[child], _con[child + 1]))
{
++child;
}
//if (_con[parent] < _con[child])
if (_comFunc(_con[parent], _con[child]))
{
std::swap(_con[parent], _con[child]);
parent = child;
child = parent * 2 + 1;
}
else
{
break;
}
}
}
//传入的是child,一般是插入进行向上调整
void AdjustUp(int child)
{
int parent = (child - 1) / 2;
while (child > 0)
{
//把大的往上调
//if (_con[parent] < _con[child])
if (_comFunc(_con[parent], _con[child]))
{
std::swap(_con[parent], _con[child]);
//child往上调
child = parent;
parent = (child - 1) / 2;
}
else
{
break;
}
}
}
void push(const T& x)
{
//1.将x放入到末尾
_con.push_back(x);
//2.向上调整 -- 最后一个元素
AdjustUp(_con.size() - 1);
}
void pop()
{
if (empty())
return;
//1.将堆顶元素和堆底元素交换
std::swap(_con[0], _con[_con.size() - 1]);
_con.pop_back();
AdjustDown(0);
}
bool empty() const
{
return _con.empty();
}
size_t size() const
{
return _con.size();
}
T& top()
{
return _con[0];
}
private:
Container _con; //使用的vector的底层结构
Compare _comFunc; //比较方式
};
};
test-priority_queue.cpp文件
#include"priority_queue.h"
using namespace zyy;
//void test1()
//{
// priority_queue<int> pq;
// pq.push(4);
// pq.push(1);
// pq.push(5);
// pq.push(6);
// pq.push(2);
//
// cout << "pq type:" << typeid(pq).name() << endl;
// for (int i = 0; i < 5; ++i)
// {
// cout << pq.top() << " ";
// pq.pop();
// }
// cout << endl;
//}
//
//void test2()
//{
// priority_queue<int, vector<int>, greater<int>> pq;
// pq.push(4);
// pq.push(1);
// pq.push(5);
// pq.push(6);
// pq.push(2);
//
// cout << "pq type:" << typeid(pq).name() << endl;
// for (int i = 0; i < 5; ++i)
// {
// cout << pq.top() << " ";
// pq.pop();
// }
// cout << endl;
//}
void test3()
{
std::vector<int> vec = { 3, 1, 4, 1, 5, 9, 2, 6, 5 };
priority_queue<int> pq(vec.begin(), vec.end());
cout << endl;
}
void test4()
{
priority_queue<int, std::vector<int>, greater<int>> pq;
pq.push(3);
pq.push(1);
pq.push(4);
pq.push(1);
pq.push(5);
pq.push(9);
pq.push(2);
pq.push(6);
pq.push(5);
for (int i = 0; i < 9; ++i)
{
cout << pq.top() << " ";
pq.pop();
}
cout << endl;
}
int main()
{
test4();
return 0;
}
代码链接:https://github.com/gaogo21/data-structure/tree/main/Queue/Queue