目录
引言
优先级队列( priority_queue )是一种特殊的容器适配器,在C++标准库中被广泛应用。与普通队列“先进先出”的特性不同,优先级队列每次从队首取出的元素总是队列中优先级最高的元素。本文将深入剖析优先级队列的底层实现原理,并通过完整的C++代码,手把手教你实现一个自定义的优先级队列模板类。
1. 优先级队列的核心概念
优先级队列通常基于堆数据结构实现,堆是一种完全二叉树,分为大顶堆和小顶堆:
- 大顶堆:每个父节点的值都大于或等于其子节点的值,因此堆顶元素是整个堆中的最大值
- 小顶堆:每个父节点的值都小于或等于其子节点的值,堆顶元素是最小值
C++标准库中的 priority_queue 默认实现为大顶堆。我们的自定义实现将支持通过自定义比较器,灵活切换大顶堆和小顶堆模式。
2. 比较器模板类设计
首先定义两个比较器模板类,用于定义元素之间的比较规则:
cpp
// 小于比较器模板类,用于构建小顶堆
template<class T>
class Less
{
public:
// 重载()运算符实现比较逻辑
bool operator()(const T& x, const T& y)
{
return x < y;
}
};
// 大于比较器模板类,用于构建大顶堆
template<class T>
class GreaTor
{
public:
bool operator()(const T& x, const T& y)
{
return x > y;
}
};
通过重载 () 运算符,我们将比较逻辑封装成可调用对象。 Less 类实现小于比较,适用于小顶堆; GreaTor 类实现大于比较,适用于大顶堆。
3. 优先级队列模板类实现
3.1 类模板定义
cpp
namespace ldg
{
// 优先级队列模板类
// T: 元素类型
// X: 底层存储容器类型,默认使用std::vector
// Compare: 比较器类型,默认使用Less
template<class T, class X = std::vector<T>, class Compare = Less<T>>
class priority_queue
{
private:
X _con; // 底层存储容器
// 堆调整函数声明
void AdjustDown(int parent);
void AdjustUp(int child);
public:
// 构造函数、插入、删除等成员函数声明
template<class Intiterator>
priority_queue(Intiterator first, Intiterator last);
void Push(const T& x);
priority_queue();
void pop();
const T& top();
bool empty();
size_t size();
};
}
通过模板参数 T 、 X 和 Compare ,我们实现了类型、容器和比较规则的高度可定制化。
3.2 堆调整核心函数
3.2.1 向下调整函数 AdjustDown
cpp
template<class T, class X, class Compare>
void ldg::priority_queue<T, X, Compare>::AdjustDown(int parent)
{
Compare com; // 创建比较器对象
int child = parent * 2 + 1; // 左孩子索引
while (child < _con.size()) // 确保孩子节点在有效范围内
{
// 如果存在右孩子且右孩子优先级更高
if (child + 1 < _con.size() && com(_con[child], _con[child + 1]))
{
child++; // 选择右孩子
}
// 如果父节点优先级低于孩子节点
if (com(_con[parent], _con[child]))
{
std::swap(_con[parent], _con[child]); // 交换父子节点
parent = child; // 更新父节点位置
child = parent * 2 + 1; // 更新孩子节点位置
}
else {
break; // 堆性质已满足
}
}
}
该函数从指定父节点开始,通过比较和交换操作,将元素下沉到合适位置,确保以 parent 为根的子树满足堆的性质。
3.2.2 向上调整函数 AdjustUp
cpp
template<class T, class X, class Compare>
void ldg::priority_queue<T, X, Compare>::AdjustUp(int child)
{
Compare com;
int parent = (child - 1) / 2; // 计算父节点索引
while (child > 0) // 向上调整直到根节点
{
// 如果孩子节点优先级高于父节点
if (com(_con[parent], _con[child]))
{
std::swap(_con[parent], _con[child]); // 交换父子节点
child = parent; // 更新孩子节点位置
parent = (child - 1) / 2; // 更新父节点位置
}
else {
break; // 堆性质已满足
}
}
}
AdjustUp 函数从指定孩子节点开始,将元素上浮,确保从该节点到根节点路径上的所有节点满足堆的性质。
3.3 成员函数实现
3.3.1 迭代器构造函数
cpp
template<class T, class X, class Compare>
template<class Intiterator>
ldg::priority_queue<T, X, Compare>::priority_queue(Intiterator first, Intiterator last)
{
while (first != last)
{
_con.push_back(*first); // 插入元素到容器
first++;
}
// 建堆操作
for (int i = (_con.size() - 2) / 2; i >= 0; i--)
{
AdjustDown(i); // 从最后一个非叶子节点开始调整
}
}
该构造函数通过迭代器范围初始化队列,并通过 AdjustDown 函数将容器中的元素构建成堆。
3.3.2 元素插入函数 Push
cpp
template<class T, class X, class Compare>
void ldg::priority_queue<T, X, Compare>::Push(const T& x)
{
_con.push_back(x); // 添加元素到容器末尾
AdjustUp(_con.size() - 1); // 向上调整新元素
}
新元素插入后,通过 AdjustUp 函数上浮到正确位置,保持堆的性质。
3.3.3 元素删除函数 pop
cpp
template<class T, class X, class Compare>
void ldg::priority_queue<T, X, Compare>::pop()
{
std::swap(_con[0], _con[_con.size() - 1]); // 交换堆顶和堆尾
_con.pop_back(); // 删除堆尾元素
AdjustDown(0); // 向下调整堆顶
}
删除操作先将堆顶元素与堆尾元素交换,移除堆尾元素后,通过 AdjustDown 函数重新调整堆顶元素。
3.3.4 其他辅助函数
cpp
template<class T, class X, class Compare>
const T& ldg::priority_queue<T, X, Compare>::top()
{
return _con[0]; // 返回堆顶元素
}
template<class T, class X, class Compare>
bool ldg::priority_queue<T, X, Compare>::empty()
{
return _con.empty(); // 判断容器是否为空
}
template<class T, class X, class Compare>
size_t ldg::priority_queue<T, X, Compare>::size()
{
return _con.size(); // 返回容器大小
}
这些函数分别用于获取堆顶元素、判断队列是否为空以及获取队列大小。
4. 测试与验证
cpp
namespace ldg
{
void test()
{
priority_queue<int> pq; // 使用默认小顶堆
pq.Push(5);
pq.Push(9);
pq.Push(3);
pq.Push(1);
pq.Push(5);
pq.Push(6);
pq.Push(0);
pq.Push(4);
std::cout << "小顶堆输出: ";
while (!pq.empty())
{
std::cout << pq.top() << " ";
pq.pop();
}
std::cout << std::endl;
// 使用大顶堆测试
priority_queue<int, std::vector<int>, GreaTor<int>> pq2;
pq2.Push(5);
pq2.Push(9);
pq2.Push(3);
pq2.Push(1);
pq2.Push(5);
pq2.Push(6);
pq2.Push(0);
pq2.Push(4);
std::cout << "大顶堆输出: ";
while (!pq2.empty())
{
std::cout << pq2.top() << " ";
pq2.pop();
}
std::cout << std::endl;
}
}
int main()
{
ldg::test();
return 0;
}
通过 test 函数,我们分别测试了默认小顶堆和自定义大顶堆的功能,验证了优先级队列的正确性。
5. 总结
本文通过详细的代码解析和注释,完整实现了一个功能强大的C++优先级队列模板类。通过自定义比较器,我们可以灵活控制堆的类型(大顶堆/小顶堆),同时深入理解了堆调整的核心算法。这种自定义实现不仅有助于掌握数据结构原理,也为实际项目中灵活使用优先级队列提供了坚实基础。