堆排序
作者:互联网
如何实现堆排序
- 使用 C++ STL派生容器 priority_queue 优先队列
- 自己写一个小根堆
两种方式各有好处,STL容器的方法用起来方便,而自己写的灵活性更大,可以自定义实现更多操作。
下面介绍一下 priority_queue 在做题的常用方法,以及手撕堆的实现。
优先队列堆排序
优先队列:队列中每个元素都有优先级,出队时按最高优先级先出。
默认是降序排列,也就是大顶堆。
声明方式:priority_queue<Type, Container, Functional>
第一个参数为优先队列里的元素类型,第二个参数为优先队列基于哪个容器实现(vector/deque),第三个参数为排序规则(greater/less)。
//默认
priority_queue<int> q;
//降序队列,与上式等同
priority_queue<int,vector<int>,less<int>> q;
//升序队列,基于deque
priority_queue<int,deque<int>,greater<int>> q;
priority_queue的头文件为
empty()
: 如果队列为空,则返回真pop()
: 删除对顶元素,删除第一个元素push()
: 加入一个元素size()
: 返回优先队列中拥有的元素个数top()
: 返回优先队列对顶元素,返回优先队列中有最高优先级的元素
利用这一特性,可以快速的实现排序(堆排序)
输入n,m和n个数
输出前m个最小的数
#include <bits/stdc++.h>
using namespace std;
const int N = 1e5 + 10;
int n, m;
int main()
{
cin >> n >> m;
priority_queue<int,vector<int>,greater<int>> h;
for(int i = 0; i < n; i++) {
int input;
cin >> input;
h.push(input);
}
while(m--) {
cout << h.top() << ' ';
h.pop();
}
return 0;
}
思路简单,将要排序的序列都push进优先队列中,然后一个个出队就好了。
手写小根堆实现堆排序
堆是一个完全二叉树的数据结构。因此堆的存储可以用一维数组heap[]
表示。根节点下标为1(不能为0,否则无法索引到子节点)。
完全二叉树的性质有:
- 节点x的左子节点为2x,右子节点为2x+1
- 节点x的父节点为x/2
接下来只要实现堆的操作就行,主要有:
- 插入一个数:
heap[++size]=x; up(size);
- 获取最小值:
heap[1];
- 删除最小值:
heap[1]=heap[size--]; down(1);
- 删除任意值:
heap[k]=heap[size--]; up(k); down(k);
- 修改任意值:
heap[k]=x; up(k); down(k);
第4和第5要访问堆中任意值的操作比较复杂,暂时不讨论。
由上面的操作可以看到,对数组的索引都是直接访问,需要实现的只有两个函数:up() 和 down()。
down()的实现
只要判断当前节点小于其两个子节点,否则与最小的子节点交换,之后递归调用,使其满足堆的条件。
注意要检查是否存在子节点,防止越界。
设当前节点为u,则其左子节点为2u,右子节点为2u+1,可以得出如下:
void down(int u)
{
int t = u; //t保存最小的节点,默认父节点是最小的
if (2 * u <= mySize && h[t] > h[2 * u]) t = 2 * u;
if (2 * u + 1 <= mySize && h[t] > h[2 * u + 1]) t = 2 * u + 1;
if (u != t) { //如果父节点不是最小的,则要交换
swap(h[u], h[t]);
down(t); //递归调用直到插入正确位置
}
}
up()的实现
每个节点的父节点只有一个,因此只要判断父节点是否小于当前节点,否则进行交换,之后递归调用即可。
注意要检查是否存在父节点,防止越界。
设当前节点为u,则其父节点为u/2,可以得出如下:
void up(int u)
{
while( u/2 && h[u/2] > h[u]) {
swap(h[u/2], h[u]);
u /= 2;
}
}
插入一个数
void myPush(int u)
{
h[++size] = u;
up(u);
}
删除一个数
void myPop() //队头元素出队(移除堆顶)
{
h[1] = h[size--];
down(1);
}
void myRemove(int u) //移除堆任意下标的元素
{
h[u] = h[size--];
up(u), down(u);
}
通过上面堆的封装,同样可以实现排序(堆排序)
输入n,m和n个数
输出前m个最小的数
#include <bits/stdc++>
using namespace std;
const int N = 100010;
int h[N], mySize;
int n, m;
void down(int u)
{
int t = u;
if (2 * u <= mySize && h[t] > h[2 * u]) t = 2 * u;
if (2 * u + 1 <= mySize && h[t] > h[2 * u + 1]) t = 2 * u + 1;
if (u != t) {
swap(h[u], h[t]);
down(t);
}
}
int main()
{
cin >> n >> m;
mySize = n;
for (int i = 1; i <= n; i++) scanf("%d", &h[i]);
for (int i = n / 2; i; i--) down(i);
while (m--) {
cout << h[1] << " ";
h[1] = h[mySize--];
down(1);
}
return 0;
}
上面的第4和第5个操作需要额外记录两个数组,分别存放第k个插入的数在堆数组中的索引;堆数组中的第k个数为第几个插入的数。因为堆中的数的索引值是会随着插入的数的大小而改变的,因此需要自己记录插入顺序到堆索引的映射,以及堆索引到插入顺序的映射,这样才能做到对第几个插入的数的删除和修改。
冷知识
sort()
函数,可以直接对多个字符串进行排序。
同理,优先队列中的 greater
两者都是默认从小到大排序,也就是有个默认参数是仿函数的greater
这两种排序的方式最终调用的都是同个方法,因此只要 sort() 可以实现的,优先队列也可以实现。这是个人猜测,没有去剖析底层验证,就算底层不一样也通用。
因此,做题遇到需要对字符串进行排序的,可以大胆使用 sort()
或者 priority_queue<string>
来排序。
标签:queue,int,堆排序,down,队列,节点,priority 来源: https://www.cnblogs.com/Ethan-Code/p/16634559.html