Treap tree

作者:Dong |可以转载, 但必须以超链接形式标明文章原始出处和作者信息及版权声明
网址:

1. 概述

同splay tree一样,treap也是一个平衡二叉树,不过Treap会记录一个额外的数据,即优先级。Treap在以关键码构成二叉搜索树的同时,还按优先级来满足堆的性质。因而,Treap=tree+heap。这里需要注意的是,Treap并不是二叉堆,二叉堆必须是完全二叉树,而Treap可以并不一定是。

2. Treap基本操作

为了使Treap 中的节点同时满足BST性质和最小堆性质,不可避免地要对其结构进行调整,调整方式被称为旋转。在维护Treap 的过程中,只有两种旋转,分别是左旋转(简称左旋)和右旋转(简称右旋)。

左旋一个子树,会把它的根节点旋转到根的左子树位置,同时根节点的右子节点成为子树的根;右旋一个子树,会把它的根节点旋转到根的右子树位置,同时根节点的左子节点成为子树的根。

struct Treap_Node
 
{
 
  Treap_Node *left,*right; //节点的左右子树的指针
 
  int value,fix; //节点的值和优先级
 
};
 
void Treap_Left_Rotate(Treap_Node *&a) //左旋 节点指针一定要传递引用
 
{
 
  Treap_Node *b=a->right;
 
  a->right=b->left;
 
  b->left=a;
 
  a=b;
 
}
 
void Treap_Right_Rotate(Treap_Node *&a) //右旋 节点指针一定要传递引用
 
{
 
  Treap_Node *b=a->left;
 
  a->left=b->right;
 
  b->right=a;
 
  a=b;
 
}

3. Treap的操作

同其他树形结构一样,treap的基本操作有:查找,插入,删除等。

3.1    查找

同其他二叉树一样,treap的查找过程就是二分查找的过程,复杂度为O(lg n)。

3.2    插入

在Treap 中插入元素,与在BST 中插入方法相似。首先找到合适的插入位置,然后建立新的节点,存储元素。但是要注意新的节点会有一个优先级属性,该值可能会破坏堆序,因此我们要根据需要进行恰当的旋转。具体方法如下:

1. 从根节点开始插入;

2. 如果要插入的值小于等于当前节点的值,在当前节点的左子树中插入,插入后如果左子节点的优先级小于当前节点的优先级,对当前节点进行右旋;

3. 如果要插入的值大于当前节点的值,在当前节点的右子树中插入,插入后如果右子节点的优先级小于当前节点的优先级,对当前节点进行左旋;

4. 如果当前节点为空节点,在此建立新的节点,该节点的值为要插入的值,左右子树为空,插入成功。

Treap_Node *root;
 
void Treap_Insert(Treap_Node *&P,int value) //节点指针一定要传递引用
 
{
 
  if (!P) //找到位置,建立节点
 
  {
 
    P=new Treap_Node;
 
    P->value=value;
 
    P->fix=rand();//生成随机的修正值
 
  }
 
  else if (value <= P->value)
 
  {
 
    Treap_Insert(P->left,r);
 
    if (P->left->fix < P->fix)
 
      Treap_Right_Rotate(P);//左子节点修正值小于当前节点修正值,右旋当前节点
 
  }
 
  else
 
  {
 
    Treap_Insert(P->right,r);
 
    if (P->right->fix < P->fix)
 
      Treap_Left_Rotate(P);//右子节点修正值小于当前节点修正值,左旋当前节点
 
  }
 
}

3.3   删除

与BST 一样,在Treap 中删除元素要考虑多种情况。我们可以按照在BST 中删除元素同样的方法来删除Treap 中的元素,即用它的后继(或前驱)节点的值代替它,然后删除它的后继(或前驱)节点。

上述方法期望时间复杂度为O(logN),但是这种方法并没有充分利用Treap 已有的随机性质,而是重新得随机选取代替节点。我们给出一种更为通用的删除方法,这种方法是基于旋转调整的。首先要在Treap 树中找到待删除节点的位置,然后分情况讨论:

情况一,该节点为叶节点或链节点,则该节点是可以直接删除的节点。若该节点有非空子节点,用非空子节点代替该节点的,否则用空节点代替该节点,然后删除该节点。

情况二,该节点有两个非空子节点。我们的策略是通过旋转,使该节点变为可以直接删除的节点。如果该节点的左子节点的优先级小于右子节点的优先级,右旋该节点,使该节点降为右子树的根节点,然后访问右子树的根节点,继续讨论;反之,左旋该节点,使该节点降为左子树的根节点,然后访问左子树的根节点,这样继续下去,直到变成可以直接删除的节点。

BST_Node *root;
 
void Treap_Delete(Treap_Node *&P, int *value) //节点指针要传递引用
 
