zoukankan      html  css  js  c++  java
  • Treap 代码实现及其原理

      Treap是一颗拥有键值,优先级两种权值的树.

      对于键值而言,这棵树是排序二叉树(BST Balance Sort Tree);

      对于优先级而言,这棵树是堆,既在这棵树的任意子树中,根节点的优先级是最大的(这个性质称为堆性质)

     1 #include<cstdio>
     2 #include<cstring>
     3 #include<cstdlib>
     4 #include<algorithm>
     5 using namespace std;
     6 struct Node{
     7     Node *ch[2]; //左右子树 
     8     int r;        // 优先级,数值越大,优先级越高
     9     int v;    //
    10     bool operator < (const Node &rhs ) const{
    11         return r < rhs.r;    //根据优先级比较节点    
    12     } 
    13     int cmp(int x) const{
    14         if( x == v ) return -1;
    15         return x < v ? 0 : 1;    
    16     }
    17 };
    18 // 注意代码中的小技巧
    19 // 因为 d = 0 | 1, d^1 = 1-d, 但计算速度更快。
    20 // 另一个关键之处在于o的类型。
    21 // 首先,o是指向一个Treap节点的指针
    22 // 因此为Node *类型,其次,O本身是可以修改的
    23 // 因此c是引用。若非如此,旋转后指针指向将不改变 
    24 void rotate( Node* &o, int d ){
    25     // d = 0: 左旋, d = 1: 右旋 
    26     Node *k = o->ch[d^1];
    27     o->ch[d^1] = k->ch[d];
    28     k->ch[d] = o;
    29     o = k;
    30 }
    31 // 插入节点时,首先随即给新节点一个优先级
    32 // 然后执行普通的插入算法.(根据键值大小判断在哪颗子树上)
    33 // 执行完毕后用左右旋让这个节点往上走,从而维持堆性质.
    34 
    35 // Treap的插入操作分为 BST插入 与 旋转 两个部分.
    36 // 若采用递归实现,则只需在递归插入后判断是否需要旋转.
    37 
    38 // 在以 o 为根的子树中插入键值x,修改o
    39 void insert( Node* &o, int x ){
    40     if( o == NULL ){
    41         o = new Node();
    42         o->ch[0] = o->ch[1] = NULL;
    43         o->v = x; o->r = rand();    
    44     }    
    45     else{ // 旋转方向与插入位置相反 
    46         int d = o.cmp(x);
    47         insert( o->ch[d], x );
    48         if( o->ch[d]->r > o->r )
    49             rotate( o, d^1 );
    50     }
    51 } 
    52 
    53 // 删除时,首先找到待删除节点.
    54 // 如果它只有一颗子树,情况就简单了.
    55 //         直接用这颗子树代替这个待删除节点成为根即可.(注意o是叶子节点也符合这个条件) 
    56 // 麻烦的是 o有两棵子树的情况.
    57 //        我们先把这两棵子树中优先级高的一颗旋转到根.
    58 //        然后递归在另一棵子树中删除节点o.
    59 //        比如,如果左子节点的优先级比较高,就必须右旋,否则会违反堆性质.
    60 //        如果右子节点优先级高,就必须左旋.代码如下.
    61 void remove( Node* &o, int x ){
    62     int d = o->cmp[x];
    63     if( d == -1 ){
    64         if( o->ch[0] == NULL ) o = o->ch[1];
    65         else if( o->ch[1] == NULL ) o = o->ch[0];
    66         else{
    67             int d2 = (o->ch[0]->r > o->ch[1]->r) ? 1 : 0; //旋转方向与优先级关系相反    
    68             retate( o, d2 );    remove( o->ch[d2], x );
    69         }    
    70     }    
    71     else    remove( o->ch[d], x );
    72 } 
    73 // 有意思的是,删除和插入是对称的. 
    74 // 事实上,前面的那个例子从前往后看是插入,从后往前看是删除.
    75 // 二者的共同特点是'只有一种旋转方法'
    76 
    77 // 上面的insert与remove 都没有处理
    78 // 待插入值已经存在, 待删除值不存在
    79 // 因此在调用相应函数之前请进行一次查找. 
    80 int find( Node *o, int x ){
    81     while( o != NULL ){
    82         int d = o->cmp(x);
    83         if( d == -1 ) return 1;
    84         else o = o->ch[d];    
    85     }    
    86     return 0;
    87 }
  • 相关阅读:
    Kafka 生产者 自定义分区策略
    同步互斥
    poj 1562 Oil Deposits(dfs)
    poj 2386 Lake Counting(dfs)
    poj 1915 KnightMoves(bfs)
    poj 1664 放苹果(dfs)
    poj 1543 Perfect Cubes (暴搜)
    poj 1166 The Clocks (暴搜)
    poj 3126 Prime Path(bfs)
    处理机调度
  • 原文地址:https://www.cnblogs.com/yefeng1627/p/3008773.html
Copyright © 2011-2022 走看看