二叉平衡搜索树的一种实现形式,在键值满足二叉搜索树的前提下,增加了priority满足堆序的条件,即为了实现二叉搜索树的平衡化,才增加了堆序的性质,可以证明,如果priority是随机的,那么treap的期望深度是O(logN)的,也就是大部分操作可在O(logN)的时间内完成。
背景:
可以证明,如果一个二叉排序树节点插入的顺序是随机的,这样我们得到的二叉排序树大多数情况下是平衡的,即使存在一些极端情况,但是这种情况发生的概率很小,所以我们可以这样建立一颗二叉排序树,而不必要像AVL那样旋转,可以证明随机顺序建立的二叉排序树在期望高度是O(logn),但是某些时候我们并不能得知所有的待插入节点,打乱以后再插入。所以我们需要一种规则来实现这种想法,也就是说节点是顺序输入的,但我们可以通过一些规则实现“随机化”,所以提出了Treap。
定义:
Treap=Tree+Heap
Treap是一棵二叉排序树,它的左子树和右子树分别是一个Treap,和一般的二叉排序树不同的是,Treap纪录一个额外的数据,就是优先级。Treap在以关键码构成二叉排序树的同时,还满足堆的性质(在这里我们假设节点的优先级大于该节点的孩子的优先级)。但是这里要注意的是Treap和二叉堆有一点不同,就是二叉堆必须是完全二叉树,而Treap可以并不一定是。
操作:
旋转:
Treap维护堆性质的方法用到了旋转,Treap只需要两种旋转,这样编程复杂度比Splay和AVL树等就要小一些,这正是Treap的特色之一。
插入
给节点随机分配一个优先级,先和二叉排序树的插入一样,先把要插入的点插入到一个叶子上,然后跟维护堆一样,如果当前节点的优先级比根大就旋转,如果当前节点是根的左儿子就右旋如果当前节点是根的右儿子就左旋。我们如果把插入写成递归形式的话,只需要在递归调用完成后判断是否满足堆性质,如果不满足就旋转,实现非常容易。由于是旋转的二叉排序树,最多进行h次(h是树的高度),插入的复杂度是log( n )的,在期望情况下,所以它的期望复杂度是 O( log( N ) )。
删除
有了旋转的操作之后,Treap的删除比二叉排序树还要简单。因为Treap满足堆性质,所以我们只需要把要删除的节点旋转到叶节点上,然后直接删除就可以了。具体的方法就是每次找到优先级小的儿子,向其方向旋转,直到那个节点被旋转到叶节点,然后直接删除。删除最多进行log( n )次旋转,期望复杂度是log( n )。
第二种删除方法:为保证效率,可以用普通二叉查找树的删除方法,找到节点的中序前缀,然后替换,删除,并使用非递归。虽然时间复杂度仍为log级别,但常数因子小了很多。
查找
和一般的二叉排序树一样,但是由于Treap的随机化结构,可以证明Treap中查找的期望复杂度是log( n )。
分离
要把一个Treap按大小分成两个Treap,只要在需要分开的位置加一个虚拟节点,然后旋至根节点删除,左右两个子树就是得出的两个Treap了。根据二叉排序树的性质,这时左子树的所有节点都小于右子树的节点。
时间相当于一次插入操作的复杂度,也就是 log( n )
合并
合并是指把两个Treap合并成一个Treap,其中第一个Treap的所有节点都必须小于或等于第二个Treap中的所有节点,也就是分离的结果所要满足的条件。合并的过程和分离相反,只要加一个虚拟的根,把两棵树分别作为左右子树,然后把根删除就可以了。
时间复杂度和删除一样,也是期望
Treap也有找前驱和后继操作,暂时略
写了个基于类封装的数组实现的:
#include <cstdio>
#include <cstring>
#include <iostream>
#include <fstream>
#include <sstream>
#include <queue>
using namespace std;
#define MAX 0X3f3f3f3f
#define MAXN 100005
class Treap {
private:
int root, treapCnt;//treapCnt为至今插入过的节点数
int key[MAXN], priority[MAXN], cnt[MAXN], size[MAXN];//从1开始存储数据
int child[MAXN][2];//0 左儿子 1 右儿子
public:
Treap():root(0),treapCnt(0) {
priority[0] = MAX;
//cnt[0] = 0;
size[0] = 0;
}
void update(int x) {
size[x] = size[child[x][0]] + size[child[x][1]] + cnt[x];
}
void rotate(int &x,int index) {//旋转 0为左旋 1为右旋
int y = child[x][index];
child[x][index] = child[y][1 - index];
child[y][1 - index] = x;
update(x);
update(y);
x = y;
}
void _insert(int &x,int value) {//按照二叉搜索树的规则进行插入操作,在插入过程维护堆序,从而能达到平衡树要求
if (x == 0) {
x = ++treapCnt;
key[treapCnt] = value;
cnt[x] = 1;
priority[x] = rand();
child[x][0] = child[x][1] = 0;
}
else {
if (key[x] == value) {//重复元素
cnt[x]++;
}
else {
int index = value > key[x];
_insert(child[x][index], value);
if (priority[child[x][index]] < priority[x]) {
rotate(x,index);
}
}
}
update(x);
}
void _delete(int &x, int value) {//不断的将要删除的节点旋转至叶子节点,再删除,旋转过程也维护堆序,从而不因为删除破坏平衡
if (x == 0) {//未找到
return;
}
else if (key[x] == value) {//找到了
if (cnt[x] > 1) {//重复值,删掉一个即可
cnt[x]--;
}
else {//删掉唯一的值
if (!child[x][0] && !child[x][1]) {//叶子节点
x = 0;
return;
}
int index = priority[child[x][0]] > priority[child[x][1]];
rotate(x, index);
_delete(child[x][1 - index], value);
}
}
else {//递归删除
_delete(child[x][key[x] < value], value);
}
update(x);
}
int _getKth(int &x,int k) {//递增排序第k个元素
if (k <= size[child[x][0]]) {
return _getKth(child[x][0], k);
}
k -= size[child[x][0]] + cnt[x];
if (k <= 0) {
return key[x];
}
return _getKth(child[x][1],k);
}
void insert(int value) {
_insert(root, value);
}
void deletes(int value) {
_delete(root,value);
}
int getKth(int k) {
return _getKth(root, k);
}
};
int main() {
Treap *treap = new Treap();
treap->insert(1);
treap->insert(2);
treap->insert(4);
treap->deletes(2);
cout << treap->getKth(1) << endl;
return 0;
}
也可以指针实现,代码暂时略