平衡树——Treap,Splay

二叉查找树(BST)的两条性质:

1.该节点的关键码不小于它的左子树中任意节点的关键码

2.该节点的关键码不大于它的右子树中任意节点的关键码

显然,二叉树的中序遍历是关键码单调递增排列的序列。

为避免越界,在BST中插入关键码为正无穷和负无穷的结点为初始情况。

基本操作:

1.检索

递归:

(1)找到结点,返回值

(2)未找到节点且无路可走(子结点为空),返回

(3)结点偏大,向左继续递归

(4)结点偏小,向右继续递归

ps:也可以用循环写,只是我习惯用深搜而已

2.插入

若发现关键值的结点不存在时,新建节点。

3.排名(Kth)

根据平衡树的性质,一个结点的排名即左子树大小+1。

4.前驱/后继

首先找到结点,根据平衡树的性质:

若找其前驱,先向左走一步(比结点小的左子树中找),随

后不断向右走直到叶节点,即为比结点小的数中最大的一个。

后既同理。

5.删除

首先,检索得到结点。

若p的子结点个数小于2,则直接删除p,令子结点直接代替其 位置,与p的父节点相连。

若p既有左子树又有右子树,则求出p的后继结点next。若next 没有左子树,直接删除next,并令next的右子树代替next的位置。 最后, 再让next节点代替p结点, 删除p即可。

Treap(单旋转)

下面是用treap写普通平衡树(BZOJ3224)的代码,代码几乎是李煜东的《算法竞赛进阶指南》上面的样子(最喜欢的一本书)。

#include <iostream>
#include <cstdlib>
using namespace std;
const int SIZE = 100005;
struct Treap{
    int l , r;
    int val , dat;
    int size , cnt;
} a[SIZE];
int tot , n , root , INF = 0x7fffffff;
int New( int val ){
    a[++tot].val = val;
    a[tot].size = a[tot].cnt = 1;
    a[tot].dat = rand();
    return tot;
}
void Update( int p ){
    a[p].size = a[a[p].l].size + a[a[p].r].size + a[p].cnt;
}

void Build(){
    New( -INF ) , New( INF );
    root = 1 , a[1].r = 2;
    Update( root );
}

void zig( int &p ){
    int q = a[p].l;
    a[p].l = a[q].r , a[q].r = p , p = q;
    Update( a[p].r ) , Update( p );
}
void zag( int &p ){
    int q = a[p].r;
    a[p].r = a[q].l , a[q].l = p , p = q;
    Update( a[p].l ) , Update( p );
}
void Insert( int &p , int val ){
    if( p == 0 ){
        p = New( val );
        return;
    }
    if( val == a[p].val ){
        a[p].cnt++ , Update( p );
        return;
    }
    if( val < a[p].val ){
        Insert( a[p].l , val );
        if( a[p].dat < a[a[p].l].dat ) zig( p );
    } else{
        Insert( a[p].r , val );
        if( a[p].dat < a[a[p].r].dat ) zag( p );
    }
    Update( p );
}
void Remove( int &p , int val ){
    if( p == 0 ) return;
    if( val == a[p].val ){
        if( a[p].cnt > 1 ){
            a[p].cnt-- , Update( p );
            return;
        }
        if( a[p].l || a[p].r ){
            if( a[p].r == 0 || a[a[p].l].dat > a[a[p].r].dat )
                zig( p ) , Remove( a[p].l , val );
            else
                zag( p ) , Remove( a[p].r , val );
            Update( p );
        } else
            p = 0;
    }
    val < a[p].val ? Remove( a[p].l , val ) : Remove( a[p].r , val );
    Update( p );
}
int GetRankByVal( int p , int val ){
    if( p == 0 ) return 0;
    if( a[p].val == val ) return a[a[p].l].size;
    if( val < a[p].val ) return GetRankByVal( a[p].l , val );
    return GetRankByVal( a[p].r , val ) + a[a[p].l].size + a[p].cnt;
}
int GetValByRank( int p , int rank ){
    if( p == 0 ) return INF;
    if( a[a[p].l].size >= rank ) return GetValByRank( a[p].l , rank );
    if( a[a[p].l].size + a[p].cnt >= rank ) return a[p].val;
    return GetValByRank( a[p].r , rank - a[a[p].l].size - a[p].cnt );
}

int GetPrev( int val ){
    int ans = 1;
    int p = root;
    while( p ){
        if( val == a[p].val ){
            if( a[p].l > 0 ){
                p = a[p].l;
                while( a[p].r ) p = a[p].r;
                ans = p;
            }
            break;
        }
        if( a[p].val < val && a[p].val > a[ans].val ) ans = p;
        p = val < a[p].val ? a[p].l : a[p].r;
    }
    return a[ans].val;
}
int GetNext( int val ){
    int ans = 2;
    int p = root;
    while( p ){
        if( val == a[p].val ){
            if( a[p].r > 0 ){
                p = a[p].r;
                while( a[p].l ) p = a[p].l;
                ans = p;
            }
            break;
        }
        if( a[p].val > val && a[p].val < a[ans].val ) ans = p;
        p = val < a[p].val ? a[p].l : a[p].r;
    }
    return a[ans].val;
}

