来自上帝的骰子---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(树堆)详解的主要内容,如果未能解决你的问题,请参考以下文章

Treap(树堆)详解

数据结构-Treap(树堆) 详解

树堆(Treap)

树堆(Treap)

模板Treap

上帝掷骰子,比特币向前进