不需要旋转,却能力压群雄的数据结构——非旋Treap 看完还不会你打我
2017-12-16 20:07
316 查看
非旋Treap讲解
Treap,一种平衡树。作为一棵平衡树,一定是遵从着某种原则,使得这棵树尽量的接近完全二叉树。除了二叉搜索树都具备的性质——左子树 ≤ 根 ≤ 右子树,顾名思义,Treap = tree+heap。这时他的特殊性质就飘出水面了——heap。有一个需要慢慢理解的东西,我觉得是本文最重要的——二叉搜索树中顺次排列所参考的参数是灵活的,谁说非要按数值??有的题你就得用下标。
一、每个node的元素组成
对于一个node,有fix(随机分配的数,用rand即可),size(这个node所引领的树的大小,包括自己),剩下的参数视情况而定。这棵树所包含点的fix值,需要满足堆的性质,一般情况下我们会使用最小堆(约定俗成)。以bzoj3224为例:struct Treap { Treap *l, *r; int fix, key, size; //fix表示随机值,key表示数值,size表示以它为根的这棵子树的大小 Treap(int key_):fix(randad()), key(key_), l(NULL), r(NULL), size(1) {} inline void updata() { //更新树的大小 size = 1 + (l?l->size:0) + (r?r->size:0); } }*root; typedef pair<Treap*, Treap*> Droot; inline int Size(Treap *x) {return x?x->size:0;}
一定要注意!在改了某节点后,一定要update!
二、基础的操作
有人会说,既然都不旋转了,那肯定操作起来特别难。好吧我告诉你们,真的挺难。不过只要认真看完我的文章(老脸一红)并多多练习,我相信你一定能使用的得心应手。①Split
先下个定义:Split(x, k)表示,把x引导的这一棵树的前k个节点(顺序是按照左子树 ≤ 根 ≤ 右子树来划定的)从树中分离出来。函数的返回结果是一个节点对,记作(x1, x2),此时x1所引导的树就是前k个节点,x2引导的树就是剩下的节点。注意:Split之后,我们已经真真切切的把它们切开了,现在我们的Treap已经不是一个整体了,一定要养成好习惯,Split之后不要忘了再合并起来(合并是我们要讲的下一个基础操作)。那具体怎么做呢?
Droot Split(Treap *x, int k) { if(!x) return Droot(NULL, NULL); Droot y; if(Size(x->l) >= k) { //如果左子树的节点数能达到k个,则前k个一定都在左子树中 y = Split(x->l, k); x->l = y.second; //x的左子树只留下来了不在前k个位置的节点 x->updata(); y.second = x; //此时x这棵树可以整个拖上去,作为以前不在前k个位置的节点集合 }else { y = Split(x->r, k - Size(x->l) - 1); //要在右子树搜索前几个,连同根节点与左子树,作为前k个 x->r = y.first; //x的右子树变为以前的右子树的前几个(此时x的size应恰为k) x->updata(); y.first = x; //整个拖上去,作为前k个位置的点的集合 } return y; }
②Merge
继续下定义:Merge(x,y)表示,把x这棵树与y这棵树合并起来(必须满足的条件:在划分顺序时,x中所有元素都应排在y中的元素的前面。所以我认为Merge最重要的性质在于有序性)。函数的返回结果是一个节点,表示合并后这4000
棵树的树根。
Treap *Merge(Treap *A, Treap *B) { if(!A) return B; if(!B) return A; if(A->fix < B->fix) { //如果A的随机数比较小,那他应该居上 A->r = Merge(A->r, B); //而B本就应该排A后面,于是把A的右子树和B合并,一并作为A的右子树(注意顺序) A->updata(); return A; }else { //B居上 B->l = Merge(A, B->l); //把A和B的左子树合并,一并作为B的左子树 B->updata(); return B; } }
于是,到现在,基本的操作你就都会了。
我们来应用一下。
看一下bzoj3224,要我们支持的操作有6个,我们一一来分析。
①先说查询一个数的排名。就和普通二叉搜索树没什么区别的,直接上代码。
inline int Getkth(Treap *x, int v) { //询问一个数是第几小 if(!x) return 0; int ans = 0, temp = (int)2e9+1; while(x) { if(v == x->key) temp = min(temp, ans + Size(x->l) + 1); if(v > x->key) ans+= Size(x->l) + 1, x = x->r; else x = x->l; } return temp==(int)2e9+1?ans:temp; }②顺便一块把查询第几名是谁说了吧。异曲同工。
int Findkth(int k) { //寻找第k小 Treap *p; p = root; while(true) { if(Size(p->l) == k - 1) return p->key; if(Size(p->l) > k - 1) p = p->l; else k-= (Size(p->l) + 1), p = p->r; } }③Insert:只加入一个数,于是我们先查询,在平衡树中有几个比x小的(用Getkth,这就是为什么我先讲查排名),记为k,然后把树分开,把节点放中间,合并。
inline void Insert(int v) { int k = Getkth(root, v); Droot x = Split(root, k); Treap *n = new Treap(v); root = Merge(Merge(x.first, n), x.second); return ; }④Delete:删除。与insert一样,分成三段——前k-1个,第k个,后面剩下的。直接合并第1、3段即可。(如果必要,最好随删随清内存,这就是指针的好处。但指针的坏处就是极其不可控,容易re)
删内存代码:
void del(Treap *p) { if(!p) return ; if(p->l) del(p->l); if(p->r) del(p->r); delete p; }⑤⑥前驱与后继,这个很简单,就是利用查排名函数。
inline int pre(Treap *k, int x) { int ans = (int)-2e9-1; while(k) { if(k->key < x) ans = max(ans, k->key), k = k->r; else k = k->l; } return ans; }
inline neg(Treap *k, int x) { int ans = (int)2e9+1; while(k) { if(k->key > x) ans = min(ans, k->key), k = k->l; else k = k->r; } return ans; }
再看一个应用,笛卡尔树。我们知道,加点的复杂度是log级的,批量加点复杂度显然有一点大(其实我只是想去掉一点常数,log级并没有那么大,但是oj也许会卡)。
批量加数时,先存进数组a中,a[0]表示一共有多少数要进树。
Treap *Build(int *a) { static Treap *x, *last; int p = 0; for(int i = 1; i <= a[0]; ++i) { x = new Treap(a[i]); last = NULL; while(p && sta[p]->fix > x->fix) { sta[p]->updata(); last = sta[p]; sta[p--] = NULL; } if(p) sta[p]->r = x; x->l = last; sta[++p] = x; } while(p) sta[p--]->updata(); return sta[1]; }
至此,Treap的基本知识已经讲完了。如果大家有兴趣看一下平衡树的操作boss题,我推荐bzoj1500维修数列。
附上我的维修数列Treap版代码:http://paste.ubuntu.com/26194333/
相关文章推荐
- 【数据结构】【平衡树】无旋转treap
- Linux中find命令用法全汇总,看完就没有不会用的!
- 【Treap/非旋转Treap】BZOJ1503 [NOI2004]郁闷的出纳员
- 《剑指offer》刷题笔记(知识迁移能力):左旋转字符串
- 【BZOJ1014】【JSOI2008】火星人prefix 哈希 非旋转treap
- SPOJ ORDERSET Order statistic set 非旋转treap
- 软件将吞掉整个世界——人类只要开发软件,让软件指挥机器,所有的其它工作智能机器人会帮你(哈哈哈,看完这篇我又爱上了做软件,而且更坚定了)——你迷茫是因为你没有分析未来的能力,这样就不能在低潮时坚持
- bzoj 3224(非旋转treap)
- [数据结构]二叉树的旋转
- 数据结构之--Treap(hdu3726)
- Python数据结构学习之旋转链表详解
- PTA中国大学MOOC-陈越、何钦铭-数据结构-起步能力自测题
- 平衡树之Treap—强大的数据结构
- [bzoj1500][NOI2005]维修数列_非旋转Treap
- 南开bbs——看完了就不会有人再感慨奶粉事件了...
- 接口和抽象类的区别---相信你看完不会再混淆了
- 数据结构-----AVL树的旋转操作
- 非旋转 Treap 学习笔记(一)
- 非旋转 Treap 学习笔记(二)
- [BZOJ3224]普通平衡树(旋转treap,STL-vector)