线段树

什么是线段树

​ 线段树,是一种 二叉搜索树 。它将一段区间划分为若干 单位区间 ,每一个节点都储存着一个区间。它功能强大 ,支持区间求和,区间最大值,区间修改,单点修改等操作。线段树的思想和分治思想很相像。线段树的每一个节点都储存着一段区间 [ L . . . R ] [ L . . .R ] [L...R] 的信息,其中叶子节点 L = R L = R L=R 。它的大致思想是:将一段大区间平均地划分成 2 2 2 个小区间,每一个小区间都再平均分成 2 2 2 个更小区间……以此类推,直到每一个区间的 L L L 等于 R R R (这样这个区间仅包含一个节点的信息,无法被划分)。通过对这些区间进行修改、查询,来实现对大区间的修改、查询。
​ 这样一来,每一次单点修改、单点查询的时间复杂度都只为 O ( l o g ⁡ 2 n ) O ( log⁡_2n ) O(log2n) 。但是,可以用线段树维护的问题必须满足区间加法 ,否则是不可能将大问题划分成子问题来解决的。

线段树的原理和实现

​ 线段树主要是把一段大区间平均地划分成两段小区间进行维护,再用小区间的值来更新大区间。这样既能保证正确性,又能使时间保持在 l o g log log ⁡级别(因为这棵线段树是平衡的)。也就是说,一个 [ L , R ] [ L , R ] [L,R] 的区间会被划分成 [ L , ( L + R ) / 2 ] [ L , (L + R)/ 2 ] [L,(L+R)/2] [ ( L + R ) / 2 + 1 , R ] [ (L + R)/ 2 + 1 , R ] [(L+R)/2+1,R] 这两个小区间进行维护,直到 L = R L = R L=R

图片描述

​ 线段树一般需要四个函数 b u i l d ( r o o t , l , n ) build(root,l,n) build(root,l,n) 建树函数, m o d i f y ( r o o t , x , k ) modify(root,x,k) modify(root,x,k) 修改函数, q u e r y ( r o o t , l , r ) query(root,l,r) query(root,l,r) 查询函数, p u s h u p ( r o o t ) pushup(root) pushup(root) 更新函数。我们最常用的操作就是遍历,而树的遍历需要从根节点开始,所以每一个函数都需要传入根节点 r o o t root root

b u i l d ( r o o t , l , n ) build( root , l , n ) build(root,l,n) 建树函数

​ 该函数是树的初始化,通过根节点,向下初始化所有子节点。

public static void build(int u, int l, int r) {
    tree[u] = new node();
    if(l == r){//叶子节点		
        tree[u].l = l;
        tree[u].r = r;
    }else {
        tree[u].l = l;
        tree[u].r = r;
        int mid = (l + r)/2;
        build(2*u, l, mid);
        build(2*u+1, mid + 1, r);
    }	  
}

m o d i f y ( r o o t , x , k ) modify( root , x , k ) modify(root,x,k) 修改函数

x x x 表示要修改的位置, k k k 表示修改的值,通过该函数对 x x x 处的值进行修改

public static void modify(int u, int x, long k) {
    if(tree[u].l == tree[u].r) {
        tree[u].v = k;
    }else {
        int mid = (tree[u].l + tree[u].r)/2;
        if(x <= mid) {
            modify(u*2, x, k);
        }else {
            modify(u*2+1, x, k);
        }
        pushup(u);//更新u
    }
}

q u e r y ( r o o t , l , r ) query( root , l , r ) query(root,l,r) 查询函数

​ 查询 l l l ~ r 区间的区间值

public static long qurey(int u, int l, int r) {
    if(l <= tree[u].l && r >= tree[u].r) {
        return tree[u].v;
    }
    long res = 0;
    int mid = (tree[u].l + tree[u].r) / 2;
    if(l <= mid ) {
        res = qurey(u*2, l, r);
    }
    if(r > mid) {
        res += qurey(u*2+1, l, r);
    }
    return res;
}

p u s h u p ( r o o t ) pushup( root ) pushup(root) 更新函数

​ 在 m o d i f y modify modify 函数进行修改时,修改的是叶子节点,但叶子节点的修改也会影响父节点,通过该函数更新被修改的叶子节点的父节点的值

private static void pushup(int u) {
    tree[u].v = tree[u*2].v + tree[u*2+1].v;
}
例题:小球与盒子

题目链接:小球与盒子 - 蓝桥云课 (lanqiao.cn)

图片描述

​ 思路:

​ 线段树叶子节点存储的是某个盒子中小球的个数,非叶子节点存储的是一段区间内盒子中小球数量的和,通过 m o d i f y modify modify 实现某个盒子增加小球的数量的操作,通过 q u e r y query query 实现查询 l l l ~ r r r 个盒子的小球总量。在 m o d i f y modify modify 直接将叶子节点的值改为了 k k k ,那么在本题中,应该是叶子节点原来的值加上 k k k

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;

public class 小球与盒子 {
    public static class node{
        int l,r;
        long v;
    }
    static node tree[];
    static BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
    static PrintWriter out = new PrintWriter(new OutputStreamWriter(System.out));
    public static void main(String[] args) throws IOException {
        String[] s = in.readLine().split(" ");
        int n = Integer.parseInt(s[0]);
        int m = Integer.parseInt(s[1]);
        tree = new node[n*4];
        build(1,1,n);
        for (int i = 0; i < m; i++) {
            s = in.readLine().split(" ");
            int op = Integer.parseInt(s[0]);
            int x = Integer.parseInt(s[1]);
            int y = Integer.parseInt(s[2]);
            if(op == 1) {
                modify(1, x, y);
            }else {
                System.out.println(qurey(1, x, y));
            }
        }	
    }
    private static void build(int u, int l, int r) {
        tree[u] = new node();
        if(l == r){		
            tree[u].l = l;
            tree[u].r = r;
        }else {
            tree[u].l = l;
            tree[u].r = r;
            int mid = (l + r)/2;
            build(2*u, l, mid);
            build(2*u+1, mid + 1, r);
        }	  
    }
    static long qurey(int u, int l, int r) {
        if(l <= tree[u].l && r >= tree[u].r) {
            return tree[u].v;
        }
        long res = 0;
        int mid = (tree[u].l + tree[u].r) / 2;
        if(l <= mid ) {
            res = qurey(u*2, l, r);
        }
        if(r > mid) {
            res += qurey(u*2+1, l, r);
        }
        return res;
    }
    static void modify(int u, int x, long k) {
        if(tree[u].l == tree[u].r) {
            tree[u].v += k;
        }else {
            int mid = (tree[u].l + tree[u].r)/2;
            if(x <= mid) {
                modify(u*2, x, k);
            }else {
                modify(u*2+1, x, k);
            }
            pushup(u);//更新u
        }
    }
    static void pushup(int u) {
        tree[u].v = tree[u*2].v + tree[u*2+1].v;
    }
}
  • 9
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值