堆进阶-Meldable Heap

本文介绍了可并堆(meldable heap)的概念及其重要性,包括二项队列、斐波那契堆、严格斐波那契堆、配对堆、秩配对堆等数据结构。特别强调了在实际应用中,配对堆由于其优秀性能而更受欢迎。此外,文章还探讨了斜堆、随机堆和左偏树等相对亲民的堆实现,它们在保持高效的同时,简化了操作复杂度。

介绍

堆作为一种相对易懂,易学,并且相对独立的数据结构(才会在STL中出现),广受大家使用,它的时间复杂度也很优秀,是排序算法中的佼佼者,仅需O\left ( nlog_{n} \right )。这次讲的堆,作为进阶,自然更难一些,当然用处也更大。这种堆得主要用处在于可以将其合并起来,并且合并的速度远优于一个一个拿出来的速度。这种堆统称为可并堆(meldable heap),合并(meld)操作和删除(delete)任意结点操作是它的特色。还是给出一个简单的概括:

可并堆(后文简称堆)是一种包含了一组结点的数据结构。每个结点都有一个不同的实际值。堆支持以下操作:

-makeheap:返回一个空的新的堆。

-insert(x,H):把一个有值的在堆外的结点x插入堆H。

-find-min(H):返回H中的最小值。

-delete-min(H):如果H不为空,删除其中最小的结点。

-meld(H1,H2):返回一个堆,其中包含不相交的两个堆H1,H2中的所有结点,并删除H1,H2。

某些堆的应用需要以下操作中的至少一种:

-decrease-key(x,Δ,H):将H中结点x的值减少Δ>0。 

-delete(x,H):将结点x从H中删除。

(此部分从Haeupler, Sen & Tarjan论文翻译得)

这种堆的数量很多,我们提一些真正能够被用到的数据结构(在工作和竞赛中,不要说什么码完考试都完了)。Vuillemin的二项队列(biominal queue,又称二项堆biominal heap)每项操作在最坏情况下都仅需O(logn)的时间。网上查找300行左右。Fredman和Tarjan提出了斐波那契堆(Fibonacci heap),这能让除delete-min和delete(均摊下O(logn))操作在O(1)的时间下做完。当然,Brodal,Lagogiannis & Tarjan共同提出了Strict Fibonacci Heap(原谅我无法翻译),这种优秀的数据结构能使在原基础上使delete-min和delete操作的渐进时间复杂度的上界达到O(logn)(不再是均摊了,对于这个来说是最慢的情况)。斐波那契堆能优化Dijkstra算法(Dijkstra’s shortest path algorithm)(优化至O\left ( V+ElogE \right )),最小树形图算法(朱刘算法或Edmonds’ minimum branching algorithm,两者应是等价的)和某些最小生成树算法。Fredman,Sedgewick,Sleator和Tarjan后来提出了一种和自适应操作相关的堆实现方法,配对堆(pairing heap)。这种结构仅对于delete-min和decrease-key操作均摊下需要O(logn)的时间,其它都是O(1)的复杂度。当然,Haeupler,Sen和Tarjan又在后面提出了秩配对堆(rank-pairing heap)(大概如此翻译,指添加顺序)。这种也极为优秀,仅delete-min和delete需要均摊下O(logn)的时间,理论上略次于Strict Fibonacci Heap。但是,经过实践验证(实践是检验真理的唯一标准),Pairing Heap在实际操作中都更具优势(快)(大神和Tarjan都如此说)。当然,还有更加优秀的算法,但极为复杂,就不赘述了。这些优秀的算法往往被人们称为黑科技。我们再来看几种更为亲民的,待会儿讲的重点也会在它们身上。(鉴于上文某些算法不算不实用程度,且中文介绍几乎找不到,如果我看懂了并且未自尽,我会花篇幅来讲讲,当然,有可能放在拓展篇里。)

