定义
数堆名字取了Tree和Heap各一半,即Treap,是二叉搜索树和堆合并构成的数据结构。而堆和二叉搜索树的性质是有冲突的,二叉搜索树满足左子树<根节点<右子树,而堆是满足根节点小于等于(或大于等于)左右子树。因此在Treap的数据结构中,并不是以单一的键值作为节点的数据域。Treap每个节点的数据域包含2个值,key和weight。key值,和原来的二叉搜索树一样,满足左子树<根节点<右子树。weight值在Treap中满足堆的性质,根节点的weight值小于等于(或大于等于)左右儿子节点。
示例Treap:
Treap
代码实现为:
struct Node
{
int key;
int weight;
Node * lchild;
Node * rchild;
Node * father;
}Node;
旋转操作
Treap大部分操作和二叉搜索树是一样的,区别是插入操作时weight值可能会不满足堆的性质,这时需要进行旋转操作
示例:
右儿子节点是新插入的,并不满足堆的性质,所以要对其进行调整,假设这个Treap满足小根堆的性质,则调整结果为
旋转之后这样最小的weight值就位于根节点处。这个操作叫做旋转。
- 将右儿子调整至根部的旋转叫做左旋
- 将左儿子调整至根部的旋转叫做右旋
左旋操作
左旋操作操作过程:
- 获取根节点A的右儿子节点B
- 将节点B的父亲节点信息更新为f,并更新节点f的子节点信息为B
- 将节点A的右儿子信息更新为节点B的左儿子D,同时将节点D的父亲节点信息更新为A
- 将节点B的左儿子信息更改为节点A,同时将节点A的父亲节点信息更改为B
代码实现为
void LeftRotate( Node &*A )
{
// step1
Node *B=A->rchild;
// step2
B->father=A->father;
if( A->father->lchild == A )
B->father->lchild=B;
else
B->father->rchild=B;
// step3
if( B->lchild!=NULL )
{
A->rchild=B->lchild;
B->lchild->father=A;
}
// step4
A->father=B;
B->lchild=A;
}
右旋操作
右旋操作其操作是左旋的镜像
- 获取根节点A的左儿子节点B
- 将节点B的父亲节点信息更新为f,并更新节点f的子节点信息为B
- 将节点A的左儿子信息更新为节点B的右儿子D,同时将节点D的父亲节点信息更新为A
- 将节点B的右儿子信息更改为节点A,同时将节点A的父亲节点信息更改为B
代码实现为
void RightRotate( Node &*A )
{
// step1
Node *B=A->lchild;
// step2
B->father=A->father;
if( A->father->lchild == A )
B->father->lchild=B;
else
B->father->rchild=B;
// step3
if( B->rchild!=NULL )
{
A->lchild=B->rchild;
B->rchild->father=A;
}
// step4
A->father=B;
B->lchild=A;
}
总结
数堆的优势为:对于一般的二叉搜索树,在某些特殊情况下根据输入数据来建树有可能退化为一条链,比如一个依次增大的数列。而如果一棵二叉排序树的节点是按照随机顺序插入,得到的二叉排序树大多数情况下是平衡的,其期望高度是O(logn)。因此Treap利用weight值作为随机因子来调整二叉树的形状,使得在大部分情况下比直接通过数据建立的二叉树要平衡。
每一次查找的期望复杂度也会降低,总体的速度也就得到了提高。
网友评论