谈谈数据结构-Treap

谈谈数据结构-Treap

分类:Data Structure Treap

1. Treap原理

Treap=(Binary Search)Tree + Heap
Treap 去掉fix域和rotate过程,就是一个很简单的排序二叉树BST。
虽然在插入数据完全离散的情况下,BST的期望复杂度也是 O(logn) O ( log ⁡ n ) ,但是,无论是在算法竞赛中,还是实际情况中,很难保证输入数据是完全离散的。所以在很多情况,BST是很容易退化成链式。
而fix,rotate的功能就是通过随机得到的fix来确定rotate是左旋还是右旋。说白了就是按照BST去插入节点,按照Heap的性质去调整树的高度、去维护二叉树的稳定性。
在随机数生成器足够稳定的情况下,具有非常良好的性能。Treap基本操作的期望复杂度是 O(logn) O ( log ⁡ n )
Treap相对AVL、SBT、Splay等平衡树来说,编程复杂度小得多!而且,Splay是一个均摊的复杂度 O(logn) O ( log ⁡ n ) ,在某一些数据上,并不如Treap稳定~~
总之,Treap简单!实用!

Treap本身并不复杂。但是,这种通过增加随机域,保证数据离散性的做法很棒。

还不明白的,可以参考以下链接:

2. 模板代码

以下代码是旋转版本的Treap的,功能比较简单。。
支持内存池静态分配节点内存+内存回收。。
Treap 的功能还有很多诶。。

#include <bits/stdc++.h>
using namespace std;

typedef pair<int, int> pii;
typedef pair<long long, long long> pll;

const int inf = 0x3f3f3f3f;
const long long infl = 0x3f3f3f3f3f3f3f3fLL;

template<typename T> inline void umax(T &a, T b) { a = max(a, b); }
template<typename T> inline void umin(T &a, T b) { a = min(a, b); }
void debug() { cout << endl; }
template<typename T, typename ...R>
void debug (T f, R ...r) { cout << "[" << f << "]"; debug (r...); }

