看图说话之左式堆(优先队列)——原理解析及java实现

一丶左式堆的基本概念

    数据结构之二叉堆(优先队列)——原理解析文章中介绍了二叉堆的基本原理。本文介绍左式堆的基本原理,二叉堆是对优先队列的一种高效实现,左式堆是针对二叉堆合并操作困难的缺点,而提出的另外一种优先队列实现方式。
    左式堆和二叉堆都具有一样的堆序性(大根堆和小根堆),只是在结构性上有所不同,二叉堆是完全二叉树,左式堆不是完全二叉树其具有非常明显的不衡特征。为了介绍左式堆的结构性,必须介绍节点的npl(null paht length,零路径长度)属性。

图 1二叉树

节点的npl是指该节点到没有两个儿子的子节点(包括自身)的最短路径长。其中有几个特殊情况需要替注意:

  • null节点的npl=-1

  • 叶子节点的npl=0。

  • 只有一个子节点的节点npl=0.

依据上述的npl定义和特殊情况,图1中二叉树各个节点的npl值如下图所示。


图2 二叉树的npl值

    对于上图所示的根节点,其右节点没有两个儿子,所以依据定义,最短路径是到右节点的路径,路径长=1。通过图2所示的二叉树npl值分布,可以得到一个规律:任意节点的npl=Min(左节点npl,右节点npl)+1。
    掌握了零路径长度的概念后,我们就可以介绍左式堆的结构性约束了,左式堆的结构性是指:对于任意节点,其左节点的npl>=右节点npl。这样的定义下左式堆的左子树深度会明显高与右子树,而且沿着右节点的右节点的路径走下去将是最短路径。如此定义一个树的结构有什么好处呢?
    观察图1不难看出,图1代表的二叉树是一个二叉堆,观察图2和图1不难看出图1代表的二叉树不仅是二叉堆,而且还是左式堆。可见二叉堆是一种特殊的左式堆,左式堆在二叉堆的基础上调整了结构性,让其拥有较短的最右路径长度,用来高效的支持后续的合并操作。

二丶左式堆的合并操作

    在了解了左式堆的基本概念后,我们知道左式堆是为了更有效的支持合并操作,而在二叉堆上作出的改进。本节通过图例的方式来讲解左式堆的合并过程。都是小根堆的前提下进行介绍。


图3 左式堆d1和左式堆d2
  1. 比较d1和d2的根节点大小,将根节点较小的堆(d1)和根节点较大(d2)的堆的右子树进行合并,利用合并后的左式堆d3取代d1的右子树。


    图4 堆合并中的拆分情况。
  2. 如图4所示,d2堆的右子树和d1堆合并后的左式堆充当d2的右子树则完成了合并操作,所以合并的问题转化成了d2堆右子树和d1堆合并的问题了。这明显是一个递归的过程,如下伪代码
publicLeftHeapNode merge(leftHeap d2,leftHeap d1){
   if(d2==null)return d2;
   d2.right = merge(d2.right,d2);
   return d2;
}
  1. d2堆右子树和根节点较小的d1堆合并,重复步骤1合并结果如下。


    图5 d1堆右子树和d1堆合并结果

    此时发现了一个问题:合并后的结果不满足堆序的特点了,对于根节点18而言,其左节点15的npl=0,右节点10的npl=1.不满足左式堆对结构性的要求,所以此时需要左一个调整,将不满足堆序特定的节点18的左右子树互换。调整结果如下:


    图6 堆序调整结果

        通过图6所示的堆序调整步骤后,可以将这次合并的结果去替换d2堆的右子树了,替换结果如下图所示。
    图7 d2堆右子树和d1堆合并结果替换d2堆右子树

        这里也需要特别注意,在替换完成之后,需要检查根元素20是否满足堆序特性,观察图7所示的替换结果,根元素20满足堆序特点,无序调整堆序。 所以该次堆合并过程结束。
        观察图7合并之后的结果,可发现合并之后依然是一个左式堆,其最右路径20-18-15依然是所有路径中最短的路径。

总结:
  • 在上述的堆合并过程中,我们可以清楚的看到堆合并的过程是递归的过程,在用代码实现堆合并过程时候,采用递归的程序设计可以更简单。

  • 在每次合并之后,需要检查根元素是否满足堆序特性,如果不满足需要调整堆序特性,调整堆序特性后需要更新npl值。

  • 从上述的结果中,合并堆的过程中,递归的次数取决于最右路径的长度,根据左式堆的结构性其最右路径最长为log(N),一次合并操作的最坏时间复杂度是log(N)

