1.priority_queue概述
priority_queue是一个拥有权值观念的queue,它允许加入新元素、移除旧元素、审视元素值等功能。由于这是一个queue,所以只允许在底端加入元素,并从顶端取出元素,除此之外别无其他存取元素的途径。
priority_queue带有权值观念,其内的元素并非依照被推入的次序排列,而是自动依照元素的权值排列(通常权值以实值表示)。权值最高者,排在最前面。
缺省情况下priority_queue系利用一个大堆完成,该大堆是一个一个以vector表现的完全二叉树。大堆可以满足priority_queue所需要的“依权值高低自动递减排序”的特性。
2.priority_queue C++标准库的基本用法
//priority_queue C++标准库的基本用法
void test_priority_queue()
{
std::priority_queue<int> mypq;
//1、push(val)--在优先级队列中插入元素val
for (int i = 0; i < 10; ++i)
mypq.push(i);
//2、empty()--检测优先级队列是否为空,是返回true,否则返回false
//3、删除优先级队列中最大(最小)元素,即堆顶元素
while (!mypq.empty())
{
std::cout << mypq.top() << " ";
mypq.pop();
}
std::cout << std::endl;
}
3.heap
3.1 heap概述
heap并不归属于STL容器组件,priority_queue允许用户以任何次序将任何元素推入容器中,但取出时一定是从优先权最高(也就是数值最高)的元素开始取。大根堆正是具有这样的特性,适合作为priority_queue底层机制。关于完全二叉树以及堆的详细介绍可以参考二叉树详解、堆的介绍这两篇文章。
堆(heap)是一种特殊的额完全二叉树,它满足下面的性质:
(1)结构性质:堆是一个完全二叉树,这意味着除了最后一层外,每一层都是完全填满的,而最后一层的节点则京可能的集中在左边。
(2)堆性质:在一个最大堆(max heap)中,每一个节点的值都大于或等于其子节点的值,根节点的值是堆中的最大值。相反,在一个最小堆(min heap)中,每个节点的值都小于或等于其子结点的值,根节点的值是堆中的最小值。
如果使用list作为priority_queue的底层机制,元素插入操作是的时间复杂度是O(1)。但是要找到list中的极值,却需要遍历整个链表。
3.2 priority_queue的实现
3.2.1 push()
为了满足二叉树的条件,新加入的元素一定要放在最下一层作为叶节点,并填补在由左至右的第一个空格,也就是把新元素插入在底层vector的end()处。新元素是否适合于其现有位置呢?为了满足max-heap的条件(每个节点的键值都大于或等于其子节点键值)。此时,我们执行一个堆的向上调整程序:将新节点拿来与其父节点比较,如果其键值(key)比父节点大,就父子对换位置。如此一直上溯,直到不需要对换或直到根节点为止,如下图所示。
3.2.1.1 向上调整算法的代码实现
写法1:
//向上调整算法--写法1
void AdjustUp_1(int child)
{
int parent = (child - 1) / 2;
while (child>0)
{
if (_con[parent] < _con[child])
{
std::swap(_con[parent], _con[child]);
child = parent;
parent = (child - 1) / 2;
}
else
{
break;
}
}
}
写法2:
//向上调整算法--写法2
void AdjustUp_2()
{
int child = _con.size()-1;
while (child > 0)
{
int parent = (child - 1) / 2;
if (_con[child] > _con[parent])
{
std::swap(_con[child], _con[parent]);
child = parent;
}
else
{
break;
}
}
}
3.2.1.2 push的实现
//3、将元素添加到优先队列中
void push(const T& val)
{
_con.push_back(val);
//AdjustUp_1(_con.size() - 1);
AdjustUp_2();
}
3.2.2 pop()
队头数据出队列,假如优先队列是降序排列,队头数据出队列即为删除大根堆堆顶数据(根节点)。如果直接pop操作取走根节点,就破坏了堆原有的结构。此时,将堆顶的数据与堆中最后一个元素进行交换。然后,删除最后一个元素,再进行一次向下调整建堆。
3.2.2.1 向下调整算法的代码实现
写法1:
//向下调整算法--写法1
void AdjustDown_1(int root)
{
int parent = root;
int child = root * 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;
}
}
}
写法2:
//向下调整算法--写法2
void AdjustDown_2()
{
int parent = 0;
int size = _con.size();
while (true)
{
int leftChild = 2 * parent + 1;
int rightChild = 2 * parent + 2;
int largest = parent;
if (leftChild < size && _con[largest] < _con[leftChild])
largest = leftChild;
if (rightChild < size && _con[largest] < _con[rightChild])
largest = rightChild;
if (largest != parent)
{
std::swap(_con[parent], _con[largest]);
parent = largest;
}
else
{
break;
}
}
}
3.2.2.2 pop的实现
//4、弹出优先队列中的最大元素
void pop()
{
if (!empty())
{
//1、先将堆顶元素与堆数组中末尾的元素进行交换
std::swap(_con[0], _con[_con.size() - 1]);
//2、此时,弹出堆数组末尾的元素,即堆中最大的元素
_con.pop_back();
//3、使用向下调整算法,调整建堆
//AdjustDown_1(0);
AdjustDown_2();
}
else
{
throw std::runtime_error("Priority queue is empty.");
}
}
4.priority_queue的实现
//实现priority_queue
template<class T, class Container=std::vector<T>>
class MyPriorityQueue
{
public:
//1、默认构造函数
MyPriorityQueue() {}
//2、构造函数,可以指定底层容器
MyPriorityQueue(const Container& c)
:_con(c)
{
//std::make_heap(_con.begin(), _con.end());
//借助向下调整算法,将底层容器调整成堆
//使用方法1的调整方式--从第一个非叶子结点开始调整
for (int i = (_con.size() - 1) / 2; i >= 0; --i)
AdjustDown_1(i);
//使用方法2的调整方式--先调整左右子树其中的一个
//for (int i = (_con.size() / 2) - 1; i >= 0; --i)
//AdjustDown_2();
}
//3、将元素添加到优先队列中
void push(const T& val)
{
_con.push_back(val);
//std::push_heap(_con.begin(), _con.end());
//AdjustUp_1(_con.size() - 1);
AdjustUp_2();
}
//4、弹出优先队列中的最大元素
void pop()
{
if (!empty())
{
//std::pop_heap(_con.begin(), _con.end());
//1、先将堆顶元素与堆数组中末尾的元素进行交换
std::swap(_con[0], _con[_con.size() - 1]);
//2、此时,弹出堆数组末尾的元素,即堆中最大的元素
_con.pop_back();
//3、使用向下调整算法,调整建堆
//AdjustDown_1(0);
AdjustDown_2();
}
else
{
throw std::runtime_error("Priority queue is empty.");
}
}
//5、访问优先队列中的最大元素
T& top()
{
if (!empty())
{
return _con[0];
}
else
{
throw std::runtime_error("Priority queue is empty.");
}
}
//6、检查优先队列是否为空
bool empty() const
{
return _con.empty();
}
//7、返回优先队列的大小
size_t size() const
{
return _con.size();
}
private:
Container _con;//使用底层元素存储优先队列的元素
//向上调整算法--写法1
void AdjustUp_1(int child)
{
int parent = (child - 1) / 2;
while (child>0)
{
if (_con[parent] < _con[child])
{
std::swap(_con[parent], _con[child]);
child = parent;
parent = (child - 1) / 2;
}
else
{
break;
}
}
}
//向上调整算法--写法2
void AdjustUp_2()
{
int child = _con.size()-1;
while (child > 0)
{
int parent = (child - 1) / 2;
if (_con[child] > _con[parent])
{
std::swap(_con[child], _con[parent]);
child = parent;
}
else
{
break;
}
}
}
//向下调整算法--写法1
void AdjustDown_1(int root)
{
int parent = root;
int child = root * 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;
}
}
}
//向下调整算法--写法2
void AdjustDown_2()
{
int parent = 0;
int size = _con.size();
while (true)
{
int leftChild = 2 * parent + 1;
int rightChild = 2 * parent + 2;
int largest = parent;
if (leftChild < size && _con[largest] < _con[leftChild])
largest = leftChild;
if (rightChild < size && _con[largest] < _con[rightChild])
largest = rightChild;
if (largest != parent)
{
std::swap(_con[parent], _con[largest]);
parent = largest;
}
else
{
break;
}
}
}
};
完整代码可参考:优先队列的使用以及实现。