树和树结构(2) : Treap树
2015-12-27 12:41
316 查看
测试题目来自 http://codevs.cn/problem/1164/
部分内容来自网络
想要了解treap树,你先要知道什么是二叉搜索树。
二叉查找树(Binary Search Tree),(又:二叉搜索树,二叉排序树)它或者是一棵空树,或者是具有下列性质的二叉树: 若它的左子树不空,则左子树上所有结点的值均小于它的根结点的值; 若它的右子树不空,则右子树上所有结点的值均大于它的根结点的值; 它的左、右子树也分别为二叉排序树。
二叉搜索树算法实现很简单,请各位自行百度。但是,二叉搜索树往往会暴露一些问题。例如当读入数据是高度有序的,如1 2 3 4 5, 树就会变成一条链:
这样的话,树就变成了一条链,查找和插入效率仅仅相当于链表,这自然是我们不愿意看到的。所以,前人想了一大堆办法使得搜索树尽量扁平,达到O(logn)的期望复杂度。其中有avl rb_tree sbt splay等许多优化方式。而其中最简单易行的就是treap。
所谓treap,顾名思义就是tree+heap,使得二叉排序树既满足树的性质又满足堆的性质(这里堆不一定是完全二叉树)。树的性质依靠存的数据完成,堆的性质则靠一个随机数据prior存储。我们在插入节点时向新节点分配一个优先值。如果新节点优先级小于其父节点,则违背了小根堆的性质,我们依靠旋转来维护堆性质。
旋转有两种方式
①: 左左情况旋转
从图中可以看出,当我们插入“节点12”的时候,此时“堆性质”遭到破坏,必须进行旋转,我们发现优先级是6<9,所以就要进行
左左情况旋转,最终也就形成了我们需要的结果。
②: 右右情况旋转
既然理解了”左左情况旋转“,右右情况也是同样的道理,优先级中发现“6<9”,进行”右右旋转“最终达到我们要的效果。
理解了这两种旋转,我们只要对bst的put加以改造就可以维护treap性质:
如果你不奢求支持erase,treap就是这样简单了。但为了透彻了解treap,我们支持一下erase操作。删除的方法有两种。这里我们介绍treap独有的——将”结点下旋“,直到该节点成为叶子节点,删除之。首先找到删除的节点。为了支持小根堆性质,我们每次选择优先值较小的节点并向反方向旋转,将当前节点下旋直到没有孩子(叶子节点)。具体代码如下:
总结
treap树是一种随机化数据结构。尽管他不能保证树的绝对平衡(avl树可以),但可以很大程度上避免链的产生。一般可以证明,在数据绝对随机的情况下,普通bst的性能是最好的,而treap尽可能通过调整使树趋于随机构建。treap是在严谨的数学证明上建立的,即他的期望复杂度为O(logn)。另外,treap的编程复杂度是所有自调整bst中最简单的,在OI中有着广泛的应用。
完整代码 对于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; }
相关文章推荐
- Android 的机型适配: 屏幕适配(包括dpi,dp,sp的概念) 版本适配 硬件适配。 以及如何和美工配合
- 第一个 C 语言编译器是怎样编写的?
- Redis中的事务
- 第四篇 PHP的成长路线
- 【重走普及路】【xfs】填充颜色
- PHP的json_encode中文被转码的问题
- SIP头域
- 输入框 js正则推断输入
- 浅谈Netfilter框架原理(二)
- 使用VS2013_X68;PCL1.7.2;QT5;VTK6.2实现qt显示PCL
- 更改你的host/mac
- python小白-day1
- Android Dialog 点击不消失
- 单调递增连续最长子序列
- LeetCode - Spiral Matrix II
- VI编辑常用命令
- hdu5601 N*M bulbs
- session原理
- 杭电1495 非常可乐(BFS过)简单搜索
- 【看书】程序计时器