平衡樹 替罪羊樹(Scapegoat Tree)

替罪羊樹(Scapegoat Tree)
html

入門模板題 洛谷oj P3369node

題目ios

  您須要寫一種數據結構(可參考題目標題),來維護一些數,其中須要提供如下操做:數組

  1. 插入xx數
  2. 刪除xx數(如有多個相同的數,因只刪除一個)
  3. 查詢xx數的排名(排名定義爲比當前數小的數的個數+1+1。如有多個相同的數,因輸出最小的排名)
  4. 查詢排名爲xx的數
  5. xx的前驅(前驅定義爲小於xx,且最大的數)
  6. xx的後繼(後繼定義爲大於xx,且最小的數)

輸入格式網絡

  第一行爲n,表示操做的個數,下面n行每行有兩個數optxopt表示操做的序號( 1opt6 )數據結構

輸出格式學習

  對於操做3,4,5,6每行輸出一個數,表示對應答案優化

輸入樣例ui

10spa

1 106465
4 1
1 317721
1 460929
1 644985
1 84185
1 89851
6 81968
1 492737
5 493598

輸出樣例

106465

84185
492737

數據範圍

1.n的數據範圍: n100000

 

2.每一個數的數據範圍: [-10^7, 10^7]

 

 

  網上的資料比較瑣碎難懂,以前看了不少資料一直不能理解平衡樹(我太弱了)……前幾天忽然莫名其妙明白了,想寫一篇筆記記錄一下(亂寫一通)。

 

0x00 二叉查找樹

  要初步弄懂平衡樹,首先要知道這是一棵二叉查找樹

  二叉查找樹(Binary Search Tree),固然也能夠叫它二叉搜索樹,或者二叉排序樹(反正都一個意思都是二叉樹),它的定義以下:

    或者是一棵空樹,或者是具備下列性質的二叉樹:

(1)若左子樹不空,則左子樹上全部結點的值均小於它的根節點的值;
(2)若右子樹不空,則右子樹上全部結點的值均大於它的根結點的值;
(3)左、右子樹也分別爲二叉排序樹;

    差很少就像下面這幅圖同樣:

 

0x01 平衡樹的用途

  在學平衡樹這個數據結構前,相信咱們必定會先有個問題:平衡樹能拿來幹什麼?

  網上的不少資料對這一點寫得不太明白(也多是我太弱了),我先試着亂總結一下:
    如今須要一種數據結構,它須要作到如下幾點:
     1. 高效地查詢一個序列中某個數的前面和後面的數(a[i-1]和a[i+1])。
     2. 高效地知道第i個數是什麼(即a[i])。
     3. 高效地插入和刪除。
  顯然,咱們能夠用普通數組優秀地完成第1點和第2點,但第3點不可以了。固然,咱們也能夠用鏈表,複雜度O(1)優秀地完成第1點和第3點,可是對於第2點,鏈表的複雜度就達到了不太理想的O(n)。
  那咱們能不能想辦法優化一下鏈表呢?


  咱們能夠回頭看一下二叉搜索樹的定義,而後咱們會發現,假設存在一個從1到8的鏈表(就像下圖,win自帶畫圖畫的,不太好看)

  其實它也知足左邊小於右邊的定義,也能夠勉強算是一棵以序號爲每一個節點的權值的二叉查找樹。

  

 

  可是這樣的一棵二叉樹是否是很難看很畸形?
  咱們怎麼想辦法把它變成一棵比較好看的樹呢?
  能夠拿筆畫畫看:
  咱們嘗試把中間的節點(4或者5,我選擇4)拎出來,而後就變成了這樣:

  

  是否是好看了一點?有點樹的形狀了,那咱們能夠嘗試繼續把兩側鏈上的中間節點繼續拎出來,不斷重複,最終會變成一棵比較好看的樹。

  
  這就是平衡二叉樹,嚴格遵照了二叉查找樹的定義——左兒子小於右兒子。
  也許你會有疑問:長成這樣的一棵樹,怎麼作到剛剛鏈表都不能徹底作到的3點要求呢?
  讓咱們再看看這棵樹:

