左式堆(leftist heap)

2023-12-05 10:38
文章标签 heap 左式 leftist

本文主要是介绍左式堆(leftist heap),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

堆合并

堆合并是指,对于任意两个堆,将其合并为一个更大的堆,如下图所示:

heap_merge

为了解决这个问题,我们可以首先进行一些初步的尝试。

简明的插入策略

最简明的思路,无非是将一个堆中的元素,逐个删除并且依次插入到另一个堆中。不妨设两个堆的规模分别为mn,则这种策略的时间复杂度为O(m * (logm + log(m + n))) = O(m * log(m + n))

Floyd批量建堆算法

简明的插入策略的时间复杂度是在是太差,实际上,我们可以将这m + n个元素组织成一个向量,使用Floyd建堆算法,其时间复杂度为O(m +n )

实际上,Floyd建堆算法对于任意一组输入数据都可以达到线性的时间复杂度,但是这里的输入并非是【任意】的输入,而是两个堆,它们在内部都是满足堆序性质的。因此我们期望这种预先满足的堆序性质得到利用,从而在一定程度上得到一个更高效率的堆合并算法。

一种递归策略

下面给出一种递归地进行堆合并的策略——设两个堆分别为AB,并且保证A堆的堆顶大于B堆的堆顶,为了将AB两个堆合并,只需要取出A的右子树,将A的右子树与B进行合并,并且将合并后的堆重新作为A的右子树。而为了将A的右子树与B合并,只需要递归地进行上述的策略即可。

这种策略的正确性是显然的,一次这样的操作后,合并后的堆,其堆序性质仍然得到满足。通过不断地取堆的右子树,递归问题的规模不断减小,直到平凡的情况,即A的右子树为空,此时直接将B堆连接到A的右子树即可,因此该算法是有穷的。

对该算法进行进一步分析,在最坏的情况下,需要不断穷举AB的右子树,直到两个堆的规模都只有1,因此在最坏情况下, 该算法需要进行rh1 + rh2次递归,其中rh1rh2分别表示两个堆的最右侧路径长度。

为了快速地取出堆的右子树,以及将某棵树连接到一个节点上,这里应该抛弃前面在完全二叉堆采用的向量结构,而是采用二叉树来作为堆的底层结构,此时每次递归的时间复杂度都是O(1),因此整体的时间复杂度为O(rh1 + rh2)。可见,为了获得更好的性能,就应该尽量减少堆的最右侧路径长度,左式堆就是这样的一种结构。

左式堆的概念

首先需要引入空节点路径长度的概念——对任意一棵二叉树,引入所有的外部节点,则任意节点的空节点路径长度(npl, null path length),定义为该节点到外部节点的最短距离,外部节点的npl定义为零,即npl(null) = 0

因此,任意内部节点x的空节点路径长度,取决于其左右节点中npl的更小者,即

npl(x) = MIN(npl(lc(x)), npl(rc(x))) + 1;

对于左式堆中的任一节点,都满足其左孩子的npl值,不小于右孩子的npl值,即左倾性

npl(lc(x)) >= npl(rc(x));

根据npl的定义,左式堆中的任一节点的npl都唯一地取决于它的右孩子,即

npl(x) = npl(rc(x)) + 1;

这样,对于左式堆的根节点,其npl等于其右子树的npl加一,而它的右子树的npl,又等于它的右子树的右子树的npl加一,即

npl(root) = npl(rc(root)) + 1 = npl(rc(rc(root))) + 2 = ... = right path length

因此,根节点的外部路径长度npl,就等于最右侧路径长度。左式堆的一个实例如下图所示:

leftist_heap

而让我们再回顾一下npl的定义,即任一节点到外部节点路径的最短距离,因此根节点到外部节点的最短路径,就是左式堆的最右侧路径。设最右侧路径的长度为d,左式堆中一定包含了以d为高度的一棵满子树,因此左式堆的全部节点数量(包括外部节点)n一定满足

n ≥ 2 d + 1 − 1 n \ge 2^{d + 1} - 1 n2d+11

反过来,即d一定满足

d ≤ l o g 2 n + 1 = O ( l o g n ) d \le log_2^{n + 1} = O(logn) dlog2n+1=O(logn)

左式堆的该性质如下图所示:

在这里插入图片描述

在这样的条件下,我们上面给出的合并算法,其时间复杂度就只有O(logm + logn)了,这无疑是对性能的一次极大改进。但是需要注意的是,左式堆只是保证最右侧路径的长度最小,并非左子堆的规模和高度一定会大于右子堆。

左式堆的实现

左式堆仍然是优先级队列的一种实现方式,因此要实现左式堆,仍然只需要实现优先级队列的几个抽象函数接口,即getMaxinsertdelMax。其中getMax的实现只需取出左式堆的根节点即可,与完全二叉堆的实现相同,在这里不再赘述。

需要指出的是,左式堆的insert接口与delMax接口,都可以借助左式堆的merge算法快速的实现。对于insert接口,无非将一个规模为1的左式堆,与当前的左式堆进行合并,其代码如下:

template <typename K, typename V>
void LeftHeap<K, V>::insert(entry<K, V> e){BinNodePosi(T) newNode = new BinNode<T>(e);newNode->npl = 1;__root = merge(__root, newNode);__root->parent = nullptr;++__size;
}

而左式堆的delMax函数,无非是在删除了根节点以后,将根节点的两个左子堆进行合并,利用merge也可以方便地实现:

template <typename K, typename V>
entry<K, V> LeftHeap<K, V>::delMax(){entry<K, V> res = getMax();__root = merge(__root->leftChild, __root->rightChild);if (__root) __root->parent = nullptr;--__size;return res;
}

