什么是堆
首先我们先了解什么是堆?堆分为大根堆和小根堆。但其实大根堆会让人误以为是不是大的元素在下面呢?为了防止错误想法,大根堆也可以叫大顶堆。
大顶堆:顶上元素最大,上一层比下一层元素大。
小顶堆:顶上元素最小,上一层比下一层元素小。
什么是priority_queue优先级队列
priority_queue就是用vector 作为 模版,然后里面的数据填充的方法用的是堆算法。所以priority_queue就是堆。只不过是提供了对应接口的堆。不用手动去敲。
怎么定义priority_queue对象
方法一:默认定义(只指定是什么元素)
priority_queue<int> q;
默认定义:造出来的直接就是一个大顶堆。
方法二:定义大顶堆(指定元素以及用什么当模版以及比较函数)
priority_queue<int, vector<int>, less<int>> q1;
上面不是说priority_queue用vector做模版吗?为什么这里我们还要写一遍。因为对于template模版 而言缺省值,只能从右给到左,如果要修改最后一个模版less<int>,那对应的前面的模版都得手动填写。
为什么大顶堆用less,因为less只是比较函数,比大小的和大顶堆的定义不冲突。可以想的是用less的话是小的元素往下跑。
方法三:定义小顶堆(指定元素以及用什么当模版以及比较函数)
priority_queue<int, vector<int>, greater<int>> q2;
和大顶堆一样。
priority_queue的成员函数(方法接口)
成员函数 | 功能 |
---|---|
push | 插入元素到队尾(并排序) |
pop | 弹出队头元素(堆顶元素) |
top | 访问队头元素(堆顶元素) |
size | 获取队列中有效元素个数 |
empty | 判断队列是否为空 |
swap | 交换两个队列的内容 |
用接口实现打印有序数组。
int main()
{
priority_queue<int> q;
q.push(3);
q.push(6);
q.push(0);
q.push(2);
q.push(1);
while (!q.empty())
{
cout << q.top() << " ";
q.pop();
}
cout << endl; // 6 3 2 1 0
return 0;
}
很简单,插入无序的元素,因为是大顶堆,所以最大的元素都在上面,每次弹出元素后,会把第二大的元素放到堆顶。以此循环直到没有元素。此时就是有序的。
堆调整算法
上文说了priority_queue就是堆,那么堆算法里有两个核心:向上调整算法和向下调整算法
注意:虽然说是数组实现的,但是用二叉树的形式表示会更加明了。所以下面用二叉树的样子演示
向上调整算法:
顾名思义:就是把数据向上调整。对应的接口有:push。push时是将元素先插入堆的最后,在调用向上调整算法,找到其位置。
图一中每个节点右边的数字代表了节点在vector数组中的位置。
算法解析:插入元素后,让元素与其父节点比较,符合就替代父节点。以此往复直到不符合或者到达0位置。
代码如下:
//堆的向上调整(大堆)
void AdjustUp(vector<int>& v, int child)
{
int parent = (child - 1) / 2; //通过child计算parent的下标
while (child > 0)//调整到根结点的位置截止
{
if (v[parent] < v[child])//孩子结点的值大于父结点的值
{
//将父结点与孩子结点交换
swap(v[child], v[parent]);
//继续向上进行调整
child = parent;
parent = (child - 1) / 2;
}
else//已成堆
{
break;
}
}
}
向下调整算法:
顾名思义:就是将元素向下调整,那有什么用呢?在pop元素时。有两种思路:
1.删除最上面的元素后,对每个节点再做一次向上调整算法。这个时间复杂度挺高的。
2.将最上面的元素和最后面的元素交换,然后pop最后一个元素,将目前最上面的元素做一次向下调整。对比1快了许多。所以就有了向下调整算法。
和向上调整一样,都是比较元素。不同的在于这里是向下走,所以说比较时符号是相反的。当然也可以是两个操作数交换位置,就不用把符号改变了。可以实现代码的复用
代码如下:
//堆的向下调整(大堆)
void AdjustDown(vector<int>& v, int n, int parent)
{
//child记录左右孩子中值较大的孩子的下标
int child = 2 * parent + 1;//先默认其左孩子的值较大
while (child < n)
{
if (child + 1 < n && v[child] < v[child + 1])//右孩子存在并且右孩子比左孩子还大
{
child++;//较大的孩子改为右孩子
}
if (v[parent] < v[child])//左右孩子中较大孩子的值比父结点还大
{
//将父结点与较小的子结点交换
swap(v[child], v[parent]);
//继续向下进行调整
parent = child;
child = 2 * parent + 1;
}
else//已成堆
{
break;
}
}
}
有了这两个调整算法以后,对于priority_queue的实现,就没什么问题了。