Treap(数堆)

字数 828阅读 490

定义

数堆名字取了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值就位于根节点处。这个操作叫做旋转。

  • 将右儿子调整至根部的旋转叫做左旋
  • 将左儿子调整至根部的旋转叫做右旋

左旋操作

左旋操作

操作过程:

  1. 获取根节点A的右儿子节点B
  1. 将节点B的父亲节点信息更新为f,并更新节点f的子节点信息为B
  2. 将节点A的右儿子信息更新为节点B的左儿子D,同时将节点D的父亲节点信息更新为A
  3. 将节点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;
}

右旋操作

右旋操作

其操作是左旋的镜像

  1. 获取根节点A的左儿子节点B
  1. 将节点B的父亲节点信息更新为f,并更新节点f的子节点信息为B
  2. 将节点A的左儿子信息更新为节点B的右儿子D,同时将节点D的父亲节点信息更新为A
  3. 将节点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值作为随机因子来调整二叉树的形状,使得在大部分情况下比直接通过数据建立的二叉树要平衡。
每一次查找的期望复杂度也会降低,总体的速度也就得到了提高。

推荐阅读更多精彩内容