因此,问题的关键就在于左式堆的merge函数,该函数基本按照我们上面提到过的策略进行,只是需要做一些细节上的修改。无论是在insert还是delMax操作的过程中,都需要动态地维护左式堆的结构,即每次将合并后的堆作为右子树插入到当前节点后,都需要对当前节点的左倾性进行检查,一旦发现左子树的npl小于右子树,则将左右子树交换。merge函数的实现如下:

#define T entry<K, V>template<typename K, typename V>
BinNodePosi(T) merge(BinNodePosi(T) a, BinNodePosi(T) b){if (a == nullptr) return b;if (b == nullptr) return a;BinNodePosi(T) temp;if (a->data < b->data){//swap a and btemp = a;a = b;b = temp;}if (a->rightChild == nullptr) a->rightChild = b;else a->rightChild = merge(a->rightChild, b);a->rightChild->parent = a;if (NPL(a->leftChild) < NPL(a->rightChild)) {// swap the right chlid and left child of atemp = a->leftChild;a->leftChild = a->rightChild;a->rightChild = temp;}a->npl = NPL(a->rightChild) + 1;return a;
}

根据前面的分析,merge的时间复杂度为O(logn),无论是在insert还是delMax中,调用merge函数以外的操作都可以在O(1)时间内完成,因此两个操作的性能都是O(logn)

这篇关于左式堆(leftist heap)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/457233

相关文章

【UVa】10755 Garbage Heap 三维前缀和

题目分析:将前缀和应用到三维,求最大子矩阵。为S[x][y][z]数组中每个坐标保存从(0,0,0)到(x,y,z)范围内的子矩阵的元素和,最后用多次区间加减法可以得到需要的子矩阵的元素和,再用类似一维求最大连续和的方法求三维最大连续和。 代码如下: #include <cstdio>#include <cstring>#include <algorithm>using

MyEclipse中Jboss启动出现Java heap space解决方案

转 http://cst.is-programmer.com/posts/16109.html 在MyEclipse中启动JBOSS,结果报: java.lang.OutOfMemoryError: Java heap space 异常,用jboss中的run.bat启动,则正常运行,而在MyEclipse中启动就报异常,百度之~~得解: 原因是对于很大的Web工程(公司的这个平台

Build Min Heap Using Array

Build min-heap using Array. 思路1:首先明白heap的底层implementation就是array,从0开始的parent和left,right的关系为, 如果现在的node index为i,那么parent index就是 (i-1)/2;   left  为2*i+1, right为 2*i+2;          ( i-1 ) / 2

报错:java.lang.OutOfMemoryError: Java heap space

该报错原因是内在不足,设置方法,打开tomcat设置页面,点击open launch configuration,打开配置窗口,在变量参数中加入空格后输入: -Xms512m -Xmx512m -XX:PermSize=512m -XX:MaxPermSize=512m

maven编译出现Java heap space,和项目编译问题

这两天遇到个问题挺头疼,1项目不编译 2maven项目打包时,因为是使用的Lombok,所以当有几百个实体类进行编译时,需要一次性读进内存中,造成maven编译时内存溢出 方法1的解决方法(多构建几次): 方法2的解决方法: File->Settings设置一下VM的参数。 -Xms258m -Xmx1024m

数据结构(邓俊辉)学习笔记】优先级队列 09——左式堆:合并算法

文章目录 1. LeftHeap模板类2. 算法3. 实现4. 实例 1. LeftHeap模板类 接下来这节,来讨论左式堆的合并算法。再给出具体算法之前,首先要给出左式堆模板类的定义。 比如这就是一种可能的实现方式,可以看到,我们这里再次利用了 C++的多重继承,只不过与完全二叉堆不同,既然左式堆已经不再满足结构性,所有元素在物理上也不可能继续保持紧密的排列,因此继续从向量

数据结构(邓俊辉)学习笔记】优先级队列 08——左式堆:结构

文章目录 1. 第一印象2. 堆之合并3. 奇中求正4. NPL5. 左倾性6. 左展右敛 1. 第一印象 在学习过常规的完全二叉堆之后,我们再来学习优先级队列的另一变种,也就是左式堆。所谓的左式堆,也就是在拓扑形态上更加倾向于向左侧倾斜的一种堆, 比如这就是一个左式堆由生到长,直至灭亡的整个生命过程。 可以看到,相对于常规的完全二叉堆,左式堆的确显得有些别致。 那么,为什

【STL源码剖析】第四章 序列式容器 之 heap和priority_queue底层实现

heap heap概述 heap并不归属于STL容器组件,它扮演priority queue的助手。binary max heap是priority queue的底层机制。 binary heap是一种complete binary tree(完全二叉树),也就是说,整棵binary tree除了最底层的叶节点(s)之外,是填满的,而最底层的叶节点(s)由左至右不得由空隙。 compl

STL系列 heap 堆-解析

STL中与堆相关的4个函数——建立堆make_heap(),在堆中添加数据push_heap(),在堆中删除数据pop_heap()和堆排序sort_heap(): 头文件 #include <algorithm> 下面的_First与_Last为可以随机访问的迭代器(指针),_Comp为比较函数(仿函数),其规则——如果函数的第一个参数小于第二个参数应返回true,否则返回false。

左式堆(数据结构篇)

数据结构之左式堆 左式堆 概念: 左式堆是一种能够高效的支持合并操作的堆,左式堆的底层也是由二叉树实现,但是左式堆不是平衡的(不由完全二叉树实现的),左式堆趋向于不平衡。左式堆也像二叉堆一样拥有着堆的特性(有序性和结构性),左式堆是最小堆左式堆是一个拥有堆序性的二叉树(不是二叉堆)加上NPL特性的一棵树,任意节点X的NPL(X)(零路径长)为从X到一个没有两个儿子的节点的最短路径的长,因此,