优先队列(堆、左高树)
优先级队列
优先级队列 (Priority Queue) 是0个或多个元素的集合,每个元素都有一个优先级 (权值)。
优先级队列的操作有:1. top 2. push 3. pop
在最大优先级队列中,查找和删除的元素都是当前集合中优先级最大的元素。
在最小优先级队列中,查找和删除的元素都是当前集合中优先级最小的元素。
(对于优先级相同的元素,其出队的顺序是未知的)
堆
- 堆的性质:
- 堆是一颗完全二叉树
- 堆是一个大根树 (小根树),父节点的优先级 大于 (小于) 或等于其子节点
- n个元素的堆的高度为 ,在 时间内可以完成插入和删除操作
- 堆的代码实现 (自己写的easy版)
#include <vector>
using std::vector;
using std::less;
// 默认容器是vector,默认仿函数是less(大根堆)
// 满足堆中所有元素都和堆顶保持比较谓词的关系 less or greater
template<class T, class _Container=vector<T>, class _Compare=less<T>>
class myHeap
{
protected:
_Container c;
_Compare cmp;
public:
myHeap() {}
// 避免不必要的隐式类型转换
explicit myHeap(const _Compare& x) : cmp(x) {}
size_t size() { return c.size(); }
bool empty() { return !c.size(); }
T top() { return c.front(); }
// 堆中插入元素,在堆底插入然后向上调整即可
void push(const T& element)
{
c.push_back(element);
if(size() > 1) up(c.size() - 1);
}
// 删除堆顶,由于线性表移动元素的时间开销较大
// 所以将堆顶和最后一个元素交换,然后尾删,对新堆顶向下调整
void pop()
{
if (size() >= 2)
{
heapSwap(0, c.size() - 1);
c.pop_back();
down(0);
}
else c.clear();
}
// 从其他容器建堆
template<class Iterator>
void makeHeap(Iterator first, Iterator end)
{
int len = end - first;
if (len < 2) return;
c.clear();
for (auto cur = first; cur != end; cur++)
c.push_back(*cur);
for (int root = (c.size() - 2) / 2; root >= 0; root--)
down(root);
}
private:
// 根据下标交换两个元素
void heapSwap(const int idxA, const int idxB)
{
T tmp = c[idxA];
c[idxA] = c[idxB];
c[idxB] = tmp;
}
// 元素向上调整
void up(const int idx)
{
int parent = (idx - 1) / 2;
int son = idx;
while (parent >= 0)
{
if (cmp(c[parent], c[son]))
{
heapSwap(son, parent);
son = parent;
parent = (son - 1) / 2;
}
else break;
}
}
// 元素向下调整
void down(const int idx)
{
// 获取父节点、左孩子、右孩子中优先级最高的
int cur = idx, left = idx * 2 + 1, right = idx * 2 + 2;
if (left < c.size() && cmp(c[cur], c[left]))
cur = left;
if (right < c.size() && cmp(c[cur], c[right]))
cur = right;
// 如果优先级最高的时某个子节点,则交换元素后进行递归
if (cur != idx)
{
heapSwap(cur, idx);
down(cur);
}
}
};
-
时间复杂度分析:
-
push
最坏情况下,插入的元素是最大的,需要沿着树枝一路向上,每次递归的复杂度是 ,递归深度是 ,总时间复杂度为 -
pop
交换堆顶和最后一个元素时间复杂度为 ,将堆顶向下调整最坏时间复杂度为 ,总时间复杂度为 -
top
直接获取堆顶,也就是第一个元素,时间复杂度为 -
makeHeap
从其他容器建堆,while循环每一次down的时间为 , 是以 为根的树的高度,在完全二叉树中为 ,在第 层最多有 个节点。可以推导出时间复杂度:其中
-
-
堆是一个隐式数据结构,其优点是空间利用效率高,但是多个优先队列合并时比较麻烦。
左高树
令 为该节点,到以该点为根的子树中最近的一个叶子节点的距离 (如果有一个孩子节点为null,则为1)
的递推式:
- 左高树是一个二叉树
- 左高树中,每一个内部节点满足
- 如果这个条件是 左孩子的权重 >= 右孩子的权重,那么这棵树被称为 WBLT
- 如果HBLT同时也是一个大根树,则其称为最大HLBT;如果是小根数则是最小HBLT
定理:
- 以为根的子树节点数目至少为
- 以为根的子树有个节点时, 最大为
- 从到一个外部节点的最右路径的长度为
- 代码实现
#include <queue>
using std::pair;
using std::queue;
using std::less;
// 二叉树节点
template <class T>
struct binaryTreeNode
{
// T在HBLT中一般是pair,first和second一般用于保存s(x)和元素
T element;
binaryTreeNode<T>* leftChild, * rightChild;
binaryTreeNode() { leftChild = rightChild = nullptr; }
binaryTreeNode(const T& theElement) : element(theElement) { leftChild = rightChild = nullptr; }
binaryTreeNode(const T& theElement, binaryTreeNode* theLeftChild, binaryTreeNode* theRightChild) :
element(theElement), leftChild(theLeftChild), rightChild(theRightChild) {}
};
// 默认仿函数是less(最大HBLT)
template <class T, class _Compare = less<T>>
class HBLT
{
public:
HBLT() { root = nullptr; treeSize = 0; }
explicit HBLT(const _Compare& x) : cmp(x) {}
void push(const T& theElement);
void pop();
T top() const { return root->element.second; }
void merge(HBLT<T>& theHBLT);
template<class Iterator>
void makeHBLT(Iterator begin, Iterator end);
private:
void merge(binaryTreeNode<pair<int, T>>*& a, binaryTreeNode<pair<int, T>>*& b);
binaryTreeNode<pair<int, T>>* root;
int treeSize;
_Compare cmp;
};
template <class T, class _Compare>
void HBLT<T, _Compare>::merge(binaryTreeNode<pair<int, T>>*& a, binaryTreeNode<pair<int, T>>*& b)
{
// 将b树合并到a树上,并以a的根为最后的根
if (b == nullptr) return;
if (a == nullptr) { a = b; return; }
// 按照仿函数堆根的值进行修改
if (cmp(a->element.second, b->element.second))
swap(a, b);
// 按照 a 的最右路径,将 b 树合并到 a 树的右子树上
merge(a->rightChild, b);
// 如果 a 的左子树为空,则将 b 树移到 a 的左子树,此时 a 的右子树一定非空
if (a->leftChild == nullptr)
{
a->leftChild = a->rightChild;
a->rightChild = nullptr;
// a 到最近的空叶子结点的距离很显然就是到右子树的距离 1
a->element.first = 1;
}
else
{
// 将高度较高的树放到左侧
if (a->leftChild->element.first < a->rightChild->element.first)
swap(a->leftChild, a->rightChild);
a->element.first = a->rightChild->element.first + 1;
}
}
template <class T, class _Compare>
void HBLT<T, _Compare>::merge(HBLT<T>& theHBLT)
{
merge(root, theHBLT.root);
treeSize += theHBLT.treeSize;
theHBLT.root = nullptr;
theHBLT.treeSize = 0;
}
template <class T, class _Compare>
void HBLT<T, _Compare>::push(const T& theElement)
{
// 新建一个只有根节点的树,将其合并到 root 上
auto newNode = new binaryTreeNode<pair<int, T>>(pair<int, T>(1, theElement));
merge(root, newNode);
treeSize++;
}
template <class T, class _Compare>
void HBLT<T, _Compare>::pop()
{
if (root == nullptr)
return;
// 将 root 删除后,将 left 设置为 root, 并将 right 合并到 root 上
auto left = root->leftChild;
auto right = root->rightChild;
delete root;
root = left;
merge(root, right);
treeSize--;
}
template <class T, class _Compare>
template <class Iterator>
void HBLT<T, _Compare>::makeHBLT(Iterator begin, Iterator end)
{
queue<binaryTreeNode<pair<int, T>>*> q;
int theSize = end - begin;
// 每个元素创建为一个子树,后续两个两个进行合并操作
for(auto cur = begin; cur != end; cur ++)
q.push(new binaryTreeNode<pair<int, T>>(pair<int, T>(1, *cur)));
// 总共theSize个点,需要theSize-1次合并
for (int i = 1; i <= theSize - 1; ++i)
{
auto b = q.front(); q.pop();
auto c = q.front(); q.pop();
merge(b, c);
q.push(b);
}
if (theSize > 0)
root = q.front();
treeSize = theSize;
}
- 时间复杂度分析
merge
需要对两个树的最右路径进行搜索,每棵树的最右路径的长度是 和 ,每次移动的时间复杂度为 ,总时间复杂度为top
的时间复杂度为makeHBLT
的时间复杂度为
- 左高树在多个优先队列合并时效率很高
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享4款.NET开源、免费、实用的商城系统
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
· 上周热点回顾(2.24-3.2)