线段树数据结构与算法

前言

线段树(Segment Tree)是一种高级数据结构,特别适用于需要在数组上进行快速的区间查询(如区间最值、区间和等)和单点更新的场景。线段树的实现和操作复杂度都非常高效,通常是 O(log⁡n) 级别,适合处理大规模数据。

线段树(Segment Tree)是一种用于解决区间查询和更新问题的高级数据结构。它在许多竞赛编程和实际应用中广泛使用,尤其是处理像数组这种静态数据结构上多次的动态查询和更新操作时。

1. 线段树的概念

线段树是一种二叉树结构,用于存储数组的区间信息。通过这棵树,可以高效地回答区间查询问题,如区间和、区间最小值、区间最大值等。线段树最适合处理的场景是频繁的区间查询和单点更新操作。

2. 线段树的结构

  • 叶子节点:每个叶子节点对应数组中的一个元素。
  • 内部节点:每个内部节点存储了其左右子节点对应区间的聚合信息(如区间和、区间最小值等)。

举例来说,给定一个数组 [1, 3, 5, 7, 9, 11],我们可以构造一棵线段树来存储它的区间和。

3.线段树的表示形式:

                [1, 3, 5, 7, 9, 11]
                   /           \
          [1, 3, 5, 7]       [9, 11]
          /       \            /   \
      [1, 3]    [5, 7]      [9]   [11]
     /    \      /    \
   [1]   [3]  [5]    [7]

  • 每个节点存储它所代表区间的和(或其他聚合信息)。
  • 根节点存储整个数组的区间和,叶子节点存储数组中的单个元素。

4.示例代码(区间和)

class SegmentTree {
    private int[] tree;
    private int n;

    // 构造函数,构建线段树
    public SegmentTree(int[] arr) {
        n = arr.length;
        tree = new int[2 * n];  // 用数组表示线段树,长度为 2n
        build(arr);
    }

    // 构建线段树的函数
    private void build(int[] arr) {
        // 将原数组的值放入线段树的叶子节点
        for (int i = 0; i < n; i++) {
            tree[n + i] = arr[i];
        }
        // 构建内部节点
        for (int i = n - 1; i > 0; --i) {
            //区间和
            tree[i] = tree[2 * i] + tree[2 * i + 1];
            //区间最小值
            //tree[i]=Math.min(tree[2*i]+tree[2*i+1]);
        }
    }

    // 区间和查询 [l, r) - 左闭右开区间
    public int query(int l, int r) {
        int sum = 0;
        int min=Integer.MAX_VALUE;
        l += n;  // 将 l 转换为线段树中叶子节点的索引
        r += n;  // 将 r 转换为线段树中叶子节点的索引

        while (l < r) {
            if ((l & 1) == 1) {  // 如果 l 是奇数
                sum += tree[l];
                //min = Math.min(min, tree[l]);
                l++;
            }
            if ((r & 1) == 1) {  // 如果 r 是奇数
                r--;
                sum += tree[r];
                //min = Math.min(min, tree[r]);
            }
            l >>= 1;  // l 移动到父节点
            r >>= 1;  // r 移动到父节点
        }
        //return min;
        return sum;
    }

    // 单点更新,将索引 idx 处的值更新为 value
    public void update(int idx, int value) {
        idx += n;  // 找到叶子节点对应的索引
        tree[idx] = value;
        // 向上更新父节点
        while (idx > 1) {
            idx >>= 1;
            tree[idx] = tree[2 * idx] + tree[2 * idx + 1];
            //tree[idx] = Math.min(tree[2 * idx], tree[2 * idx + 1]);
        }
    }

    // 测试函数
    public static void main(String[] args) {
        int[] arr = {1, 3, 5, 7, 9, 11};
        SegmentTree st = new SegmentTree(arr);

        // 查询区间和 [1, 5) -> 应该返回 3+5+7+9 = 24
        System.out.println(st.query(1, 5));  // 输出 24

        // 更新 arr[1] 的值为 10
        st.update(1, 10);

        // 再次查询区间和 [1, 5) -> 应该返回 10+5+7+9 = 31
        System.out.println(st.query(1, 5));  // 输出 31
    }
}

说明:构建的树所对应下标和对应区间关系如下图。

                      [0,5]
                   /          \
                /             [2, 5]
             /              /        \
      [0, 1]      [2, 3]     [4,  5]
     /     \        /     \      /        \ 
   [0]   [1]  [2]    [3]    [4]     [5]

查询区间和时,l累加的是当l为右节点的值。r累加的是当r为右节点时r-1处的值。

总结

线段树是一种强大的数据结构,适合处理静态数组上的动态查询和更新问题。它提供了高效的查询和更新操作,使其在解决区间问题时非常实用。通过线段树,复杂的区间操作可以在对数级别的时间内完成,从而大大提升了算法的效率。

  • 5
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值