{
 
   if (value==P->value) //找到要删除的节点 对其删除
 
   {
 
     if (!P->right || !P->left) //情况一,该节点可以直接被删除
 
     {
 
       Treap_Node *t=P;
 
       if (!P->right)
 
         P=P->left; //用左子节点代替它
 
       else
 
         P=P->right; //用右子节点代替它
 
       delete t; //删除该节点
 
     }
 
     else //情况二
 
     {
 
       if (P->left->fix < P->right->fix) //左子节点修正值较小,右旋
 
       {
 
         Treap_Right_Rotate(P);
 
         Treap_Delete(P->right,r);
 
       }
 
       else //左子节点修正值较小,左旋
 
       {
 
         Treap_Left_Rotate(P);
 
          Treap_Delete(P->left,r);
 
       }
 
     }
 
   }
 
   else if (value < P->value)
 
     Treap_Delete(P->left,r); //在左子树查找要删除的节点
 
   else
 
     Treap_Delete(P->right,r); //在右子树查找要删除的节点
 
}

4. Treap应用

Treap可以解决splay tree可以解决的所有问题,具体参见另一篇博文:《数据结构之伸展树》

可以这样定义结构体:

struct Treap_Node
 
{
 
  Treap_Node *left,*right; //节点的左右子树的指针
 
  int value,fix,weight,size; //节点的值,优先级,重复计数(记录相同节点个数,节省空间),子树大小
 
  inline int lsize(){ return left ?left->size ?0; } //返回左子树的节点个数
 
  inline int rsize(){ return right?right->size?0; } //返回右子树的节点个数
 
};


5. 总结

Treap 作为一种简洁高效的有序数据结构,在计算机科学和技术应用中有着重要的地位。它可以用来实现集合、多重集合、字典等容器型数据结构,也可以用来设计动态统计数据结构。

6. 参考资料

(1)Treap:http://www.nocow.cn/index.php/Treap

(2)随机平衡二叉查找树Treap 的分析与应用:http://www.byvoid.com/blog/wp-content/uploads/2010/12/treap-analysis-and-application.pdf


TreapTree + Heap)是一种结合了二叉搜索树(BST)和堆(Heap)特性的平衡二叉搜索树数据结构。其核心原理在于每个节点维护两个值:一个用于二叉搜索树性质的键值(key),以及一个用于最大堆性质的优先级(priority)。通过这两个属性,Treap在插入和删除操作时保持树的平衡性,从而确保操作的时间复杂度接近于对数级别。 ### Treap的基本原理 1. **二叉搜索树性质**:对于任意节点,其左子树中所有节点的键值小于当前节点的键值,右子树中所有节点的键值大于当前节点的键值。 2. **堆性质**:每个节点的优先级大于其子节点的优先级,这样可以确保树的结构在插入或删除时通过旋转操作保持平衡。 在插入新节点时,Treap首先按照二叉搜索树的方式找到合适的位置,并赋予该节点一个随机的优先级。如果该节点的优先级违反了堆性质,则通过旋转操作调整树的结构以恢复堆性质。删除操作类似,通过旋转确保堆性质得以维持。 ### 无旋Treap的实现 无旋Treap(Non-Rotating Treap)是Treap的一个变种,它通过分裂(Split)和合并(Merge)操作来实现树的平衡,而不是传统的旋转操作。分裂操作将树按照某个键值或位置分割为两部分,而合并操作将两个树合并为一个。这种方式简化了实现逻辑,尤其是在处理复杂操作时[^2]。 例如,分裂操作可以通过以下方式实现(以键值为分割点): ```python def split(node, key): if node is None: return (None, None) if node.key <= key: left, right = split(node.right, key) node.right = left return (node, right) else: left, right = split(node.left, key) node.left = right return (left, node) ``` 合并操作则需要确保堆性质的维护: ```python def merge(left, right): if left is None: return right if right is None: return left if left.priority > right.priority: left.right = merge(left.right, right) return left else: right.left = merge(left, right.left) return right ``` ### 应用场景 Treap由于其高效的平衡特性,广泛应用于需要高效查找、插入和删除操作的场景。常见的应用包括: - **数据库索引**:Treap可以用于实现高效的索引结构,支持快速的数据检索。 - **内存中的集合与映射**:在需要频繁插入和删除元素的场景中,Treap提供了良好的性能保障。 - **算法竞赛**:在某些需要高效数据结构的竞赛题中,Treap常被用来实现动态集合操作[^1]。 ### 实现细节 在实现Treap时,需要注意以下几点: - **随机优先级生成**:为了保证树的平衡性,每个节点的优先级应随机生成,通常使用大范围的整数以减少冲突的可能性。 - **递归与非递归实现**:虽然递归实现较为直观,但在大规模数据处理时可能会导致栈溢出,因此在实际应用中可以考虑非递归实现。 - **类型安全与内存管理**:特别是在使用Rust等语言时,需注意类型约束和内存安全,以确保程序的稳定性和可靠性[^1]。 通过理解Treap的原理和实现方式,开发者可以更好地将其应用于实际项目中,并根据需求进行扩展和优化。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值