025堆结构和堆排序
堆(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;
}
}
本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来源 言和和和!
评论
