非旋Treap維護普通平衡樹的基本操作

前言

其實我寫過旋轉的Treap,我也寫過可以旋轉不停的Splay
但是我發現Splay克我,所以我學了一下神奇的非旋Treap
效率並不低,效率較低的部分在insert,要調用4次 O ( l o g 2 n ) 的函數
其他操作或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來維護,而是通過神奇的
S p l i t M e r g e
超神奇,還免去了大量的討論,使得代碼長度可觀了許多
這兩個操作非常重要先講述一下 M e r g e
這裡會用到 p a i r 來代表樹的兩個部分
但是你也可以用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;
  }
}

你沒有看錯,核心操作 M e r g e 才這麼几行
你似乎還有疑問,他是如何維護二叉搜索樹的性質的呢
我們要求a這棵樹中的所有節點都小於b中的任何一個節點
這樣就可以滿足二叉搜索樹的性質了
時間複雜度 O ( l o g 2 n ) 樹高的複雜度
这里写图片描述这里写图片描述这里写图片描述

Split

S p l i t 有兩種寫法,一個是根據 v a l 分裂,另一種是根據 s i z e 分裂,複雜度都是一樣的,但是對於後面的函數寫法不太一樣,本文只介紹根據 s i z e 分裂

//講前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;
  }//一個道理
}

這樣就分裂完了,期望複雜度 O ( l o g 2 n ) 最多只能分裂樹高次
真是很簡單誒
这里写图片描述这里写图片描述这里写图片描述
我們再加兩個操作就可以爆肝普通平衡樹了

find_kth(查詢在一棵樹內第K小元素)

真是智障直接粘代碼,不懂的去別的平衡樹家學

注意非旋Treap每個節點上只能有一個數,即使同一個 v a l 有好幾個,也要存在好幾個點,不要問我為什麼
这里写图片描述

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
直接粘代碼,不會的可以去別的平衡树家和 f i n d k t h 一塊學
这里写图片描述这里写图片描述这里写图片描述
代碼:

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大
所以可以都寫可以更快

猜你喜欢

转载自blog.csdn.net/assass_cannotin/article/details/79309828
今日推荐