ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

堆排序

2022-08-30 17:33:10  阅读:180  来源: 互联网

标签:queue int 堆排序 down 队列 节点 priority


如何实现堆排序

  1. 使用 C++ STL派生容器 priority_queue 优先队列
  2. 自己写一个小根堆

两种方式各有好处,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的头文件为 ,操作和队列基本相同:

  1. empty(): 如果队列为空,则返回真
  2. pop(): 删除对顶元素,删除第一个元素
  3. push(): 加入一个元素
  4. size(): 返回优先队列中拥有的元素个数
  5. 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

接下来只要实现堆的操作就行,主要有:

  1. 插入一个数:heap[++size]=x; up(size);
  2. 获取最小值:heap[1];
  3. 删除最小值:heap[1]=heap[size--]; down(1);
  4. 删除任意值:heap[k]=heap[size--]; up(k); down(k);
  5. 修改任意值: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 仿函数和 less 仿函数也可以对字符串排序。

两者都是默认从小到大排序,也就是有个默认参数是仿函数的greater

这两种排序的方式最终调用的都是同个方法,因此只要 sort() 可以实现的,优先队列也可以实现。这是个人猜测,没有去剖析底层验证,就算底层不一样也通用。

因此,做题遇到需要对字符串进行排序的,可以大胆使用 sort() 或者 priority_queue<string> 来排序。

标签:queue,int,堆排序,down,队列,节点,priority
来源: https://www.cnblogs.com/Ethan-Code/p/16634559.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有