算法竞赛进阶指南---0x46(二叉查找树与平衡树初步) 普通平衡树

题面

在这里插入图片描述

输入样例

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

输出样例

106465
84185
492737

题解(模板)

  1. 这里的平衡树我们说的是 treap ,treap是由BST(二叉搜索树)+ heap(堆)组成的数据结构 ,它有的性质的当然是BST和heap(这里用大根堆)所有的性质,比如:当前节点的左子树中的任何一个点的权值(key) < 当前节点的权值 ,如果有相同的权值,可以在节点 上加上 cnt 属性 (表示出现的次数) ,还有就是父节点的优先级(val)一定大于它儿子的优先级,这是大根堆的性质
  1. 要想建立一颗平衡树,首先定义节点及其属性,一般节点中存放 题中所需信息,具体根据题中要求,这里得val其实是为了优化树的高度,如图我们的二叉树最坏的情况是可以退化成一个链表,那么它的复杂度就变成了O(n) ,但是我们如果加入了val优先级(大根堆),那么就可以降低树的高度,而且是不影响二叉树的性质的

在这里插入图片描述

//定义节点的属性
struct Node {
    
    
    int l, r;  //此节点的左右儿子节点
    int key;   //二叉树的权值
    int val;  //堆的优先级
    int cnt; //当前值的数量
    int size;//当前节点子树中节点的数量(包括自己)
} tr[N];

//新建一个节点
int get_node(int key) {
    
    
    tr[++idx].key = key;   //为其赋值
    tr[idx].val = rand();  //随机生成一个优先级
    tr[idx].cnt = 1;       //值得数量为1(自己)
    tr[idx].size = 1;     //开始肯定是叶子节点,没有左右儿子,只有自己,为1
    return idx;
}

//由儿子的信息来维护父节点的信息
void pushup(int p) {
    
    
    tr[p].size = tr[tr[p].l].size + tr[tr[p].r].size + tr[p].cnt;
}
  1. (主要操作) 左/右旋 :在treap 的左右旋还是比较简单的:首先先介绍左右旋的性质 ,旋转后中序遍历顺序是不变的,还有就是左右旋其实是一个对称的过程我们看图以右旋为例讲解,右旋就是将y节点变为根节点,x 节点就变成了 y 的 右儿子 ,(将左儿子变为根节点)中序遍历不变,y的左子树在旋转完后任然是y的左子树,y的右子树 z 在旋转前是大于y节点小于x节点的,所以在旋转完应该变为 x 的左子树, x 的右子树不变

在这里插入图片描述

右旋代码: 因为我们在旋转之后改变了节点中的值,所以应该传入指针p, q 节点就是p的左儿子,看图旋转之后哪些子树的位置发生了改变就写哪部分: p的左儿子就等于q的右儿子,q的右儿子就是p ,然后指针 p 指向 q ,因为我们旋转后节点位置发生了变化,所以也要相应的更新节点中的属性 ,因为左右旋是对称的,所以代码只是左右不一样,自行查看即可

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);  //现在是旋转完后,p的右儿子是x节点
    pushup(p);    //p是y节点
}

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);
}
  1. 建立treap ,为了防止越界等一些情况,我们先设置两个哨兵节点,确保以后的插入的节点都在其中,我们初始化树先让根节点等于这个1号点,然后让2号点作为1号点的右儿子,,因为我们的val是随机的,所以如果2号点(儿子)的val大于1号点(父亲),我们还要左旋一下,把val大的作为父节点,这里要注意一下,因为我们在建treap的过程中增加了哨兵节点,所以在算排名的时候要将哨兵节点排除
//建平衡树(treap)
void build() {
    
    
    get_node(-INF);
    get_node(INF);
    root = 1;  //让根节点等于1号点(-INF)
    tr[1].r = 2; //1号点的右儿子等于2号点
    pushup(root);
    if (tr[1].val < tr[2].val) zag(root);
}
  1. 插入操作:从根节点开始,如果这个节点不存在,我们就新建一个节点,如果存在,看插入的值是否等于当前节点的值,如果等于,只需要给当前节点的cnt++,就表示这个值的数量+1,如果是插入的值小于当前节点的值,那么就要继续去当前节点的左子树中找,当然在插入完之后,还要维护树的高度,就是优先级,因为是在左子树中插入,那么就看左子树的值是否大于根节点的值,如果大于,就应该让左儿子成为父节点,就是对应的右旋
//插入操作
void insert(int &p, int key) {
    
    
    if (!p) p = get_node(key);
    else if (tr[p].key == key) tr[p].cnt++;
    else if (tr[p].key > key) {
    
    
        insert(tr[p].l, key);
        if (tr[tr[p].l].val > tr[p].val) zig(p);
    } else {
    
    
        insert(tr[p].r, key);
        if (tr[tr[p].r].val > tr[p].val) zag(p);
    }
    pushup(p);
}
  1. 删除操作:因为删除非叶子节点的操作要考虑它的子节点,所以我们在删除一个节点时,如果它不是叶子节点,我们就要通过左/右旋将这个节点变成叶子节点,然后再删除

