二叉搜索树 & 平衡树学习笔记
注意,这是一篇学习笔记。
二叉搜索树(BST)
定义
- 空树是二叉搜索树
- 若二叉搜索树的左子树非空,左子树内每个点的权值均 小于 二叉搜索树根节点的权值
- 若二叉搜索树的右子树非空,右子树内每个点的权值均 大于 二叉搜索树根节点的权值
- 二叉搜索树的左右子树为二叉搜索树
二叉搜索树中每个节点有一个权值 \(\mathit{val}\),一个重复数量 \(\mathit{cnt}\)。还需要额外记录子树大小 \(\mathit{siz}\)。
性质
由定义可得一颗二叉搜索树的中序遍历的节点序列的权值单调递增。可以此完成 \(O(n)\) 的遍历。
操作
设树高为 \(h\),则以下操作时间复杂度均为 \(O(h)\)。
极值
由性质得最小值在二叉搜索树最小值在最左边的节点,最大值则在最右边的节点。
搜索值
当在二叉搜索树中搜索 \(x\) 时,对于当前根节点 \(r\),
- 若 \(r\) 为空,返回
false
- 若 \(r.\mathit{val}=x\),返回
true
- 若 \(x<r.\mathit{val}\),在左子树中递归查找
- 若 \(r.\mathit{val}<x\),在右子树中递归查找
插入值
类似于搜索值:
- 若 \(r\) 为空,返回一个新节点
- 若 \(r.\mathit{val}=x\),\(r.\mathit{cnt}\gets r.\mathit{cnt}+1\)
- 若 \(x<r.\mathit{val}\),在左子树中递归
- 若 \(r.\mathit{val}<x\),在右子树中递归
删除值
依然类似,但稍有不同:
- 若 \(r\) 为空,则没有这个值,直接返回
- 若 \(r.\mathit{val}=x\),\(r.\mathit{cnt}\gets r.\mathit{cnt}-1\),此时若 \(r.\mathit{cnt}=0\),
- 若 \(r\) 为叶子,直接删除即可
- 若 \(r\) 只有一个儿子,让此儿子代替 \(r\) 即可
- 若 \(r\) 有两个儿子,让左子树中的最大值\右子树的最小值代替 \(r\) 即可
- 若 \(x<r.\mathit{val}\),在左子树中递归
- 若 \(r.\mathit{val}<x\),在右子树中递归
值求排名
一个值的排名:升序排序后第一个相同元素前面的元素个数 \(+1\)。
对于
1 1 4 5 1 4
排序后
1 1 1 4 4 5
其中 \(4\) 的排名为 \(4\)。
实现:
搜索过程中若往右子树走,则加上左子树的 \(\mathit{siz}\),最后再加上到达的节点的左子树 \(\mathit{siz}\)。
显然左子树中的数都比 \(x\) 小。
排名求值
在一棵子树中,根节点的排名取决于其左子树的大小。
对于根节点 \(r\),求 该子树中 的排名为 \(k\) 的值,
- 若 \(k\le r.ls.\mathit{siz}\),则所求在左子树中,递归查询左子树的排名 \(k\)
- 若 \(r.ls.\mathit{siz}+1\le k\le r.ls.\mathit{siz}+r.\mathit{cnt}\),则返回 \(r.\mathit{val}\)
- 若 \(r.ls.\mathit{siz}+r.\mathit{cnt}<k\),则递归查询右子树的排名 \(k-(r.ls.\mathit{siz}+r.\mathit{cnt})\)
平衡树
使用搜索树的目的之一是缩短插入、删除、修改和查找(插入、删除、修改都包括查找操作)节点的时间。
但有一个小小的问题:当二叉搜索树退化为链表时,\(h=n\),复杂度随之退化为 \(O(n)\)。
所以我们需要使整棵二叉搜索树平衡。
树堆(Treap)
本节为旋转 Treap。
节点
每个节点有两个值:权值 \(v\) 和优先级 \(p\)。其中 \(p\) 是随机给出的。
首先要明确的是,对于一个排序过的序列,对它建立 BST,该 BST 有很多形态。许多形态是我们不想要的,因为它们太“瘦长”了。
有了 \(p\),我们的 Treap 不仅要满足 BST 的性质,还要满足堆(Heap)的性质,即子节点的 \(p\) 大(小于)父节点的 \(p\)。这也正是 Treap 名字的由来:Treap=Tree+Heap。
通过随机化,我们成功地让 Treap 保持了弱平衡。
旋转
旋转操作分为左旋和右旋。
直观理解:
对于左旋节点 \(x\),若 \(x\) 右儿子为 \(y\)
- \(x\) 左儿子,\(y\) 的右儿子不变;
- 以 \(y\) 作为新根节点,\(x\) 的右儿子改为 \(y\) 的左儿子,\(y\) 的左儿子改为 \(x\)。
右旋同理。