树简介
笛卡尔树是平衡二叉树的一种,他和我们之前学习的AVL树一样通过旋转来调整,使平衡树达到平衡态。不过,不同的是:笛卡尔树除了用于决定节点向左/右分布的key外,还有个value,用来决定节点的层级先后。
笛卡尔树的分布存在以下特点:
- key:分布遵循BST的规律,即左子树key<根key<右子树key
- value:分布遵循堆的规律,即父value < 子value/子value < 父value
- 父value < 子value: 最小堆
- 子value < 父value:最大堆
树操作算法概述
笛卡尔树我们只介绍建树的过程。忽略树的修改操作。
建树
笛卡尔树的key
遵循二叉搜索树的特点。value
遵循堆的特点。所以我们先把数据按照key
从小到大进行排序。【以此为例,如果你硬要从大到小也是一样的道理】
接下来我们只需要从第一个节点开始串下去就行了,后面的肯定比前面的大,所以在前面节点的右子树或者父节点,而是右子树还是父节点,通过value
判定。
一个二叉树大概长下面这样【网上盗的图,不是搜索树,大概看个结构就行】:
既然不是当右儿子就是当爸爸,我们在构建过程中直接把根节点到最右子树存下来,方便为新来的节点找到进入的位置。
存储的东西如下:
每新来一个节点,我们拿着value
一个一个比较,因为父value
要比子value
大。而且有一个特点,新插入一个点后,原来他右下位置的点就变成他的左子树了,右链砍掉了一部分。所以有两种思路:
- 每次从根往右遍历
- 和当前的节点比较,当前节点比插入值大就继续向后遍历,直到遇到一个比他小的,然后插到他的前面,他后面的节点做插入点的左子树
- 每次从右下角遍历
- 和当前的节点比较,当前节点比插入值小就过,继续向前遍历,找到对应位置,插进去
两种方法其实都是一个思路:遍历,直到找到合适位置,然后插入。
在网上找到的很多源码都提到了O(n)时间复杂度的笛卡尔树构造方法,说是借助栈,他们一般用的是我介绍的第二种思路:和栈顶比较,不行就扔掉,插进去后入站,然后新的右链就又在栈中了。
如果你思路清晰了,完全可以不借助栈,直接从根节点右子树右子树的遍历就行,都不用维护栈了。
源码
直接放项目的地址了,我是用的java写的增删操作,没有用C。还有就是没有专门为这个建git,源码在com.gateway.learn.tree
下面。github地址:https://github.com/LiPengcheng1995/gateway-parent.git
树应用场景
官方话:
笛卡尔树是一种特定的二叉树数据结构,可由数列构造,在范围最值查询、范围top k查询(range top k queries)等问题上有广泛应用。它具有堆的有序性,中序遍历可以输出原数列。
(用数组下标当key)
自己的话:
笛卡尔树我认为其实最重要的并不是key
,而是value
:
key
存在的意义是为了让数列中相邻的节点继续相邻,比较形象一点的形容就是只看key
的话可以把key
当作数列的下标,然后从中间把这个数列拎起来,就形成了树结构。
value
存在的意义你可以看作数组中存的值,你给整个数列做了一个排序后的存储,只不过你把排序结果放在了树的上下结构中,节点下标没变。
问题一
这个样一个问题:求[a,b]范围中的最大值,你只需要找到a,b节点的LCA(Lowest Common Ancestor),即最近公共祖先。
思路分析
- 构建笛卡尔树
- 依靠二叉排序树的特点快速定位LCA
- 返回查到的LCA的
value
值
本文算法时间复杂度分析
- 构建笛卡尔树 O(n)。(因为以下标为
key
,不需要重新排序了,用栈来辅助理解就是每个元素只会有一次进栈) - 依靠二叉排序树特点快速定位LCA O(log2(n))【平衡二叉树树深,虽然笛卡尔树的平衡不一定有VAL树那么平,但是大概还是平的】
-
this.key < a
-->右边走 -
this.key > b
--->左边走 - 遇到的第一个
a
, this.value
就是结果
-
return this.value
所以时间复杂度为O(n)。当然,如果多次查询只需要一次建树,这就很爽了,时间复杂度为O(log2(n))。
传统算法时间复杂度分析
顺序遍历一遍,记一下最大就行,时间复杂度为O(n)。
比较
本文算法优点:
- 多次查询只需一次建树
- 查询效率稳定,(已经构建笛卡尔树后,区间范围越大查询效果往往越快)
本文算法缺点:
- 存储树结构有一一定开销
- 不稳定,存在极度变态数列情况下整棵树被拉成一个链的可能
问题二
这样一个问题:已知一个无序数组,我给你其中一个下标,你要告诉我这个下标对应的值是多大范围内的最大/小值
思路分析
- 构建笛卡尔树,如果求最大值,就按照最大堆构建【后面都按照最大来讲,最小也是同理】
- 笛卡尔树看下标是树,看值是堆,找到对应的点,它下面的值都比它小,找到他的子树中的最左边点、最右边点即可
本文算法时间复杂度
- 构建树,需要
- 找节点对应子树的最大最小,即是两个范围点【找最左/最右子树即可】,复杂度为树深度,都是
综上,复杂度为
问题三
这个样一个问题:求[a,b]范围中的第n大的值。
思路分析
- 构建笛卡尔树
- 依靠二叉排序树的特点快速定位LCA
- 看LCA下面一层够不够数
- 够就找到第n-1大的
- 不够就从再下一层找,设本层有m个点
- 这一层够就找第n-m-1大的
- 不够再去下一层。。。。。。
时间复杂度分析
- 构建笛卡尔树 O(n)
- 找LCA O(log2(n))
- 找第n大的,由于树每靠上一层,本曾的节点数以指数倍减少,所以我们可以近似看成常数(可以。。。吧,总之需要比较的很少了)、
所以时间复杂度为O(n)。当然,如果多次查询只需要一次建树,这就很爽了,时间复杂度为O(log2(n))。
传统算法时间复杂度分析
得对这一段区间进行排序,时间复杂度为O(nlog2(n))
比较
如果区间随便给,这么查的话,还是本文的算法好。缺点和上一个问题一样就不提了。
还有一个明显的缺点,就是如果对同一个区间你经常查top k 。直接用传统方法排个序反而比较快。看自己需要什么了。
问题四
给出n个非负的整数,表示直方图中每个条的高度,且每个条的宽度均为1,找出这些条覆盖的最大面积的矩形。
思路分析
- 构建笛卡尔树,(最小堆)
- 后序遍历,将每个节点的所有子节点数目存在这个节点里,并求出子节点数目和此节点
value
的乘积。找到乘积最大的那个结果
帮助理解:
- 子节点数目 = 这个节点的
value
是多少个节点中最小的 = 涂色长方形的底边宽是多少- 此节点
value
= 这n个节点的高度最小值 = 涂色长方形的高
注意:
- 因为是按照下标为
key
进行排的,原来相邻的长条现在还是相邻,所以才能这么等效
时间复杂度分析
构建笛卡尔树 O(n)
后续遍历、计算、存储,因为只遍历一遍,所以时间复杂度是 O(n)
思路分析(基础)
上面的虽然说着通顺,但是正常人的思路并不是这样的。你可以这样想,底和高都变化,我们需要先找两者的关联,先构建出一个合适的计算公式:
从底入手
我们需要遍历底边的所有匹配段情况,并算出区域内的最低点【数组一端区间内的最小值】,这样需要构建笛卡尔树,因为匹配的不确定性,我们需要枚举底边的(1+2+...+n)种情况,复杂度是。当然,如果你硬要把每次确定最小值的操作算到求高的操作中去的话,就是 。
从高入手
前面一个思路,我们解决的最大的问题是把范围确定后的定高问题解决了,但是确定底边取值范围开销太大了,达到了级别。
遇到了这么乱的思路,一般有两个可能:
- 问题就是挺乱的。就像我们某些不合理的需求一样,屎一样的需求,再怎么捋代码也是乱七八糟
- 我们抽象的不对
我们抽象出了底边x和高y,然后求xy的乘积最大值。我们求x时即使使用普通的排序方法,不用笛卡尔树也还有,但是找底边时我们直接就冲着去了,这就给人不协调的感觉了。
所以我们进行更近一步的抽象,设从x到y,取这范围内的长方形,设高的最小值为z,这样x,y,z的判断就都是了,当然,直接一下,复杂度直接就是了。
我们需要找到三个变量之间的关系。
如果我们确定一个x,后面y每个取值对应的高度都有可能变化,变化根据数组来变,没有规律,而没有规律就意味着遍历,意味着低效。
如果确定y,是一样的道理。
如果我们确定z,那就无所谓了,x和y往开的撑就行了。撑的越开,面积越大。
所以我们从z入手,算出每个z的值都能对应最大多大的(y-x),也就是z值都是多大区间内的最小值。此处回到问题二。只需要遍历即可,所以时间复杂度是:
- 构建笛卡尔树
- 一一求出每个高度对应的底边最大值【对应的最大最小的范围】(n个,每个,也就是
- n个方案,在一一计算时顺手存下最小的值以及计算方案即可,无需多余的时间复杂度
所以时间复杂度降低到了。
文献参考
- https://www.cnblogs.com/pushing-my-way/archive/2012/08/24/2653709.html
- https://blog.csdn.net/pipisorry/article/details/39033269
- https://baike.baidu.com/item/%E7%AC%9B%E5%8D%A1%E5%B0%94%E6%A0%91
- https://agatelee.cn/2016/07/%E6%A0%91%E7%B1%BB%E9%97%AE%E9%A2%98%E7%AC%9B%E5%8D%A1%E5%B0%94%E6%A0%91/
- https://zh.wikipedia.org/wiki/%E7%AC%9B%E5%8D%A1%E5%B0%94%E6%A0%91