template<class T> struct Treap {
    static const int TREAP_SIZE = 1000005;
    struct TNode {
        int fix;
        T val;
        int size;
        int cnt;
        TNode* ch[2];
        void init() {
            fix = rand();
            size = cnt = 0;
            ch[0] = ch[1] = NULL;
        }
        TNode() { init(); }
        TNode(T v) : val(v) { init(); }
    } node[TREAP_SIZE], *pRoot;
    queue<TNode*> trash; // 内存回收
    int tot;
    void init() {
        tot = 0;
        pRoot = NULL;
        while (!trash.empty()) trash.pop();
    }
    Treap() { init(); }
    inline int size(TNode* rt) { return rt == NULL ? 0 : rt->size; }
    void pushUp(TNode* rt) {
        rt->size = rt->cnt;
        rt->size += size(rt->ch[0]);
        rt->size += size(rt->ch[1]);
    }
    TNode* cre_node(T val) {
        TNode* pt = NULL;
        if (!trash.empty()) {
            pt = trash.front(); trash.pop();
        } else {
            pt = &node[tot++];
        }
        pt->init();
        pt->val = val;
        pt->size = pt->cnt = 1;
        return pt;
    }
    /**
     * 对子树进行旋转
     * @param  rt 旋转前子树根节点
     * @param  d 左旋false,右旋true
     * @return    旋转后子树根节点
     */
    TNode* rotate(TNode* rt, bool d) {
        if (rt == NULL || rt->ch[d ^ 1] == NULL) return rt;
        TNode* pt = rt->ch[d ^ 1];
        rt->ch[d ^ 1] = pt->ch[d];
        pt->ch[d] = rt;
        return pt;
    }
    /**
     * 在子树中插入一个节点
     * @param  rt  插入前子树根节点指针
     * @param  val 插入的值
     * @return     插入后子树根节点指针
     */
    TNode* insert(TNode* rt, const T& val) {
        if (rt == NULL) return cre_node(val);
        if (rt->val == val) {
            rt->cnt += 1;
            rt->size += 1;
            return rt;
        }
        bool d = (val > rt->val);
        rt->ch[d] = insert(rt->ch[d], val);
        if (rt->ch[d]->fix < rt->fix) rt = rotate(rt, d ^ 1);
        pushUp(rt);
        return rt;
    }
    /**
     * 在子树中删除一个节点,分两种情况旋转
     * @param  rt  删除前子树根节点指针
     * @param  val 删除的值
     * @return     删除后子树根节点指针
     */
    TNode* remove(TNode* rt, const T& val) {
        if (rt == NULL) return NULL;
        if (rt->val == val) {
            if (rt->cnt > 1) {
                rt->cnt -= 1;
                return rt;
            }
            if (rt->ch[0] == NULL || rt->ch[1] == NULL) {
                // 当前节点只有一个子节点,直接删除
                trash.push(rt);
                if (rt->ch[0] != NULL) return rt->ch[0];
                else return rt->ch[1];
            } else {
                // 当前节点只有两个子节点,先旋转再删除
                bool d = (rt->ch[0]->fix < rt->ch[1]->fix);
                rt = rotate(rt, d);
                rt->ch[d] = remove(rt->ch[d], val);
            }
        } else {
            bool d = (val > rt->val);
            rt->ch[d] = remove(rt->ch[d], val);
            d = (val > rt->val);
            if (rt->ch[d] && rt->ch[d]->fix < rt->fix) rt = rotate(rt, d ^ 1);
        }
        pushUp(rt);
        return rt;
    }
    /**
     * 获取子树的第k小数, indexed from 0
     * @param  rt 子树根节点
     * @return    k
     */
    T kth(TNode* rt, const int& k) {
        assert(k < size(rt));
        int lsz = size(rt->ch[0]);
        if (k < lsz) return kth(rt->ch[0], k);
        else if (k < lsz + rt->cnt) return rt->val;
        else return kth(rt->ch[1], k - lsz - rt->cnt);
    }
    /**
     * 获取在子树中,按从小到大,小于val的数有多少个
     * @param  rt  子树根节点
     * @param  val 值
     * @return     个数(排名)
     */
    int rank(TNode* rt, const T& val) {
        if (rt == NULL) return 0;
        if (val == rt->val) return size(rt->ch[0]);
        else if (val < rt->val) return rank(rt->ch[0], val);
        else return rank(rt->ch[1], val) + size(rt->ch[0]) + rt->cnt;
    }
    /**
     * 顺序输出子树序列
     * @param rt 子树根节点
     */
    void print(TNode* rt) {
        if (rt == NULL) return;
        print(rt->ch[0]);
        debug(rt->val, rt->cnt);
        print(rt->ch[1]);
    }
};
Treap<int> treap;

