Leftist Tree(Leftist Heap) - 左偏树(左倾堆)


左偏树

左偏树是一种类似小根堆的二叉树,它的根节点总是树中的最小值。左偏树的每次取出/加入操作的时间复杂度为,与小根堆相同。与堆不同的是,合并两个数量为的堆的时间复杂度为(因为合并两个堆需要从其中一个取出元素加入另一个,重复次),而合并两个数量为的左偏树的时间复杂度为

左偏树的主要操作有: 合并两个左偏树; 插入新节点; 查找最值; 删除最值。其中的实现依赖于,合并操作是左偏树的核心。

左偏树中的节点的距离是该节点到最右下叶子节点的距离。左偏树具有以下性质:

父节点的值小于等于其左右孩子节点的值,即。最小的值在根节点类似小根堆的头节点;

父节点的左孩子节点的距离大于等于右孩子节点的距离,即

父节点的距离等于其右孩子节点的距离加,即

具有个节点的左偏树的根节点的距离小于等于,即

下图中每个节点上,上面的数字代表节点的值,下面的数字代表距离。合并两个左偏树的操作,分为两步:

从根节点开始递归向下合并两个左偏树。每次合并时比较两个子树根节点的值,总是用根节点较大的子树(设)替换根节点较小的子树的右孩子节点,替换后成为新的子树。然后递归的,继续考虑作为根节点的子树与作为根节点的子树;

从叶子节点开始递归向上更新所有节点的距离。对于每个节点,首先若不满足则将左右孩子子树交换,其次若不满足则更新父结点的距离

下图是合并两个左偏树的过程:

LeftistTree2.png

比较两树的根节点,节点沿着节点向右下寻找第个满足的节点,替换作为节点的新右孩子节点。该节点为节点),节点的原右孩子节点暂时脱离;

LeftistTree3.png

节点沿着节点向右下寻找第个满足的节点,替换作为节点的新右孩子节点。该节点为节点),节点的原右孩子节点暂时脱离;

LeftistTree4.png

节点沿着节点向右下寻找第个满足的节点,替换作为节点的新右孩子节点。该节点为节点),节点的原右孩子节点暂时脱离;

LeftistTree5.png

节点沿着节点向右下寻找第个满足的节点,替换作为节点的新右孩子节点。该节点为节点),节点的原右孩子节点暂时脱离;

LeftistTree6.png

节点沿着节点向右下寻找第个满足的节点,节点没有右孩子节点,因此节点直接成为节点的右孩子节点,不再需要替换,合并操作结束;

LeftistTree7.png

向右下插入节点的操作会影响到左偏树的平衡性,右子树变得越来越庞大。而且所有节点的距离也是错的(没有更新)。实际上每一步合并操作后还需要检查左右子树的距离属性:对于的情况,交换左右子树;对于的情况,更新

节点距离的更新必须从叶子节点开始向上进行,对于之前步骤中修改过的节点,重新遍历计算其距离(其中):

对于节点,不需要交换左右子树,更新

LeftistTree8.png

对于节点,不需要交换左右子树,更新

LeftistTree9.png

对于节点,不需要交换左右子树,更新

LeftistTree10.png

对于节点,需要交换子树和子树,更新

LeftistTree11.png

对于节点,需要交换子树和子树,更新

LeftistTree12.png

对于节点,不需要交换左右子树,更新

编码时通过递归的方式将合并和更新距离两个操作放在同一个递归函数中,合并函数Merge返回合并后左偏树的根节点的距离,在Merge中调用左右孩子的合并操作,获取左右孩子的距离,然后再决定是否交换左右子树,并更新父节点的距离。本文的将两个步骤分开是为了方便理解算法。

左偏树的插入操作,可以看作左偏树与一个只有根节点的左偏树的合并操作;删除最值的操作,可以看作删除根节点后,合并左右子树的操作。

左偏树的合并操作、插入节点操作、删除根节点操作的时间复杂度都为


源码

LeftistTree.h

LeftistTree.cpp

测试

LeftistTreeTest.cpp