0x02 查詢前驅和後驅
  對於一個咱們已知的節點
i,咱們先定義與它深度相同的都是它的兄弟節點。

  那麼很顯然,i的左兄弟及其子樹上的全部節點都比i的左兒子及其子樹上的全部節點來得小,且i的左兒子及其子樹上的全部點都比i的父親更大,因此顯然,i的前驅要在i的左兒子及其子樹上找。

  同理,仔細觀察圖,會發現咱們所要找的前驅,存在於i的左兒子子樹的最右側,就是i的左兒子的右兒子的右兒子的右兒子的右兒子……(直到最後一個右兒子)

  這樣,咱們就能夠O(log n)地求出i的前驅了。i的後驅同理。

 

0x03 查詢第i個數

  前面說過,咱們用數的序列編號做爲節點的排序權值,因此咱們只要像線段樹那樣從根部開始查一遍就能夠了。詳細解釋:

  從根結點開始,若是第i個數比當前節點j的序號小,就往左兒子搜,反之右兒子。直到找到第i個數,時間複雜度仍是O(log n)。


0x04 插入和刪除
  插在原序列的末尾,因此新節點的編號是n+1。而後咱們把這個節點變成根結點的右兒子的右兒子的右兒子……(變成最後一個沒有右兒子的節點的右兒子)。

  刪除。其實就是把i節點打個被刪除掉的標記(甚至不打也能夠)。而後讓i的左右兒子之一成爲i的父節點的新兒子(就是讓某一個兒子取代i節點)。複雜度同O(log n)

  基本操做差很少就是這樣。

  那麼其實還有一個問題:若是操做太多,致使一棵原本平衡的樹變成了一條鏈表,複雜度爆炸,怎麼辦呢?

 

0x05 從新建樹
  若是你選擇的是替罪羊樹,那就是優雅的暴力了。替罪羊樹在每一個節點上記錄子樹的節點數size,同時還有一個平衡因子alpha(一般在0.5左右,我選擇0.7),當每次更新後,遞歸回去檢查i節點的左右兒子分別乘以平衡因子,是否大於另外一個兒子,若是大了,表明這棵樹有退化的傾向,趕忙拍平重建(就是把樹壓成鏈表,從新建樹)。

  大概就是這樣,手機打的好難受,直接上代碼好了。

  (其實我一開始作平衡樹時以爲能夠用線段樹模擬的emmm,就不說了)

  代碼能夠配合洛谷的模板題食用

 

 

#include <cstdio>
#include <iostream>
using namespace std;
const int INF=1000000000;
const int MAX_N=2000005;
const double alpha=0.75;

int n;
inline int read(){
    register int ch=getchar(),x=0,f=1;
    while (ch<'0'||ch>'9'){
        if (ch=='-')    f=-1;
        ch=getchar();
    } while (ch>='0'&&ch<='9'){
        x=x*10+ch-'0';
        ch=getchar();
    } return x*f;
}
struct Tree{
    int fa;
    int size;
    int value;
    int son[2];
}tree[MAX_N];
int cnt=2;
int root=1;
int node[MAX_N];
int sum;

