树——笛卡尔树

树简介

笛卡尔树是平衡二叉树的一种,他和我们之前学习的AVL树一样通过旋转来调整,使平衡树达到平衡态。不过,不同的是:笛卡尔树除了用于决定节点向左/右分布的key外,还有个value,用来决定节点的层级先后

笛卡尔树的分布存在以下特点:

  • key:分布遵循BST的规律,即左子树key<根key<右子树key
  • value:分布遵循堆的规律,即父value < 子value/子value < 父value
    • 父value < 子value: 最小堆
    • 子value < 父value:最大堆

树操作算法概述

笛卡尔树我们只介绍建树的过程。忽略树的修改操作。

建树

笛卡尔树的key遵循二叉搜索树的特点。value遵循堆的特点。所以我们先把数据按照key从小到大进行排序。【以此为例,如果你硬要从大到小也是一样的道理】

接下来我们只需要从第一个节点开始串下去就行了,后面的肯定比前面的大,所以在前面节点的右子树或者父节点,而是右子树还是父节点,通过value判定

一个二叉树大概长下面这样【网上盗的图,不是搜索树,大概看个结构就行】:

1.png

既然不是当右儿子就是当爸爸,我们在构建过程中直接把根节点到最右子树存下来,方便为新来的节点找到进入的位置

存储的东西如下:

2.png

每新来一个节点,我们拿着value一个一个比较,因为父value要比子value大。而且有一个特点,新插入一个点后,原来他右下位置的点就变成他的左子树了,右链砍掉了一部分。所以有两种思路:

  1. 每次从根往右遍历
    • 和当前的节点比较,当前节点比插入值大就继续向后遍历,直到遇到一个比他小的,然后插到他的前面,他后面的节点做插入点的左子树
  2. 每次从右下角遍历
    • 和当前的节点比较,当前节点比插入值小就过,继续向前遍历,找到对应位置,插进去

两种方法其实都是一个思路:遍历,直到找到合适位置,然后插入。

在网上找到的很多源码都提到了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),即最近公共祖先。

思路分析

  1. 构建笛卡尔树
  2. 依靠二叉排序树的特点快速定位LCA
  3. 返回查到的LCA的value

本文算法时间复杂度分析

  1. 构建笛卡尔树 O(n)。(因为以下标为key,不需要重新排序了,用栈来辅助理解就是每个元素只会有一次进栈)
  2. 依靠二叉排序树特点快速定位LCA O(log2(n))【平衡二叉树树深,虽然笛卡尔树的平衡不一定有VAL树那么平,但是大概还是平的】
    • this.key < a-->右边走
    • this.key > b --->左边走
    • 遇到的第一个athis.value就是结果
  3. return this.value

所以时间复杂度为O(n)。当然,如果多次查询只需要一次建树,这就很爽了,时间复杂度为O(log2(n))。

传统算法时间复杂度分析

顺序遍历一遍,记一下最大就行,时间复杂度为O(n)。

比较

本文算法优点:

  1. 多次查询只需一次建树
  2. 查询效率稳定,(已经构建笛卡尔树后,区间范围越大查询效果往往越快)

本文算法缺点:

  1. 存储树结构有一一定开销
  2. 不稳定,存在极度变态数列情况下整棵树被拉成一个链的可能

问题二

这样一个问题:已知一个无序数组,我给你其中一个下标,你要告诉我这个下标对应的值是多大范围内的最大/小值

思路分析

  1. 构建笛卡尔树,如果求最大值,就按照最大堆构建【后面都按照最大来讲,最小也是同理】
  2. 笛卡尔树看下标是树,看值是堆,找到对应的点,它下面的值都比它小,找到他的子树中的最左边点、最右边点即可

本文算法时间复杂度

  1. 构建树,需要
  2. 找节点对应子树的最大最小,即是两个范围点【找最左/最右子树即可】,复杂度为树深度,都是

综上,复杂度为

问题三

这个样一个问题:求[a,b]范围中的第n大的值。

思路分析

  1. 构建笛卡尔树
  2. 依靠二叉排序树的特点快速定位LCA
  3. 看LCA下面一层够不够数
    • 够就找到第n-1大的
    • 不够就从再下一层找,设本层有m个点
      • 这一层够就找第n-m-1大的
      • 不够再去下一层。。。。。。

