[Dr.Lib]Note:Data Structures – 左偏树

左偏树真是个超好写的东西!支持合并,插入,删除最小值三个操作。后两个操作都可以看成第一个操作的拓展,如删除最小值是合并根的两棵子树,插入则直接将元素看作一个左偏树——所以只要写个Merge就可以了!

左偏树保证左子树深度大于等于右子树深度,同时符合堆性质。合并LT1,LT2(假设LT1中最小值比LT2小,否则交换)时,递归合并LT1的右子树和LT2替换掉LT1,然后如果LT1不符合左偏性质,则交换LT1的子树。

小小的提示:构树时,逐个插入是O(nlgn)的。这里可以有O(n)的复杂度的算法:先把所有元素构成n个单元素左偏树放进队列,然后每次取出两个合并后放入队列尾,直到只剩下一棵左偏树。

——http://seter.is-programmer.com/posts/30733.html

 

[Dr.Lib]Note:Data Structures – Bottom-up SPLAY

Let us play!

Let u splay!

 

BST 平均 O(logn)

Treap 期望 O(logn)

Splay 均摊 O(logn)

AVL 严格 O(logn)

SBT 严格 O(logn)

AVL不大指望能在赛场写出来,SBT在绝大多数时候是个不错的选择,Treap…嗯……但偏偏Splay被人看成是BST中的万金油……为什么呐?

Splay一个最大的特点就是:每一个操作都要splay一下……除此之外没了……

Splay

splay操作的作用就是把某一节点旋转到根,以加快之后的访问速度。它还可以在旋转过程中维持树的基本平衡。

  1. zig: 当目标节点是根节点的 左子节点 或 右子节点 时,进行一次zig,将目标节点调整到根节点的位置。
    709px-Splay_tree_zig.svg
  2. zig-zig:当目标节点是根节点的 左(右)子节点的左(右)子节点 时,进行一次zig-zig,将目标节点调整到根节点位置。
    Zigzig
  3. zig-zag:当目标节点是根节点的 左(右)子节点的右(左)子节点 时,进行一次zig-zag,将目标节点调整到根节点位置。
    Zigzag

 

重复这几种操作直到目标节点为根。

一次zig、zig-zig、zig-zag又可以分解为几次单旋。

Search/Insert/Rank/Select

与BST相同,只不过记得把插入的节点Splay到根。

Join

作用是把两棵子树S1,S2连成一个Splay,其中S1中所有元素小于S2。

先找到S1中的最大节点x,Splay到S1的根,再把x的右子树链接到S2。

Delete

先找到目标节点,Splay到根,执行Join操作即可。

代码

 

 

 

[Dr.Lib]Note:Data Structures – Size Balanced Tree – II

。。也没什么好说的了,《Size Balanced Tree》(CQF WC 2006)说的很清楚了……

