配对堆(Pairing Heap)是一个简单实用的min-heap结构(当然也可以做成max-heap)。它是一颗多路树(multiway tree),类似于Leftist Heap和Skew Heap,但是与Binomial Tree和Fibonacci Heap不一样。它的基本操作是两个多路树的连接(link),所以取名叫Pairing Heap。连接操作(参考以下实现中的方法linkPair)类似于Binomial Tree和Fibonacci Heap中的link操作,即将root key值最大的树作为key值最小的树的孩子(一般作为最左边的孩子,特别是Binomial Heap必须这样做),其复杂度是常数级。因为Pairing Heap只有一棵树,所以它的merge操作(类似于Fibonacci Heap中的union)也很简单,只需要link两棵树就可以了,平摊复杂度与Fibonacci Heap类似,都是常数级操作,而在Binomial Heap中需要union两个root lists,所以复杂度为O(logn)。在算法分析中,往往有很多数据结构实现起来比较简单,但是分析起来很复杂,例如快速排序(Quicksort),配对堆也是一个典型例子。配对堆的merge,insert和findMin的平摊复杂度都是O(1),extract-min的平摊复杂度是O(logn),这与Fibonacci Heap中的相应操作的复杂度相当。但是,decrease-key的平摊复杂度比Fibonacci Heap大,后者的decrease-key的平摊复杂度是O(1)。关于配对堆的decrease-key操作的平摊复杂度结果可以参考:http://en.wikipedia.org/wiki/Pairing_heap。

在以下实现中,Pairing Heap采用“leftmost child,right sibling”(左孩子,右兄弟)方式表示,而且每一个结点还有一个left属性:对于第一个孩子,left属性表示该孩子的父结点;对于其他结点,left属性表示该结点的左兄弟。Extract-Min操作比较有意思,首先采用类似Binomial Heap和Fibonacci Heap中做法,即先删除root结点,然后得到root的孩子结点双向链表,链表中每一个结点对应一个子堆(subheap);接下来考虑如何将子堆合并到原来的堆中,在这里可以比较一下二项堆,Fibonacci堆和配对堆的合并做法:在Binomial Heap中将孩子结点倒排,生成按degree从小到大顺序的单向链表,然后将该单链表跟原来剩余的堆结点root list链表作union操作。在Fibonacci Heap中的做法是,将孩子结点依次添加到root list中(不用考虑先后次序),然后通过consolidate生成degree唯一的双向循环链表。二者都是在Extract-min时让每个堆结构变得更加紧凑,恢复成理想的状态,同时Extract-min的操作成本也相对比较高。在Pairing Heap中做法类似:如果没有Extract-min操作,其他的操作(比如insert,merge,decrease-key)势必使得root结点的孩子链表变得很长,通过Extract-Min两两合并,让Pairing Heap变得更加有序。Extract-Min两两合并做法是:先从左到右将相邻的孩子结点两两link,生成一个缩减的双向链表,然后对该新的双向链表从右到左link(上一次合并的结果作为下一次link中的右兄弟结点)。

实现:

