一、前言

Treap平衡树是一种树形数据结构,它是对二叉查找树的一种优化方式,但是学习的过程是真的非常的…痛苦。

二、二叉查找树的概念

首先,我们称具有以下3个性质的树为二叉查找树(BST)
1.左子树上所有结点的值均小于或等于它的根结点的值。
2.右子树上所有结点的值均大于或等于它的根结点的值。
3.左、右子树也分别为二叉排序树。
如图

在这张图中我们可以发现,在二叉查找树中,从根结点开始查找一个点的时间复杂度为logn级别,在我们想对树进行插入查找的时候都非常方便。

但是很遗憾,二叉查找树是有着它的缺陷的,例如:假如我们最开始有一个这样的树

然后我们如果不断插入比6小的数,那么二叉查找树就会变成…

一点都不好看!而且这样的话,我们查找的时间复杂度就又变成了n了,二叉查找树就失去了优势。

三、Treap平衡树的概念

对于上面那张图,我们觉得它一点也不方便,也不好看,我们希望那棵树是这个样子的

即使不能变成上图那种最完美的树,但是我们也还是想要去达到这个效果

然后,我们了解一下树的旋转

下面这张图就是把节点4右转后形成的新图,仍然符合二叉查找树的定义,左转同理

旋转又什么用呢?我们先再看看那张不漂亮的图

如果我们以节点5右转后会发生什么呢?

唉?好像好看多了,那这样的话岂不是我们多旋转几次不就越来越好看了吗?
但是我们需要思考,我们要怎么样才能让它转起来呢?

对此,我们实现的方法是:每一个点,我们用结构体除了封装它的左右儿子以及自己的值以外,我们还封装一个变量val,val的值不是我们决定的,而是在定义点的时候由rand()函数随机赋予一个数。我们规定在我们的树中,上面的节点的val值必须要大于下面的节点的val值,我们在建树,插入数字的时候要不断检查,一旦val值大的节点在val值小的节点的下方,我们就要通过旋转来让val值满足要求。

通过这样的方法,只要val值足够随机,那么我们就能保证我们的树不会出现上文那种特别难看的情况,从而增加我们的查找以及修改效率。

例如,假如现在我们的树是这样的,红字代表val值

我们要插入一个值为4的点,val值被rand()函数随机赋值为30,我们先把它插进去

很明显,点4和5的val值不符合要求,以5右转

点4和6依然不符合,继续以6右转,就会变成这样

唉?好像树的高度是没有变的啊!那我们的辛苦是为了什么?别急,如果我们要再加入一个点3,val为21,图会变成这样

而如果不处理,我们的树会变成这样

从这里我们就可以看出,我们的操作是有意义的,它成功的“美化”了我们的树。
再次重复,只要赋予的val值足够随机,那么一棵树在操作中就会维持一个比较美丽的状态,而rand()函数是可以提供足够随机数的,这就让我们Treap平衡树的实现有了保证。

三、Treap平衡树的代码实现

例题链接:Acwing 普通平衡树

您需要写一种数据结构(可参考题目标题),来维护一些数,其中需要提供以下操作:插入数值 x。
删除数值 x(若有多个相同的数,应只删除一个)。
查询数值 x 的排名(若有多个相同的数,应输出最小的排名)。
查询排名为 x 的数值。
求数值 x 的前驱(前驱定义为小于 x 的最大的数)。
求数值 x 的后继(后继定义为大于 x 的最小的数)。
注意: 数据保证查询的结果一定存在。输入格式
第一行为 n,表示操作的个数。接下来 n 行每行有两个数 opt 和 x,opt 表示操作的序号(1≤opt≤6)。输出格式
对于操作 3,4,5,6 每行输出一个数,表示对应答案。数据范围
1≤n≤100000,所有数均在 −1e7 到 1e7 内。输入样例:
8
1 10
1 20
1 30
3 20
4 2
2 10
5 25
6 -1
输出样例:
2
20
20
20

这题的代码真的真的真的真的很难写!!!!!而且由于代码量的巨大导致调试起来非常非常非常非常的痛苦!!!!这道题是需要足够的耐心去写的。具体思路在代码中会解释。

看懂代码你需要知道以下知识点
1.结构体的简单操作
2.rand函数的使用
3.耐心
4.耐心
5.还**的是耐心