今晚(昨晚?手敲的代码……不知道如何……

 

[Dr.Lib]Note:Data Structures – Size Balanced Tree – I

。。按惯例笔记贴都是摘抄、转载的……TAT看了半天貌似还是不会写的样子……滚回去复习主席树了

Via NOCOW http://www.nocow.cn/index.php/Size_Balanced_Tree

Size Balanced Tree(SBT)是一种平衡二叉查找树。它的论文由中国广东中山纪念中学的陈启峰于2006年底完成,并在Winter Camp 2007中发表。由于SBT的拼写很容易找到中文谐音,它常被中国的OIer们戏称为“傻X树”、“Super BT”等。但它的性能并不SB,编写起来也并不BT。恰恰相反,SBT易于实现,且据陈启峰论文中所言,“这是目前为止速度最快的高级二叉搜索树”。它能在O(logn)的时间内完成所有BST的相关操作。而且由于SBT赖以保持平衡的是Size域而不是其他“无用”的域,它可以很方便地实现动态顺序统计中的select和rank。

性质

Size Balanced Tree(SBT)是一种通过大小(Size)域来保持平衡的二叉搜索树,它也因此得名。它总是满足:
对于SBT的每一个结点 t:

  1. 性质(a) s[right[t] ]≥s[left[left[t]]],s[right[left[t]]]
  2. 性质(b) s[left[t] ]≥s[right[right[t]]],s[left[right[t]]]

即每棵子树的大小不小于其兄弟的子树大小。

 

 

 

 

Sbt1.PNG
如图(圈代表结点,三角代表SBT,下同):

 

 

 

 

  1. s[R] ≥ s[A],s[B]
  2. s[L] ≥ s[C],s[D]

旋转

SBT的旋转(Rotations)与其他许多高级BST相同。它是下面提到的Maintain操作的基础。
Sbt2.PNG

 

 

保持性质(Maintain)

当我们插入或删除一个结点后,SBT的大小就发生了改变。这种改变有可能导致性质(a)或(b)被破坏。这时,我们需要用Maintain操作来修复这棵树。Maintain操作是SBT中最具活力的一个独特过程;Maintain(T)用于修复以T为根的 SBT。调用Maintain(T)的前提条件是T的子树都已经是SBT了。
我们需要讨论的有4种情况。由于性质a和性质b是对称的,所以我们仅仅详细的讨论性质a。

  1. 第一种情况:s[left[left[t]]>s[right[t]]
    Sbt1.PNG
    如图3,执行完Insert(left[t],v)后发生S[A]>S[R],我们可以执行以下的指令来修复SBT:
    1. 首先执行Right-Ratote(t),这个操作让图3变成图4;
      Sbt4.PNG
    2. 在这之后,有时候这棵树还仍然不是一棵SBT,因为 s[C]>s[B] 或者 s[D]>s[B] 也是可能发生的。所以就有必要继续调用Maintain(T)。
    3. 结点L的右子树有可能被连续调整,因为有可能由于性质的破坏需要再一次运行Maintain(L)。
  2. 第二种情况:s[right[left[t]]>s[right[t]]
    Sbt5.PNG
    在执行完Insert(left[t],v)后发生s[B]>s[R],如图5,这种调整要比情况1复杂一些。我们可以执行下面的操作来修复:
    1. 在执行完Left-Ratote(L)后,图5就会变成下面图6那样了。
      Sbt6.PNG
    2. 然后执行Right-Ratote(T),最后的结果就会由图6转变成为下面的图7。
      Sbt7.PNG
    3. 在第1步和第2步过后,整棵树就变得非常不可预料了。万幸的是,在图7中,子树A、E、F和R仍就是SBT,所以我们可以调用Maintain(L)和Maintain(T)来修复结点B的子树。
    4. 在第3步之后,子树都已经是SBT了,但是在结点B上还可能不满足性质a或性质b,因此我们需要再一次调用Maintain(B)。
  3. 第三种情况:s[right[right[t]]>s[left[t]]
    与情况1对称。
  4. 第四种情况:s[left[right[t]]>s[left[t]]
    与情况2对称。

通过前面的分析,很容易写出一个普通的Maintain。

前面的标准过程的伪代码有一点复杂和缓慢。通常我们可以保证性质a和性质b的满足,因此我们只需要检查情况1和情况2或者情况3和情况4,这样可以提高速度。所以在那种情况下,我们需要增加一个布尔(boolean)型变量:flag,来避免毫无意义的判断。如果flag是false,那么检查情况1和情况2;否则检查情况3和情况4。

为什么Maintain(left[t],true)和Maintain(right[t],false)被省略了呢?

……且听下回分解

[Dr.Lib]Note:Data Structures – 主席树 via 函数式线段树

偶然间翻PDF时看到了FHQ神犇的WC论文……看了一遍没记住什么……除了函数式编程其他也……就那样吧……顺手把CLJ的论文也看了一遍……//可持久化做法很多,函数式是种比较科学的实现……暴力复制、wzk树什么的就不说了……

然后函数式编程早就接触过但从来没有研究过TAT显然函数式线段树最成功的应用(之一?)就是主席树了……

函数式编程

修改元素是数据结构的一个基本操作,平常的实践中我们比较常见的就是直接改储存的值,而函数式编程中,我们从不去修改值,而是再定义一个。

对应在线段树操作中,我们每次修改值,都不会去修改任何节点,而是返回一棵新的树。

函数式Treap假想图 函数式线段树

图片来自范_wc2012

因为保证所有节点都不会被修改,所以我们可以重用未受影响的节点。显然,函数式线段树中,插入操作的时间、空间复杂度都是O(log n),其他操作与正常线段树无异。

主席树

主席树是一种利用函数式线段树维护数列的数据结构,一般用来解决区间第K大数问题。设数列长度为NN,数据范围为n,已离散化。

不支持修改的主席树(前缀和主席树)

……不支持修改的主席树在我的笔记中记为前缀和主席树…自己取的名字。。。

前缀和主席树由N棵线段树组成,第i棵Ti是对于[1,i]建一棵以序列里的值为下标,储存区间里出现该值的次数的线段树,也可以看作是Ti-1插入a[i]后的线段树。

注意到所有的树是同构(?)的,Ts-1和Tt可以直接相减得到T[s,t],在树上进行二分就可以得到区间K大值。

当然,所有的线段树都指函数式线段树。

代码:Via http://www.cnblogs.com/Rlemon/archive/2013/05/23/3094635.html 我自己码的实现惨不忍睹…这是和我的风格最接近的一个…以下是按我的笔记格式码的…

 

支持修改的主席树(树状主席树)

还记得当年学数据结构时如何维护带修改区间[s,t]的区间和吗?

树状数组,简单高效。既然我们维护的是一个线段树的前缀和,何不用树状数组维护呢?

脑补一棵树状数组,每个节点是一棵线段树 。修改时,我们仅需修改log n棵线段树即可。

//貌似只有我叫它树状主席树……正解是树状数组套主席树。

代码

 

临时建立节点的主席树

//待补完
参见clj的wc论文