个人学习笔记平台,欢迎大家交流

红叶~

Be humble, communicate clearly, and respect others.

堆排序、堆的模拟

一、堆的概念测试

1、堆的性质

  • 是一颗完全二叉树
  • 每个节点的值都大于或等于子节点的值,为最大堆;反之为最小堆

2、堆的存储

一般用数组来表示堆,下标从0开始。则下标为 i 的节点的父节点下标为(i-1)/2,其左右子节点分别为(2i + 1)(2i + 2)

下标从1开始 左右节点2i 、 2i+1 i / 2

二、堆排序

1、基本思想

利用大顶堆(小顶堆)堆顶记录的是最大(小)关键字这一特性,每次从无序数组中选出最大(最小)值。

1、将待排序序列造成一个最大堆,此时根节点为最大值

2、依次将根节点与待排序序列最后一个元素交换

3、维护从根节点到该元素的前一个节点为最大堆

2、基本操作

down:把数往下沉,从而再次变成堆的操作,要考虑和两个左右子节点比较。

up:底下的数往上走,再次变成堆的操作,只要和父节点比较就可以。

插入一个数:heap[++size], up(size)

删除最小值:把最后一个元素覆盖到堆顶,heap[1] = heap[size], size--, down(1)

删除任意一个元素:heap[k]=heap[size];size--;down(k);up(k);dwon和up只会执行一个

修改任意元素:heap[k]=x;down();up()

时间复杂度:nlog n

堆排序:每次取出堆顶后,把最后一个数放到堆顶,进行下沉操作,直到堆最后只剩一个元素为止。

模板

#include<iostream> 
#include<algorithm>

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]) // 再比较右儿子,如果上面更新了则h[t]是左儿子的值,最终h[t]=3个点中最小的值
        t = 2 * u + 1;
    if (u != t) // 根节点不是最小值,交换,把根节点下沉down
    {
        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;
}

三、模拟堆

模拟堆

维护一个集合,初始时集合为空,支持如下几种操作:

  1. I x,插入一个数 x;
  2. PM,输出当前集合中的最小值;
  3. DM,删除当前集合中的最小值(数据保证此时的最小值唯一);
  4. D k,删除第 k 个插入的数;
  5. C k x,修改第 k个插入的数,将其变为 x;

现在要进行 N次操作,对于所有第 22 个操作,输出当前集合的最小值。

image-20211206093933691

定义两个数组:分别是 hp 和 ph

hp--> 堆数组中下标 -> 第k个插入 heap pointer

ph--> 第k个插入 -> 堆数组中下标 pointer heap

这两个函数是互为反函数的。

当在堆中交换两个元素时,那么第k个插入的数的下标要变化、下标对应的数是第几个也要变化。swap操作传入需要交换的堆数组的下标

image-20211206125645512

void swap_heap(int a, int b) {
    swap(ph(hp[a]), ph[hp[b]]); // 第k个插入的数的下标变化
    swap(hp[a],hp[b]); // 
    swap(h[a],h[b]); // 交换数组响应下标对应的数值
}
#include<iostream>
#include<string>
using namespace std;
const int N = 100010;
int h[N], mysize, cnt; // cnt记录第k次插入
int ph[N], hp[N];
// ph[k]: 第k个插入 --> 下标
// hp[k]: 下标k -->第几个插入
// 交换堆中的两个数,那么下标和第几次插入的关系也要变化
void swap_heap(int a, int b) {
    swap(ph[hp[a]], ph[hp[b]]);
    swap(hp[a], hp[b]);
    swap(h[a],h[b]); // 交换数值
}

void down(int i) {
    int t = i;
    if(2 * i <= mysize && h[2 * i] < h[t])  t = 2 * i;
    if(2 * i + 1 <= mysize && h[2 * i + 1] < h[t])  t = 2 * i + 1;
    if(t != i) {
        swap_heap(t, i);
        down(t);
    }
}

void up(int i) {
    while(i / 2 && h[i] < h[i / 2]) {
        swap_heap(i, i / 2);
        i /= 2;
    }
}
// 插入一个数,在最后插入
void insert(int x) {
    mysize++; // 记录下标
    cnt++; // 第几次插入
    ph[cnt] = mysize, hp[mysize] = cnt;
    h[mysize] = x;
    up(mysize);
}
// 删除第k个插入数
void del(int k) {
    k = ph[k]; // 第k个插入的数对应的下标
    swap_heap(k, mysize);
    mysize--;
    down(k);
    up(k);
}
// 删除最小值,堆顶
void Del() {
    swap_heap(1, mysize);
    mysize--;
    down(1);
}

// 修改第k个插入的数
void change(int k, int x) {
    k = ph[k]; // 第k个插入的数对应的下标
    h[k] = x;
    down(k);
    up(k);
}
int main() {
    int n;
    cin >> n;
    string op;
    int x, y;
    while(n--) {
        cin >> op;
        if(op == "I") {
            cin >> x;
            insert(x);
        }else if(op == "PM") {
            cout << h[1] << endl;
        }else if(op == "D") {
            cin >> x;
            del(x);
        }else if(op == "DM") {
            Del();
        }else {
            cin >> x >> y;
            change(x, y);
        }
    }
    return 0;
}
posted @ 2022-02-15 23:30  红叶~  阅读(105)  评论(0)    收藏  举报