平衡树相关知识:

平衡树相关知识:

小知识: C + + 的 S T L 中的 s e t 容器内部封装为平衡树 小知识:C++的STL中的set容器内部封装为平衡树 小知识:C++STL中的set容器内部封装为平衡树

1. 二叉搜索树(BST)

特点:

  • 左子树中的元素一定小于根节点,右子树的元素一定大于根节点,若有相同数则用 c n t + + 记忆即可 左子树中的元素一定小于根节点,右子树的元素一定大于根节点,若有相同数则用 cnt ++ 记忆即可 左子树中的元素一定小于根节点,右子树的元素一定大于根节点,若有相同数则用cnt++记忆即可
  • 其中序遍历一定为从小到大排序 其中序遍历一定为从小到大排序 其中序遍历一定为从小到大排序

本质:

动态维护一个有序序列 动态维护一个有序序列 动态维护一个有序序列

时间复杂度:

  • 和树的高度成正比,当数据越有序,时间复杂度越差,一般 O ( l o g n ) 最坏 O ( n ) , 和树的高度成正比,当数据越有序,时间复杂度越差,一般O(logn)最坏O(n), 和树的高度成正比,当数据越有序,时间复杂度越差,一般O(logn)最坏O(n),
    当数据完全升序或降序时,退化成一条链,时间复杂度 O ( n ) 当数据完全升序或降序时,退化成一条链,时间复杂度O(n) 当数据完全升序或降序时,退化成一条链,时间复杂度O(n)

用法:

  1. 插入 插入 插入
  2. 删除 删除 删除
  3. 找前驱 / 后继(中序遍历的前一个位置,后一个位置) 找前驱/后继(中序遍历的前一个位置,后一个位置) 找前驱/后继(中序遍历的前一个位置,后一个位置)
  4. 找最大 / 最小 找最大/最小 找最大/最小
  5. 某个值 ( k e y ) 的排名(排名:该子树中小于该值 k e y 的节点的个数 + 1 ) 某个值(key)的排名(排名:该子树中小于 该值key 的节点的个数 + 1) 某个值(key)的排名(排名:该子树中小于该值key的节点的个数+1)
  6. 排名是 r a n k 的数是哪一个 排名是rank的数是哪一个 排名是rank的数是哪一个
  7. 比某个数 ( k e y ) 小的最大值 比某个数(key)小的最大值 比某个数(key)小的最大值
  8. 比某个数 ( k e y ) 大的最小值 比某个数(key)大的最小值 比某个数(key)大的最小值

注: S T L 中的 s e t 也支持前 4 个操作 注:STL中的set也支持前4个操作 注:STL中的set也支持前4个操作

2. treap

简介:

  • T r e a p (树堆)是一种弱平衡的二叉搜索树。它同时符合二叉搜索树和堆的性质, Treap(树堆)是一种 弱平衡 的 二叉搜索树。它同时符合二叉搜索树和堆的性质, Treap(树堆)是一种弱平衡的二叉搜索树。它同时符合二叉搜索树和堆的性质,
    故,其名字也因此为 t r e e (树)和 h e a p (堆)的组合。 故,其名字也因此为 tree(树)和 heap(堆)的组合。 故,其名字也因此为tree(树)和heap(堆)的组合。

为什么引入treap:

  • t r e a p 完全包含 B S T , 且比 B S T 更优 treap完全包含BST, 且比BST更优 treap完全包含BST,且比BST更优
  • 一组数据经过堆的维护可以形成确定唯一的 t r e a p 结构 一组数据经过堆的维护可以形成确定唯一的treap结构 一组数据经过堆的维护可以形成确定唯一的treap结构

核心思想:

让 B S T 变得随机 , 从而避免使时间复杂度维持为 O ( l o g n ) 让BST变得随机, 从而避免使时间复杂度维持为O(logn) BST变得随机,从而避免使时间复杂度维持为O(logn)

核心操作:

旋转操作 旋转操作 旋转操作

旋转操作的含义: 旋转操作的含义: 旋转操作的含义:

  • 在不影响搜索树性质的前提下,把和旋转方向相反的子树变成根节点 在不影响搜索树性质的前提下,把和旋转方向相反的子树变成根节点 在不影响搜索树性质的前提下,把和旋转方向相反的子树变成根节点
    (如左旋,就是把右子树变成根节点) (如左旋,就是把右子树变成根节点) (如左旋,就是把右子树变成根节点)
  • 不影响性质,并且在旋转过后,跟旋转方向相同的子节点变成了原来的根节点 不影响性质,并且在旋转过后,跟旋转方向相同的子节点变成了原来的根节点 不影响性质,并且在旋转过后,跟旋转方向相同的子节点变成了原来的根节点
    (如左旋,旋转完之后的左子节点是旋转前的根节点) (如左旋,旋转完之后的左子节点是旋转前的根节点) (如左旋,旋转完之后的左子节点是旋转前的根节点)
  • 例子: 例子: 例子:平衡树_核心操作_左旋右旋.png

下面各操作看代码实现即可:


例题:

平衡树基本操作

代码及注释如下:

#include <iostream>
#include <algorithm>
#include <cstring>

using namespace std;

const int N = 100010, INF = 1e8;

int n;