bool balance(int x){    //判斷是否平衡 
    return (double)tree[x].size*alpha>=(double)tree[tree[x].son[0]].size&&(double)tree[x].size*alpha>=(double)tree[tree[x].son[1]].size;
}
int build(int l,int r){    //從新遞歸建樹 
    if (l>r)    return 0;
    int mid=(l+r)>>1;
    tree[tree[node[mid]].son[0]=build(l,mid-1)].fa=node[mid],tree[tree[node[mid]].son[1]=build(mid+1,r)].fa=node[mid];
    tree[node[mid]].size=tree[tree[node[mid]].son[0]].size+tree[tree[node[mid]].son[1]].size+1;
    return node[mid];
}
void recycle(int x){    //把樹壓成數列 
    if (tree[x].son[0])    recycle(tree[x].son[0]);
    node[++sum]=x;
    if (tree[x].son[1])    recycle(tree[x].son[1]);
}
void rebuild(int x){
    sum=0;
    recycle(x);
    int fa=tree[x].fa,son=(tree[tree[x].fa].son[1]==x),now=build(1,sum);
    tree[tree[fa].son[son]=now].fa=fa;
    if (x==root)    root=now;
}
void insert(int x){
    int i=root,now=++cnt;    //新節點序號 
    tree[now].size=1,tree[now].value=x;
    while (true){
        tree[i].size++;
        bool son=(x>=tree[i].value); 
        if (tree[i].son[son])    i=tree[i].son[son];
        else{
            tree[tree[i].son[son]=now].fa=i;
            break;
        }
    }
    int flag=0;
    for (int j=now;j;j=tree[j].fa)    //logn找不平衡的節點 
        if (!balance(j))    flag=j;
    if (flag)    rebuild(flag);    //重建樹 
}


int get_num(int x){     
    int i=root;
    while (true){
        if(tree[i].value==x)    return i;
        else    i=tree[i].son[tree[i].value<x];
    }
}
void erase(int x){    //刪除 
    if (tree[x].son[0]&&tree[x].son[1]){
        int now=tree[x].son[0];
        while (tree[now].son[1])    now=tree[now].son[1];
        tree[x].value=tree[now].value;
        x=now;
    }
    int son=(tree[x].son[0])?tree[x].son[0]:tree[x].son[1];
    int k=(tree[tree[x].fa].son[1]==x);
    tree[tree[tree[x].fa].son[k]=son].fa=tree[x].fa;
    for (int i=tree[x].fa;i;i=tree[i].fa)
        tree[i].size--;
    if (x==root)
        root=son;
}
int get_rank(int x){
    int i=root,ans=0;
    while (i)
        if(tree[i].value<x)    ans+=tree[tree[i].son[0]].size+1,i=tree[i].son[1];
        else    i=tree[i].son[0];
    return ans;
}
int get_kth(int x){
    int i=root;
    while (true)
        if (tree[tree[i].son[0]].size==x-1)    return i;
        else if (tree[tree[i].son[0]].size>=x)    i=tree[i].son[0];
        else    x-=tree[tree[i].son[0]].size+1,i=tree[i].son[1];
    return i;
}
int get_front(int x){
    int i=root,ans=-INF;
    while(i)
        if(tree[i].value<x)    ans=max(ans,tree[i].value),i=tree[i].son[1];
        else    i=tree[i].son[0];
    return ans;
}
int get_behind(int x){
    int i=root,ans=INF;
    while(i)
        if(tree[i].value>x)    ans=min(ans,tree[i].value),i=tree[i].son[0];
        else    i=tree[i].son[1];
    return ans;
}
int main(){
//    freopen("test1.in","r",stdin);
    tree[1].value=-INF,tree[1].size=2,tree[1].son[1]=2;
    tree[2].value=INF,tree[2].size=1,tree[2].fa=1;
    n=read();
    for(int i=1,op,x;i<=n;i++){
        op=read(),x=read();
        if(op==1)    insert(x);
        if(op==2)    erase(get_num(x));
        if(op==3)    printf("%d\n",get_rank(x));
        if(op==4)    printf("%d\n",tree[get_kth(x+1)].value);
        if(op==5)    printf("%d\n",get_front(x));
        if(op==6)    printf("%d\n",get_behind(x));
    }
}

 

 

 

 

 

 

其餘例題

大致上按難度排序?我太弱了也搞不懂。

一.[HNOI2002]營業額統計 (2019.4.10更新)

洛谷oj P2234

  聽說有各類神犇用許多神奇的解法A掉了……可是我這種蒟蒻就先用平衡樹練手了。

  min{|該天之前某一天的營業額-該天營業額|},即不大於a[i]的最大值和不小於a[i]的最小值,就是尋找a[i]的前驅和後驅,分別減去a[i]取絕對值,再所有加起來,複雜度應該是O(nlogn)。

  代碼:

