算法与数据结构八日谈之六——数据结构专题(uncompleted)

这是个大工程
我要慢慢写
朱军稍候
UPD:暂时更完了,代码部分再等会吧,估计很长。

1.最·基础数据结构

数组

别问我为什么把这放进来,我就是闲着没事做

可以在栈空间不够用时用手写栈来进行dfs。

队列

bfs所需的基础数据结构,在spfa时也会用到

链表

在插入操作较多时可以使用链表来维护
hash时可将重复元素以链表的形式挂在一张表上

2.基础数据结构

上面那些东西(除了链表)是毫无实现难度的,学过计算机语言的就会使用,下面这些则需要一点点的算法知识

树状数组

代码简洁,常数小。
可以维护单点修改和区间求和/极值
利用两个树状数组可以区间修改和单点求值
利用三个树状数组可以区间修改和区间求值(然而并没有什么卵用)
一般可以在两行解决问题

线段树

思路简单,编程复杂度小
可以维护区间和/极值。支持区间修改和单点修改

权值线段树

线段树的区间端点由原序列的下标变为值域
在数值与 n 同阶或可以离线操作的情况下各种操作的速度比大多数平衡树要快(跟vEB树差不多)
于是排名就是前缀和,其它的基本操作随便搞搞就可以了

zkw线段树

zkw神的线段树,由顶向下实现,(据说)速度奇快无比。具体的参见其论文《统计的力量》

(普通的)堆

堆可以动态维护集合里的极值。
支持将极值从集合中弹出,向集合中加入一个数,询问极值。
(普通的堆)插入和删除都是O(lgn)
可以在 O(n) 的时间内建一个大小为n的堆
堆排序即利用堆按大小顺序依次取出每个元素,时间复杂度是 O(nlgn)

3.进阶数据结构

平衡树

平衡树维护可以判断元素大小关系的集合。

伸展树Splay

伸展树的核心操作是伸展(废话)
每次访问节点,则需要将其旋转到根的位置,这个操作被称为伸展
splay能称作平衡的核心是在伸展中的某种trick
当祖孙三代方向一致时,先转父亲,再转儿子,否则转儿子转两次。
由势能分析可以得到伸展树的均摊复杂度是 Θ(lgn) 的(别问我,我不知道咋分析)
伸展树最重要的操作是提取区间,这是它能在oi界中生存的最大的依仗(之一),每次将待提取的区间的左端点左边那个点伸展到根节点,再将区间右端点右边那个点伸展到根节点右儿子处,此时根节点右儿子的左儿子就是所求区间。
splay可以解决很多需要线段树解决的问题。
非常实用的数据结构,不学不算搞过oi

树堆Treap

可以证明(你™就是不会证瞎扯个啥),随机插入的二叉排序树的深度是 O(lgn) 的。于是我们可以强制使插入的数据呈随机的顺序插入。
Treap=Tree+heap,我们给每个节点增加一个域,并将该域的值赋为一个随机数,姑且叫做 w[n] 。对于原数据,treap为一棵二叉检索树;对于 w[i] treap是一个大根/小根堆。
treap的实现很简单,与普通的二叉检索树没有很大的区别。
如果在插入后子树的 w 域不满足堆的性质,则将其旋转至原节点上方。
同理,删除时也要考虑维护堆的性质。
treap的编程复杂度较低,非常适合在考场上写,而且效率是相当不错的。

节点大小平衡树SBT

喜闻乐见的傻逼树。
不需要额外的域来保存信息。
论文上说的很神,但可以用人字形的数据卡掉。。。
实际效率比上面那俩要高,但它的maintain操作太淡腾并不好写。
(好吧我不会写这玩意,我在瞎哔哔)

朝鲜树

湖北的上古神犇albus随手yy出的数据结构,为vfk领悟下面这玩意做了铺垫(叫这个名字只因为金正中的名字与朝鲜前领导人二儿子重合)

替罪羊树Scapegoat Tree

神一样的数据结构(之一),在实践中是最快的平衡树。
通过设置参数来平衡插入与查询的常数复杂度。
当插入时存在一个节点的左右儿子大小的最大值大于α乘该节点大小,则重建以该节点为根的子树。
通过复杂度分析可以知道(我不知道),替罪羊树的“各种”操作的复杂度是均摊 Θ(lgn) 的。
而且其不需要旋转的特性为其套上其它数据结构提供了可能。

块状链表

万能数据结构
splay能干的它都能干。
splay不能干的它也能干。
它啥都能干。
基于分块思想的数据结构,在处理询问时相当暴力,这也为其提供了很大的灵活性。
所有操作几乎都是暴力。套上平衡树后还能顺便解决区间k大值问题。
说起来简单,但实现却真是哔了狗了,参见orzjry这道题。

4.高端数据结构(雾)

可持久化数据结构

可持久化(函数式)线段树,也称主席树

在完成普通线段树能完成的任务时,还能支持询问历史版本。
将权值线段树的思想应用到主席树上,可以用主席树解决静态的区间k大值问题。
函数式的思想是,只定义,不修改,我们每次插入一个值时,都从根开始建一(/半)棵新的主席树,因为有一半是相同的,所以可以利用原来的线段树的信息从而减少空间消耗。

可持久化字典树