Sleator和Tarjan提出了斜堆(skew heap,又称自适应堆self-adjusting heap),它基于普通的二叉堆,均摊下每个操作的时间复杂度都为O(logn)(准确来说是O(log_{\phi }n)\phi=\frac{1+\sqrt{5}}{2}约等于O\left ( 1.44log_{2}n \right ))。Gambin和Malinowski提出了随机堆(randomized meldable heap),它与斜堆类似,只不过用了随机的玄学操作,每种操作的渐进时间复杂度(或者最坏情况下)都为O(logn)。Crane发明了左偏树(leftist heap,我觉得左偏堆更准确),它的特色在于保持左边的结点更重(改成右边是等价的)。它的delete-min,insert,merge操作渐进时间复杂度都为O(logn),并且decrease-key操作达到了惊人的O(n)(未免令人汗颜)。原生的二叉堆delete-min,decrease-key最坏O(logn),insert为O(logn),merge为O(n)并且不支持delete操作。但是有一种启发式合并操作,可以达到O\left ( nlog^{2}n \right )

算法

鉴于我没有意向写初步版本的堆,我在这重述一下基础的二叉堆。

本篇文章以小根堆为例,大根堆自行更改符号、名称。堆保证子节点大于或等于父节点,二叉堆保证堆是一棵(近似的)完全二叉树。插入一个点时将这个点放在末尾,执行pushup向上推;删除根时,将根置为最末一个节点,执行pushdown向下推。

对于合并复杂度较低的堆结构,它们就不再以完全二叉树为基础了。所以应使用链式结构储存数据。左偏树、斜堆、随机堆是类似的数据结构。对于它们的时间复杂度都可以证明,读者自证可得。左偏树需要记录每个结点dis值,即它到最近外结点的距离。保证左儿子的dis大于或等于右儿子的。合并时将根节点较小的设为x,另一个设为y。在x右子树中递归合并y,递归返回时,若不符合dis性质,交换左右子树。其它操作类似二叉堆。

斜堆类似但不同于左偏树,递归过后直接交换左右子树即可,不需记录dis。随机堆和斜堆几乎一样,只是把交换操作随机进行。

代码

//a: a vector which denotes the heap
void pushup(int i)
{
	int child=i;
	int father=i>>1;
	int temp=a[i]
	while(father>=0)
	{
		if(a[father]<=temp)break;
		else
		{
			a[child]=a[father];
			child=father;
			father=child>>1;
		}
	}
	a[child]=temp;
	return;
}
void pushdown(int root)
{
	int father=root;
	int child=root<<1;
	int temp=a[father];
	while(child<a.size())
	{
		if(a[child]>=temp)break;
		else
		{
			a[father]=a[child];
			father=child;
			child=father<<1;
		}
	}
	a[father]=temp;
	return;
}

Leftist Heap

*node merge(*node x,*node y)
{
	if(x==NULL)return y;
	if(y==NULL)return x;
	if(x->key>y->key)swap(x,y);
	x->right=merge(x->right,y);
	if(x->lc==NULL||(x->lc->dis<x->rc->dis))swap(x->lc,x->rc);
	return x;
}

Skew Heap

*node merge(*node x,*node y)
{
	if(x==NULL)return y;
	if(y==NULL)return x;
	if(x->key>y->key)swap(x,y);
	x->right=merge(x->right,y);
	swap(x->lc,x->rc);
	return x;
}

Randomized Meldable Heap

*node merge(*node x,*node y)
{
	if(x==NULL)return y;
	if(y==NULL)return x;
	if(x->key>y->key)swap(x,y);
	x->right=merge(x->right,y);
	if(rand()%2)swap(x->lc,x->rc);
	return x;
}

 

