Java数据结构之线段树中的懒操作详解

yizhihongxing

Java数据结构之线段树中的懒操作详解

什么是线段树

线段树是一种常用的数据结构,用于快速解决区间查询类问题。 线段树可以支持区间修改,单点查询,区间查询等操作。

线段树是采用二叉树的结构形成的,一个节点表示一个区间[left, right]。每个节点包含三个值:节点对应的区间范围[left, right]、节点代表的值val、以及节点所拥有的标记,通常标记用来记录区间修改的信息,也称为懒标记。

线段树中的懒操作

线段树自上而下递归下去,如果遇到一个节点的区间完全包含查询区间,则直接返回节点上的值,否则就分别递归该节点的左儿子和右儿子进行查询。 我们每次递归到一个节点时,都需要将该节点的lazy标记下传到其左右子节点。 为了避免重复下传标记,当节点被标记之后,我们将懒标记暂存,到下次对区间取值或区间修改时再进行下传,这样可以使得标记下传的复杂度为O(logn)。

线段树中主要涉及到以下两种类型的操作:

区间修改

区间修改操作是在指定区间内增加或修改某个值。当需要修改某个区间的值时,我们可以标记该区间需要被修改,而对于该区间的子节点,我们将修改信息暂时存储到懒标记中,等到子节点被查询到时,才下推修改。

这个过程比较简单,如果我们需要将区间[l, r]内的值都增加c,我们只需要将该区间对应节点的val值增加c即可,同时要将这个修改操作保存到懒标记中,让其下传到子节点。具体代码如下:

void update(int rt, int l, int r, int L, int R, int val) {  // rt 表示当前节点的编号,表示区间[l, r]。l, r 表示节点表示的区间范围。L, R 表示需要修改的区间。
    if(l >= L and r <= R) {
        tr[rt] += val * (r - l + 1);
        lazy[rt] += val;
        return;
    }
    pushdown(rt, r - l + 1);
    int mid = (l + r) >> 1;
    if(L <= mid) update(rt << 1, l, mid, L, R, val);
    if(R > mid) update(rt << 1 | 1, mid + 1, r, L, R, val);
    pushup(rt);
}

区间查询

区间查询即为查找某个区间[l, r]内的值,该操作比较简单,如果当前查询的区间[l, r]与当前节点表示的区间有重合,则需要向下递归查询其左右子节点,具体代码如下:

int query(int rt, int l, int r, int L, int R) { // rt 表示当前节点的编号,表示区间[l, r]
    if(l >= L and r <= R) {
        return tr[rt];
    }
    pushdown(rt, r - l + 1);
    int mid = (l + r) >> 1;
    int res = 0;
    if(L <= mid) res += query(rt << 1, l, mid, L, R);
    if(R > mid) res += query(rt << 1 | 1, mid + 1, r, L, R);
    return res;
}

示例说明:

以下是一个建立线段树,并进行区间修改和区间查询的示例代码:

import java.util.Arrays;

public class SegmentTree {

    private int[] tr;
    private int[] lazy;

    public SegmentTree(int n) { 
        tr = new int[n << 2];
        lazy = new int[n << 2];
        Arrays.fill(lazy, 0); // 初始化时,所有节点的懒标记都为0
    }

    private void pushup(int rt) {
        tr[rt] = tr[rt << 1] + tr[rt << 1 | 1];  // 更新父节点的值
    }

    private void pushdown(int rt, int len) {
        if(lazy[rt] != 0) {
            lazy[rt << 1] += lazy[rt];
            lazy[rt << 1 | 1] += lazy[rt];
            tr[rt << 1] += lazy[rt] * (len - (len >> 1));
            tr[rt << 1 | 1] += lazy[rt] * (len >> 1);
            lazy[rt] = 0;
        }
    }

    public void update(int rt, int l, int r, int L, int R, int val) {
        if(l >= L && r <= R) {
            tr[rt] += val * (r - l + 1);
            lazy[rt] += val;
            return;
        }
        pushdown(rt, r - l + 1);
        int mid = (l + r) >> 1;
        if(L <= mid) update(rt << 1, l, mid, L, R, val);
        if(R > mid) update(rt << 1 | 1, mid + 1, r, L, R, val);
        pushup(rt);
    }

    public int query(int rt, int l, int r, int L, int R) {
        if(l >= L && r <= R) {
            return tr[rt];
        }
        pushdown(rt, r - l + 1);
        int mid = (l + r) >> 1;
        int res = 0;
        if(L <= mid) res += query(rt << 1, l, mid, L, R);
        if(R > mid) res += query(rt << 1 | 1, mid + 1, r, L, R);
        return res;
    }

    public static void main(String[] args) {

        int[] nums = {1, 3, 5, 7, 8, 9};
        SegmentTree st = new SegmentTree(nums.length);
        buildTree(1, 0, nums.length - 1, nums, st);

        // 对[0,2]区间进行加10操作
        st.update(1, 0, nums.length - 1, 0, 2, 10);
        System.out.println(st.query(1, 0, nums.length - 1, 0, 2)); // 输出:36

        // 对[3,5]区间进行加5操作
        st.update(1, 0, nums.length - 1, 3, 5, 5);
        System.out.println(st.query(1, 0, nums.length - 1, 3, 5)); // 输出:22
    }