利用可持久化字典树可以解决区间最大异或和问题。
仍然是利用函数式的原理,每次插入时都新建节点。每次询问从高位向低位贪心,实现与主席树类似。

可持久化平衡树

平衡树的旋转操作很可怕,修改了一大堆的东西,不适合可持久化。
我们对treap进行一些修改,引入split和merge两个操作,分别是把一个treap以某个数为界分为俩treap,还有把两个值域不相交的treap合并。
这样的话插入或删除都只需要先split再merge即可,而这两个操作不需要旋转,这对可持久化提供了可能。
具体的实现我不会,所以上面这坨也是我瞎哔哔的,所以还是去搜论文吧。

数据结构的嵌套

线段树套线段树,也称二维线段树

最“常见”的数据结构嵌套的情形之一,白书上就提供了具体的实现。
实现虽然复杂,但是原理却相当简单,如果明白了给出足够的时间是可以写出来的。
但二维线段树不支持区间修改。
但二维线段树不支持区间修改。
因为比较重要所以要说两遍。
所以很多时候需要差分或者其它的把区间修改为单点修改。

线段树套线段树套线段树,也称三维线段树

sto VFleaKing orz

树状数组套主席树

在解决有修改操作的区间k大值时,原版的主席树就不能直接上了,因为需要修改 O(n) 棵主席树,这是无法接受的复杂度。
考虑主席树维护的是前缀和,而树状数组维护的前缀和只需要访问 O(lg) 个节点,所以可以考虑树状数组套主席树。
每次修改时,修改树状数组对应的 O(lgn) 棵主席树即可,所以修改的复杂度是 O(lg2n) ,询问类似,复杂度与修改相当。
这是相当优秀的一种待修改区间k大值问题的解法,树状数组极低的编程复杂度决定了它的实用性。

线段树套平衡树

线段树这傻大个,不会旋转,所以可以随便套。
对于每棵线段树,我们对于其中的值建一棵平衡树。
空间 O(n2) 会炸?
有这种想法的同学再想想线段树到底是啥。
线段树套平衡树可以求区间k大值,区间排名,区间前驱,区间后继,以及修改(二逼平衡树)。
对于区间排名,只需将原区间以线段树的方式分为 O(lgn) 个区间,然后对于每个区间的平衡树上求出小于其的数的个数之和,再加1就是排名,复杂度是 O(lg2n) 的。
然后是区间k大,二分答案,直到排名等于k,复杂度是 O(lg3n) ,在时限接受的数据范围内与 O(nlgn) 的块链套平衡树差不多,但后者常数太大,效率没法比。
前驱后继与平衡树的操作类似,不再赘述。

替罪羊树套平衡树

继续sto VFleaKing orz
vfk的神题:带插入区间k大值。
有了插入操作,除了块链,前面的树套树变得毫无意义。
插入就需要平衡,平衡需要旋转,一旋转就炸飞了。
我们需要考虑不需要旋转或者旋转次数很少的平衡树。
对,替罪羊树!
对于原区间的我们不再使用线段树,而是改成用替罪羊树维护。
在插入时如果大小不满足替罪羊树的要求,就对于该子区间和那些子区间所对应的平衡树重建。
可以证明,复杂度是均摊 Θ(lglgn) 的。
因为重建的原因,在处理时要使用垃圾回收,不然空间复杂度不能接受。

k-d树

这玩意我为什么会单独扯出一个小节呢,是有原因的。

k-d树的原理

k-d树的每个节点对应了空间的一部分。
k-d树是基于二分的数据结构,在建树时,轮换进行分割的维,然后取中位数为当前子树的根,再递归进行建树操作。

k-d树的基本应用
k近邻查询

在询问时将可能成为k近邻的点放入堆中,然后在分别访问子树中可能成为答案的点。

空间最近点对

暴力一点,对于每个点,求离它最近的点,然后更新答案。

询问给定点的最近曼哈顿距离
询问给定点的最远曼哈顿距离
询问给定点的最近欧几里得距离
询问给定点的最远欧几里得距离

以上四种询问都需要一个估价函数,估价函数对于一个区域进行估价,并返回估价值。
在查询时左/右子树谁的估价更好就优先访问该子树,当然如果没有当前答案优就可以直接剪枝。
理论上最坏的查询复杂度是 O(n)

k-d树的进阶应用
维护凸壳

原理也比较简单,在每次插入时去掉那些不可能在凸壳上的点,然后询问与k近邻查询类似。

维护多维空间的区域和

利用k-d树独特的性质,可以用其处理一些用二(多)维线段树处理的问题。

利用替罪羊重建维护k-d树的平衡

在插入时如果都在某个子树上时,k-d树的时间复杂度会退化为 O(n)
而k-d树的结构注定其无法旋转,于是考虑替罪羊树的结构,我们也可用同样地方法来维护。

k-d树的实现技巧
估价函数的写法

要注意k-d树对应的是一个区域,所以我们在维护时顺便维护区域的极值点(例如矩形的两个顶点),然后在估价时以区域可能的最佳取值为估价值。

坐标转换

对于某些询问,询问的区域是一个菱形,我们可以通过适当的操作把询问变换为正方形。就像这样

x=x+yy=xy

然后就完了。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值