1. 程式人生 > 其它 >平衡樹Treap

平衡樹Treap

treap:

treap=tree+heap,樹+堆

也就是說,這個東西是個樹,但是滿足堆的性質。

前置知識:

BST二叉搜尋樹:

若它的左子樹不空,則左子樹上所有結點的值均小於它的根結點的值; 若它的右子樹不空,則右子樹上所有結點的值均大於它的根結點的值。

也就是說,你把它從根節點中序遍歷一邊就能得到一個從小到大的數列。

大概長這樣子:

對於4:左邊子樹節點的權值為0 1 2 3,都比4小,右邊子樹節點的權值為5 6 7,都比4大。

對於1:左邊子樹節點權值為0,比1小,右邊子樹節點權值為2 3,比1大(且比4小)。

對於其他節點同理。

一個不難理解的東西。

堆:

priority_queue。。。

堆中某個節點的值總是不大於或不小於其父節點的值;

這裡就不再手寫堆了,‘’

回過頭來:

根據BST的定義我們可以知道,任意選取一個數做為根節點,在整個數列中一定能找到比他小的數和比他大的數(或者其中一個不存在),同樣的,對於它的子樹也是根節點不確定的。

有些毒瘤! 出題人會拿duliu資料把BST卡成一條鏈,然後多次詢問,複雜度直接爆炸。

那麼為了均攤一下BST的深度,使得每次詢問時間複雜度都接近logn的話,我們需要對它進行一系列操作:

賦權值:

我們隨機為每一個節點賦值一個權值pri,對於pri我們要求pri低的深度小,也就是小根堆的性質,因為rand每次都隨機的話是可以均攤一下節點的深度(具體的原理我還一時半會說不清。。感性李姐)

如何調整節點使得其滿足堆序:

在節點插入時,和bst一樣建立,找到自己節點應該在的位置,噹噹前節點為空時,建立新節點,rand它的pri後返回,對其進行左右旋轉操作。

左旋:就是把根節點轉移到其左子節點的位置,並維護BST。

方法:直接上圖:

我們可以按照紅線的方法來劃分子樹;

這樣就是上面的一條鏈和下面的又子節點的子樹兩部分了。

旋轉後,綠色節點到達根節點位置,也就是把整條鏈往左拽了一下;子樹換了父親,認左邊的節點為爸爸了:

就這樣完了。

右旋:換個方向搞左旋。

刪除節點:

刪除時,如果該節點對應size>1,則size--,否則將其權值pri賦為inf或者清空。之後我們可以通過比較左右子樹pri的大小決定誰做新的根(為了滿足堆的性質,單數不管是左子樹根節點還是右子樹根節點做新的根,都不會影響BST性質,感性模擬),之後根節點就變成了原來左子樹/右子樹中的一員,繼續遞迴下去,直到成為葉節點,也就是這一段開頭說的那句話的情況。之後我們就可以直接刪除它了。

求前驅後繼:

一個數x的前驅定義為小於這個數的最大的數。後繼就是大於等於這個數的最小的數。

理解如何遞迴:

以前驅舉例子,因為要找小於這個數的最大的數,那麼肯定到左子樹去找;

如果當前節點大於等於根節點的值,到左子樹裡面找;

如果當前節點空的,返回inf,

else,如果當前節點的val<x 則返回max(v,去右子樹遞迴的返回值);

else,說明答案還在左子樹裡面,那麼就到左子樹去找。

原理基本講解完畢。

例題 :【模板】普通平衡樹

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<string>
#include<algorithm>
#include<cmath>
#include<vector>
#include<queue>
#include<stack>
#include<map>
using namespace std;
#define lovelive long long
int tot;
int pos[100020];
int p[100020];
map <int,int> s;
priority_queue<int,vector<int>,greater<int> > so;
struct question
{
  int num,x;
}q[100020];
struct tree
{
  int l,r,sum;
}t[400020];
void buildtree(int i,int l,int r)
{
  t[i].l=l;
  t[i].r=r;
  if(l==r)
  {
      pos[l]=i;
    return ;
  }
  int mid=(l+r)>>1;
  buildtree(i<<1,l,mid);
  buildtree(i<<1|1,mid+1,r);
}
void insert(int i,int point)
{
  t[i].sum++;
  int mid=(t[i].l+t[i].r)>>1;
  if(t[i].l==t[i].r)
    return ;
  if(point>mid)
    insert(i<<1|1,point);
  else
    insert(i<<1,point);
}
void Delete(int i,int point)
{
  t[i].sum--;
  int mid=(t[i].l+t[i].r)>>1;
  if(t[i].l==t[i].r)
    return ;
  if(point>mid)
    Delete(i<<1|1,point);
  else
    Delete(i<<1,point);
}
int find_num(int i,int x)
{
  if(t[i].l==t[i].r)
    return t[i].l;
  if(x>t[i<<1].sum)
    find_num(i<<1|1,x-t[i<<1].sum);
  else
    find_num(i<<1,x);
}
int query(int i,int l,int r)
{
  if(r<t[i].l||l>t[i].r)
    return 0;
  if(r>=t[i].r&&l<=t[i].l)
    return t[i].sum;
  return query(i<<1,l,r)+query(i<<1|1,l,r);
}
int find_rank(int x)
{
  return query(1,1,x-1)+1;
}
int find_pre(int x)
{
  int p=find_rank(x);
  return find_num(1,p-1);
}
int find_next(int x)
{
  int p=find_rank(x);
  return find_num(1,p+t[pos[x]].sum);
}
int main()
{
  int n;
  scanf("%d",&n);
  for(int i=1;i<=n;i++)
    {
      scanf("%d%d",&q[i].num,&q[i].x);
      if(q[i].num!=4) 
        so.push(q[i].x);
    }
  int point = -2e9-1;
  while(!so.empty())
  {
      if(so.top()!=point)
        ++tot;
      s[so.top()]=tot;
      p[tot]=so.top();
      point=so.top();
      so.pop();
  }  
  buildtree(1,1,tot);
  for(int i=1;i<=n;i++)
    if(q[i].num!=4)
        q[i].x=s[q[i].x];
  for(int i=1;i<=n;i++)
  {
      switch(q[i].num)
      {
        case 1:insert(1,q[i].x);break;
        case 2:Delete(1,q[i].x);break;
        case 3:cout<<find_rank(q[i].x)<<"\n";break;
        case 4:cout<<p[find_num(1,q[i].x)]<<"\n";break;
        case 5:cout<<p[find_pre(q[i].x)]<<"\n";break;
        case 6:cout<<p[find_next(q[i].x)]<<"\n";break;
    }
  }
  return 0;
}