//#pragma GCC optimize(2)
#include<iostream>
#include<iomanip>
#include<cstdio>
#include<string>
#include<algorithm>
#include<cmath>
#include<queue>
#include<vector>
#include<map>
#include<stack>
#include<set>
#include<bitset>
#include<ctime>
#include<cstring>
#include<list>
#define ll long long
#define ull unsigned long long
#define INF 0x3f3f3f3f
#define mem(a,b) memset(a,b,sizeof(a))
using namespace std;
typedef  pair<int, int> PII;
const int N = 1e6 + 7;
int n;struct Node
{int l, r;  //左右节点编号int k;  //键值int val;  //随机分配的堆中的编号,不断旋转保证编号大的点一定在编号小的点的上方int cnt, size;   //cnt代表k有多少个,size代表以u为根的树有多少个点
}tr[N];int root, idx;void pushup(int u)
{tr[u].size = tr[tr[u].l].size + tr[tr[u].r].size + tr[u].cnt;
}int new_node(int k)  //新建点
{tr[++idx].k = k;tr[idx].val = rand();  //rand函数赋予随机值tr[idx].cnt = 1;tr[idx].size = 1;return idx;  //返回节点编号方便递归建树
}void zag(int& u)  //左旋函数,看不懂可以画图理解一下
{int q = tr[u].r;tr[u].r = tr[q].l;tr[q].l = u;u = q;pushup(tr[u].l);pushup(u);
}void zig(int& u)  //右转函数
{int q = tr[u].l;tr[u].l = tr[q].r;tr[q].r = u;u = q;pushup(tr[u].r);pushup(u);
}void build()  //建树,树的初始化
{new_node(-INF), new_node(INF);  //防止越界的哨兵root = 1, tr[1].r = 2;pushup(root);if (tr[1].val < tr[2].val) zag(root);
}void del(int& u, int k)  //树的删除操作
{if (u == 0) return;  //没有这个点就不操作if (tr[u].k == k)  //找到了这个点{if (tr[u].cnt > 1) tr[u].cnt--;  //这个点的数量大于1就直接减去一个就可以了else{if (tr[u].l || tr[u].r)  //不是叶子节点{if (!tr[u].r || tr[tr[u].l].val)  //如果左边有点就把要删除的点通过右转转到右边去,然后通过递归不断旋转直到转到叶子节点后删除{zig(u);del(tr[u].r, k);}else  //否则转到左边去{zag(u);del(tr[u].l, k);}}else   //是叶子节点就可以直接删除了u = 0;}}else if (tr[u].k > k) del(tr[u].l, k);//如果没有找到就判断一下在左右两边的哪一边else del(tr[u].r, k);//找一下pushup(u);//上传更改
}void insert(int& u, int k)  //点的插入操作
{if (u == 0) u = new_node(k);  //走到0说明当前位置没有点,就把点插在这里else{if (tr[u].k == k)   //重复点数量直接加1tr[u].cnt++;else{if (tr[u].k > k)  //比当前点的键值大就插入左边{insert(tr[u].l, k);if (tr[tr[u].l].val > tr[u].val) zig(u);  //平衡旋转}else   //否则插入右边{insert(tr[u].r, k);if (tr[tr[u].r].val > tr[u].val) zag(u);  //平衡旋转} }}pushup(u);//更新节点信息
}int get_rank(int u, int k)  //求k的位置
{if (u == 0) return 0;//是0随便返回就行if (tr[u].k == k) return tr[tr[u].l].size + 1;//相等了那排名应该就是左边的数量加上自己if (tr[u].k > k) return get_rank(tr[u].l, k);//大了找左边return tr[tr[u].l].size + tr[u].cnt + get_rank(tr[u].r, k);//找右边
}int get_key(int u, int rank)  //求该位置的值
{if (u == 0) return INF;if (tr[tr[u].l].size >= rank) return get_key(tr[u].l, rank);//找左边if (tr[tr[u].l].size + tr[u].cnt >= rank) return tr[u].k;//如果满足条件就直接returnreturn get_key(tr[u].r, rank - tr[tr[u].l].size - tr[u].cnt);//不然就找右边
}int get_pr(int u, int k)//前驱,小于k的最大的数
{if (u == 0) return -INF;if (tr[u].k >= k) return get_pr(tr[u].l, k);//找左边return max(get_pr(tr[u].r, k), tr[u].k);//可能是右边可能是这个数,所以用个max
}int get_ne(int u, int k)//后继,大于k的最小的数
{if (u == 0) return INF;//后继的写法和前驱相反,大家可以注意一下if (tr[u].k <= k) return get_ne(tr[u].r, k);return min(get_ne(tr[u].l, k), tr[u].k);
}void solve()
{build();cin >> n;while (n--){int op, x;cin >> op >> x;switch (op){case 1:{insert(root, x);break;}case 2:{del(root, x);break;}case 3:{cout << get_rank(root, x) - 1 << endl;break;}case 4:{cout << get_key(root, x + 1) << endl;break;}case 5:{cout << get_pr(root, x) << endl;break;}case 6:{cout << get_ne(root, x) << endl;break;}}}
}int main()
{//std::ios::sync_with_stdio(false);//cin.tie(0), cout.tie(0);solve();return 0;
}

作者:Avalon Demerzel,喜欢我的博客就点个赞吧,更多图论与数据结构知识点请见作者专栏《图论与数据结构》

