算法与数据结构八日谈之六——数据结构专题(uncompleted)
2015-07-05 22:53
561 查看
这是个大工程
我要慢慢写
朱军稍候
UPD:暂时更完了,代码部分再等会吧,估计很长。
hash时可将重复元素以链表的形式挂在一张表上
可以维护单点修改和区间求和/极值
利用两个树状数组可以区间修改和单点求值
利用三个树状数组可以区间修改和区间求值(然而并没有什么卵用)
一般可以在两行解决问题
可以维护区间和/极值。支持区间修改和单点修改
在数值与n同阶或可以离线操作的情况下各种操作的速度比大多数平衡树要快(跟vEB树差不多)
于是排名就是前缀和,其它的基本操作随便搞搞就可以了
支持将极值从集合中弹出,向集合中加入一个数,询问极值。
(普通的堆)插入和删除都是O(lgn)的
可以在O(n)的时间内建一个大小为n的堆
堆排序即利用堆按大小顺序依次取出每个元素,时间复杂度是O(nlgn)的
每次访问节点,则需要将其旋转到根的位置,这个操作被称为伸展
splay能称作平衡的核心是在伸展中的某种trick
当祖孙三代方向一致时,先转父亲,再转儿子,否则转儿子转两次。
由势能分析可以得到伸展树的均摊复杂度是Θ(lgn)的(别问我,我不知道咋分析)
伸展树最重要的操作是提取区间,这是它能在oi界中生存的最大的依仗(之一),每次将待提取的区间的左端点左边那个点伸展到根节点,再将区间右端点右边那个点伸展到根节点右儿子处,此时根节点右儿子的左儿子就是所求区间。
splay可以解决很多需要线段树解决的问题。
非常实用的数据结构,不学不算搞过oi
Treap=Tree+heap,我们给每个节点增加一个域,并将该域的值赋为一个随机数,姑且叫做w[n]。对于原数据,treap为一棵二叉检索树;对于w[i]treap是一个大根/小根堆。
treap的实现很简单,与普通的二叉检索树没有很大的区别。
如果在插入后子树的w域不满足堆的性质,则将其旋转至原节点上方。
同理,删除时也要考虑维护堆的性质。
treap的编程复杂度较低,非常适合在考场上写,而且效率是相当不错的。
不需要额外的域来保存信息。
论文上说的很神,但可以用人字形的数据卡掉。。。
实际效率比上面那俩要高,但它的maintain操作太淡腾并不好写。
(好吧我不会写这玩意,我在瞎哔哔)
通过设置参数来平衡插入与查询的常数复杂度。
当插入时存在一个节点的左右儿子大小的最大值大于α乘该节点大小,则重建以该节点为根的子树。
通过复杂度分析可以知道(我不知道),替罪羊树的“各种”操作的复杂度是均摊Θ(lgn)的。
而且其不需要旋转的特性为其套上其它数据结构提供了可能。
splay能干的它都能干。
splay不能干的它也能干。
它啥都能干。
基于分块思想的数据结构,在处理询问时相当暴力,这也为其提供了很大的灵活性。
所有操作几乎都是暴力。套上平衡树后还能顺便解决区间k大值问题。
说起来简单,但实现却真是哔了狗了,参见orzjry这道题。
将权值线段树的思想应用到主席树上,可以用主席树解决静态的区间k大值问题。
函数式的思想是,只定义,不修改,我们每次插入一个值时,都从根开始建一(/半)棵新的主席树,因为有一半是相同的,所以可以利用原来的线段树的信息从而减少空间消耗。
仍然是利用函数式的原理,每次插入时都新建节点。每次询问从高位向低位贪心,实现与主席树类似。
我们对treap进行一些修改,引入split和merge两个操作,分别是把一个treap以某个数为界分为俩treap,还有把两个值域不相交的treap合并。
这样的话插入或删除都只需要先split再merge即可,而这两个操作不需要旋转,这对可持久化提供了可能。
具体的实现我不会,所以上面这坨也是我瞎哔哔的,所以还是去搜论文吧。
实现虽然复杂,但是原理却相当简单,如果明白了给出足够的时间是可以写出来的。
但二维线段树不支持区间修改。
但二维线段树不支持区间修改。
因为比较重要所以要说两遍。
所以很多时候需要差分或者其它的把区间修改为单点修改。
考虑主席树维护的是前缀和,而树状数组维护的前缀和只需要访问O(lg)个节点,所以可以考虑树状数组套主席树。
每次修改时,修改树状数组对应的O(lgn)棵主席树即可,所以修改的复杂度是O(lg2n),询问类似,复杂度与修改相当。
这是相当优秀的一种待修改区间k大值问题的解法,树状数组极低的编程复杂度决定了它的实用性。
对于每棵线段树,我们对于其中的值建一棵平衡树。
空间O(n2)会炸?
有这种想法的同学再想想线段树到底是啥。
线段树套平衡树可以求区间k大值,区间排名,区间前驱,区间后继,以及修改(二逼平衡树)。
对于区间排名,只需将原区间以线段树的方式分为O(lgn)个区间,然后对于每个区间的平衡树上求出小于其的数的个数之和,再加1就是排名,复杂度是O(lg2n)的。
然后是区间k大,二分答案,直到排名等于k,复杂度是O(lg3n),在时限接受的数据范围内与O(n√lgn)的块链套平衡树差不多,但后者常数太大,效率没法比。
前驱后继与平衡树的操作类似,不再赘述。
vfk的神题:带插入区间k大值。
有了插入操作,除了块链,前面的树套树变得毫无意义。
插入就需要平衡,平衡需要旋转,一旋转就炸飞了。
我们需要考虑不需要旋转或者旋转次数很少的平衡树。
对,替罪羊树!
对于原区间的我们不再使用线段树,而是改成用替罪羊树维护。
在插入时如果大小不满足替罪羊树的要求,就对于该子区间和那些子区间所对应的平衡树重建。
可以证明,复杂度是均摊Θ(lglgn)的。
因为重建的原因,在处理时要使用垃圾回收,不然空间复杂度不能接受。
k-d树是基于二分的数据结构,在建树时,轮换进行分割的维,然后取中位数为当前子树的根,再递归进行建树操作。
在询问时将可能成为k近邻的点放入堆中,然后在分别访问子树中可能成为答案的点。
空间最近点对
暴力一点,对于每个点,求离它最近的点,然后更新答案。
询问给定点的最近曼哈顿距离
询问给定点的最远曼哈顿距离
询问给定点的最近欧几里得距离
询问给定点的最远欧几里得距离
以上四种询问都需要一个估价函数,估价函数对于一个区域进行估价,并返回估价值。
在查询时左/右子树谁的估价更好就优先访问该子树,当然如果没有当前答案优就可以直接剪枝。
理论上最坏的查询复杂度是O(n√)的
原理也比较简单,在每次插入时去掉那些不可能在凸壳上的点,然后询问与k近邻查询类似。
维护多维空间的区域和
利用k-d树独特的性质,可以用其处理一些用二(多)维线段树处理的问题。
利用替罪羊重建维护k-d树的平衡
在插入时如果都在某个子树上时,k-d树的时间复杂度会退化为O(n)。
而k-d树的结构注定其无法旋转,于是考虑替罪羊树的结构,我们也可用同样地方法来维护。
要注意k-d树对应的是一个区域,所以我们在维护时顺便维护区域的极值点(例如矩形的两个顶点),然后在估价时以区域可能的最佳取值为估价值。
坐标转换
对于某些询问,询问的区域是一个菱形,我们可以通过适当的操作把询问变换为正方形。就像这样
x′=x+yy′=x−y
然后就完了。
我要慢慢写
朱军稍候
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(n√lgn)的块链套平衡树差不多,但后者常数太大,效率没法比。
前驱后继与平衡树的操作类似,不再赘述。
替罪羊树套平衡树
继续sto VFleaKing orzvfk的神题:带插入区间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′=x−y
然后就完了。
相关文章推荐
- Lua教程(七):数据结构详解
- 解析从源码分析常见的基于Array的数据结构动态扩容机制的详解
- C#数据结构揭秘一
- 数据结构之Treap详解
- JavaScript数据结构和算法之图和图算法
- Java数据结构及算法实例:冒泡排序 Bubble Sort
- Java数据结构及算法实例:插入排序 Insertion Sort
- Java数据结构及算法实例:考拉兹猜想 Collatz Conjecture
- java数据结构之java实现栈
- java数据结构之实现双向链表的示例
- Java数据结构及算法实例:选择排序 Selection Sort
- Java数据结构及算法实例:朴素字符匹配 Brute Force
- Java数据结构及算法实例:汉诺塔问题 Hanoi
- Java数据结构及算法实例:快速计算二进制数中1的个数(Fast Bit Counting)
- java数据结构和算法学习之汉诺塔示例
- Java数据结构及算法实例:三角数字
- Java数据结构之简单链表的定义与实现方法示例
- 数据结构之AVL树详解
- qqwry.dat的数据结构图文解释第1/2页
- JavaScript中数据结构与算法(五):经典KMP算法