平衡树初步——Treap

本文介绍了平衡树(BST)的概念,重点讲解了二叉查找树的性质以及主要操作,如插入、删除、查找前驱和后继。作者分享了一段包含近300行的BST实现代码,并提到为了简化理解和记忆,作者会提供一个更为简洁的实现版本。
摘要由CSDN通过智能技术生成

平衡树算是比较进阶一点的数据结构,这里只是对学习简单平衡树的记录

BST——二叉查找树

书上说,二叉树最重要的两种数据结构的性质有两种:堆性质 和 BST性质

二叉查找树满足以下性质:

  • 每个节点都有一个关键值
  • 每个节点的关键值大于其左子节点的关键值
  • 每个节点的关键值小于其右子节点的关键值

由于每个节点是顺序插入的,所以我们容易知道,一个节点的左子树的所有节点一定小于该节点,右子树的所有节点一定大于该节点

后继

大于某个节点的最小节点

前驱

小于某个节点的最大节点

BST的主要操作

  • 检索
  • 插入
  • 求前驱后继等

在学习平衡树初期,对于普通平衡树我打了一个三百多行的BST实现代码,实现的细节和原理相当复杂,不便于修改和模板式记忆,所以我在写这篇博客的同时,将根据资料书写出更为简单的BST实现

代码实现

#include <cstdio>
#include <iostream>
#include <cmath>
#include <ctime>

#define MAX_ 0x7fffffff

using namespace std;

struct BST
{
    int l;
    int r;
    int val;
    int size;
    int num;
    int dat;
} a[100000];

int tot;
int root;

int NEW(int val)
{
    a[++tot].val = val;
    a[tot].num = 1;
    a[tot].size = 1;
    a[tot].dat = rand();
    return tot; // return the id of a new node
}
void update(int p)
{
    a[p].size = a[a[p].l].size + a[a[p].r].size + a[p].num;
}
void Build()
{
    NEW(-MAX_);
    NEW(MAX_); // building with an infinity node
               // this is a step that needed for the treap
               // to avoid rotate the infinity node to go down
    a[1].r = 2;
    root = 1;
    update(root);
}

int getVal_byRank(int p, int rank)
{
    if (p == 0)
    {
        return -1; // no such a rank
    }
    if (rank <= a[a[p].l].size)
    {
        return getVal_byRank(a[p].l, rank);
    }
    else if (rank > a[p].size - a[a[p].r].size)
    {
        return getVal_byRank(a[p].r, rank - a[a[p].l].size - a[p].num);
    }
    else
    {
        return a[p].val;
    }
}

int getRank_byVal(int p, int val)
{
    if (p == 0)
    {
        return 0;
    }
    if (val > a[p].val)
    {
        return getRank_byVal(a[p].r, val) + a[a[p].l].size + a[p].num;
    }
    else if (val < a[p].val)
    {
        return getRank_byVal(a[p].l, val);
    }
    else
    {
        return a[a[p].l].size + 1;
    }
}

void Rrotate(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 Lrotate(int &p)
{
    int q = a[p].r;
    a[p].r = a[a[p].r].l;
    a[q].l = p;
    p = q;
    update(a[p].l);
    update(p);
}

void insert(int &p, int val) // p is the reference of the father's pointer towards this node
{
    // cout << p << ' ' << a[p].l << ' ' << a[p].val << ' ' << val << endl;
    //   as well it's the id of this node
    if (p == 0)
    {
        p = NEW(val); // there is no such a node so create one and set the father's pointer
        return;
    }
    if (val == a[p].val)
    {
        a[p].num++;
        update(p);

        return;
    }
    else if (val < a[p].val)
    {
        insert(a[p].l, val); // this way,we can set the l while passing down the id of the current node' child
        if (a[p].dat < a[a[p].l].dat)
        {
            // cout << "Y";
            Rrotate(p);
        }
    }
    else if (val > a[p].val)
    {
        insert(a[p].r, val);
        if (a[p].dat < a[a[p].r].dat)
        {
            // cout << "N";
            Lrotate(p);
        }
    }
    update(p);
}

int getPre(int val)
{
    int ans = -1e9;
    int p = root;
    while (true)
    {if (p == 0)
        {
            return ans;
        }
        if (a[p].val < val)
        {
            ans = max(ans, a[p].val);
        }

        
        if (a[p].val == val)
        {
            if (a[p].l == 0)
            {
                return ans;
            }
            p = a[p].l;
            while (a[p].r != 0)
            {
                p = a[p].r;
            }
            return a[p].val;
        }
        p = a[p].val > val ? a[p].l : a[p].r;
    }
}

int getNext(int val)
{
    int ans = 1e9;
    int p = root;
    while (true)
    {
        if (p == 0)
        {
            return ans;
        }//this place must be placed before the "min"
         //otherwise the mininus is must be '0'
        if (a[p].val > val)
        {
            ans = min(ans, a[p].val);
        }

        if (a[p].val == val)
        {
            if (a[p].r == 0)
            {
                return ans;
            }
            p = a[p].r;
            while (a[p].l != 0)
            {
                p = a[p].l;
            }
            return a[p].val;
        }
        p = a[p].val > val ? a[p].l : a[p].r;
    }
}

void remove(int &p, int val)
{
    if (p == 0)
    {
        return;
    }
    if (val == a[p].val)
    {
        if (a[p].num > 1)
        {
            a[p].num--;
            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)
            {
                Rrotate(p);
                remove(a[p].r, val);
            }
            else
            {
                Lrotate(p);
                remove(a[p].l, val);
            }
            update(p);
        }
        else
        {
            p = 0;
        }
        return;
    }
    a[p].val > val ? remove(a[p].l, val) : remove(a[p].r, val);
    update(p);
}

void tra(int p)
{
    if (p == 0)
        return;
    cout << a[p].val << ' ' << a[p].size << endl;
    tra(a[p].l);
    tra(a[p].r);
}

int main()
{
    Build();
    srand(time(0));

    int n;
    cin >> n;
    for (int i = 0; i < n; i++)
    {
        int op, x;
        cin >> op >> x;
        // cout << op << ' ';
        switch (op)
        {
        case 1:
            insert(root, x);
            break;
        case 2:
            remove(root, x);
            break;
        case 3:
            cout << getRank_byVal(root, x) - 1 << endl;
            break;
        case 4:
            cout << getVal_byRank(root, x + 1) << endl;
            break;
        case 5:
            cout << getPre(x) << endl;
            break;
        case 6:
            cout << getNext(x) << endl;
            break;
        case 7:
            tra(root);
            cout << tot << endl;
            break;
        default:
            break;
        }
    }
}
  • 在以上代码中,所有的传递的p为引用时,说明传递的参数必须是对应父节点的左右节点,这样在修改p时可以一同修改父节点的子节点信息了
  • 对于 insert()remove()之类的操作需要更新祖先节点的size大小,所以需要采用递归的方式修改,而对于查询之类的操作不需要更新祖先节点数值,所以采用循环的方式更加快速
  • 这是一道平衡树的模板题,但是仍然免不了将近三百行的代码
  • 12
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值