• 树和树结构(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;
    }
    
  • 相关阅读:
    Kotlin函数之fun?function?
    Kotlin什么是top-level?
    Kotlin基础类型
    Hello,Kotlin!
    delphi 窗体自适应屏幕分辨率
    关于 Delphi 中流的使用(7) 压缩与解压缩(TCompressionStream、TDecompressionStream)
    delphi视频聊天
    两个DataGridEHToExcel
    用Delphi实现网络视频编程
    delphi Base64编码/解码及数据压缩/解压知识
  • 原文地址:https://www.cnblogs.com/ljt12138/p/6684402.html
Copyright © 2020-2023  润新知