    private static void buildTree(int rt, int l, int r, int[] nums, SegmentTree st) {
        if(l == r) {
            st.tr[rt] = nums[l];
        } else {
            int mid = (l + r) >> 1;
            buildTree(rt << 1, l, mid, nums, st);
            buildTree(rt << 1 | 1, mid + 1, r, nums, st);
            st.pushup(rt);
        }
    }
}

该示例代码建立了一个线段树,并对[0,2]区间进行加10操作后,进行区间查询,输出结果为36。然后再对区间[3,5]进行加5操作,进行区间查询,输出结果为22。

以上就是Java数据结构之线段树中的懒操作详解的完整攻略。

本站文章如无特殊说明,均为本站原创,如若转载,请注明出处:Java数据结构之线段树中的懒操作详解 - Python技术站

(0)
上一篇 2023年6月27日
下一篇 2023年6月27日

相关文章

  • DOS批处理中%~dp0等扩充变量语法详解

    DOS批处理中%~dp0等扩充变量语法详解攻略 在DOS批处理脚本中,%~dp0是一种扩充变量语法,用于获取当前批处理脚本所在的目录路径。这个语法非常有用,可以帮助我们在脚本中获取当前目录的路径,从而方便地执行一些操作。 语法解释 %~dp0:%0表示当前批处理脚本的名称,d表示获取驱动器号,p表示获取路径,0表示获取脚本的完整路径。 示例说明 示例一 假设…

    other 2023年8月9日
    00
  • php上传功能集后缀名判断和随机命名(强力推荐)

    PHP上传功能集后缀名判断和随机命名攻略 在PHP中,实现上传功能时,通常需要对上传的文件进行后缀名判断和随机命名,以增加安全性和避免文件名冲突。下面是一个完整的攻略,包含了后缀名判断和随机命名的实现。 后缀名判断 获取上传文件的原始文件名和临时文件路径。 使用pathinfo()函数获取文件的后缀名。 使用in_array()函数判断后缀名是否在允许的文件…

    other 2023年8月5日
    00
  • 帝国CMS数据库配置文件是哪个文件?

    要了解帝国CMS的数据库配置文件,我们需要先来了解一下配置文件的概念。 配置文件是什么? 配置文件是应用程序中的一个文本文件,用于保存应用程序与所依赖的其他组件之间的参数和选项的信息。它们通常以定义的格式编写,与应用程序的逻辑和代码独立。 帝国CMS数据库配置文件 帝国CMS通过配置文件来连接数据库。该配置文件位于网站根目录下的/data/config/db…

    other 2023年6月25日
    00
  • iis 服务器应用程序不可用的解决方法

    针对“iis 服务器应用程序不可用”的问题,以下是解决方法的完整攻略。 问题背景 当我们在使用IIS(Internet Information Services)服务器,尝试打开应用程序时,出现应用程序不可用的情况。 这可能是由于多种因素引起的,包括配置不正确,端口被占用等等。下面我们一步步来解决这个问题。 解决方法 1.检查应用程序池 首先,检查应用程序池…

    other 2023年6月25日
    00
  • 深度理解Python中Class类、Object类、Type元类

    深度理解Python中Class类、Object类、Type元类 在 Python 中,所有的对象都是基于类(Class)创建的。Class 是一种特殊的对象,它拥有创建其他对象的能力。在本文中,我们将深入学习Python中的 Class、Object类 和 Type元类。 Class类 在 Python 中,我们可以用 Class 来定义一个新的类型,通过…

    other 2023年6月27日
    00
  • Go语言的结构体还能这么用?看这篇就够了

    让我来给你详细讲解一下“Go语言的结构体还能这么用?看这篇就够了”的完整攻略。 1. 简介 Go语言的结构体是一种自定义数据类型,它可以包含各种不同类型的数据,如数字、字符串、布尔值等。除此之外,结构体还可以嵌套、匿名等等,使其更加灵活多变。在本篇攻略中,我们将探讨结构体的一些高级用法,让你更好地掌握它。 2. 结构体的嵌入 2.1 基本用法 结构体的嵌入是…

    other 2023年6月27日
    00
  • 一键配置jdk环境变量的批处理代码

    下面是一键配置jdk环境变量的批处理代码的完整攻略。 步骤一:下载JDK安装包 首先需要下载JDK安装包,可以从Oracle官网下载。下载之后将安装包保存到本地电脑中。 步骤二:创建批处理文件 打开文本编辑器,输入以下代码,保存为“setjdk.bat”,记得选择编码格式为ANSI。其中path_to_jdk需要修改为自己电脑中JDK的安装路径。 @echo…

    other 2023年6月27日
    00
  • golang中的int类型和uint类型到底有多大?

    Golang中的int类型和uint类型到底有多大? 在Golang中,int类型和uint类型的大小取决于所运行的操作系统和CPU架构。在本攻略中,我们将详细讲解Golang中int类型和uint类型的大小,并提两个示例说明。 int类型和uint类型的大小 在Golang中,int类型和uint类型的大小决所运行的操作系统和CPU架构。在大多数情况下,i…

    other 2023年5月8日
    00
合作推广
合作推广
分享本页
返回顶部