有趣的数据结构实现方式1:使用树状数组来实现普通平衡树

310 阅读2分钟

简易实现普通平衡树

介绍:✨

大家可以把平衡树简略的理解为可以lognlogn级别复杂度内实现这么几种操作的数据结构

  • 插入一个数
  • 删除一个数
  • 查询一个数的排名
  • 查询排名为x的数
  • 求x的前驱
  • 求x的后继

我们知道这些就是普通的平衡树所支持的操作

而且时间复杂度几乎都是lognlogn级别的

困扰:😫

大家都知道市面上有很多平衡树的分类:

  • AVL
  • 红黑树
  • splay
  • 有/无旋treap(FHQtreap等)

等等等等

无一例外的常数略大,代码难写。

有没有可能有一种比较好写的方案,并且常数也十分优秀呢?

当然是有的,如果你的需求只有平衡树的上述基础操作,不妨试试我的解决方案吧~

引出主角:😃

那么我们就要引出今天的主角:Fenwick---树状数组

树状数组我们就不仔细介绍了,这是一个比较基础的数据结构。

简单说一下就是这个数据结构可以lognlogn单点修改lognlogn区间查询。

最最最最重要的一点是树状数组代码敲好写,常数敲小,难道这不就是我们所追求的嘛~

解决:😎

在这之前你可能要稍微了解一下:

  • 树状数组的原理
  • 下标为值域的树状数组
  • 树状数组如何树上二分
  • 值域离散化

在这之后我们就可以对每一个操作来剖析了

  1. 插入和删除
    • 这个非常好解决,只要对离散化后的下标加减一就可以了
  2. 查询一个数的排名
    • 也非常好解决,只需要查询比他小的索引和+1就可以了
  3. 查询排名为x的数
    • 这个需要使用到我们的树上二分,就需要你对树状数组有比较深的理解(一会可以看代码)
  4. 求x的前驱后继
    • 也非常简单,就是查询比他排名小一位和大一位的数,然后再用这个排名询问值

代码:🚙

template <typename T>
struct Fenwick {
    const int n;
    std::vector<T> a;
    Fenwick(int n) : n(++n), a(n) {}
    void add(int x, T v) {
        for (int i = x; i <= n; i += i & -i) {
            a[i] += v;
        }
    }
    T sum(int x) {
        T ans = 0;
        for (int i = x; i > 0; i -= i & -i) {
            ans += a[i];
        }

        return ans;
    }
    T rangeSum(int l, int r) {
        return sum(r) - sum(l - 1);
    }
    T rank(int p)
    {
        return sum(p - 1) + 1;
    }
    T val(int rk)
    {   
        int _log = ((ceil(log(n) / log(2))));
        int pos = 1 << _log;

        for (int i = _log - 1; ~i; i -- )
            if (a[pos - (1 << i)] >= rk) pos -= (1 << i);
            else rk -= a[pos - (1 << i)];

        return pos;
    }
    T pre(int p)
    {
        return val(rank(p) - 1);
    }
    T nxt(int p)
    {
        return val(rank(p + 1));
    }
};

功能代码也就是30来行,有没有颠覆你对平衡树的认知呢?
树状数组代码初模板来自国内顶尖高手jiangly 致敬我的偶像蒋ls~
在这个基础上由我进行封装成了现在这样


这样一个平衡树的基本功能就用一个更好的方式来实现了~

平衡树模板题 可以拿去试试跑的速度了,快的狠呢~

那我们下一篇有趣的数据结构实现方式再见咯😄