<think>我们正在分析用户的问题:用户想了解 `jmap --heap` 命令输出各指标的含义。 根据之前的对话历史,我们已经详细介绍了jmap的多种用法,现在用户特别关注`jmap -heap`(注意:官方文档中实际是`jmap -heap`,而不是`--heap`,但用户习惯可能写成`--heap`,我们按标准写法)的输出指标含义。 根据引用[2]:基本使用语法是`jmap [option] <pid>`,其中`-heap`选项用于打印内存配置和使用概览。 我们需要详细解释`jmap -heap <pid>`输出的各个部分及其指标含义。 输出通常分为几个部分: 1. 配置(Heap Configuration) 2. 使用情况(Heap Usage),包括各个内存区域(如年轻代、老年代、元空间等)的使用情况 注意:不同Java版本和垃圾收集器,输出可能略有不同,但主要内容一致。 下面我们详细解释各指标: </think>### `jmap -heap` 命令输出指标详解 `jmap -heap <pid>` 用于展示JVM内存的详细配置和使用情况。以下是对输出关键指标的系统解析(以典型输出为例): --- ### 一、配置(Heap Configuration) | 指标 | 含义 | 默认值/示例值 | 重要性 | |---------------------------|----------------------------------------------------------------------------------------------------|---------------------------|--------| | **`MinHeapFreeRatio`** | GC后空间的最小空闲比例(低于此值会扩展) | 40% | ⭐⭐⭐ | | **`MaxHeapFreeRatio`** | GC后空间的最大空闲比例(高于此值会收缩) | 70% | ⭐⭐⭐ | | **`MaxHeapSize`** | 最大容量(对应启动参数 `-Xmx`) | 2147483648 (2GB) | ⭐⭐⭐⭐ | | **`NewSize`** | 年轻代初始大小 | 44564480 (42.5MB) | ⭐⭐⭐ | | **`MaxNewSize`** | 年轻代最大容量 | 357892096 (341.5MB) | ⭐⭐ | | **`OldSize`** | 老年代初始大小 | 89653248 (85.5MB) | ⭐⭐⭐ | | **`NewRatio`** | 老年代与年轻代的大小比例(Old:Young)<br>例:`NewRatio=2` 表示老年代是年轻代的2倍 | 2 | ⭐⭐⭐⭐ | | **`SurvivorRatio`** | Eden区与Survivor区的比例<br>例:`SurvivorRatio=8` 表示Eden:S0:S1=8:1:1 | 8 | ⭐⭐⭐ | | **`MetaspaceSize`** | 元空间初始大小(Java 8+) | 21807104 (20.796875MB) | ⭐⭐⭐ | | **`CompressedClassSpaceSize`** | 压缩类指针空间大小(启用压缩指针时) | 1073741824 (1024MB) | ⭐⭐ | > 💡 **配置验证**:通过对比 `MaxHeapSize` 与启动参数 `-Xmx`,可验证JVM参数是否生效[^2]。 --- ### 二、使用情况(Heap Usage) #### 1. 年轻代(Young Generation) ```bash PS Young Generation Eden Space: capacity = 33554432 (32.0MB) # 当前容量 used = 16777216 (16.0MB) # 已使用量 (占比50%) free = 16777216 (16.0MB) # 剩余空间 50.0% used # 使用率 Survivor Space: capacity = 5242880 (5.0MB) used = 0 (0.0MB) free = 5242880 (5.0MB) 0.0% used ``` - **`capacity`**:当前内存区域总容量(动态调整) - **`used`**:已使用的内存(含存活对象和待回收对象) - **`free`**:空闲内存(可分配给新对象) - **使用率**:`used / capacity * 100%` ,超过80%需警惕[^4] #### 2. 老年代(Old Generation) ```bash PS Old Generation capacity = 89653248 (85.5MB) used = 0 (0.0MB) free = 89653248 (85.5MB) 0.0% used ``` - **监控重点**:持续高使用率(如>75%)可能预示内存泄漏 - **Full GC触发**:当老年代空间不足时触发STW(Stop-The-World) #### 3. 元空间(Metaspace) ```bash Metaspace: used = 10574KB capacity = 10816KB committed = 11264KB reserved = 10752KB ``` - **`used`**:加载的类元数据实际大小 - **`committed`**:向操作系统申请的内存 - **`reserved`**:预留但未使用的内存 - **泄漏特征**:`used` 持续增长且GC后不下降[^3] --- ### 三、关键诊断场景 1. **内存泄漏排查** - 老年代使用率持续上升 → 结合 `jmap -histo` 分析对象分布 ```bash jmap -histo:live 12345 | head -20 # 查看TOP20对象 ``` 2. **GC性能优化** - 若 `Survivor` 使用率接近100% → 调整 `-XX:SurvivorRatio` - 频繁Full GC → 检查老年代容量是否过小 3. **参数验证** - 对比 `MaxHeapSize` 与 `-Xmx` → 确保JVM参数生效 ```bash # 启动参数示例 java -Xmx2g -Xms2g -XX:SurvivorRatio=6 ... ``` --- > ️ **注意事项** > - **生产环境慎用**:执行可能短暂阻塞应用线程(建议在低峰期操作)[^1] > - **替代方案**: > - 监控工具:`jstat -gcutil <pid> 1000`(实时GC统计) > - 安全分析:Arthas的 `heapdump` 命令(低侵入) > - OOM自动转储:`-XX:+HeapDumpOnOutOfMemoryError`
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值