struct Node
{
    int l, r;
    int key, val; // 实际的值 堆(大根堆)中的编号(便于打乱)
    int cnt, size; // 此数的数量 该子树中的数的数量
}tr[N];

int root, idx;

// 子节点更新父节点信息
void pushup(int p)
{
    tr[p].size = tr[tr[p].l].size + tr[tr[p].r].size + tr[p].cnt;
}

// 创建新节点
int get_node(int key)
{
    idx ++;
    tr[idx].key = key;
    tr[idx].val = rand();
    tr[idx].cnt = tr[idx].size = 1;
    return idx;
}

void zig(int &p)    // 右旋(将根节点变为左子树)
{
    int q = tr[p].l;
    tr[p].l = tr[q].r, tr[q].r = p, p = q;
    pushup(tr[p].r), pushup(p);
}

void zag(int &p)    // 左旋(将根节点变为右子树)
{
    int q = tr[p].r;
    tr[p].r = tr[q].l, tr[q].l = p, p = q;
    pushup(tr[p].l), pushup(p);
}

// 初始化根节点,同时初始化两个哨兵 -INF 和 INF
void build()
{
    get_node(-INF), get_node(INF);
    root = 1, tr[1].r = 2;
    pushup(root);
    
    if(tr[1].val < tr[2].val) zag(root);
}

void insert(int &p, int key)
{
    if(!p) p = get_node(key); // 树种节点为0,创建新节点即可
    else if(tr[p].key == key) tr[p].cnt ++; // 子树的根节点恰好为key,令 key 的 cnt ++ 即可
    else if(tr[p].key > key) 
    {
        insert(tr[p].l, key); // 递归插入key
        
        // 若新插入的左子树的key > 根节点的key则,右旋,令左子树为根
        if(tr[tr[p].l].val > tr[p].val) zig(p); 
    }
    else 
    {
        insert(tr[p].r, key); // 递归插入key
        
        // 若新插入的右子树的key > 根节点的key则,左旋,令右子树为根
        if(tr[tr[p].r].val > tr[p].val) zag(p);
    }
    pushup(p);
}

// 一直左旋或右旋直到叶子节点,然后删去即可
void remove0(int &p, int key)
{
    if(!p) return ;
    if(tr[p].key == key)
    {
        if(tr[p].cnt > 1) tr[p].cnt --;
        else if(tr[p].l || tr[p].r)
        {
            // 若右节点不存在,或左节点在堆的val > 右节点的val
            if(!tr[p].r || tr[tr[p].l].val > tr[tr[p].r].val)
            {
                // 先右旋,使左节点为根节点保证大根堆
                zig(p); 
                // 此时根节点旋转至右节点,原函数等效于删除右节点
                remove0(tr[p].r, key); 
            }
            else // 若左节点不存在,或右节点在堆的val > 右节点的val
            {
                // 先左旋,使右节点为根节点保证大根堆
                zag(p);
                // 此时根节点旋转至左节点,原函数等效于删除左节点
                remove0(tr[p].l, key);
            }
        }
        else p = 0; // 找到叶子节点p,令其 == 0 即为删除
    }
    else if(tr[p].key > key) remove0(tr[p].l, key); // 没找到递归左子树
    else remove0(tr[p].r, key); // 没找到递归右子树
    
    pushup(p);
}

int get_rank_by_key(int p, int key) // 根据值求排名
{
    if(!p) return 0; // 本题不存在此情况
    if(tr[p].key == key) return tr[tr[p].l].size + 1;
    else if(tr[p].key > key) return get_rank_by_key(tr[p].l, key);
    else return tr[tr[p].l].size + tr[p].cnt + get_rank_by_key(tr[p].r, key);
}

int get_key_by_rank(int p, int rank) // 根据排名求值
{
    if(!p) return INF;
    if(tr[tr[p].l].size >= rank) return get_key_by_rank(tr[p].l, rank);
    else if(tr[tr[p].l].size + tr[p].cnt >= rank) return tr[p].key;
    else return get_key_by_rank(tr[p].r, rank - tr[tr[p].l].size - tr[p].cnt);
}

int get_prev(int p, int key) // 找到严格小于key的数
{
    if(!p) return -INF;
    if(tr[p].key >= key) return get_prev(tr[p].l, key);
    return max(tr[p].key, get_prev(tr[p].r, key));
}

int get_next(int p, int key) // 找到严格大于key的数
{
    if(!p) return INF;
    if(tr[p].key <= key) return get_next(tr[p].r, key);
    return min(tr[p].key, get_next(tr[p].l, key));
}

int main()
{
    build();
    
    scanf("%d", &n);
    
    int opt, x;
    for (int i = 1; i <= n; i ++ )
    {
        scanf("%d%d", &opt, &x);
        if (opt == 1) insert(root, x);
        else if (opt == 2) remove0(root, x);
        else if (opt == 3) printf("%d\n", get_rank_by_key(root, x) - 1); // 初始化哨兵-INF,要 - 1
        else if (opt == 4) printf("%d\n", get_key_by_rank(root, x + 1)); // 有哨兵-INF,要 + 1
        else if (opt == 5) printf("%d\n", get_prev(root, x));
        else printf("%d\n", get_next(root, x));
    }
    
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

AC自动寄

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值