现在给你一个问题,每次右两种操作,第一种操作是向集合中添加一个数x,第二种操作是询问集合中不大于k的最大的数是多少。现在我们不用任何stl里面的容器的情况下,你要怎样解决这个问题呢?一个最暴力的方法是,从一个数组存数,然后每次询问去遍历这个数组,这样每次插入数的时间复杂度是O(1),而查询时间复杂是O(n),所以这种方法对于查询操作比较多时,会很慢.有基础的同学可能会想到用二分去找,但是用二分必须要求整个序列是有序的,所以要保证你的数组是有序的,每次需要把一个数插入到合适的位置,然后把这个数后面的都往后移动一位,这样的话询问时间复杂度是O(logn),但是插入时间复杂度是O(n),这种方法对于插入操作比较多时,也很慢。跟上面暴力的方法相比复杂度没有本质的变化。当然这个问题好像可以用跳表解决,也就是在链表上做二分,当然这个我不会。也不是今天要谈论的内容。学过二叉排序树的同学,也许会想到用二叉排序树来做,这样每次插入的复杂度是树的高度,询问的复杂度也是树的高度。这种方法比上面的两种方法都要高效,但是有一个致命的弱点,就是当所给的序列是一个递增序列时,你的二叉树会退化成一条链,这样每次插入的复杂度和询问的复杂度都是O(n).那么我们有没有一种方法可以避免这种情况呢?答案是有的。就是今天我们要讲的数据结构Treap,树堆,是Tree + Heap的结合体。同时满足二叉排序树和堆的性质。在树堆中每个节点除了值value外另外还加入一个值w,这个值每次初始化时时随机的一个值。value满足二叉排序树的特点,而w满足堆的特点。这样每次插入一个树时,我们按value的值插入,也就是插入之后情况一定时满足二叉排序树的,但是因为我们的w值是随机生成的,所以有可能会不满足堆的性质,不满足怎么办?当然是调整啊,在堆中我们调整的方法是跟父亲节点的值交换,在这里面当然不能也这样做,因为这样会破坏二叉排序树的性质。这里我们调整只能通过旋转根来完成这个工作,这样既不会破坏二叉排序树的性质,也可以调整w,使得其满足堆的性质。旋转有左旋和右旋,左旋是把根的右儿子节点旋转到根,根变成右儿子节点的左孩子,原来右儿子的左儿子节点(如果有的话,也有可能没有)变成原来根节点的右儿子。右旋和左旋类似。这样就完成了调整这个操作。你肯定要问,这样做有什么用?这样做的目的,是通过w的随机性,保证树的平衡性,通俗来说就是是树不要退化成链。你可以看到,Treap这个东西依赖与随机来保证树的平衡,这个东西也是看脸的,但是平均,也就是期望的复杂度是O(logn).下面是一道实际的题目,这个题目的数据比较弱,不用Treap,只用二叉排序树也能过。
#1325 : 平衡树·Treap
描述
小Ho:小Hi,我发现我们以前讲过的两个数据结构特别相似。
小Hi:你说的是哪两个啊?
小Ho:就是二叉排序树和堆啊,你看这两种数据结构都是构造了一个二叉树,一个节点有一个父亲和两个儿子。 如果用1..n的数组来存储的话,对于二叉树上的一个编号为k的节点,其父亲节点刚好是k/2。并且它的两个儿子节点分别为k*2和k*2+1,计算起来非常方便呢。
小Hi:没错,但是小Hi你知道有一种办法可以把堆和二叉搜索树合并起来,成为一个新的数据结构么?
小Ho:这我倒没想过。不过二叉搜索树满足左子树<根节点<右子树,而堆是满足根节点小于等于(或大于等于)左右儿子。这两种性质是冲突的啊?
小Hi:恩,你说的没错,这两种性质的确是冲突的。
小Ho:那你说的合并是怎么做到的?
小Hi:当然有办法了,其实它是这样的....
输入
第1行:1个正整数n,表示操作数量,10≤n≤100,000
第2..n+1行:每行1个字母c和1个整数k:
若c为'I',表示插入一个数字k到树中,-1,000,000,000≤k≤1,000,000,000
若c为'Q',表示询问树中不超过k的最大数字
输出
若干行:每行1个整数,表示针对询问的回答,保证一定有合法的解
5 I 3 I 2 Q 3 I 5 Q 4样例输出
3 3
#include <bits/stdc++.h>
using namespace std;
const int inf = 0x3f3f3f3f;
struct node{
node *L, *R;//左右儿子节点
node *F;//父亲节点
node(){
L = NULL;
R = NULL;
F = NULL;
w = rand() % inf;
}
node(int _value){
value = _value;
w = rand() % inf;
L = NULL;
R = NULL;
F = NULL;
}
int value;
int w;//随机值
};
int n;
class Treap{
public:
node *Root;
Treap()
{
Root = NULL;
}
node* insert(node *root, int value)//插入一个值,返回该节点
{
if(root == NULL)
{
Root = new node(value);
return Root;
}
else
{
if(value <= root->value)
{
if(root->L == NULL)
{
root->L = new node(value);
root->L->F = root;
return root->L;
}
else
{
return insert(root->L, value);
}
}
else
{
if(root->R == NULL)
{
root->R = new node(value);
root->R->F = root;
return root->R;
}
else
{
return insert(root->R, value);
}
}
}
}
void adjust(node *p)//调整树的结构,使其满足大根堆的性质
{
if(p == NULL) return;
if((p->L == NULL && p->R != NULL && p->w < p->R->w) || (p->L != NULL && p->R != NULL && p->w < p->R->w && p->L->w <= p->R->w))//左旋
{
if(p->F != NULL)
{
if(p->F->L == p)
{
p->F->L = p->R;
}
else p->F->R = p->R;
}
if(p == Root) Root = p->R;
node *rson = p->R;
rson->F = p->F;
p->F = rson;
node *temp = rson->L;
rson->L = p;
p->R = temp;
if(temp != NULL) temp->F = p;
adjust(rson);
}
else if((p->L != NULL && p->R == NULL && p->w < p->L->w) || (p->L != NULL && p->R != NULL && p->w < p->L->w && p->R->w <= p->L->w))//右旋
{
if(p->F != NULL)
{
if(p->F->L == p)
{
p->F->L = p->L;
}
else p->F->R = p->L;
}
if(p == Root) Root = p->L;
node *lson = p->L;
lson->F = p->F;
p->F = lson;
node *temp = lson->R;
lson->R = p;
p->L = temp;
if(temp != NULL) temp->F = p;
adjust(lson);
}
else return;
}
int query(int key, node *root)//询问不大于key的最大值
{
if(root == NULL) return -inf;
if(key == root->value) return key;
if(key < root->value) return query(key, root->L);
if(key > root->value) return max(root->value, query(key, root->R));
}
};
int main()
{
scanf("%d", &n);
Treap t;
char op[10];
int key;
for(int i = 1; i <= n; i++)
{
scanf("%s %d", op, &key);
if(op[0] == 'I')
{
node *newnode = t.insert(t.Root, key);
t.adjust(newnode->F);
}
else
{
printf("%d\n", t.query(key, t.Root));
}
}
return 0;
}