时间复杂度分析

  1. 构建笛卡尔树 O(n)
  2. 找LCA O(log2(n))
  3. 找第n大的,由于树每靠上一层,本曾的节点数以指数倍减少,所以我们可以近似看成常数(可以。。。吧,总之需要比较的很少了)、

所以时间复杂度为O(n)。当然,如果多次查询只需要一次建树,这就很爽了,时间复杂度为O(log2(n))。

传统算法时间复杂度分析

得对这一段区间进行排序,时间复杂度为O(nlog2(n))

比较

如果区间随便给,这么查的话,还是本文的算法好。缺点和上一个问题一样就不提了。

还有一个明显的缺点,就是如果对同一个区间你经常查top k 。直接用传统方法排个序反而比较快。看自己需要什么了。

问题四

给出n个非负的整数,表示直方图中每个条的高度,且每个条的宽度均为1,找出这些条覆盖的最大面积的矩形。

3.png

思路分析

  1. 构建笛卡尔树,(最小堆)
  2. 后序遍历,将每个节点的所有子节点数目存在这个节点里,并求出子节点数目和此节点value的乘积。找到乘积最大的那个结果

帮助理解:

  • 子节点数目 = 这个节点的value是多少个节点中最小的 = 涂色长方形的底边宽是多少
  • 此节点value = 这n个节点的高度最小值 = 涂色长方形的高

注意:

  • 因为是按照下标为key进行排的,原来相邻的长条现在还是相邻,所以才能这么等效

时间复杂度分析

  1. 构建笛卡尔树 O(n)

  2. 后续遍历、计算、存储,因为只遍历一遍,所以时间复杂度是 O(n)

思路分析(基础)

上面的虽然说着通顺,但是正常人的思路并不是这样的。你可以这样想,底和高都变化,我们需要先找两者的关联,先构建出一个合适的计算公式:

从底入手

我们需要遍历底边的所有匹配段情况,并算出区域内的最低点【数组一端区间内的最小值】,这样需要构建笛卡尔树,因为匹配的不确定性,我们需要枚举底边的(1+2+...+n)种情况,复杂度是。当然,如果你硬要把每次确定最小值的操作算到求高的操作中去的话,就是 。

从高入手

前面一个思路,我们解决的最大的问题是把范围确定后的定高问题解决了,但是确定底边取值范围开销太大了,达到了级别。

遇到了这么乱的思路,一般有两个可能:

  1. 问题就是挺乱的。就像我们某些不合理的需求一样,屎一样的需求,再怎么捋代码也是乱七八糟
  2. 我们抽象的不对

我们抽象出了底边x和高y,然后求xy的乘积最大值。我们求x时即使使用普通的排序方法,不用笛卡尔树也还有,但是找底边时我们直接就冲着去了,这就给人不协调的感觉了。

所以我们进行更近一步的抽象,设从x到y,取这范围内的长方形,设高的最小值为z,这样x,y,z的判断就都是了,当然,直接一下,复杂度直接就是了。

我们需要找到三个变量之间的关系

如果我们确定一个x,后面y每个取值对应的高度都有可能变化,变化根据数组来变,没有规律,而没有规律就意味着遍历,意味着低效。

如果确定y,是一样的道理。

如果我们确定z,那就无所谓了,x和y往开的撑就行了。撑的越开,面积越大。

所以我们从z入手,算出每个z的值都能对应最大多大的(y-x),也就是z值都是多大区间内的最小值。此处回到问题二。只需要遍历即可,所以时间复杂度是:

  1. 构建笛卡尔树
  2. 一一求出每个高度对应的底边最大值【对应的最大最小的范围】(n个,每个,也就是
  3. n个方案,在一一计算时顺手存下最小的值以及计算方案即可,无需多余的时间复杂度

所以时间复杂度降低到了。

文献参考

  1. https://www.cnblogs.com/pushing-my-way/archive/2012/08/24/2653709.html
  2. https://blog.csdn.net/pipisorry/article/details/39033269
  3. https://baike.baidu.com/item/%E7%AC%9B%E5%8D%A1%E5%B0%94%E6%A0%91
  4. 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/
  5. https://zh.wikipedia.org/wiki/%E7%AC%9B%E5%8D%A1%E5%B0%94%E6%A0%91

你可能感兴趣的:(树——笛卡尔树)