/**
*
* Pairing Heap
*
* Copyright (c) 2011 ljs (http://blog.csdn.net/ljsspace/)
* Licensed under GPL (http://www.opensource.org/licenses/gpl-license.php)
*
* @author ljs
* 2011-09-06
*
*/
public class PairingHeap {
//left-child, right-sibling representation
static class Node{
private int key;
//left is the parent for first child; is the left sibling for other children
private Node left;
private Node sibling;
//child points to the leftmost-child
private Node child;
public Node(int key){
this.key = key;
}
public String toString(){
return String.valueOf(this.key);
}
}
private Node root;
private Node linkPair(Node first,Node second){
if(second==null) return first;
if(first==null) return second;
if(first.key<second.key){
//second is linked to first as a child
//retain the sibling relation
Node secondzSibling = second.sibling;
first.sibling = secondzSibling;
if(secondzSibling != null) secondzSibling.left = first;
Node firstzChild = first.child;
//update second's left and sibling pointers
second.left = first;
second.sibling = firstzChild;
//update first.child's pointer
if(firstzChild != null) firstzChild.left = second;
//update first's child
first.child = second;
return first;
}else{
//first is linked to second as a child
//retain the sibling relation
Node firstzLeft = first.left;
second.left = firstzLeft;
if(firstzLeft != null){
if(firstzLeft.child == first){
//firstzLeft is first's parent
firstzLeft.child = second;
}else{
//firstzLeft is first's left sibling
firstzLeft.sibling = second;
}
}
Node secondzChild = second.child;
//update first's left and sibling pointers
first.left = second;
first.sibling = secondzChild;
//update second's child pointer
if(secondzChild != null) secondzChild.left = first;
//update second's child
second.child = first;
return second;
}
}
public Node insert(Node node){
if(root==null)
root = node;
else
root = linkPair(node,root);
return node;
}
public void decreaseKey(Node x,int k) throws Exception{
if(x.key<k) throw new Exception("key is not decreased!");
x.key = k;
if(x!=root){
//cut x subtree from its siblings
Node xzLeft = x.left;
//if x is not root, its left (i.e. xzLeft) can never be null
if(xzLeft.child==x){//xzLeft is x's parent
xzLeft.child = x.sibling;
}else{//xzLeft is x's left sibling
xzLeft.sibling = x.sibling;
}
if(x.sibling!=null){
x.sibling.left = xzLeft;
}
//merge this tree with x subtree
x.left = null;
x.sibling = null;
root = this.linkPair(x, root);
}
}
public void merge(Node rhs){
if(this.root==null) {
this.root = rhs;
return;
}
if(rhs==null) return;
this.root = this.linkPair(this.root, rhs);
}
public Node findMin(){
return this.root;
}
public Node extractMin(){
Node z = this.root;
if(z!=null){
if(z.child==null)
root = null;
else{
Node firstSibling = z.child;
firstSibling.left = null;
root = mergeSubHeaps(firstSibling);
}
}
return z;
}
private Node mergeSubHeaps(Node firstSibling){
//the 1st pass: merge pairs from left side
Node first = firstSibling;
Node second = first.sibling;
Node tail = first;
if(second!=null){
tail = this.linkPair(first, second);
first = tail.sibling;
if(first!= null)
second = first.sibling;
else
second = null;
}
while(first != null && second!=null){
tail = this.linkPair(first, second);
first = tail.sibling;
if(first!= null)
second = first.sibling;
else
second = null;
}
//the 2nd pass: merge pairs from right side
if(first!=null){
tail = first;
}
Node prev = tail.left;
while(prev!=null){
tail = this.linkPair(prev, tail);
prev = tail.left;
}
return tail;
}
public void print(){
System.out.println("Pairing Heap:");
this.print(0, this.root);
}
private void print(int level, Node node){
for (int i = 0; i < level; i++) {
System.out.format(" ");
}
System.out.format("|");
for (int i = 0; i < level; i++) {
System.out.format("-");
}
System.out.format("%d%n", node.key);
Node child = node.child;
while(child!=null){
print(level + 1, child);
child = child.sibling;
}
}
public static void main(String[] args) throws Exception {
PairingHeap pheap = new PairingHeap();
Node node7=pheap.insert(new Node(7));
pheap.insert(new Node(19));
Node node2=pheap.insert(new Node(2));
PairingHeap pheap2 = new PairingHeap();
pheap2.insert(new Node(9));
pheap2.insert(new Node(17));
pheap2.insert(new Node(12));
pheap2.insert(new Node(14));
pheap.merge(pheap2.root);
pheap2 = new PairingHeap();
pheap2.insert(new Node(15));
pheap2.insert(new Node(18));
pheap2.insert(new Node(16));
pheap2.insert(new Node(5));
Node node11=pheap2.insert(new Node(11));
pheap.merge(pheap2.root);
pheap2 = new PairingHeap();
pheap2.insert(new Node(4));
pheap2.insert(new Node(8));
pheap.merge(pheap2.root);
pheap2 = new PairingHeap();
Node node3=pheap2.insert(new Node(3));
pheap2.insert(new Node(13));
pheap2.insert(new Node(10));
pheap.merge(pheap2.root);
pheap.insert(new Node(6));
pheap.print();
Node min = pheap.findMin();
System.out.format("min: %d%n", min.key);
pheap.decreaseKey(node11, 0);
pheap.decreaseKey(node7, 4);
pheap.decreaseKey(node2, 1);
pheap.decreaseKey(node3, 2);
min = pheap.extractMin();
while(min!=null){
System.out.format("%d ",min.key);
min = pheap.extractMin();
}
}
}

测试输出:

Pairing Heap:
|2
 |-6
 |-3
  |--10
  |--13
 |-4
  |--8
 |-5
  |--11
  |--15
   |---16
   |---18
 |-9
  |--14
  |--12
  |--17
 |-7
  |--19
min: 2
0 1 2 4 4 5 6 8 9 10 12 13 14 15 16 17 18 19

