来自上帝的骰子---Treap(树堆)详解
Posted C_YCBX Py_YYDS
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了来自上帝的骰子---Treap(树堆)详解相关的知识,希望对你有一定的参考价值。
文章目录
为什么说是上帝的骰子?
解释这个问题,首先由这个数据结构的名字开始,Treap = Tree + Heap,即为树堆之意,然而实际上用到堆的地方就是利用了一个随机的值标记每个结点,然后根据这个值对树进行左旋、右旋操作来调整父子树直接的关系,你可以严格让它遵循小根堆也可以遵循大根堆,这都无所谓。
也就是说每个结点的结构需要额外存储一个随机值,来决定它是否旋转调整,这对比普通的BST就要优越很多了,但这个也看运气,如果骰子没摇好,出现极端的情况,则可能即便是旋转了多次还是效率低下,我后面对BST、Treap、AVL进行了各方面的对比。
相对AVL,它不需要记录深度,不需要根据深度来判断是否旋转,旋转这件事就完全交给老天了,而且也不存在复杂的旋转情况,只有左旋和右旋。
注意:我写的这个Treap是以大根堆的方式进行维护的!也就是父节点大于子节点的随机值。
Treap实现
1. 结点的结构
struct node {
//值、优先级(随机数、该数的总结点数、该结点的val次数
int val, priority, length, cnt;
node *lchild;
node *rchild;
node() : val(0), length(1), cnt(1), lchild(nullptr), rchild(nullptr) {
srand((unsigned) time(NULL));//记得重新设定种子
priority = rand();
}
node(int val) : val(val), length(1), cnt(1), lchild(nullptr), rchild(nullptr) {
srand((unsigned) time(NULL));
priority = rand();
}
void update() {
length = cnt;
if (lchild != nullptr)length += lchild->length;
if (rchild != nullptr)length += rchild->length;
}
};
2. Treap的抽象数据结构
class Treap {
node *head;
int length;
public:
/*construct&destruct*/
Treap() : head(nullptr), length(0) {}
Treap(int val) : head(new node(val)), length(1) {}
public://内部类设计迭代器
class iterator {
node *head;
node *root;
public:
/*迭代器部分*/
iterator(node *head, node *root) : head(head), root(root) {}
iterator &operator++() {
root = queryNext(head, root->val);
return *this;
}
iterator operator++(int) {
iterator t = *this;
root = queryNext(head, root->val);
return t;
}
iterator &operator--() {
root = queryPre(head, root->val);
return *this;
}
iterator operator--(int) {
iterator t = *this;
root = queryPre(head, root->val);
return t;
}
int operator*() {
return root->val;
}
bool operator!=(const iterator &t) {
return t.root != root;
}
};
private:
/*static function*/
/*rotate*/
static node *rotateLeft(node *root);
static node *rotateRight(node *root);
/*insert&remove*/
static node *insert(node *root, int val, int &size);
static node *remove(node *root, int val, int &size);
/*query rank&value*/
static int getLength(node *root);
static int queryRank(node *root, int val);//快速查询val的排名
static int queryValue(node *root, int rank);//快速查询排名为rank的数
/*query pre&next*/
static node *queryPre(node *root, int val);
static node *queryNext(node *root, int val);
static void inorder_print(node *root);
static void destroy(node *root);
public:
/*public function*/
/*insert&remove*/
void insert(int val) {
head = insert(head, val, length);
}
void remove(int val) {
head = remove(head, val, length);
}
int size() {
return length;
}
bool isEmpty() {
return length == 0;
}
/*query rank&value*/
int queryRank(int val) {
return queryRank(head, val);
}
int queryValue(int rank) {
return queryValue(head, rank);
}
void inorder_print() {
inorder_print(head);
}
/*begin&end*/
iterator begin() {
node *t = head;
while (t->lchild != nullptr) {
t = t->lchild;
}
return iterator(head, t);
}
iterator end() {
return iterator(head, nullptr);
}
};
3. 左旋和右旋
具体可以看我之前AVL树对左旋右旋的描述,这里只开发源码查看,文章链接
左旋:
node *Treap::rotateLeft(node *root) {
node *son = root->rchild;
root->rchild = son->lchild;
son->lchild = root;
root->update();//记得先更新底下的情况
son->update();
return son;
}
右旋:
node *Treap::rotateRight(node *root) {
node *son = root->lchild;
root->lchild = son->rchild;
son->rchild = root;
root->update();
son->update();
return son;
}
4. 插入和删除
插入:
node *Treap::insert(node *root, int val, int &size) {
if (root == nullptr) {
++size;
return new node(val);
}
if (root->val == val) {
root->cnt++;
size++;
} else if (root->val > val) {
root->lchild = insert(root->lchild, val, size);
//根据优先级判断是否右旋,因为只可能在左边增加长度,通过维持优先级的大根堆
if (root->priority < root->lchild->priority) root = rotateRight(root);
} else if (root->val < val) {
root->rchild = insert(root->rchild, val, size);
if (root->priority < root->rchild->priority) root = rotateLeft(root);
}
root->update();//注意更新长度信息
return root;
}
删除:
删除这里还是详解一下:
找到要删除的目标节点后,我们根据让树旋转使得优先级较大的孩子替换掉父亲(目标节点)。然后继续追杀父亲结点,直到该结点被逼到叶子结点,删除即可。
node *Treap::remove(node *root, int val, int &size) {
if (root == nullptr)return nullptr;//没找到
if (root->val == val) {
//含有多个相同值,直接操作cnt即可
if (root->cnt > 1) {
root->cnt--;
--size;
}
//分为两类情况:叶子结点情况和非叶子结点情况
else if (root->lchild != nullptr || root->rchild != nullptr) {
//只有左子树或者左子树优先级大于右子树情况
if (root->rchild == nullptr || root->lchild != nullptr && root->lchild->priority > root->rchild->priority) {
root = rotateRight(root);//右旋后继续追杀
root->rchild = remove(root->rchild, val, size);
} else {//只有右子树或者右子树优先级大于左子树的情况
root = rotateLeft(root);//左旋后继续追杀
root->lchild = remove(root->lchild, val, size);
}
} else {//叶子结点情况,直接删除,然后把
delete root;
root = nullptr;
--size;
}
} else if (root->val > val) {
root->lchild = remove(root->lchild, val, size);
} else if (root->val < val) {
root->rchild = remove(root->rchild, val, size);
}
if (root)
root->update();
return root;
}
5. 查询排名
原理:由于是排序二叉树,而且记录了树的结点个数,所以我们根据左边个数(小于当前结点的个数),如果我们的值小于当前结点的值,则小于它的个数范围肯定是在当前结点的左边,直接迁移到左孩子即可,如果大于当前结点,则当前结点的左边大小+它自身也是无法满足要查询的值的排名,rank加上该值后root继续右移。如果出现找到该元素的情况,则直接返回rank+左边长度。如果其中不存在,则最后得出的rank肯定是需要+1的。
inline int Treap::getLength(node *root) {
if (root == nullptr)
return 0;
return root->length;
}
int Treap::queryRank(node *root, int val) {//相当于查询有多少个数小于等于val
int rank = 0;
while (root != nullptr) {
if (root->val == val)return rank + getLength(root->lchild) + root->cnt;
else if (root->val > val)root = root->lchild;
else rank += getLength(root->lchild) + root->cnt, root = root->rchild;
}
return rank + 1;//如果未找到,则在原来的基础上+1
}
6. 按排名查询值
inline int Treap::getLength(node *root) {
if (root == nullptr)
return 0;
return root->length;
}
int Treap::queryValue(node *root, int rank) {//相当于得到第k大的数:支持重复元素是最骚的!!!
while (root != nullptr) {
if (getLength(root->lchild) + root->cnt > rank) {
if (getLength(root->lchild) + 1 > rank)
root = root->lchild;
else
return root->val;
} else if (getLength(root->lchild) + root->cnt < rank) {
rank -= getLength(root->lchild) + root->cnt;
root = root->rchild;
} else {//rank与左子树的大小相等的情况
return root->val;
}
}
return 0;
}
7. 查询前驱后继
前驱:
node *Treap::queryPre(node *root, int val) {//一样的道理:如果有左子树,就是左子树中最大的结点,如果没有则是最接近该结点的父节点(应在它的右侧
node *res = nullptr;
while (root != nullptr) {
if (root->val < val)res = root, root = root->rchild;
else root = root->rchild;
}
return res;
}
后继:
node *Treap::queryNext(node *root, int val) {//寻找后继
node *res = nullptr;
while (root != nullptr) {
if (root->val > val)res = root, root = root->lchild;
else root = root->rchild;
}
return res;
}
8. 销毁Treap
void Treap::destroy(node *root) {
if (root == nullptr)
return;
destroy(root->lchild);
destroy(root->rchild);
delete root;
root = nullptr;
}
9. 迭代器的设计
public://内部类设计迭代器
class iterator {
node *head;
node *root;
public:
/*迭代器部分*/
iterator(node *head, node *root) : head(head), root(root) {}
iterator &operator++() {
root = queryNext(head, root->val);
return *this;
}
iterator operator++(int) {
iterator t = *this;
root = queryNext(head, root->val);
return t;
}
iterator &operator--() {
root = queryPre(head, root->val);
return *this;
}
iterator operator--(int) {
iterator t = *this;
root = queryPre(head, root->val);
return t;
}
int operator*() {
return root->val;
}
bool operator!=(const iterator &t) {
return t.root != root;
}
};
完整源代码
Treap.h
//
// Created by Alone on 2021/10/14.
//
#ifndef MY_TINY_STL_TREAP_H
#define MY_TINY_STL_TREAP_H
#include <cstdio>
#include <cstdlib>
#include <ctime>
struct node {
int val, priority, length, cnt;
node *lchild;
node *r以上是关于来自上帝的骰子---Treap(树堆)详解的主要内容,如果未能解决你的问题,请参考以下文章