堆(Heap)是什么?
堆是一种特殊的完全二叉树,它满足以下两个关键性质:

结构性:它必须是一棵完全二叉树,这意味着除了最后一层,其他所有层都是完全满的,并且最后一层的节点都靠左排列。这种结构使得它可以用一个简单的数组来高效存储。
堆序性:每个节点的值都必须满足特定的关系。
大顶堆(Max-Heap):每个父节点的值都大于或等于其子节点的值。
小顶堆(Min-Heap):每个父节点的值都小于或等于其子节点的值。

在数组中,如果节点从索引 1 开始,那么父子关系可以通过简单的公式计算:

父节点:i/2
左孩子:2i
右孩子:2i+1

时间复杂度Onlogn 用常量增倍法
把n个数建立堆 上限是n个数依次进入 第n个高度logn 所以上限nlogn
假设2n个数 是否以这个做下限 后n个数高度大于logn 为什么是上限也是下限 说明只能是它

输出前m个小的数
import java.util.Scanner;

public class Main {
    // N: 堆的最大容量
    // h: 存储堆元素的数组,索引从1开始
    // size: 堆中当前元素的数量
    static int n, m;
    static int N = 100010;
    static int[] h = new int[N];
    static int size = 0;

    /**
     * 将指定索引u处的元素向下调整(下沉),以维护小顶堆的性质。
     * 从u开始,与其左右孩子比较,选择最小的那个进行交换,然后递归地对交换后的子节点执行down操作。
     *
     * @param u 要下沉的元素的索引
     */
    public static void down(int u) {
        int t = u; // 临时变量t,用于存储u、左孩子、右孩子中最小元素的索引
        
        // 如果左孩子存在且比t小,更新t
        if (u * 2 <= size && h[u * 2] < h[t]) {
            t = u * 2;
        }
        
        // 如果右孩子存在且比t小,更新t
        if (u * 2 + 1 <= size && h[u * 2 + 1] < h[t]) {
            t = u * 2 + 1;
        }
        
        // 如果u不是最小的,进行交换并递归下沉
        if (u != t) {
            swap(h, u, t);
            down(t);
        }
    }

    /**
     * 将指定索引u处的元素向上调整(上浮),以维护小顶堆的性质。
     * 从u开始,与其父节点比较,如果比父节点小,则交换,然后继续向上调整,直到到达根节点或满足堆性质。
     *
     * @param u 要上浮的元素的索引
     */
    public static void up(int u) {
        // 循环条件:u不是根节点且比父节点小
        while(u / 2 > 0 && h[u / 2] > h[u]) {
            swap(h, u / 2, u);
            u /= 2; // u更新为父节点,继续向上
        }
    }

    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        n = sc.nextInt(); // n: 初始元素的数量
        m = sc.nextInt(); // m: 要执行的删除操作次数
        
        // 读取n个初始元素到数组中,从索引1开始存储
        for(int i = 1; i <= n; i ++) {
            h[i] = sc.nextInt();
        }
        size = n; // 初始化堆的大小
        
        // 建堆操作:从最后一个非叶子节点开始,依次向上执行下沉操作
        // 这是一个高效的O(n)建堆方法
        for(int i = n / 2; i >= 1; i --) {
            down(i);
        }

        // 执行m次删除操作
        while(m -- > 0) {
            // 1. 输出堆顶元素(最小值)
            System.out.print(h[1] + " ");
            
            // 2. 交换堆顶元素和最后一个元素
            swap(h, 1, size);
            
            // 3. 缩小堆的大小,等同于删除最后一个元素(原堆顶)
            size --;
            
            // 4. 将新的堆顶元素下沉,重新维护堆
            down(1);
        }
    }
    
    /**
     * 交换数组中两个索引位置的元素
     * @param a 数组
     * @param i 索引1
     * @param j 索引2
     */
    private static void swap(int[] a, int i, int j) {
        int tmp = a[i];
        a[i] = a[j];
        a[j] = tmp;
    }
}