码农知识堂 - 1000bd
  •   Python
  •   PHP
  •   JS/TS
  •   JAVA
  •   C/C++
  •   C#
  •   GO
  •   Kotlin
  •   Swift
  • 算法学习笔记(21): 平衡树(二)


    合集 - 算法学习笔记(27)
    1.算法学习笔记(1): 欧几里得算法及其扩展01-122.算法学习笔记(2): 欧拉定理与逆元01-133.算法学习笔记(3): 倍增与ST算法01-124.算法学习笔记(4): 并查集及其优化01-125.算法学习笔记(5): 最近公共祖先(LCA)01-126.算法学习笔记(6): 树链剖分01-127.算法学习笔记(7): 二分图01-138.算法学习笔记(8): 网络流01-139.算法学习笔记(8.0): 网络流前置知识01-1310.算法学习笔记(8.1): 网络最大流算法 EK, Dinic, ISAP01-1411.算法学习笔记(8.2): 上下界网络流01-1412.算法学习笔记(9): 中国剩余定理(CRT)以及其扩展(EXCRT)01-1513.算法学习笔记(10): BSGS算法及其扩展算法01-1614.算法学习笔记(11): 原根01-1615.算法学习笔记(12): 线性基02-0316.算法学习笔记(13): Manacher算法02-0717.算法学习笔记(14): 字符串哈希02-0618.算法学习笔记(15): Trie(字典树)02-0819.算法学习笔记(16): 组合数学基础02-0820.算法学习笔记(17): 快速傅里叶变换(FFT)02-1021.算法学习笔记(18): 平衡树(一)03-1022.算法学习笔记(19): 树上启发式合并(DSU on tree)03-1823.算法学习笔记(20): AC自动机03-26
    24.算法学习笔记(21): 平衡树(二)05-13
    25.算法学习笔记(22): 逆序对与原序列05-3126.算法学习笔记(23): 马尔可夫链中的期望问题06-0127.算法学习笔记(24): 狄利克雷卷积和莫比乌斯反演06-08
    收起

    平衡树(二)

    平衡树(一)链接:算法学习笔记(18): 平衡树(一) - jeefy - 博客园

    本文中将讲述一下内容:

    • 可持久化Treap

    • 基于Trie的 类 平衡树(后文称之为 BSTrie)

    • BSTrie的可持久化

    可持久化Treap

    可持久化Treap基于FHQ-Treap。其实不难发现,FHQ-Treap在分裂和合并时在每一层只对一个结点产生影响。于是我们可以大胆的可持久化此结构,且保证复杂度为 O(log⁡n)" role="presentation">O(logn)O(log⁡n)。

    我们以此图为例。我们只需要复制下被影响的结点,基于原树获得一条新链:

    红色节点是新产生的节点(可能实际上产生的顺序不一样)

    其中 (8, 11) 作为新右树的根,(7, 8) 作为新左树的根

    也就是说,我们经过一个结点复制一次即可。

    inline void splitVal(int p, int val, int &x, int &y, bool simple = true) {
        if (!p) return (void)(x = y = 0);
        int np = simple ? p : clone(p);
        if (val(p) <= val)
            x = np, splitVal(rp(p), val, rp(x), y, simple), update(x);
        else
            y = np, splitVal(lp(p), val, x, lp(y), simple), update(y);
    }
    

    simple就是标志是否需要可持久化……特别简单

    其实到这里就已经讲完了可持久化Treap了……因为其他绝大部分操作只需要对于分裂时持久化,在合并时并不需要持久化。

    例如删除操作:

    inline int insert(int root, int val, bool simple = false) {
        int x(0), y(0), p(++usage);
        nodes[p].init(val);
        splitVal(root, val, x, y, simple);
        return merge(merge(x, p), y);
    }
    

    这也请读者自行思考为什么不需要合并时可持久化。

    基于Trie的 类 平衡树(BSTrie)

    这里基于的Trie指的是 01Trie" role="presentation">01Trie01Trie,考虑其实每一个数都可以被拆分为二进制,于是有了此做法。

    说实话,代码无比之简短,并且十分迅速,除了空间复杂度较大之外,令我不禁想要抛弃WBLT……

    后记:空间复杂度……真的很大,很多普通平衡树能过的空间,Trie真不一定能过

    我们首先只考虑正数的情况。如果我们把每一个数都扩展成同一个位长,从高位向低位保存成一棵树。我们从左到右(认为0在左,而1在右)观察其叶节点所对应的值(类似于Leafy Tree),可以知道是单调递增的,于是我们可以轻易的将之进行魔改,从而做到普通平衡树所能做到的事。


    template<int N = 100000>
    class BSTrie {
    private:
        int siz[N << 5];
        int ch[N << 5][2];
        int usage;
        #define newNode() ({ \
            ++usage; \
            siz[usage] = ch[usage][0] = ch[usage][1] = 0; \
            usage; \
        })
    }
    

    这是这一颗树需要用到的内容。其实从这里就应该可以知道,其空间复杂度为 O(nlog⁡C)" role="presentation">O(nlogC)O(nlog⁡C) 其中 C" role="presentation">CC 表示值域大小,一般为 32" role="presentation">3232。这与其他空间为 O(n)" role="presentation">O(n)O(n) 的平衡树相比远远不如……

    插入

    首先看代码:

    void insert(int x) {
        int p = 1; 
        for (int i = BS; ~i; --i) {
            int bt = bit(x, i), &np = ch[p][bt];
            if (!np) np = newNode();
            p = np, ++siz[p];
        }
    }
    

    写法有些许迷惑,见谅

    其中BS指的是 ⌈log⁡C⌉" role="presentation">⌈logC⌉⌈log⁡C⌉

    由于我们需要用到子树的大小以方便 rank,kth" role="presentation">rank,kthrank,kth 操作,所以对于路径上 ++siz[p]

    不就是普通的 Trie 插入操作吗?不讲了。

    删除

    void remove(int x) {
        int p = 1;
        for (int i = BS; ~i; --i) {
            int np = ch[p][bit(x, i)];
            if (!np) return;
            p = np;
        }
    
        p = 1;
        for (int i = BS; ~i; --i) {
            p = ch[p][bit(x, i)], --siz[p];
        }
    }
    

    这里需要注意的是需要两遍向下,以防止 x 并不存在的情况。

    与普通的 Trie 删除操作类似,想必代码也十分易懂。

    查询排名

    在普通平衡树内查询排名怎么查,这里就怎么查:

    • 进入右子节点,则累加左子树的大小

    • 进入左子节点,则不累加

    • 没有子节点,直接返回当前结果

    int rank(int x, bool within = false) {
        int p = 1;
        int rk = 0;
        if (x >= 0) rk += siz[1];
        for (int i = BS; ~i; --i) {
            int bt = bit(x, i), np = ch[p][bt];
            if (bt) rk += siz[ch[p][0]];
            if (!np) return rk;
            p = np;
        }
    
        return within ? rk + siz[p] : rk;
    }
    

    这里对于加入了 within 参数,用于提示是否需要包含 x 出现的次数。

    为什么在第8行直接返回是正确的?简单来说就是空子树不会对结果造成影响。具体一点请读者自行思考。

    查询第k大

    呃,令 x 为当前结果:

    • 若进入左子树,则令 x = x << 1

    • 否则令 x = (x << 1) | 1(打括号是为了方便理解)

    如果保证树内存在至少 k" role="presentation">kk 个数,则一定可以找到正确答案,且不会进入空子树。

    但是没有这么多个数……则结果不可预测

    int kth(int k) {
        int p = 1;
        int x = 0;
        for (int i = BS; ~i; --i) {
            if (k <= siz[lc(p)]) p = lc(p), x <<= 1;
            else k -= siz[lc(p)], p = rc(p), x = (x << 1) | 1;
        }
        return x;
    }
    

    于是,你可以在100行内写出一个优秀的平衡树了……


    现在考虑有复数的情况。有两种解决方法:

    • 依据符号位,建两棵树。根据补码的知识,对于有符号类型的整数,其对应的无符号整型数值越大,其值越大。所以负数也可以利用同样的代码处理。

      而改变方法也很简单,将语句 int p = 1 改为 int p = x < 0 ? 1 : 2(标号随意)即可。
      只是在 kth 时,需要有:int x = k <= siz[1] ? (1 << (32 - BS)) - 1 : 0;

      在 rank 前要多加一句:if (x >= 0) rk += siz[1];

      其他的都没大区别。

    • 第二种方案相对简单,把所有数都加上一个 offset,保证为正整数即可……(这种方法很简答,而且可持久化时也更简单)

    于是我们优先采用第二种方法(尤其是需要可持久化的时候)。


    可持久化BSTrie

    可持久化 Trie 会吧……OK,下课!

    还是提一下可持久化的思想:

    把每一个经过的结点复制出来即可……类比可持久化Treap的操作

  • 相关阅读:
    RabbitMQ(二)
    C++程序设计
    鸿蒙系统扫盲(二):再谈鸿蒙是不是安卓套壳?
    【Python进阶】近200页md文档14大体系第4篇:Python进程使用详解(图文演示)
    基于Spring实现策略模式
    C复习-指针
    redis键的过期删除策略
    计算机毕业设计Java滁州市的围棋协会网站(源码+系统+mysql数据库+lw文档)
    面试官:设计模式之简单工厂模式
    golang面试题:reflect(反射包)如何获取字段tag​?为什么json包不能导出私有变量的tag?
  • 原文地址:https://www.cnblogs.com/jeefy/p/17396697.html
  • 最新文章
  • 攻防演习之三天拿下官网站群
    数据安全治理学习——前期安全规划和安全管理体系建设
    企业安全 | 企业内一次钓鱼演练准备过程
    内网渗透测试 | Kerberos协议及其部分攻击手法
    0day的产生 | 不懂代码的"代码审计"
    安装scrcpy-client模块av模块异常,环境问题解决方案
    leetcode hot100【LeetCode 279. 完全平方数】java实现
    OpenWrt下安装Mosquitto
    AnatoMask论文汇总
    【AI日记】24.11.01 LangChain、openai api和github copilot
  • 热门文章
  • 十款代码表白小特效 一个比一个浪漫 赶紧收藏起来吧!!!
    奉劝各位学弟学妹们,该打造你的技术影响力了!
    五年了,我在 CSDN 的两个一百万。
    Java俄罗斯方块,老程序员花了一个周末,连接中学年代!
    面试官都震惊,你这网络基础可以啊!
    你真的会用百度吗?我不信 — 那些不为人知的搜索引擎语法
    心情不好的时候,用 Python 画棵樱花树送给自己吧
    通宵一晚做出来的一款类似CS的第一人称射击游戏Demo!原来做游戏也不是很难,连憨憨学妹都学会了!
    13 万字 C 语言从入门到精通保姆级教程2021 年版
    10行代码集2000张美女图,Python爬虫120例,再上征途
Copyright © 2022 侵权请联系2656653265@qq.com    京ICP备2022015340号-1
正则表达式工具 cron表达式工具 密码生成工具

京公网安备 11010502049817号