int main() {
#ifdef ___LOCAL_WONZY___
    freopen("input.txt", "r", stdin);
#endif // ___LOCAL_WONZY___
    // int arr[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
    for (int i = 1; i <= 10; ++i) {
        treap.pRoot = treap.insert(treap.pRoot, i);
    }
    treap.pRoot = treap.insert(treap.pRoot, 5);
    treap.pRoot = treap.insert(treap.pRoot, 5);
    treap.pRoot = treap.remove(treap.pRoot, 5);
    treap.print(treap.pRoot);
    debug("kth:", treap.kth(treap.pRoot, 7));
    debug("rank", treap.rank(treap.pRoot, 7));
#ifdef ___LOCAL_WONZY___
    cout << "Time elapsed: " << 1.0 * clock() / CLOCKS_PER_SEC * 1000 << "ms." << endl;
#endif // ___LOCAL_WONZY___
    return 0;
}
<think>我们正在讨论FHQTreap(无旋Treap数据结构及其实现。FHQTreap是一种基于Treap(树堆)和分裂合并操作实现数据结构,它不需要旋转操作,因此称为无旋Treap。它通过两个基本操作split(分裂)和merge(合并)来实现平衡二叉搜索树的各种功能。根据提供的引用,我们可以总结FHQTreap实现要点:1.**节点结构**:每个节点包含键值(key或val)、随机优先级(priority或rnd)、左右子节点指针以及子树大小(size)。2.**创建节点**:创建一个新节点,初始化其键值、随机优先级(通常用rand()生成)和子树大小(初始为1)。3.**更新节点信息**(push_up或update):更新当前节点的子树大小,即左子树大小+右子树大小+1。4.**分裂(split)**:将一棵Treap按照给定的值或大小拆分成两棵Treap,一棵的所有节点键值小于等于给定值,另一棵大于给定值(或者按照排名拆分)。5.**合并(merge)**:将两棵Treap合并成一棵,这里要求其中一棵的所有节点键值都小于另一棵的所有节点键值。合并时根据节点的随机优先级来决定谁作为根,以保持堆的性质。现在,我们将详细介绍这些操作,并给出代码实现(参考引用中的代码风格,但会进行整合和注释)。###节点定义```cppstructNode{intval;//节点存储的值intl,r;//左右子节点在数组中的下标,0表示空intsize;//以该节点为根的子树大小intpriority;//随机优先级};Nodetr[maxn];//节点数组intcnt=0;//节点计数器```###创建节点```cppintcreate(intv){cnt++;tr[cnt].val=v;tr[cnt].l=tr[cnt].r=0;tr[cnt].size=1;tr[cnt].priority=rand();//生成随机优先级returncnt;}```###更新子树大小```cppvoidupdate(intp){tr[p].size=tr[tr[p].l].size+tr[tr[p].r].size+1;}```###分裂操作分裂操作有两种方式:按值分裂和按大小分裂。这里先介绍按值分裂(将树分裂成两个部分,一部分所有节点值小于等于给定值,另一部分大于给定值)。```cpp//按值分裂:将以p为根的树按值key分裂成x和y两棵树//x树包含所有值<=key的节点,y树包含所有值>key的节点voidsplit(intp,intkey,int&x,int&y){if(!p){x=y=0;return;}if(tr[p].val<=key){x=p;split(tr[p].r,key,tr[x].r,y);update(x);}else{y=p;split(tr[p].l,key,x,tr[y].l);update(y);}}```###合并操作合并操作需要保证x树的所有节点值都小于y树的所有节点值。```cppintmerge(intx,inty){if(!x||!y)returnx|y;//若其中一个为空,返回另一个//按照优先级决定根节点(这里假设优先级小的在上,即大顶堆)if(tr[x].priority>tr[y].priority){tr[x].r=merge(tr[x].r,y);update(x);returnx;}else{tr[y].l=merge(x,tr[y].l);update(y);returny;}}```###基本操作利用分裂和合并,我们可以实现插入、删除等操作:-**插入值v**:先按v分裂成x和y,然后新建一个节点,再合并x和新节点,最后再与y合并。```cppvoidinsert(int&root,intv){intx,y;split(root,v,x,y);//按v分裂root=merge(merge(x,create(v)),y);}```-**删除值v**:先按v分裂成x和z,再对x按v-1分裂成x和y(这样y中所有节点值都等于v?注意,按值分裂时,y树是大于v的,而x树是小于等于v的,所以我们需要再对x树进行一次分裂,得到所有小于v的树和等于v的树)。然后我们删除一个等于v的节点(通常是合并y的左右子树,从而去掉根节点),最后合并。```cppvoidremove(int&root,intv){intx,y,z;split(root,v,x,z);//x:<=v,z:>vsplit(x,v-1,x,y);//x:<=v-1,y:[v](注意,这里y树中所有节点值都等于v?不一定,但至少>=v,而由于第一次分裂x<=v,所以y树的值在[v,v]之间?实际上,按值分裂并不是精确等于,所以我们需要另一种方式:删除一个等于v的节点)//实际上,我们想要将y树中等于v的节点删除一个,但y树中可能包含多个等于v的节点。通常的做法是:如果y非空,我们删除y的根节点(因为分裂后y树中可能包含多个值等于v的节点,但删除一个我们通常删除根节点,然后合并左右子树)if(y){y=merge(tr[y].l,tr[y].r);}root=merge(merge(x,y),z);}```注意:删除操作有多种实现方式,上面的代码是删除一个等于v的节点(如果存在多个,只删一个)。如果希望删除所有等于v的节点,则直接丢弃y树即可(即root=merge(x,z);)。###其他操作FHQTreap还可以实现查询第k大、查询排名、查询前驱后继等操作,这些都可以通过分裂操作和树的性质来实现。###总结FHQTreap通过分裂和合并操作实现了平衡树的基本功能,其核心在于利用随机优先级来保持树的平衡性(近似于二叉搜索树和堆的性质)。由于不使用旋转,代码相对简单,且支持持久化。§§相关问题§§1.FHQTreap的分裂操作有哪几种方式?它们分别适用于什么场景?2.在FHQTreap中,如何实现查询第k大的元素?3.FHQTreap相对于其他平衡树(如AVL树、红黑树)有什么优缺点?4.如何实现FHQTreap的持久化(可持久化)版本?</think>### FHQ Treap 数据结构及其实现 FHQ Treap(无旋Treap)是一种基于**随机优先级**和**分裂-合并操作**实现的平衡二叉搜索树,无需旋转操作即可维持平衡。其核心在于两个基本操作:`split`(分裂)和`merge`(合并),通过组合这两个操作实现插入、删除、查询等所有功能。 #### 一、核心组件 1. **节点结构**: ```cpp struct Node { int val; // 存储的值 int l, r; // 左右子节点指针 int size; // 子树大小 int priority; // 随机优先级 }; ``` - `priority`:随机生成的优先级值,用于维持堆性质(小顶堆或大顶堆) - `size`:子树节点总数,支持按排名操作[^1][^2] 2. **创建节点**: ```cpp int create(int v) { cnt++; tree[cnt].val = v; tree[cnt].l = tree[cnt].r = 0; tree[cnt].size = 1; tree[cnt].priority = rand(); // 生成随机优先级 return cnt; } ``` - 初始化子树大小为1,左右子节点为空[^2] 3. **更新子树大小**: ```cpp void update(int p) { tr[p].size = tr[tr[p].l].size + tr[tr[p].r].size + 1; } ``` - 递归计算左右子树大小之和再加1[^1][^3] --- #### 二、核心操作 1. **分裂(Split)** 将树按给定值 `key` 拆分为两棵子树: ```cpp void split(int p, int key, int &x, int &y) { if (!p) { x = y = 0; return; } if (tr[p].val <= key) { x = p; split(tr[p].r, key, tr[x].r, y); // 递归分裂右子树 update(x); } else { y = p; split(tr[p].l, key, x, tr[y].l); // 递归分裂左子树 update(y); } } ``` - `x` 树:所有节点值 ≤ `key` - `y` 树:所有节点值 > `key` - 时间复杂度:$O(\log n)$ 2. **合并(Merge)** 合并两棵树 `x` 和 `y`(需满足 `x` 所有值 < `y` 所有值): ```cpp int merge(int x, int y) { if (!x || !y) return x | y; // 任一为空则返回非空树 if (tr[x].priority < tr[y].priority) { tr[x].r = merge(tr[x].r, y); // x优先级更高,合并到右子树 update(x); return x; } else { tr[y].l = merge(x, tr[y].l); // y优先级更高,合并到左子树 update(y); return y; } } ``` - 按优先级决定父子关系(小顶堆) - 时间复杂度:$O(\log n)$ --- #### 三、衍生操作 1. **插入** 通过分裂-合并实现: ```cpp void insert(int &root, int v) { int x, y; split(root, v, x, y); // 按v分裂 root = merge(merge(x, create(v)), y); // 合并x→新节点→y } ``` 2. **删除** 删除值为 `v` 的节点: ```cpp void remove(int &root, int v) { int x, y, z; split(root, v, x, z); // 分裂出≤v的树x split(x, v-1, x, y); // 从x中分裂出<v的树x和==v的树y if (y) y = merge(tr[y].l, tr[y].r); // 合并y的左右子树(删除根) root = merge(merge(x, y), z); // 重组 } ``` 3. **查询第k大** 利用子树大小定位: ```cpp int kth(int p, int k) { while (p) { int left_size = tr[tr[p].l].size; if (k <= left_size) p = tr[p].l; else if (k == left_size + 1) return tr[p].val; else { k -= left_size + 1; p = tr[p].r; } } return -1; // 未找到 } ``` --- #### 四、特性分析 | **特性** | **说明** | |----------------|--------------------------------------------------------------------------| | **平衡性** | 随机优先级使树高期望为 $O(\log n)$ | | **无需旋转** | 所有操作基于分裂/合并,避免旋转的复杂性 | | **功能强大** | 支持分裂/合并,天然支持区间操作 | | **代码简洁** | 核心操作仅需约20行代码 | | **持久化支持** | 可低成本实现可持久化版本 | > **关键优势**:FHQ Treap 通过分裂操作可直接提取任意区间子树,使其在**区间操作**(如区间翻转、区间求和)上比传统平衡树更高效[^3]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值