📜  来自中序遍历的笛卡尔树 |段树(1)

📅  最后修改于: 2023-12-03 15:10:41.698000             🧑  作者: Mango

来自中序遍历的笛卡尔树 | 段树

介绍

笛卡尔树是一种特殊的二叉树,它的中序遍历即为它的原序列。它的构造方法非常简单,从原序列中选出一个数作为根节点,那么它的左子树就是原序列中在它左边的所有数,右子树就是原序列中在它右边的所有数,这样不断递归下去,即可构造出整棵树。

段树(线段树)是一种用于解决区间查询问题的数据结构,它将区间划分为若干个小区间,并在每个小区间中维护一些信息,不仅可以高效地查询区间信息,还可以支持区间修改操作。在笛卡尔树中,我们可以使用段树维护每个节点的区间信息。

实现

笛卡尔树可以用递归实现,也可以用单调栈实现。这里我们介绍一下递归实现的做法。

首先是笛卡尔树的构造:

struct TreeNode {
    int val;
    int idx;
    TreeNode *left;
    TreeNode *right;
    TreeNode(int x, int i) : val(x), idx(i), left(NULL), right(NULL) {}
};

TreeNode* build(vector<int>& nums, vector<int>& idxs, int l, int r) {
    if (l > r) return NULL;
    int max_val = INT_MIN;
    int max_idx = -1;
    for (int i = l; i <= r; i++) {
        if (nums[i] > max_val) {
            max_val = nums[i];
            max_idx = i;
        }
    }
    TreeNode* root = new TreeNode(max_val, idxs[max_idx]);
    root->left = build(nums, idxs, l, max_idx - 1);
    root->right = build(nums, idxs, max_idx + 1, r);
    return root;
}

这个函数接受原序列和序列中每个数对应的下标,以及中序遍历区间的左右边界,返回构造好的笛卡尔树的根节点。

接下来是段树的实现:

struct Node {
    int l;
    int r;
    int sum;
};

vector<Node> seg_tree;

void build(int k, int l, int r) {
    seg_tree[k].l = l;
    seg_tree[k].r = r;
    if (l == r) {
        seg_tree[k].sum = 0;
        return;
    }
    int mid = l + (r - l) / 2;
    build(k * 2 + 1, l, mid);
    build(k * 2 + 2, mid + 1, r);
    seg_tree[k].sum = seg_tree[k * 2 + 1].sum + seg_tree[k * 2 + 2].sum;
}

void update(int k, int i, int val) {
    if (seg_tree[k].l == i && seg_tree[k].r == i) {
        seg_tree[k].sum += val;
        return;
    }
    int mid = seg_tree[k].l + (seg_tree[k].r - seg_tree[k].l) / 2;
    if (i <= mid) {
        update(k * 2 + 1, i, val);
    } else {
        update(k * 2 + 2, i, val);
    }
    seg_tree[k].sum = seg_tree[k * 2 + 1].sum + seg_tree[k * 2 + 2].sum;
}

int query(int k, int l, int r) {
    if (seg_tree[k].l >= l && seg_tree[k].r <= r) {
        return seg_tree[k].sum;
    }
    int mid = seg_tree[k].l + (seg_tree[k].r - seg_tree[k].l) / 2;
    int sum = 0;
    if (l <= mid) {
        sum += query(k * 2 + 1, l, r);
    }
    if (r > mid) {
        sum += query(k * 2 + 2, l, r);
    }
    return sum;
}

这里实现了三个函数,build函数用于建立线段树,update函数用于修改线段树中的值,query函数用于查询区间和。其中,每个结点都记录了它所代表的区间的左右边界和区间和。

如何将这两个东西结合起来,就可以得到中序遍历的笛卡尔树上的区间查询和区间修改问题的算法。我们可以在中序遍历时给每个节点一个下标,将笛卡尔树转化为一棵二叉搜索树。每个节点所代表的区间就是在其子树中最小的数和最大的数所代表的区间。然后,以此构建线段树,就可以支持区间查询和区间修改操作了。

示例

以下是一个简单的示例,用于说明中序遍历的笛卡尔树上的区间查询和区间修改问题的算法:

vector<int> nums = {3, 2, 1, 6, 0, 5};
int n = nums.size();
vector<int> idxs(n);
for (int i = 0; i < n; i++) {
    idxs[i] = i;
}
TreeNode* root = build(nums, idxs, 0, n - 1);

seg_tree.resize(4 * n);
build(0, 0, n - 1);

update(0, root->left->idx, 1);
update(0, root->right->idx, 1);
cout << query(0, 0, n - 1) << endl; // 输出 2

update(0, root->left->idx, -1);
cout << query(0, 0, n - 1) << endl; // 输出 1

我们以序列 {3, 2, 1, 6, 0, 5} 为例,先构建中序遍历的笛卡尔树和线段树,然后对它们进行操作:

  • 将中序遍历的笛卡尔树上下标为 1 和 4 的节点的区间加 1,此时区间查询和为 2。
  • 将中序遍历的笛卡尔树上下标为 1 的节点的区间减 1,此时区间查询和为 1。
总结

中序遍历的笛卡尔树是一个非常有趣的数据结构,它将一个序列转化为一棵特殊的二叉树,并且能够支持区间查询和区间修改操作。在笛卡尔树上,我们可以使用线段树维护每个节点的区间信息。这种算法的时间复杂度为 $O(n \log n)$,空间复杂度为 $O(n)$,对于中等大小的数据集是非常适用的。