int main(){
    Build();
    int n;
    cin >> n;
    while( n-- ){
        int f , x;
        cin >> f >> x;
        switch( f ){
        case 1:
            Insert( root , x );
            break;
        case 2:
            Remove( root , x );
            break;
        case 3:
            cout << GetRankByVal( root , x ) << "\n";
            break;
        case 4:
            cout << GetValByRank( root , x + 1 ) << "\n";
            break;
        case 5:
            cout << GetPrev( x ) << "\n";
            break;
        case 6:
            cout << GetNext( x ) << "\n";
            break;
        }
    }
    return 0;
}

splay(伸展树)

伸展树(Splay Tree),也叫分裂树,是一种二叉排序树,它能在O(log n)内完成插入、查找和删除操作。

概括的说其操作——把一个结点旋转到根。

概括的说其转法——如果结点是父结点的左子节点,则进行右旋;如果结点是父节点的右子节点,则进行左旋。

逐渐可以理解转的原理的。

#include <bits/stdc++.h>
using namespace std;

const int _ = 1e5 + 20;

int n , opt , x;

struct Splay{
    int root , cnt , val[_] , ch[_][2] , siz[_] , sz[_] , par[_];

    bool chk( int x ){
        return ch[par[x]][1] == x;
    }

    void pushup( int x ){
        if( !x ) return;
        siz[x] = siz[ch[x][0]] + siz[ch[x][1]] + sz[x];
        //cout<<x<<' '<<siz[x]<<"       ";
    }

    int newnode( int x ){
        ++cnt;
        val[cnt] = x , sz[cnt] = siz[cnt] = 1;
        ch[cnt][0] = ch[cnt][1] = 0;
        return cnt;
    }

    void rotate( int x ){
        int y = par[x] , z = par[y] , k = chk( x ) , w = ch[x][k ^ 1];
        ch[y][k] = w , par[w] = y;
        ch[z][chk( y )] = x , par[x] = z;
        ch[x][k ^ 1] = y , par[y] = x;
        pushup( y ) , pushup( x );
    }

    void splay( int x , int goal = 0 ){
        while( par[x] != goal ){
            int y = par[x] , z = par[y];
            if( z != goal ){
                if( chk( x ) == chk( y ) ) rotate( y );
                else
                    rotate( x );
            }
            rotate( x );
        }
        if( !goal ) root = x;
    }

    void insert( int x ){
        int cur = root , p = 0;
        while( cur && x != val[cur] ) p = cur , cur = ch[cur][x > val[cur]];
        if( cur ) ++sz[cur] , splay( cur );
        else
            cur = ch[p][x > val[p]] = newnode( x ) , par[cur] = p , splay( cur );
    }

    void find( int x ){
        if( !root ) return;
        int cur = root;
        while( ch[cur][x > val[cur]] && x != val[cur] ) cur = ch[cur][x > val[cur]];
        splay( cur );
    }

    int rank( int x ){
        find( x );
        return siz[ch[root][0]] + 1;
    }

    int kth( int k ){
        int cur = root;
        while( true ){
            if( ch[cur][0] && k <= siz[ch[cur][0]] ) cur = ch[cur][0];
            else if( k > siz[ch[cur][0]] + sz[cur] )
                k -= siz[ch[cur][0]] + sz[cur] , cur = ch[cur][1];
            else
                return cur;
        }
    }

    int pre( int x ){
        find( x );
        if( val[root] < x ) return root;
        int cur = ch[root][0];
        while( ch[cur][1] ) cur = ch[cur][1];
        return cur;
    }

    int suc( int x ){
        find( x );
        if( val[root] > x ) return root;
        int cur = ch[root][1];
        while( ch[cur][0] ) cur = ch[cur][0];
        return cur;
    }

    void remove( int x ){
        int u = pre( x ) , v = suc( x );
        //cout<<u<<' '<<v<<endl;
        splay( u ) , splay( v , u );
        int w = ch[v][0];
        if( sz[w] > 1 ) --sz[w] , splay( w );
        else
            siz[0] = sz[0] = ch[v][0] = 0;
        //pushup(v),pushup(u);
    }

    void maintain( int x ){
        if( !ch[x][0] && !ch[x][1] ) siz[x] = sz[x];
        if( ch[x][0] ) maintain( ch[x][0] );
        if( ch[x][1] ) maintain( ch[x][1] );
        pushup( x );
    }

    void print( int x ){
        cout << x << ' ' << val[x] << ' ' << sz[x] << ' ' << siz[x] << ' ' << ch[x][0] << ' ' << ch[x][1] << ' ' << endl;
        if( ch[x][0] ) print( ch[x][0] );
        if( ch[x][1] ) print( ch[x][1] );
    }
} T;

int main(){
    T.insert( -0x3f3f3f3f );
    T.insert( 0x3f3f3f3f );
    cin >> n;
    for( int i = 1; i <= n; i++ ){
        cin >> opt >> x;
        if( opt == 1 ) T.insert( x );
        else if( opt == 2 )
            T.remove( x );
        else if( opt == 3 )
            cout << T.rank( x ) - 1 << '\n';
        else if( opt == 4 )
            cout << T.val[T.kth( x + 1 )] << '\n';
        else if( opt == 5 )
            cout << T.val[T.pre( x )] << '\n';
        else if( opt == 6 )
            cout << T.val[T.suc( x )] << '\n';
    }
    return 0;
}
posted @ 2019-08-15 00:06  hélium  阅读(317)  评论(0)    收藏  举报