树和树结构(2) : Treap树

测试题目来自 http://codevs.cn/problem/1164/
部分内容来自网络

想要了解treap树,你先要知道什么是二叉搜索树。

二叉查找树(Binary Search Tree),(又:二叉搜索树,二叉排序树)它或者是一棵空树,或者是具有下列性质的二叉树: 若它的左子树不空,则左子树上所有结点的值均小于它的根结点的值; 若它的右子树不空,则右子树上所有结点的值均大于它的根结点的值; 它的左、右子树也分别为二叉排序树。
二叉搜索树算法实现很简单,请各位自行百度。但是,二叉搜索树往往会暴露一些问题。例如当读入数据是高度有序的,如1 2 3 4 5, 树就会变成一条链:

    1
      
       2
        
         3
          
           4
            
             5

这样的话,树就变成了一条链,查找和插入效率仅仅相当于链表,这自然是我们不愿意看到的。所以,前人想了一大堆办法使得搜索树尽量扁平,达到O(logn)的期望复杂度。其中有avl rb_tree sbt splay等许多优化方式。而其中最简单易行的就是treap。

所谓treap,顾名思义就是tree+heap,使得二叉排序树既满足树的性质又满足堆的性质(这里堆不一定是完全二叉树)。树的性质依靠存的数据完成,堆的性质则靠一个随机数据prior存储。我们在插入节点时向新节点分配一个优先值。如果新节点优先级小于其父节点,则违背了小根堆的性质,我们依靠旋转来维护堆性质。

旋转有两种方式
①: 左左情况旋转
从图中可以看出,当我们插入“节点12”的时候,此时“堆性质”遭到破坏,必须进行旋转,我们发现优先级是6<9,所以就要进行
左左情况旋转,最终也就形成了我们需要的结果。

左左旋转 图片转载

②: 右右情况旋转
既然理解了”左左情况旋转“,右右情况也是同样的道理,优先级中发现“6<9”,进行”右右旋转“最终达到我们要的效果。

右右旋转 图片转载

void change_right(node<T>* &n)
{
        /*
        * 向右旋转(顺时针) 把当前节点的左孩子作为父节点.
        */
        node<T> *temp = n->left_child;
        n->left_child = temp->right_child;
        temp->right_child = n;
        n = temp;
        temp = 0;
}
void change_left (node<T>* &n)
{
        /*
        * 向左旋转 与向右相反
        */
        node<T> *temp = n->right_child;
        n->right_child = temp->left_child;
        temp->left_child = n;
        n = temp;
        temp = 0;
}

理解了这两种旋转,我们只要对bst的put加以改造就可以维护treap性质:

void put (node<T>* &n, T data)
{
        /*节点为空,新建并插入数据*/
        if (!n) {
                n = new node<T>;
                n->data = data;
                n->left_child = n->right_child = 0;
                n->prior = rand()%1000000007;
                /*随机数据作为优先值*/
                return;
        }
        /*否则左右查找*/
        if (data == n->data) {
                n->data = data;
                //cout << data.first << " " << data.second << endl;
        } else if (data < n->data) {
                put (n->left_child, data);
                if (n->left_child->prior < n->prior)
                        change_right(n);
                /*如果左节点优先值比当前小, 即破坏了小根堆的性质
                * 则向右旋转当前节点以维护堆性质
                */
        } else {
                put (n->right_child, data);
                if (n->right_child->prior < n->prior)
                        change_left(n);
                /*与上面刚好相反*/
        }
}

