前言
其實我寫過旋轉的Treap,我也寫過可以旋轉不停的Splay
但是我發現Splay克我,所以我學了一下神奇的非旋Treap
效率並不低,效率較低的部分在insert,要調用4次
的函數
其他操作或1次或2次
表現較好在我們學習之前我們可能需要一些關於旋轉Treap的知識
帶旋轉Treap
本文並不是講述帶旋轉Treap的文章,但是非旋Treap需要借用它的一些思想
畢竟都是Treap
Treap=Tree+Heap
就是一棵具有堆性質的二叉搜索樹
二叉搜索樹性質體現在權值上,左兒子權值小與父親權值,右兒子權值大於父親權值
堆體現在我們再隨機出一些附加值,使得每個點的附加值像堆一樣,父亲的優先度高於兒子
由於堆是完全二叉樹,所以可以使得Treap接近平衡
其維護堆性质的方法就是rotate,rotate,rotate,一旦破壞該性質就rotate,使得Treap像是一顆完全二叉樹
非旋Treap
struct node
{
node *son[2];
int val,key,size;
inline void push_up(){size=son[0]->size+son[1]->size+1;}
node(const int &x);
}*nil=new node(0),*Root=nil;
node::node(const int &x)
{
size=1;//不要忘了到時候把nil的size清零,否則你會爆零//Root也要清
val=x;
key=Random();
son[0]=son[1]=nil;
}
非旋Treap借用了Treap的堆的概念,在二叉搜索樹基礎上維護了一個堆
原理是一樣的,但是他不通過rotate來維護,而是通過神奇的
超神奇,還免去了大量的討論,使得代碼長度可觀了許多
這兩個操作非常重要先講述一下
這裡會用到
來代表樹的兩個部分
但是你也可以用assass_cannotin的神奇模板
template<class A,class B>class Pair
{
public:
A first;
B second;
friend bool operator<(Pair a,Pair b){return a.second<b.second;}
};
好吧,這和stl沒什麼區別,但是會更快,構造函數什麼的可以自己加,運算符什麼的自己隨便加,函數什麼的自己也隨便加
Merge
就是合併兩個Treap
合併時要遵循堆的性質
上代碼
node *merge(node *a,node *b)
{
if(a==nil)return b;
if(b==nil)return a;//p話
if(a->key<b->key)//維護小根堆
{
a->son[1]=merge(a->son[1],b);//把a的右兒子和b合併
a->push_up();
return a;
}
else
{
b->son[0]=merge(a,b->son[0]);//把b的左兒子和a合併
b->push_up();
return b;
}
}
你沒有看錯,核心操作
才這麼几行
你似乎還有疑問,他是如何維護二叉搜索樹的性質的呢
我們要求a這棵樹中的所有節點都小於b中的任何一個節點
這樣就可以滿足二叉搜索樹的性質了
時間複雜度
樹高的複雜度
Split
有兩種寫法,一個是根據 分裂,另一種是根據 分裂,複雜度都是一樣的,但是對於後面的函數寫法不太一樣,本文只介紹根據 分裂
//講前K個元素分成一棵樹,後k個一棵樹,還能滿足Merge要求的性質
Pair<node*,node*>split(node *root,int k)
{
if(root==nil)//不存在的節點分出來不存在的樹
{
Pair<node*,node*>p;
p.first=p.second=nil;
return p;
}
Pair<node*,node*>p;//快使用奇怪的模板
if(root->son[0]->size>=k)//前k個都在左子樹
{
p=split(root->son[0],k);//分裂!
root->son[0]=p.second;//p.second實際上還要加上root的剩餘部分
root->push_up();
p.second=root;//完美
return p;
}
else
{
p=split(root->son[1],k-root->son[0]->size-1);
root->son[1]=p.first;
root->push_up();
p.first=root;
return p;
}//一個道理
}
這樣就分裂完了,期望複雜度
最多只能分裂樹高次
真是很簡單誒
我們再加兩個操作就可以爆肝普通平衡樹了
find_kth(查詢在一棵樹內第K小元素)
真是智障直接粘代碼,不懂的去別的平衡樹家學
注意非旋Treap每個節點上只能有一個數,即使同一個
有好幾個,也要存在好幾個點,不要問我為什麼
int find_kth(node *root,int k)
{
if(root==nil)return 0;
if(k<=root->son[0]->size)return find_kth(root->son[0],k);
else if(k>root->son[0]->size+1)return find_kth(root->son[1],k-root->son[0]->size-1);
else return root->val;
}
Rank(查詢樹上有幾個元素比他小)
重點是比他小而不是他的排名,講道理,他的排名是他的Rank+1
直接粘代碼,不會的可以去別的平衡树家和
一塊學
代碼:
int Rank(node*root,int k)
{
if(root==nil)return 0;
if(k<=root->val)return Rank(root->son[0],k);
else return Rank(root->son[1],k)+root->son[0]->size+1;
}
下面是最慢的兩個操作,因為要調用4次上面的函數
Insert
具體思想:將比他小的數放成一堆,merge,merge就行了
inline void insert(int x)
{
Pair<node*,node*>p=split(Root,Rank(Root,x));
node *temp=new node(x);
Root=merge(merge(p.first,temp),p.second);
}
Del
具體思想:找到比他小的數,再找到比他大的數,merge
inline void del(int x)
{
Pair<node*,node*>a=split(Root,Rank(Root,x)),b=split(a.second,1);
Root=merge(a.first,b.second);
}
好了,下面的操作複雜度比上面兩個優秀很多
查找前驅
傻逼操作,無腦做就可以
write(find_kth(Root,Rank(Root,x))),putchar(10);
解決
查找後繼
傻逼操作,無腦做就可以
write(find_kth(Root,Rank(Root,x+1)+1)),putchar(10);
解決
完美
附上普通平衡樹AC代碼
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
inline void read(int &x)
{
int s=0,w=1;
char c=getchar();
while(c<'0'||c>'9')
{
if(c=='-')w=-1;
c=getchar();
}
while(c>='0'&&c<='9')
{
s=(s<<3)+(s<<1)+c-'0';
c=getchar();
}
x=s*w;
}
inline void write(int x)
{
if(x<0)putchar('-'),x=-x;
if(x>9)write(x/10);
putchar('0'+x%10);
}
template<class A,class B>class Pair
{
public:
A first;
B second;
friend bool operator<(Pair a,Pair b){return a.second<b.second;}
};
int seed=2333;
inline int Random()
{
return seed=seed*seed+seed;
}
struct node
{
node *son[2];
int val,key,size;
inline void push_up(){size=son[0]->size+son[1]->size+1;}
node(const int &x);
}*nil=new node(0),*Root=nil;
node::node(const int &x)
{
size=1;
val=x;
key=Random();
son[0]=son[1]=nil;
}
node *merge(node *a,node *b)
{
if(a==nil)return b;
if(b==nil)return a;
if(a->key<b->key)
{
a->son[1]=merge(a->son[1],b);
a->push_up();
return a;
}
else
{
b->son[0]=merge(a,b->son[0]);
b->push_up();
return b;
}
}
Pair<node*,node*>split(node *root,int k)
{
if(root==nil)
{
Pair<node*,node*>p;
p.first=p.second=nil;
return p;
}
Pair<node*,node*>p;
if(root->son[0]->size>=k)
{
p=split(root->son[0],k);
root->son[0]=p.second;
root->push_up();
p.second=root;
return p;
}
else
{
p=split(root->son[1],k-root->son[0]->size-1);
root->son[1]=p.first;
root->push_up();
p.first=root;
return p;
}
}
int find_kth(node *root,int k)
{
if(root==nil)return 0;
if(k<=root->son[0]->size)return find_kth(root->son[0],k);
else if(k>root->son[0]->size+1)return find_kth(root->son[1],k-root->son[0]->size-1);
else return root->val;
}
int Rank(node*root,int k)
{
if(root==nil)return 0;
if(k<=root->val)return Rank(root->son[0],k);
else return Rank(root->son[1],k)+root->son[0]->size+1;
}
inline void insert(int x)
{
Pair<node*,node*>p=split(Root,Rank(Root,x));
node *temp=new node(x);
Root=merge(merge(p.first,temp),p.second);
}
inline void del(int x)
{
Pair<node*,node*>a=split(Root,Rank(Root,x)),b=split(a.second,1);
Root=merge(a.first,b.second);
}
int n,opt,x;
int main(int argc, char const *argv[])
{
read(n);
nil->size=0;
Root->size=0;
while(n--)
{
read(opt),read(x);
if(opt==1)insert(x);
else if(opt==2)del(x);
else if(opt==3)write(Rank(Root,x)+1),putchar(10);
else if(opt==4)write(find_kth(Root,x)),putchar(10);
else if(opt==5)write(find_kth(Root,Rank(Root,x))),putchar(10);
else write(find_kth(Root,Rank(Root,x+1)+1)),putchar(10);
}
return 0;
}
注:兩種Split在不同題目下效率不同,比如刪除值為K和刪除第K大
所以可以都寫可以更快