在这里插入图片描述

//删除操作
void remove(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) {
    
      //如果不是叶子节点
            if (!tr[p].r || tr[tr[p].l].val > tr[tr[p].r].val) {
    
    
                zig(p);
                //右旋之后p的值会变,变为原来的右儿子
                remove(tr[p].r, key);
            } else {
    
    
                zag(p);
                remove(tr[p].l, key);
            }
        } else {
    
      //如果是叶子节点
            p = 0;
        }
    } else if (tr[p].key > key) {
    
    
        remove(tr[p].l, key);
    } else {
    
    
        remove(tr[p].r, key);
    }
    pushup(p);
}
  1. 查询数值x的排名(若有多个相同的数,应输出最小的排名) 本题中数据保证结果一定存在,所以!p可以不写,从根节点开始往下遍历,如果当前节点的值>x,就应该去左子树中找(因为二叉树中当前的节点的有子树肯定是比当前节点的值大,那么就更大于x),如果是当前节点的值 = x ,那么就找了,就返回,当前节点的左子树中所有节点的数量+1(因为左子树中所有节点的值肯定是小于x的,然后题中要求输出最小,那么+1即可),如果当前节点的值是小于x的,说明就要去右子树中查找,那么最终返回的结果应该是左子树中的所有节点数量+当前节点中值的数量+右子树中找到的数量三部分
//查询数值x的排名(若有多个相同的数,应输出最小的排名)。
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;
    if (key < tr[p].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);
    }
}
  1. 查询排名为x的数值 :从根节点开始向下找,如果左子树中的数量>=rank , 说明这个x就在左子树中,继续递归即可,如果是左子树中数量不够,但是加上当前节点的cnt值就够了,那么这个x就是当前节点的值,如果加上还不够,说明在还要在右子树中找,那么此时数量应该减去左子树中的数量和当前节点值的数量
//查询排名为x的数值
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);
}
  1. 找前驱(严格小于key的最大数)/后继(严格大于key的最小数):找前驱,从根节点开始向下,如果说当前节点的值已经大于x,说明要找的值在左子树中(只有左子树中才是小于当前节点值的数),==如果当前节点的值小于x,我们就要从右子树和当前节点中找一个最接近当前值且比它小的数 ==,,后继同理
//找前驱(严格小于key的最大数)
int get_prev(int p, int key) {
    
    
    if (!p) return -INF;
    if (tr[p].key >= key) return get_prev(tr[p].l, key);
    else return max(tr[p].key, get_prev(tr[p].r, key));
}


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

代码

#include<iostream>
#include<cstdio>
#include<string>
#include<cstring>
#include<algorithm>

using namespace std;
const int N = 1e5 + 10, INF = 1e8;

int n;
int root, idx;

struct Node {
    
    
    int l, r;
    int key, val;
    int cnt;
    int size;
} tr[N];

//由儿子的信息来维护父节点的信息
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) {
    
    
    tr[++idx].key = key;
    tr[idx].val = rand();
    tr[idx].cnt = 1;
    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);
}


//建平衡树
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);
    else if (tr[p].key == key) tr[p].cnt++;
    else if (tr[p].key > key) {
    
    
        insert(tr[p].l, key);
        if (tr[tr[p].l].val > tr[p].val) zig(p);
    } else {
    
    
        insert(tr[p].r, key);
        if (tr[tr[p].r].val > tr[p].val) zag(p);
    }
    pushup(p);
}

//删除操作
void remove(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) {
    
      //如果不是叶子节点
            if (!tr[p].r || tr[tr[p].l].val > tr[tr[p].r].val) {
    
    
                zig(p);
                //右旋之后p的值会变,变为原来的右儿子
                remove(tr[p].r, key);
            } else {
    
    
                zag(p);
                remove(tr[p].l, key);
            }
        } else {
    
      //如果是叶子节点
            p = 0;
        }
    } else if (tr[p].key > key) {
    
    
        remove(tr[p].l, key);
    } else {
    
    
        remove(tr[p].r, key);
    }
    pushup(p);
}

//查询数值x的排名(若有多个相同的数,应输出最小的排名)。
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;
    if (key < tr[p].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);
    }
}

//查询排名为x的数值
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);
}

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

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

int main() {
    
    

    build();
    cin >> n;
    while (n--) {
    
    
        int opt, x;
        cin >> opt >> x;
        if (opt == 1) insert(root, x);  //插入操作
        else if (opt == 2) remove(root, x);  //删除操作
        else if (opt == 3) cout << get_rank_by_key(root, x) - 1 << endl;  //查询数值x的排名(若有多个相同的数,应输出最小的排名)。
        else if (opt == 4) cout << get_key_by_rank(root, x + 1) << endl;  //查询排名为x的数值
        else if (opt == 5) cout << get_prev(root, x) << endl;  //找前驱
        else cout << get_next(root, x) << endl;  //找后继
    }

    return 0;
}

猜你喜欢

转载自blog.csdn.net/qq_44791484/article/details/113811726