如果你不奢求支持erase,treap就是这样简单了。但为了透彻了解treap,我们支持一下erase操作。删除的方法有两种。这里我们介绍treap独有的——将”结点下旋“,直到该节点成为叶子节点,删除之。首先找到删除的节点。为了支持小根堆性质,我们每次选择优先值较小的节点并向反方向旋转,将当前节点下旋直到没有孩子(叶子节点)。具体代码如下:

    /*删除某个元素*/
        void erase_T(node<T>* &tree, T n)
        {
                if (!tree)
                        return;
                /*如果不是则按照bst左右查找*/
                if (!(tree->data == n)) {
                        if (n < tree->data)
                                erase_T(tree->left_child, n);
                        else
                                erase_T(tree->right_child, n);
                        return;
                }
                /*如果已经没有左右节点, 删除之*/
                if (!tree->left_child && !tree->right_child) {
                        delete tree;
                        tree = 0;
                } else if (tree->left_child && tree->right_child){
                /*如果有两个孩子 则按照优先级查找删除*/
                        if (tree->left_child->prior <= tree->right_child->prior) {
                                change_right(tree);
                                erase_T(tree->right_child, n);
                        } else {
                                change_left(tree);
                                erase_T(tree->left_child, n);
                        }
                } else if (tree->left_child) {
                /*只有左孩子 相当于右节点prior无穷大*/
                        change_right(tree);
                        erase_T(tree->right_child, n);
                } else if (tree->right_child) {
                /*反之同理*/
                        change_left(tree);
                        erase_T(tree->left_child, n);
                }
        }

总结
treap树是一种随机化数据结构。尽管他不能保证树的绝对平衡(avl树可以),但可以很大程度上避免链的产生。一般可以证明,在数据绝对随机的情况下,普通bst的性能是最好的,而treap尽可能通过调整使树趋于随机构建。treap是在严谨的数学证明上建立的,即他的期望复杂度为O(logn)。另外,treap的编程复杂度是所有自调整bst中最简单的,在OI中有着广泛的应用。


完整代码 对于http://codevs.cn/problem/1164/

#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <ctime>
#include <map>
using namespace std;


template <typename T>
class node {
        public:
        T data;
        node *left_child, *right_child;
        int prior;
};


template <typename T>
class treap {
        private:
        node<T> *root;

        void change_right(node<T>* &n)
        {
                /*
                * 向右旋转(顺时��?) 把当前节点的左孩子作为父节点.
                */
                node<T> *temp = n->left_child;
                n->left_child = temp->right_child;
                temp->right_child = n;
                n = temp;
                temp = 0;
        }

        void change_left (node<T>* &n)
        {
                /*
                * 向左旋转 与向右相��?
                */
                node<T> *temp = n->right_child;
                n->right_child = temp->left_child;
                temp->left_child = n;
                n = temp;
                temp = 0;
        }

        /*私有成员函数*/
        /*insert的私有实��?*/
        void put (node<T>* &n, T data)
        {
                /*节点为空,新建并插入数据*/
                if (!n) {
                        n = new node<T>;
                        n->data = data;
                        n->left_child = n->right_child = 0;
                        n->prior = rand()%1000000007;
                        /*随机数据作为优先��?*/
                        return;
                }
                /*否则左右查找*/
                if (data == n->data) {
                        n->data = data;
                        //cout << data.first << " " << data.second << endl;
                } else if (data < n->data) {
                        put (n->left_child, data);
                        if (n->left_child->prior < n->prior)
                                change_right(n);
                        /*如果左节点优先值比当前��?, 即破坏了小根堆的性质
                        * 则向右旋转当前节点以维护堆性质
                        */
                } else {
                        put (n->right_child, data);
                        if (n->right_child->prior < n->prior)
                                change_left(n);
                        /*与上面刚好相��?*/
                }
        }

        /*find的私有实��?*/
        node<T>* get (node<T>* n, T data)
        {
                if (!n)
                        return 0;
                /*找不��?*/
                if (n->data == data)
                        return n;
                else if (data < n->data)
                        return get(n->left_child, data);
                else
                        return get(n->right_child, data);
                /*左右查找*/
        }

        /*析构函数私有实现*/
        void del (node<T> *n)
        {
                if (n) {
                        /*删除左右节点*/
                        del (n->left_child);
                        del (n->right_child);
                        delete n;
                        /*释放父节��?*/
                }
        }

