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 }

 

你可能感兴趣的:(代码)