【数据结构】图文详解Treap平衡树相关推荐

  1. 目录树 删除 数据结构_数据结构:B树和B+树的插入、删除图文详解

    B树 1.1B树的定义 B树也称B-树,它是一颗多路平衡查找树.我们描述一颗B树时需要指定它的阶数,阶数表示了一个结点最多有多少个孩子结点,一般用字母m表示阶数.当m取2时,就是我们常见的二叉搜索树. ...

  2. 数据结构:B树和B+树的插入、删除图文详解

    目录 B树 1.1B树的定义 1.2B树的插入操作 1.3B树的删除操作 B+树 2.1 B+树的定义 2.2 B+树的插入操作 2.3 B+树的删除操作 B树 1.1B树的定义 B树也称B-树,它是 ...

  3. 【图文详解】一文全面彻底搞懂HBase、LevelDB、RocksDB等NoSQL背后的存储原理:LSM-tree日志结构合并树...

    LSM 树广泛用于数据存储,例如 RocksDB.Apache AsterixDB.Bigtable.HBase.LevelDB.Apache Accumulo.SQLite4.Tarantool.W ...

  4. 面渣逆袭:Redis连环五十二问,图文详解,这下面试稳了

    大家好,我是老三,面渣逆袭系列继续,这节我们来搞定Redis--不会有人假期玩去了吧?不会吧? 基础 1.说说什么是Redis? Redis是一种基于键值对(key-value)的NoSQL数据库. ...

  5. php 8 jit,PHP JIT 是什么?PHP8 新特征之 JIT 图文详解_后端开发

    PHP8 alpha1已经在昨天宣布,置信关于JIT是人人最体贴的,PHP8 JIT是什么,又怎样用,又有什么要注意的,以及机能提拔究竟咋样? 视频教程引荐:<PHP编程从入门到通晓> 起 ...

  6. 红黑树的实现(图文详解)

    红黑树的实现 红黑树的定义 红黑树本质上也是一棵二叉搜索树,满足二叉搜索树的基本性质,但二叉搜索树容易形成单边树,导致搜索效率下降,需要进行平衡限制 例如AVL树就通过引平衡因子来实现平衡树AVL树的 ...

  7. spssχ2检验_一致性检验和配对卡方检验的SPSS实例操作图文详解

    一致性检验和配对卡方检验的SPSS实例操作图文详解,配对计数资料的卡方检验. 一.问题与数据 有两种方法可用于诊断某种癌症,A方法简单易行,成本低,患者更容易接受,B方法结果可靠,但操作繁琐,患者配合 ...

  8. 图文详解两种算法:深度优先遍历(DFS)和广度优先遍历(BFS)

    图文详解两种算法:深度优先遍历(DFS)和广度优先遍历(BFS) 阅读本文前,请确保你已经掌握了递归.栈和队列的基本知识,如想掌握搜索的代码实现,请确保你能够用代码实现栈和队列的基本操作. 深度优先遍 ...

  9. 大数据学习系列之七 ----- Hadoop+Spark+Zookeeper+HBase+Hive集群搭建 图文详解

    引言 在之前的大数据学习系列中,搭建了Hadoop+Spark+HBase+Hive 环境以及一些测试.其实要说的话,我开始学习大数据的时候,搭建的就是集群,并不是单机模式和伪分布式.至于为什么先写单 ...

  10. 单细胞分析的 Python 包 Scanpy(图文详解)

    文章目录 一.安装 二.使用 1.准备工作 2.预处理 过滤低质量细胞样本 3.检测特异性基因 4.主成分分析(Principal component analysis) 5.领域图,聚类图(Neig ...

最新文章

  1. VC6迁移到VS2008几个问题——良好的代码,从我做起,从现在开始。
  2. 图神经网络新课上架:​宾大2020秋季在线课程开课,视频上线B站
  3. WinForm - 两个窗体之间的方法调用
  4. 前端学习(1685):前端系列实战课程之设置难度
  5. web.xml中webAppRootKey
  6. Python使用Condition对象实现多线程同步
  7. FreeMarker基本操作(二)
  8. free store VS heap(自由存储区VS堆)
  9. 8 个弱点助我成为更好的开发人员!
  10. linux buffer 刷到磁盘,Linux下的磁盘缓存 linux page buffer cache深入理解
  11. 你知道abrt-hook-ccpp吗?
  12. 微信小程序开发深入解读
  13. 阿里云域名解析和记录值
  14. Mac使用Python接入东方财富量化接口Choice,调试与获取数据
  15. UE4-地形植被自动分布方法
  16. QT中读取STL并显示
  17. 计算机专业考研复试上机算法学习
  18. 王国维《人间词话》:“古今之成大事业、大学问者,必经过三种之境界”
  19. 雾里看花般的迷茫--货运APP
  20. 原神通数据库、oracle转postgresql进行适配

热门文章

  1. Centos7.4源码搭建zabbix3.4.11企业级监控
  2. 在阿里云创建子域名,配置nginx,使用pm2部署node项目到ubuntu服务器
  3. Lync 2010迁移Lync 2013 PART5:支持旧版Lync
  4. stm32学习笔记——外部中断的使用
  5. SpringMVC一路总结(一)
  6. XINS 3.1.0 Alpha2 发布,远程 API 调用规范
  7. exchange2010 DAG备份
  8. Python零基础学习代码实践 —— 99乘法表
  9. Java图书管理系统练习程序(六)
  10. 35岁,程序员过不去的坎?