#include <cstdio>
#include <iostream>
using namespace std;
const int INF=1000000000;
const int MAX_N=2000005;
const double alpha=0.75;

int n;
inline int read(){
    register int ch=getchar(),x=0,f=1;
    while (ch<'0'||ch>'9'){
        if (ch=='-')    f=-1;
        ch=getchar();
    } while (ch>='0'&&ch<='9'){
        x=x*10+ch-'0';
        ch=getchar();
    } return x*f;
}
struct Tree{
    int fa;
    int size;
    int value;
    int son[2];
}tree[MAX_N];
int cnt=2;
int root=1;
int node[MAX_N];
int sum;

bool balance(register int x){    //判斷是否平衡 
    return (double)tree[x].size*alpha>=(double)tree[tree[x].son[0]].size&&(double)tree[x].size*alpha>=(double)tree[tree[x].son[1]].size;
}
inline int build(register int l,register int r){    //從新遞歸建樹 
    if (l>r)    return 0;
    int mid=(l+r)>>1;
    tree[tree[node[mid]].son[0]=build(l,mid-1)].fa=node[mid],tree[tree[node[mid]].son[1]=build(mid+1,r)].fa=node[mid];
    tree[node[mid]].size=tree[tree[node[mid]].son[0]].size+tree[tree[node[mid]].son[1]].size+1;
    return node[mid];
}
void recycle(register int x){    //把樹壓成數列 
    if (tree[x].son[0])    recycle(tree[x].son[0]);
    node[++sum]=x;
    if (tree[x].son[1])    recycle(tree[x].son[1]);
}
void rebuild(register int x){
    sum=0;
    recycle(x);
    int fa=tree[x].fa,son=(tree[tree[x].fa].son[1]==x),now=build(1,sum);
    tree[tree[fa].son[son]=now].fa=fa;
    if (x==root)    root=now;
}
void insert(register int x){
    int i=root,now=++cnt;    //新節點序號 
    tree[now].size=1,tree[now].value=x;
    while (true){
        tree[i].size++;
        bool son=(x>=tree[i].value); 
        if (tree[i].son[son])    i=tree[i].son[son];
        else{
            tree[tree[i].son[son]=now].fa=i;
            break;
        }
    }
    int flag=0;
    for (int j=now;j;j=tree[j].fa)    //logn找不平衡的節點 
        if (!balance(j))    flag=j;
    if (flag)    rebuild(flag);    //重建樹 
}

inline int get_front(register int x){
    register int i=root,ans=-INF;
    while(i)
        if(tree[i].value<x)    ans=max(ans,tree[i].value),i=tree[i].son[1];
        else    i=tree[i].son[0];
    return ans;
}
inline int get_behind(register int x){
    register int i=root,ans=INF;
    while(i)
        if(tree[i].value>x)    ans=min(ans,tree[i].value),i=tree[i].son[0];
        else    i=tree[i].son[1];
    return ans;
}
bool flag[1000000+5];
int result;
int main(){
//  freopen("test1.in","r",stdin);
    tree[1].value=-INF,tree[1].size=2,tree[1].son[1]=2;
    tree[2].value=INF,tree[2].size=1,tree[2].fa=1;
    n=read();
    int kkk=read();
    result+=kkk;
    insert(kkk);
    flag[kkk]=true;
    for (int i=2,a,min,max;i<=n;i++){
        a=read();
        if (flag[a])    continue;
        flag[a]=true;
        insert(a);
        min=get_front(a);
        max=get_behind(a);
        if (min==-INF){
            result+=(max-a);
            continue;
        }
        else if (max==INF){
            result+=(a-min);
            continue;
        }
        result+=(a-min>max-a?max-a:a-min);
    }
    printf("%d",result);
    return 0;
}

 

 

 

 

[學習自百度百科和其餘網絡資料]

相關文章
相關標籤/搜索