堆排序 | 豆包MarsCode AI刷题

107 阅读2分钟

堆的结构和性质

堆是一种完全二叉树,满足以下条件:

  • 完全二叉树:所有层次除了最后一层都被完全填满,最后一层从左到右填充。
  • 堆性质
    1. 最大堆:任意父节点的值 ≥ 子节点的值。
    2. 最小堆:任意父节点的值 ≤ 子节点的值。

堆在数组中的存储方式

存储方式:用一维数组:下标为1的点是根节点,下标为x的节点左子节点下标是2x,节点x的左子节点下标是2x+1 在数组中存储堆时,节点之间的关系为:

  • 父节点的索引ii
  • 左子节点的索引2×i2×i
  • 右子节点的索引2×i+12×i+1

例如,对于数组:

Index:    1   2   3   4   5   6   7
Values:   5   3   8   2   1   6   4
  • 节点 1 是根节点,子节点是 23
  • 节点 2 的子节点是 45
  • 节点 3 的子节点是 67

相关操作

  • 插入一个数
    堆的大小加一,并将该元素插入至堆尾,进行上浮操作维护堆的性质
  • 求集合当中最小值
    堆顶元素即集合当中最小值
  • 删除最小值
    由于数组删除元素比较麻烦,删除操作可以等价于第一步用堆底的元素覆盖根节点,第二步删除尾节点,第三步下沉
  • 删除任意一个元素
    删除任意一个元素操作可以类比删除最小值,第一步用堆底的元素覆盖任意节点,第二步删除尾节点,第三步下沉上升(注意这里只会执行一次)维护堆的性质
  • 修改任意一个元素
    修改任意一个节点的值,然后下沉上升

代码

#include <iostream>
using namespace std;

const int N = 1e5 + 10;
int n, m;
int heap[N], siz;

void down(int x) {
    int t = x;
    if (2 * x <= siz && heap[t] > heap[2 * x]) t = 2 * x;
    if (2 * x + 1 <= siz && heap[t] > heap[2 * x + 1]) t = 2 * x + 1;
    if (t != x) {
        swap(heap[t], heap[x]);
        down(t);
    }
}

int main()
{
    cin >> n >> m;
    for (int i = 1; i <= n; i ++) cin >> heap[i];
    siz = n;
    
    // 建堆
    for (int i = n / 2; i; i --) down(i);
    
    while (m --) {
        // 输出最小值
        printf("%d ", heap[1]);
        // 删除堆顶元素并更新堆
        heap[1] = heap[siz];
        siz --;
        down(1);
    }
    
    return 0;
}

down函数解释

  1. 假设当前节点,下标为x的节点是最小节点,用t记录子树中最小节点的下标
  2. 检查左子节点是否存在:2 * x <= siz
    如果左子节点存在,并且其值小于当前节点的值:heap[t] > heap[2 * x],更新t为左子节点的索引。
  3. 检查右子节点是否存在2 * x + 1 <= siz
    如果右子节点存在,并且其值小于当前最小值:heap[t] > heap[2 * x + 1],更新t为右子节点的索引。
  4. 如果当前节点不是最小值x != t,则交换当前节点与最小节点的值 交换后,递归调用down(t),继续调整子树