左式堆(leftist heap)

news/2024/5/20 7:38:31 标签: 左式堆, , 优先队列, 数据结构, 算法

合并

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

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 b
		temp = 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 a
		temp = 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)


http://www.niftyadmin.cn/n/817026.html

相关文章

mysql百万级全文索引及match快速查找

建立全文索引的表的存储引擎类型必须为MyISAM 问题是match against对中文模糊搜索支持不是太好 新建一个utf8 MyISAM类型的表并建立一个全文索引 &#xff1a; CREATE TABLE articles ( id INT UNSIGNED AUTO_INCREMENT NOT NULL PRIMARY KEY, title VARCHAR(200), …

哲学家就餐问题及其实现

哲学家就餐问题描述 哲学家就餐问题是指&#xff0c;有五个哲学家围坐一桌&#xff0c;每两个哲学家之间都有一只叉子&#xff0c;一共有五只叉子。每个哲学家都只有两个动作&#xff0c;即思考和就餐&#xff0c;哲学家思考的时候不需要任何的资源&#xff0c;但只有同时拿起…

Appium启动app

首先要获取包名&#xff0c;然后获取launcherActivity。获取这两个关键东西的方法很多&#xff0c;这里就不一一多说&#xff0c;小伙伴们可以各显神通。小编这里主要给大家推荐一个sdk自带的实用工具aapt. aapt即Android Asset Packaging Tool&#xff0c;在SDK的build-tools…

进程管理(5):死锁

死锁的基本概念 为什么会发生死锁&#xff1f; 其实在前面进程管理的总结中&#xff0c;已经多次出现了死锁的概念&#xff0c;比如在信号量机制中&#xff0c;如果信号量使用不慎&#xff0c;就会出现死锁。实际上&#xff0c;在现实生活中&#xff0c;死锁现象也经常会出现&a…

盒子模型之CSS3学习--边框(Border)

盒子模型之border 边框相关属性 border-width 控制边框的大小  用长度赋值 border-style 控制边框的样式  none 没有边框  solid 实线  dotted 点线  dashed 虚线  double 双线条 border-color 控制边框的颜色  四种颜色表示法 border-top 控制上边框的…

串匹配之kmp算法

字符串匹配问题是算法中的常见问题&#xff0c;即对于一个较长的文本串T&#xff0c;以及一个较短的模式串P&#xff0c;返回模式串P在文本串T中是否出现&#xff0c;或者首先出现的位置&#xff0c;或者所有出现的位置。实际上&#xff0c;在实际生活中&#xff0c;具有大量的…

[aspnetcore]asp.net core程序部署到Ubuntu中的路径问题

先标记下正确写法 new FileInfo(Environment.CurrentDirectory "/Config/Log4net.config") 很多同行喜欢这样写&#xff1a; new FileInfo(Environment.CurrentDirectory "\\Config\\Log4net.config") 在windows下是没有问题的&#xff0c;但是在linux下…

串匹配之bm算法

以终为始 在串匹配之kmp算法中&#xff0c;已经简单介绍了kmp算法的基本原理与实现。kmp算法的基本思想&#xff0c;是利用已经成功匹配的字符的信息&#xff0c;快速跳过无意义的对齐位置&#xff0c;从而实现更高的效率。下面&#xff0c;我们首先对这样的思想进行更深入的研…