三丶左式堆的插入操作

    在知道左式堆合并的基础上,插入操作就很简单了。插入操作可以看成左式堆和只具有一个节点的左式堆的合并操作。那么其最坏时间复杂度也是log(N)。

四丶左式堆的删除操作

左式堆的删除操作在理解合并操作的基础上也十分简单,分为如下两个步骤:

  1. 返回根节点值

  2. 删除根节点,将左子树和右子树合并,合并后的结果就是删除操作后的新左式堆。

五丶左式堆的建堆过程

左式堆的建堆过程就是对输入元素重复插入过程,最坏时间复杂度Nlog(N),平均时间复杂度O(N)。

六丶左式堆的java代码实现。

    二叉堆因为是完全二叉树的结构所以采用数组的存储结构,但是左式堆不是完全二叉树需采用链式的结构(需要支持合并操作,也需要采用链式存储结构)。

java代码的如下:

public classLeftHeap{
public HeapNode root;

public LeftHeap(int val){
   root= new HeapNode(val);
}

public void merge(LeftHeap heap){
   if(heap!=null){
       root=internalMerge(root,heap.root);
   }
}

private HeapNode internalMerge(HeapNode h1,HeapNode h2){
   if(h1==null) return h2;
   if(h2==null) return h1;
   HeapNode result =null;
   if(h1.val>=h2.val){
       h2.right = internalMerge(h2.right,h1);
       result =h2;
   }else{
       h1.right = internalMerge(h1.right,h2);
       result =h1;
   }
   //如果不满足结构性,则调整
   intleftNPL = result.left==null?-1:result.left.npl;
   intrightNPL = result.right==null?-1:result.right.npl;
   if(leftNPL<rightNPL){
       HeapNode temp = result.right;
       result.right = result.left;
       result.left = temp;
   }
   //更新npl值。
   result.npl = Math.min(leftNPL,rightNPL)+1;
   returnresult;
}
//对外暴露的插入函数
public void insert(int val){
   LeftHeap heap = new LeftHeap(val);
   merge(heap);
}
//对外暴露的删除函数
public int deleteMin(){
   if(root==null) return -1; //-1这里代表错误码,堆为空不删除。
   HeapNode node = root;
   root = internalMerge(root.left,root.right);
   returnnode.val;
}
public boolean isEmpty(){
   returnroot==null?true:false;
}

public static void main(String [] args){
   LeftHeap heap = new LeftHeap(2);
   for(int i=1;i<10;i++){
       heap.insert(i);
   }
   while(!heap.isEmpty()){
       System.out.print(heap.deleteMin()+" ");
   }
}
//左式堆的节点的定义
private static class HeapNode{
   publicint npl;
   publicHeapNode left;
   publicHeapNode right;
   publicint val;
   publicHeapNode(int val){
       this.val = val;
       left = null;
       left =null;
       npl =0;
   }
}

}
Reference:
[1] 数据结构与算法 java语言描述
[2] 博客原文

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,717评论 6 496
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,501评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,311评论 0 350
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,417评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,500评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,538评论 1 293
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,557评论 3 414
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,310评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,759评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,065评论 2 330
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,233评论 1 343
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,909评论 5 338
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,548评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,172评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,420评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,103评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,098评论 2 352

推荐阅读更多精彩内容

  • 0.目录 1.优先队列ADT 2.几种实现 3.二叉堆 4.d-堆 5.左式堆 6.斜堆 7.二项队列 8.斐波那...
    王侦阅读 3,083评论 1 2
  • 树的概述 树是一种非常常用的数据结构,树与前面介绍的线性表,栈,队列等线性结构不同,树是一种非线性结构 1.树的定...
    Jack921阅读 4,445评论 1 31
  • 基于树实现的数据结构,具有两个核心特征: 逻辑结构:数据元素之间具有层次关系; 数据运算:操作方法具有Log级的平...
    yhthu阅读 4,268评论 1 5
  • 课程介绍 先修课:概率统计,程序设计实习,集合论与图论 后续课:算法分析与设计,编译原理,操作系统,数据库概论,人...
    ShellyWhen阅读 2,272评论 0 3
  • 如果开发比较大的网站项目,会有很多个app。如果每个app的url都写在根目录的urls文件里会显得比较混乱,不好...
    hs_a2d1阅读 161评论 0 0