Segment Tree

320 阅读2分钟

今天写了一些题目,做的全是线段树的题。但我打算明天复习树状数组,对可以用好多种做法做的题目打算按照线段树写一遍,再用树状数组写一遍。

基本上来说线段树是来做区间修改和查询的。在有修改的情况下,动态查询一个区间内的最大值、最小值或者和一类的指标,这种情况下我们可以用线段树。比方说一个求区间和的线段树,我们会定义一个类,叫SegmentTreeNode,然后有三个方法,buildTree、updateTree和querySum。代码模板如下:

class SegmentTreeNode {
    public int start, end, sum;
    public SegmentTreeNode left, right;
    
    public SegmentTreeNode(int start, int end, int sum, SegmentTreeNode left, SegmentTreeNode right) {
        this.start = start;
        this.end = end;
        this.sum = sum;
        this.left = left;
        this.right = right;
    }
}

    private SegmentTreeNode buildTree(int start, int end, int[] nums) {
        if (start == end) {
            return new SegmentTreeNode(start, end, nums[start], null, null);
        }
        
        int mid = start + (end - start) / 2;
        SegmentTreeNode left = buildTree(start, mid, nums);
        SegmentTreeNode right = buildTree(mid + 1, end, nums);
        
        return new SegmentTreeNode(start, end, left.sum + right.sum, left, right);
    }
    
    private void updateTree(SegmentTreeNode root, int index, int val) {
        if (root.start == index && root.end == index) {
            root.sum = val;
            return;
        }
        
        int mid = root.start + (root.end - root.start) / 2;
        if (index <= mid) {
            updateTree(root.left, index, val);
        }
        else {
            updateTree(root.right, index, val);
        }
        
        root.sum = root.left.sum + root.right.sum;
    }
    
    private int querySum(SegmentTreeNode root, int i, int j) {
        if (root.start == i && root.end == j) {
            return root.sum;
        }
        
        int mid = root.start + (root.end - root.start) / 2;
        if (j <= mid) {
            return querySum(root.left, i, j);
        }
        else if (i > mid) {
            return querySum(root.right, i, j);
        }
        
        return querySum(root.left, i, mid) + querySum(root.right, mid + 1, j);
    }

从上述模板我们也可以看出一些线段树的使用场景。比方说buildTree,是一次性建完这棵树的,后面或许对于树上节点的值进行修改,但树的结构从建出来之后就再也没有改过。也就是说如果在某道题中,你需要动态增删树的一些节点,那应该是不能用线段树。 在很多时候,如果跟区间修改查询相关,可以往这个方向想。如果这样的话最需要考虑的东西就是“以什么建树”和“树上携带什么信息”。当然上面这个题目是以输入数组建树的,但实质上应该去思考当你要做区间查询的时候,所谓区间的每个点代表什么。上面这个题目是以下标为查询的key的,所以树也是按照下标来做查询的。那么如果有些题目要查询某个范围内的值,那就要用“值”来建树。