史上最强图解Treap总结, 不是浅谈!
来源:互联网 发布:java设计用户管理 编辑:程序博客网 时间:2024/06/05 00:20
大家都很强, 可与之共勉。
Treap = Tree + Heap.
树堆,在数据结构中也称Treap,是指有一个随机附加域满足堆的性质的二叉搜索树,其结构相当于以随机数据插入的二叉搜索树。其基本操作的期望时间复杂度为O(logn)。相对于其他的平衡二叉搜索树,Treap的特点是实现简单,且能基本实现随机平衡的结构。
Treap 维护堆的性质的方法只用到了左旋和右旋, 编程复杂度比Splay小一点, 并且在两者可完成的操作速度有明显优势
开始
每一个节点需要保存至少四个信息,当前节点的数值 ( val ), 优先级 ( key ), 左右儿子 ( ls, rs )。 除此之外, 可能还会保存以该节点为根的树的大小 ( siz ), 以及该节点相同的数个数 ( same )。
typedef class TreapNode {public: int val; int siz, key, same; TreapNode *ls, *rs; inline TreapNode ( ) { } inline TreapNode ( int val, TreapNode* & node ) : val ( val ), key ( rand ( ) ), siz ( 1 ), same ( 1 ) { ls = rs = node; } inline void update ( ) { siz = ls -> siz + rs -> siz + same; }} Node;
当然, 博主是用的指针实现, 指针相比数组有更多的细节需要注意。 在BZOJ-3224中大概比RBT慢2ms。
关于随机函数
< cstdlib > 中的rand ( ) 速度比较慢, 而在数据结构中对于素的如果要求过高, 可以使用手写 rand ( )。
inline int rand ( ) { static int seed = 233; return seed = ( int ) seed * 482711LL % 2147483647; }
其中seed可以随便取一个非零的数。 具体原理是什么, 当然我也不会证明了。
首先是旋转操作
分为左旋和右旋, 我习惯用Zag, Zig来叫。
图解一下
具体的代码如下
一定要记住的是当前节点的要旋转节点为null时, 不转。 否则root很有可能变成null, 将会影响一系列的操作。
inline void Zig ( Node* &nd ) { tmp = nd -> ls; if ( tmp == null ) return; nd -> ls = tmp -> rs; tmp -> rs = nd; tmp -> siz = nd -> siz; nd -> update ( ); nd = tmp;}inline void Zag ( Node* &nd ) { tmp = nd -> rs; if ( tmp == null ) return; nd -> rs = tmp -> ls; tmp -> ls = nd; tmp -> siz = nd -> siz; nd -> update ( ); nd = tmp;}
接下来是插入操作
插入的过程中, 一定要满足Treap的特点。
即左儿子的值比当前节点小, 右儿子的值比当前节点大。
与维护关于key值堆的性质
看完, 你一定会理解原理, 并且熟练掌握。
插入值为18,优先级为20的结点后,违反了最小堆的定义,因此要进行调整,把优先级小的往上提,也就是小的优先级插入的是右子树,那么需要进行一次左旋转( Zag ),这里进行一次旋转过后就OK了。
同样,这种情况左旋转,旋转后发现还是不满足最小堆的定义,并且小优先级的结点在左子树,所以还需要进行右旋转 ( Zig ),如下图所示:
右旋后,还是不满足性质, 还需要左旋 ( Zag ):
当然这是在递归调用之中实现的。
如果该节点为null, 就新开一个。 用构造函数会很方便。
如果遇到插入两个相同的值, 那么该节点的same直接+1就好。
完成每一个节点的插入, 都要update
这个update()操作的优秀之处就在于null空节点, 若使用NULL系统自带的空指针, 那么直接指向左右儿子就会指出去, 报错。。。
代码如下:
inline void Insert ( Node* &nd, int& val ) { if ( nd == null ) { nd = ++tail; *nd = Node ( val, null ); return; } if ( nd -> val == val ) ++nd -> same; else { if ( val > nd -> val ) { Insert ( nd -> rs, val ); if ( nd -> rs -> key < nd -> key ) Zag ( nd ); } else { Insert ( nd -> ls, val ); if ( nd -> ls -> key < nd -> key ) Zig ( nd ); } } nd -> update ( );}
之后是删除操作
注意:
这是二叉树删除法
对比插入, 相对于删除要复杂一些, 可以直接看代码明白。
不要忘了update()!!!
inline void Delete ( Node* &nd, int x ) { if ( nd == null ) return; if ( nd -> val == x ) { if ( nd -> same > 1 ) { --nd -> same; nd -> update ( ); return; } if ( nd -> ls == null && nd -> rs == null ) { nd = null; return; } else if ( nd -> ls == null && nd -> rs ) nd = nd -> rs; else if ( nd -> ls && nd -> rs == null ) nd = nd -> ls; if ( nd -> ls -> key < nd -> rs -> key ) { Zig ( nd ); Delete ( nd -> rs, x ); } else { Zag ( nd ); Delete ( nd -> ls, x ); } } else if ( x > nd -> val ) Delete ( nd -> rs, x ); else Delete ( nd -> ls, x ); nd -> update ( );}
主要的就是这两个那么还有前驱,后驱,排名,第K大。
那么唯一注意的是前后驱在题目中的定义。
如BZOJ-1588
前后驱可以等于节点数值本身
那么就应该这么写
inline int query_pre ( Treap* &nd, int val ) { if ( nd == null ) return 0xefefefef; if ( val < nd -> val ) return query_pre ( nd -> ls, val ); return max ( nd -> val, query_pre ( nd -> rs, val ) );}inline int query_post ( Treap* &nd, int val ) { if ( nd == null ) return 0x7fffffff; if ( val > nd -> val ) return query_post ( nd -> rs, val ); return min ( nd -> val, query_post ( nd -> ls, val ) );}
如果是BZOJ-3224, 读错题了就Wa爽了。。。
inline void query_pre ( Node* &nd, int &ans, int val ) { if ( nd == null ) return; if ( val > nd -> val ) ans = nd -> val, query_pre ( nd -> rs, ans, val ); else query_pre ( nd -> ls, ans, val );}inline void query_post ( Node* &nd, int &ans, int val ) { if ( nd == null ) return; if ( val < nd -> val ) ans = nd -> val, query_post ( nd -> ls, ans, val ); else query_post ( nd -> rs, ans, val );}
当然根据定义也可以用kth 与 rank 两者一起求出前后驱。
具体代码及细节请读者自己推敲
给出完整版参考代码
注意的是 Init( ) 中的 null 维护的节点信息。
#include <bits/stdc++.h>unsigned int seed, n, opt;int ans, x;inline int rand ( ) { return seed = ( int ) seed * 482711LL % 2147483647; }typedef class TreapNode {public: int val; int siz, key, same; TreapNode *ls, *rs; inline TreapNode ( ) { } inline TreapNode ( int val, TreapNode* & node ) : val ( val ), key ( rand ( ) ), siz ( 1 ), same ( 1 ) { ls = rs = node; } inline void update ( ) { siz = ls -> siz + rs -> siz + same; }} Node;Node *pool, *root, *tail, *null, *tmp;inline void Init ( ) { seed = 233; pool = new Node [ n + 5 ]; root = null = tail = pool; null -> siz = 0, null -> same = 0, null -> val = 0, null -> key = rand( ), null -> ls = null -> rs = null;}inline void Zig ( Node* &nd ) { tmp = nd -> ls; if ( tmp == null ) return; nd -> ls = tmp -> rs; tmp -> rs = nd; tmp -> siz = nd -> siz; nd -> update ( ); nd = tmp;}inline void Zag ( Node* &nd ) { tmp = nd -> rs; if ( tmp == null ) return; nd -> rs = tmp -> ls; tmp -> ls = nd; tmp -> siz = nd -> siz; nd -> update ( ); nd = tmp;}inline void Insert ( Node* &nd, int& val ) { if ( nd == null ) { nd = ++tail; *nd = Node ( val, null ); return; } if ( nd -> val == val ) ++nd -> same; else { if ( val > nd -> val ) { Insert ( nd -> rs, val ); if ( nd -> rs -> key < nd -> key ) Zag ( nd ); } else { Insert ( nd -> ls, val ); if ( nd -> ls -> key < nd -> key ) Zig ( nd ); } } nd -> update ( );}inline int kth ( Node* &nd, int k ) { int tmp = nd -> ls -> siz + nd -> same; if ( nd -> ls -> siz < k && k <= tmp ) return nd -> val; return nd -> ls -> siz >= k ? kth ( nd -> ls, k ) : kth ( nd -> rs, k - tmp );}inline void Delete ( Node* &nd, int x ) { if ( nd == null ) return; if ( nd -> val == x ) { if ( nd -> same > 1 ) { --nd -> same; nd -> update ( ); return; } if ( nd -> ls == null && nd -> rs == null ) { nd = null; return; } else if ( nd -> ls == null && nd -> rs ) nd = nd -> rs; else if ( nd -> ls && nd -> rs == null ) nd = nd -> ls; if ( nd -> ls -> key < nd -> rs -> key ) { Zig ( nd ); Delete ( nd -> rs, x ); } else { Zag ( nd ); Delete ( nd -> ls, x ); } } else if ( x > nd -> val ) Delete ( nd -> rs, x ); else Delete ( nd -> ls, x ); nd -> update ( );}inline int query_rank ( Node* &nd, int x ) { if ( nd == null ) return 0; if ( nd -> val == x ) return nd -> ls -> siz + 1; else return ( x > nd -> val ) ? nd -> ls -> siz + nd -> same + query_rank ( nd -> rs, x ) : query_rank ( nd -> ls, x );}inline void query_pre ( Node* &nd, int &ans, int val ) { if ( nd == null ) return; if ( val > nd -> val ) ans = nd -> val, query_pre ( nd -> rs, ans, val ); else query_pre ( nd -> ls, ans, val );}inline void query_post ( Node* &nd, int &ans, int val ) { if ( nd == null ) return; if ( val < nd -> val ) ans = nd -> val, query_post ( nd -> ls, ans, val ); else query_post ( nd -> rs, ans, val );}using std :: cin;using std :: cout;using std :: endl;int main ( ) { cin >> n; Init ( ); register int i = 1; loop :; { cin >> opt >> x; switch ( opt ) { case 1: Insert ( root, x ); break; case 2: Delete ( root, x ); break; case 3: cout << query_rank( root, x ) << endl; break; case 4: cout << kth ( root, x ) << endl; break; case 5: query_pre ( root, ans, x ); cout << ans << endl; break; case 6: query_post ( root, ans, x ); cout << ans << endl; break; } } if ( ++i <= n ) goto loop;}
- 史上最强图解Treap总结, 不是浅谈!
- 史上最强大的Saiku开发资源总结
- 史上最强的绕口令
- 史上最强的黑客
- 史上最强的回帖
- 史上最强的跟帖
- 史上最强的自我介绍
- 史上最强的科幻小说!
- 史上最强的笑话
- 史上最强的YY
- 史上最强的英文翻译
- 史上最强验证码
- 中华史上最强武将
- 史上最强的绕口令
- 史上最强的面试
- 史上最强的绕口令
- 史上最强骂人帖
- 史上最强验证码
- Zookeeper 日志输出到指定文件夹
- WPF Window 中实现WndProc
- Kotlin基础教程-属性和字段
- C++中c_str()函数用法
- java操作多线程
- 史上最强图解Treap总结, 不是浅谈!
- 编译chromium时出现的错误
- Logistic逻辑回归用初等数学解读逻辑回归
- (0001) H5开发之WebStorm 开发H5如何用手机打开测试
- 判断两个数组是否相等的方法
- 搭建JEESZ分布式架构8--消息中间件安装单、多节点
- NOIP2017模拟赛(三)总结
- 安全机构调查称WannaCry攻击者或成员可能使用中文
- UVa 1605 Building for UN ——思路题