priority_queue模拟实现
1. priority_queue介绍
优先级队列是一种容器适配器,优先级队列默认使用vector作为其底层存储数据的容器,在vector上又使用了堆算法将vector中元素构造成堆的结构,因此priority_queue就是堆,所有需要用到堆的位置,都可以考虑使用priority_queue。
注意:默认情况下priority_queue是大堆。
2. priority_queue使用
2.1 priority_queue显示定义
priority_queue默认vector作为容器,默认情况下为大堆。
//默认
priority_queue<int> p1;
//显示为大堆
priority_queue<int, vector<int>, less<int>> p2;
若是容器为vector,结构为小堆
priority_queue<int, vector<int>, greater<int>> p3;
2.2priority_queue接口使用
成员函数 | 说明 |
---|---|
push | 往优先级队列中尾插数据 |
pop | 删除堆顶元素 |
top | 获取堆顶元素 |
empty | 判断优先级队列是否为空 |
size | 获取优先级队列的元素个数 |
示例:
void test()
{
priority_queue<int> pq;
pq.push(1);
pq.push(3);
pq.push(4);
pq.push(5);
pq.push(2);
pq.push(6);
pq.push(7);
while (!pq.empty())
{
cout << pq.top() << " ";
pq.pop();
}
cout << endl;
}
3. 仿函数
在我们模拟实现priority_queue前,还需要了解仿函数。
仿函数(functor),就是使一个类的使用看上去像一个函数。其实现就是类中实现一个operator(),这个类就有了类似函数的行为,就是一个仿函数类了。
示例:
struct Less
{
bool operator()(const int& val1, const int& val2)
{
return val1 < val2;
}
};
int main()
{
Less lessfunc;
cout << lessfunc(1, 2) << endl;
cout << lessfunc.operator()(1, 2) << endl;//显示调用
cout << Less()(1, 2) << endl;//匿名对象
return 0;
}
4. priority_queue模拟实现
priority_queue底层默认为堆,所以我们需要清楚向上调整算法和向下调整算法(这里以大堆例)。
4.1 向上调整算法
堆的向上调整算法思想(大堆):
- 目标结点与父结点相比较
- 如果目标结点大于父结点,目标结点就与父结点交换位置,后用当前新的位置作为目标结点继续向上调整。如果目标结点小于父结点,则停止向上调整。此时堆为大堆。
例子: 我们往堆中插入数据89
与父结点相比较大于父结点就交换,继续向上调整。
数据89大于88交换。
直到符合条件为止。
代码:
void adjust_up(size_t child)
{
size_t parent = (child - 1) / 2;
while (child > 0)
{
if (c[child]>c[parent])
{
swap(c[child], c[parent]);
child = parent;
parent = (child - 1) / 2;
}
else
{
break;
}
}
}
4.2 向下调整算法
堆的向下调整算法思想(大堆):
- 目标结点与子结点比较
- 若是子结点大于目标结点,则与子结点交换位置。用当前新的位置作为目标结点继续向下调整。若是子结点小于目标结点,则停止向下调整,此时堆为大堆。
示例:
此时堆的左右子树都为大堆,因此我们可以将父结点向下调整。
判断子节点与当前位置数据的大小,大于则交换位置。
继续判断,向下调整。
直到符合目标结点大于子结点为止,此时堆为大堆。
代码:
void adjust_down(size_t parent)
{
size_t child = parent * 2 + 1;
while (child < c.size())
{
//检查左结点是否大于右结点
if (child + 1 < c.size() && c[child+1]>c[child])
{
child++;
}
if (c[child]>c[parent])
{
swap(c[child], c[parent]);
parent = child;
child = parent * 2 + 1;
}
else
{
break;
}
}
}
4.3 实现priority_queue的接口
bool empty() const
{
return c.empty();
}
size_t size() const
{
return c.size();
}
const T& top()
{
return c[0];
}
void push(const T& x)
{
c.push_back(x);
adjust_up(c.size()-1);
}
void pop()
{
swap(c[0], c[size() - 1]);
c.pop_back();
adjust_down(0);
}
4.4 使用仿函数控制priority_queue默认大小堆
我们通过查阅文档可以发现,c++通过改变仿函数对priority_queue队列的默认大小堆的控制。
优势: 相比于函数指针,仿函数更加方便简单,更加利于控制(只需要跟换默认的仿函数)。
4.5 priority_queue模拟实现代码
namespace bit
{
template <class T>
struct less
{
bool operator()(const T& val1,const T& val2)
{
return val1 < val2;
}
};
template <class T>
struct greater
{
bool operator()(const T& val1, const T& val2)
{
return val1 > val2;
}
};
template <class T, class Container = vector<T>, class Compare = less<T> >
//这里默认容器是vector 因为要把优先级队列看成一个大堆,堆的物理结构是有序结构。
class priority_queue
{
public:
void adjust_up(size_t child)
{
Compare com;
size_t parent = (child - 1) / 2;
while (child > 0)
{
if (com(c[parent],c[child]))
{
swap(c[child], c[parent]);
child = parent;
parent = (child - 1) / 2;
}
else
{
break;
}
}
}
void adjust_down(size_t parent)
{
size_t child = parent * 2 + 1;
while (child < c.size())
{
Compare com;
if (child + 1< c.size() && com(c[child],c[child+1]))
{
child++;
}
if (com(c[parent], c[child]))
{
swap(c[child], c[parent]);
parent = child;
child = parent * 2 + 1;
}
else
{
break;
}
}
}
bool empty() const
{
return c.empty();
}
size_t size() const
{
return c.size();
}
const T& top()
{
return c[0];
}
void push(const T& x)
{
c.push_back(x);
adjust_up(c.size()-1);
}
void pop()
{
swap(c[0], c[size() - 1]);
c.pop_back();
adjust_down(0);
}
private:
Container c;
Compare comp;
};
};