34.Acwing基础课第838题-简单-堆排序

34.Acwing基础课第838题-简单-堆排序

题目描述

输入一个长度为 n 的整数数列,从小到大输出前 m 小的数。

输入格式

第一行包含整数 n 和 m。

第二行包含 n 个整数,表示整数数列。

输出格式

共一行,包含 m 个整数,表示整数数列中前 m 小的数。

数据范围

\(1≤m≤n≤10^5\)

\(1≤数列中元素≤10^9\)

输入样例:

5 3
4 5 1 3 2

输出样例:

1 2 3

代码:

#include <iostream>
#include <algorithm>
using namespace std;

// 全局常量与变量定义
const int N = 100010;  // 堆的最大容量(适配题目数据范围)
int h[N];              // 存储小根堆的数组(下标从1开始,符合堆的经典实现)
int a[N];              // 存储原始输入数据的数组
int cnt;               // 堆的当前元素个数(动态维护,等价于堆的size)

// 小根堆down操作(下沉):将下标u的节点向下调整,使以u为根的子树满足小根堆性质
// 核心逻辑:找u、左儿子、右儿子中的最小值,若u不是最小值则交换并递归调整
void down(int u)
{
    int t = u; // t:记录u、左儿子、右儿子中最小值的下标,初始化为u
    
    // 第一步:比较左儿子(2*u),若左儿子存在且值更小,更新t为左儿子下标
    if(u*2 <= cnt && h[2*u] < h[t]) t = 2*u;
    // 第二步:比较右儿子(2*u+1),若右儿子存在且值更小,更新t为右儿子下标
    if(2*u+1 <= cnt && h[2*u+1] < h[t])  t = 2*u+1;
    
    // 若u不是最小值下标(t≠u),说明需要调整
    if(t != u)
    {
        swap(h[u], h[t]); // 交换u和t位置的值,让最小值到u位置
        down(t);          // 递归调整t位置(原u的值),确保子树仍满足堆性质
    }
}

// 小根堆up操作(上浮):将下标u的节点向上调整,使其满足小根堆性质
// 核心逻辑:若当前节点值小于父节点,交换并继续向上比较
void up(int u)
{
    // 循环条件:父节点存在(u/2 > 1)且父节点值 > 当前节点值(小根堆上浮条件)
    while(u/2 > 1 && h[u/2] > h[u]) 
    {
        swap(h[u], h[u/2]); // 交换当前节点与父节点的值
        u /= 2;             // 移动到父节点下标,继续向上检查
    }
}

// 建堆函数:将原始数组a[]构建为小根堆(时间复杂度O(n))
// 参数:a[]-原始数组,n-数组元素个数
void build_heap(int a[], int n)
{
    cnt = n; // 初始化堆的元素个数为n
    // 第一步:将原始数组拷贝到堆数组h[]中(下标1~n)
    for(int i = 1; i <= n; i++)  h[i] = a[i];
    // 第二步:从最后一个非叶子节点(n/2)倒序遍历,执行down操作
    // 原理:叶子节点无需调整,非叶子节点从下到上调整,保证整棵树满足堆性质
    for(int i = n/2; i >= 1; i--)  down(i);
}

// 堆插入操作:向堆中插入一个数x
// 核心逻辑:堆尾插入 + 上浮调整,保证堆性质
void heap_insert(int x)
{
    h[++cnt] = x; // 1. 堆尾新增元素(cnt先自增,再赋值,下标从1开始)
    up(cnt);      // 2. 对新增的堆尾元素执行上浮,归位到正确位置
}

// 堆查询操作:获取堆中的最小值(小根堆堆顶即为最小值)
// 返回值:堆顶元素h[1]
int heap_get_min()
{
    return h[1]; // 小根堆的核心特性:堆顶(下标1)是整个堆的最小值
}

// 堆删除操作:删除堆中的最小值(即删除堆顶元素)
// 核心逻辑:堆尾覆盖堆顶 + 缩小堆大小 + 堆顶下沉调整
void heap_delete_min()
{
    h[1] = h[cnt--]; // 1. 堆尾元素覆盖堆顶 2. cnt自减,删除堆尾(等价于删除原堆顶)
    down(1);         // 对新堆顶执行下沉,重新维护堆性质
}

// 堆删除操作:删除堆中第k个位置的元素(k从1开始)
// 参数:k-要删除的元素下标
void heap_delete_k(int k)
{
    if(k < 1 || k > cnt)  return; // 边界检查:k非法则直接返回,避免越界
    h[k] = h[cnt--];              // 1. 堆尾元素覆盖第k个元素 2. cnt自减删除堆尾
    down(k),up(k);                // 2. 下沉+上浮调整(仅一个生效)
                                  // 原理:新值可能比子节点大(需down)或比父节点小(需up)
}

// 堆修改操作:将堆中第k个位置的元素修改为x
// 参数:k-要修改的元素下标,x-新值
void heap_modify_k(int k, int x)
{
    if(k < 1 || k > cnt)  return; // 边界检查:k非法则直接返回
    h[k] = x;                     // 1. 修改第k个位置的元素值为x
    down(k), up(k);               // 2. 下沉+上浮调整(仅一个生效)
                                  // 原理:x比原值大则down生效,x比原值小则up生效
}

int main()
{
    int n, m;
    cin >> n >> m; // 输入:n-原始数据个数,m-要输出的最小值个数
    // 读取原始数据到数组a[](下标1~n)
    for(int i = 1; i <= n; i++)  cin >> a[i];
    
    cnt = n;          // 初始化堆大小为n
    build_heap(a, n); // 构建小根堆
    
    for(int i = 0; i < m; i++)  
    {
        // 关键:除第一个数外,其余数输出前先打印空格(避免开头/结尾多余空格)
        if(i > 0) cout << " ";  
        cout << heap_get_min(); // 输出当前最小值(不换行)
        heap_delete_min();      // 删除最小值,堆自动调整为新的小根堆
    }
    cout << endl; // 所有数输出完成后,统一打印一个换行(符合OJ格式要求)
    
    return 0;
}
posted @ 2026-04-05 23:49  CodeMagicianT  阅读(0)  评论(0)    收藏  举报