配对堆(Pairing Heap)相关推荐

  1. 配对堆Pairing Heap

    前言 最近做一道Dijkstra的题目,因为边数实在太大了(10910910^9),用STL的priority_queue直接超时(事实上还会MLE).有同学写配对堆的.刚好很久没学习新的数据结构了, ...

  2. Jemalloc 深入分析 之 配对堆Pairing Heap

    为了更好的阅读效果,推荐下载pdf文档: 详细文章请参考:<jemalloc 深入分析> https://github.com/everschen/tools/blob/master/DO ...

  3. 结构之美——优先队列三大结构(三)——Pairing Heap

    转自http://dsqiu.iteye.com/blog/1714961 1.Pairing Heap简介 斐波那契堆主要有两个缺点:编程实现难度较大和实际效率没有理论的那么快(由于它的存储结构和四 ...

  4. 堆(heap)与栈(stack)的区别(一)

    堆区(heap):一般由程序员分配和释放,若程序员不释放,程序结束时可能由操作系统回收,但它与数据结构中的堆不是一回事,分配方式类似于链表. 栈(stack):由编译器自动分配和释放,存函数的参数值, ...

  5. 【编程】堆(heap)和栈(stack)的区别

    从C/C++的内存分配(与操作系统相关)上来说,堆(heap),栈(stack)属于内存空间的一段区域. 效率: 栈是机器系统提供的数据结构,计算机会在底层对栈提供支持(有专门的寄存器存放栈的地址,压 ...

  6. C++二叉堆binary heap (附完整源码)

    二叉堆binary heap 二叉堆binary heap 算法的完整源码(定义,实现,main函数测试) 二叉堆binary heap 算法的完整源码(定义,实现,main函数测试) #includ ...

  7. 11.JDK8内存模型、本地方法栈、虚拟机栈、栈帧结构(局部变量表、操作数栈、方法出口、虚拟机栈与本地方法栈的关系、寄存器、方法区、堆(Heap)、jvm中的常量池、Metaspace(元空间))

    11.JDK8内存模型 11.1.本地方法栈(Native Method Stacks) 11.2.虚拟机栈(Java Virtual Machine Stacks) 11.3.栈帧结构 11.3.1 ...

  8. 返回局部变量或临时变量的地址_值传递和地址返回两者在堆区(Heap)应用的三种易错点...

    1.指针变量作为参数进行值传递给函数的形参,并在堆区(Heap)进行内存分配和赋值 程序源码: 1 结果: Segmentation fault (core dumped) 分析: 如上图,指针变量p ...

  9. 看动画学算法之:二叉堆Binary Heap

    文章目录 简介 二叉堆的特性 二叉堆的作用 二叉堆的构建 获取二叉堆的最大值 二叉堆的插入 insert操作的时间复杂度 二叉堆的提取Max操作 extractMax的时间复杂度 创建二叉堆 简介 我 ...

最新文章

  1. 【Qt】编译QtCreator
  2. 《HTML5与CSS3实战指南》——2.5 构建The HTML5 Herald
  3. windows7 php的php-ssh2,windows7下安装php的php-ssh2扩展教程_PHP教程
  4. 民生银行场景化数据中台是如何炼成的?
  5. 第四章 分治策略 4.1 最大子数组问题 (暴力求解算法)
  6. Android开发之EditText输入框限制输入数字和字母的实现方式
  7. 前后端分离架构一直没机会实战?1周完成Vue+Core WebApi移动商城实战(含源码)!...
  8. 大龄程序员失业后,看他们是如何破局突围的? | 技术头条
  9. xml实现删除一个节点
  10. sccm2012 客户端推送安装故障解决一例
  11. Hadoop生态圈-Flume的组件之自定义Sink
  12. 【转】Google Chrome浏览器调试
  13. 张磊:极少有人真正理解时间的价值
  14. 计算方法 matlab,计算方法及其MATLAB实现
  15. 联想电脑安装黑苹果全教程
  16. Facebook安卓Feed流的内存优化实践
  17. HTML实现获取验证码功能
  18. 增长战略五大维度:单点突破、由内而外、锚点绑定、群体延伸、圈层建设
  19. 找到一个最全的,抽空把它看完
  20. 关于内存溢出遇到的两种情况

热门文章

  1. 复试这样做真的会被刷!避雷指南请收好!
  2. 回归Qt——写在Qt5.10发布之日
  3. 小米、华为、苹果、OV教给我们的战略课
  4. 进化:从孤胆极客到高效团队_询问如何做极客:学习Office功能区,使用旧BIOS引导到USB以及捕捉Windows...
  5. android nfc 鸡肋,手机NFC真的鸡肋吗?这些功能很实用
  6. C#代码实现九点标定
  7. windows的一些装B用法
  8. 场景实验室吴声:新市井商业,无法后退的新现实
  9. MySQL导入数据错误 Incorrect string value: ‘\xF0\xA0\xAE\xB7\xE5\x8F...‘ for column ‘news_text‘ at row 1
  10. 酷派7019获得root教程 亲测