树和树结构(2) : Treap树

来源:互联网 发布:淘宝自创品牌怎么做 编辑:程序博客网 时间:2024/05/22 21:14

测试题目来自 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\n", 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;}
0 0
原创粉丝点击
热门问题 老师的惩罚 人脸识别 我在镇武司摸鱼那些年 重生之率土为王 我在大康的咸鱼生活 盘龙之生命进化 天生仙种 凡人之先天五行 春回大明朝 姑娘不必设防,我是瞎子 报价失误报低了怎么办 期望薪资说低了怎么办 期望薪资说高了怎么办 面试工资说低了怎么办 期望薪资谈低了怎么办 请年假公司不批怎么办 期望工资填低了怎么办 面试工资要高了怎么办 找工作期望薪资写低了怎么办 期望工资写少了怎么办 不给工人发工资怎么办 天亮了怎么办我好想你 亲爱的我想你我怎么办 人在澳大利亚悉尼找不到了怎么办 红米手机忘记手势密码怎么办 捡到苹果手机怎么办才能自己用 日语会读不会写怎么办 手术后nbp过低怎么办 我的手破了怎么办英文 平板手机屏坏了怎么办 他很优秀我该怎么办 洗澡的花洒漏水怎么办 高三了文科成绩很差怎么办 骑缝章最后一页没盖全怎么办 机票取早了没有登机口怎么办 机票早订比晚订贵怎么办? 孩子考差了父母怎么办 保险公司不给业务员办退司怎么办 我不习惯没有你我怎么办 锁坏了打不开了怎么办 要上班老人生病无人照顾怎么办 苹果手机一直说英文怎么办 公司很抠门怎么办英文怎么说 过了截港时间怎么办 截关日期是假日怎么办 恒温阀冷水进水堵塞怎么办 缺氧液泵管道堵塞怎么办 货物包装大集装箱装不下怎么办 微信收藏的视频格式错误怎么办 乙方被刑拘房租未付清怎么办 房贷银行卡号弄错怎么办