        /*删除某个元素*/
        void erase_T(node<T>* &tree, T n)
        {
                if (!tree)
                        return;
                /*如果不是则按照bst左右查找*/
                if (!(tree->data == n)) {
                        if (n < tree->data)
                                erase_T(tree->left_child, n);
                        else
                                erase_T(tree->right_child, n);
                        return;
                }
                /*如果已经没有左右节点, 删除��?*/
                if (!tree->left_child && !tree->right_child) {
                        delete tree;
                        tree = 0;
                } else if (tree->left_child && tree->right_child){
                /*如果有两个孩��? 则按照优先级查找删除*/
                        if (tree->left_child->prior <= tree->right_child->prior) {
                                change_right(tree);
                                erase_T(tree->right_child, n);
                        } else {
                                change_left(tree);
                                erase_T(tree->left_child, n);
                        }
                } else if (tree->left_child) {
                /*只有左孩��? 相当于右节点prior无穷��?*/
                        change_right(tree);
                        erase_T(tree->right_child, n);
                } else if (tree->right_child) {
                /*反之同理*/
                        change_left(tree);
                        erase_T(tree->left_child, n);
                }
        }

        /*dfs私有实现*/
        void dfs_lr (node<T>* &n, void (*function)(T t))
        {
                if (!n) return;
                /*如果为空直接退��?*/
                dfs_lr(n->left_child, function);
                function(n->data);
                dfs_lr(n->right_child, function);
                /*否则左右遍历*/
        }

        public:
        treap ()
        {
                root = 0;
                srand(time(NULL));
        }

        ~treap()
        {
               del (root);
               /*删除��?*/
        }

        /*成员函数*/

        /*
        * 函数��? insert(插入)
        * O(log2n) -> O(n)
        * 参数说明 data:插入的数��?
        * 请重��?"<"以自定义数据类型或降��?(默认升序)
        * 例如
        * struct data {
        *         int d;
        *         friend bool operator < (data a, data b) {
        *                 return a.d > b.d;
        *                 //降序
        *         }
        * }
        */

        void insert (T data)
        {
                put (root, data);
                /*调用私有成员函数*/
        }

        /*
        * 函数��? find(查找数据)
        * O(log2n) -> O(n)
        * 参数说明 data:查找的数��?
        * 请重��?"<"以自定义数据类型或降��?(默认升序)
        */
        node<T>* find (T data)
        {
                return get (root, data);
        }

        /*
        * 函数��? empty(判断是否为空��?)
        * O(1)
        */
        bool empty()
        {
                return (root == 0);
        }

        /*
        * 函数��? clear(清空)
        * O(n)
        */
        void clear ()
        {
                del (root);
        }

        /*
        * 函数��? count(返回data是否存在)
        * O(log2n) -> O(n)
        */

        bool count(T data)
        {
                return find(data) != 0;
        }

        /*
        * 函数��? eraze(删除元素)
        * O(log2n) -> O(n)
        */
        void erase(T data)
        {
                erase_T(root, data);
        }

        /*
        * 函数��? dfs(深度优先遍历)
        * O(n)
        * 参数说明 function: 一个函数指��?,进行深度优先遍历会执行function(T), T为遍历到的数��?
        */

        void dfs (void (*function)(T t))
        {
                dfs_lr(root, function);
                /*调用私有成员函数*/
        }

};

struct p {
        int x;
        int times;
        friend bool operator < (p a, p b)
        {
                return a.x < b.x;
        }
        friend bool operator == (p a, p b)
        {
                return a.x == b.x;
        }

};

void out(p a)
{
        printf ("%d %d
", a.x, a.times);
}

int main()
{
        treap< p > tr;
        int n, temp;
        scanf ("%d", &n);
        for (int i=1; i<=n; i++) {
                scanf ("%d", &temp);
                p a; a.x = temp;
                if (!tr.find(a)) {
                        a.times = 1;
                        tr.insert(a);
                } else {
                        a.times = tr.find(a)->data.times + 1;
                        tr.insert(a);
                }
                //tr.dfs(out);
        }
        tr.dfs(out);
        return 0;
}
原文地址:https://www.cnblogs.com/ljt12138/p/6684402.html